1
0
forked from 0ad/0ad

Make the Ptolemaic lighthouse reveal the shore on the entire map.

Fixes #3174

This was SVN commit r16628.
This commit is contained in:
Nicolas Auvray 2015-05-06 18:47:02 +00:00
parent 4db041e3c6
commit 642500b49e
8 changed files with 225 additions and 144 deletions

View File

@ -17,7 +17,7 @@
<GenericName>Lighthouse</GenericName>
<SpecificName>Pharos</SpecificName>
<Classes datatype="tokens">Lighthouse Town -City</Classes>
<Tooltip>Build along the shore to reveal the shorelines over the entire map (Not implemented). Very large vision range: 180 meters.</Tooltip>
<Tooltip>Build along the shore to reveal the shorelines over the entire map. Very large vision range: 180 meters.</Tooltip>
<History>The Ptolemaic dynasty in Egypt built the magnificent Lighthouse of Alexandria near the harbor mouth of that Nile Delta city. This structure could be seen for many kilometers out to sea and was one of the Seven Wonders of the World.</History>
<Icon>structures/lighthouse.png</Icon>
<RequiredTechnology>phase_town</RequiredTechnology>
@ -41,6 +41,7 @@
</TerritoryInfluence>
<Vision>
<Range>180</Range>
<RevealShore>true</RevealShore>
</Vision>
<VisualActor>
<Actor>structures/ptolemies/lighthouse.xml</Actor>

View File

@ -512,7 +512,12 @@ void CTemplateLoader::CopyFoundationSubset(CParamNode& out, const CParamNode& in
// Foundations should be visible themselves in fog-of-war if their base template is,
// but shouldn't have any vision range
if (out.GetChild("Entity").GetChild("Vision").IsOk())
{
CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range></Vision></Entity>");
// Foundations should not have special vision capabilities either
if (out.GetChild("Entity").GetChild("Vision").GetChild("RevealShore").IsOk())
CParamNode::LoadXMLString(out, "<Entity><Vision><RevealShore>false</RevealShore></Vision></Entity>");
}
}
void CTemplateLoader::CopyConstructionSubset(CParamNode& out, const CParamNode& in)

View File

@ -306,6 +306,121 @@ const Grid<u16>& CCmpPathfinder::GetPassabilityGrid()
return *m_Grid;
}
Grid<u16> CCmpPathfinder::ComputeShoreGrid(bool expandOnWater)
{
PROFILE3("ComputeShoreGrid");
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
// TODO: these bits should come from ICmpTerrain
CTerrain& terrain = GetSimContext().GetTerrain();
// avoid integer overflow in intermediate calculation
const u16 shoreMax = 32767;
// First pass - find underwater tiles
Grid<bool> waterGrid(m_MapSize, m_MapSize);
for (u16 j = 0; j < m_MapSize; ++j)
{
for (u16 i = 0; i < m_MapSize; ++i)
{
fixed x, z;
TileCenter(i, j, x, z);
bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z));
waterGrid.set(i, j, underWater);
}
}
// Second pass - find shore tiles
Grid<u16> shoreGrid(m_MapSize, m_MapSize);
for (u16 j = 0; j < m_MapSize; ++j)
{
for (u16 i = 0; i < m_MapSize; ++i)
{
// Find a land tile
if (!waterGrid.get(i, j))
{
// If it's bordered by water, it's a shore tile
if ((i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1))
|| (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1))
|| (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1))
)
shoreGrid.set(i, j, 0);
else
shoreGrid.set(i, j, shoreMax);
}
// If we want to expand on water, we want water tiles not to be shore tiles
else if (expandOnWater)
shoreGrid.set(i, j, shoreMax);
}
}
// Expand influences to find shore distance
for (u16 y = 0; y < m_MapSize; ++y)
{
u16 min = shoreMax;
for (u16 x = 0; x < m_MapSize; ++x)
{
if (!waterGrid.get(x, y) || expandOnWater)
{
u16 g = shoreGrid.get(x, y);
if (g > min)
shoreGrid.set(x, y, min);
else if (g < min)
min = g;
++min;
}
}
for (u16 x = m_MapSize; x > 0; --x)
{
if (!waterGrid.get(x-1, y) || expandOnWater)
{
u16 g = shoreGrid.get(x - 1, y);
if (g > min)
shoreGrid.set(x - 1, y, min);
else if (g < min)
min = g;
++min;
}
}
}
for (u16 x = 0; x < m_MapSize; ++x)
{
u16 min = shoreMax;
for (u16 y = 0; y < m_MapSize; ++y)
{
if (!waterGrid.get(x, y) || expandOnWater)
{
u16 g = shoreGrid.get(x, y);
if (g > min)
shoreGrid.set(x, y, min);
else if (g < min)
min = g;
++min;
}
}
for (u16 y = m_MapSize; y > 0; --y)
{
if (!waterGrid.get(x, y-1) || expandOnWater)
{
u16 g = shoreGrid.get(x, y - 1);
if (g > min)
shoreGrid.set(x, y - 1, min);
else if (g < min)
min = g;
++min;
}
}
}
return shoreGrid;
}
void CCmpPathfinder::UpdateGrid()
{
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
@ -369,113 +484,11 @@ void CCmpPathfinder::UpdateGrid()
// Obstructions or terrain changed - we need to recompute passability
// TODO: only bother recomputing the region that has actually changed
Grid<u16> shoreGrid = ComputeShoreGrid();
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
// TOOD: these bits should come from ICmpTerrain
CTerrain& terrain = GetSimContext().GetTerrain();
// avoid integer overflow in intermediate calculation
const u16 shoreMax = 32767;
// First pass - find underwater tiles
Grid<bool> waterGrid(m_MapSize, m_MapSize);
for (u16 j = 0; j < m_MapSize; ++j)
{
for (u16 i = 0; i < m_MapSize; ++i)
{
fixed x, z;
TileCenter(i, j, x, z);
bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z));
waterGrid.set(i, j, underWater);
}
}
// Second pass - find shore tiles
Grid<u16> shoreGrid(m_MapSize, m_MapSize);
for (u16 j = 0; j < m_MapSize; ++j)
{
for (u16 i = 0; i < m_MapSize; ++i)
{
// Find a land tile
if (!waterGrid.get(i, j))
{
if ((i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1))
|| (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1))
|| (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1))
)
{ // If it's bordered by water, it's a shore tile
shoreGrid.set(i, j, 0);
}
else
{
shoreGrid.set(i, j, shoreMax);
}
}
}
}
// Expand influences on land to find shore distance
for (u16 y = 0; y < m_MapSize; ++y)
{
u16 min = shoreMax;
for (u16 x = 0; x < m_MapSize; ++x)
{
if (!waterGrid.get(x, y))
{
u16 g = shoreGrid.get(x, y);
if (g > min)
shoreGrid.set(x, y, min);
else if (g < min)
min = g;
++min;
}
}
for (u16 x = m_MapSize; x > 0; --x)
{
if (!waterGrid.get(x-1, y))
{
u16 g = shoreGrid.get(x-1, y);
if (g > min)
shoreGrid.set(x-1, y, min);
else if (g < min)
min = g;
++min;
}
}
}
for (u16 x = 0; x < m_MapSize; ++x)
{
u16 min = shoreMax;
for (u16 y = 0; y < m_MapSize; ++y)
{
if (!waterGrid.get(x, y))
{
u16 g = shoreGrid.get(x, y);
if (g > min)
shoreGrid.set(x, y, min);
else if (g < min)
min = g;
++min;
}
}
for (u16 y = m_MapSize; y > 0; --y)
{
if (!waterGrid.get(x, y-1))
{
u16 g = shoreGrid.get(x, y-1);
if (g > min)
shoreGrid.set(x, y-1, min);
else if (g < min)
min = g;
++min;
}
}
}
// Apply passability classes to terrain
for (u16 j = 0; j < m_MapSize; ++j)
{

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -242,6 +242,8 @@ public:
virtual const Grid<u16>& GetPassabilityGrid();
virtual Grid<u16> ComputeShoreGrid(bool expandOnWater = false);
virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& ret);
virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify);

View File

@ -1807,6 +1807,39 @@ public:
}
}
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);
}
}
}
/**
* Returns whether the given vertex is outside the normal bounds of the world
* (i.e. outside the range of a circular map)
@ -1861,23 +1894,7 @@ public:
m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
}
// 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;
MarkVisibilityDirtyAroundTile(owner, i, j);
}
ASSERT(counts[idx] < 65535);
@ -1907,7 +1924,13 @@ public:
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
i32 i = i0 + idx - idx0;
MarkVisibilityDirtyAroundTile(owner, i, j);
}
}
}
inline void MarkVisibilityDirtyAroundTile(u8 owner, i32 i, i32 j)
{
// 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;
@ -1926,8 +1949,6 @@ public:
if (n4 != n1 && j < m_TerrainVerticesPerSide && i < m_TerrainVerticesPerSide)
m_DirtyVisibility[n4] |= sharedDirtyVisibilityMask;
}
}
}
/**
* Update the LOS state of tiles within a given circular range,

View File

@ -21,8 +21,8 @@
#include "ICmpVision.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpValueModificationManager.h"
class CCmpVision : public ICmpVision
@ -30,6 +30,7 @@ class CCmpVision : public ICmpVision
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_ValueModification);
componentManager.SubscribeToMessageType(MT_Deserialized);
}
@ -39,18 +40,29 @@ public:
// Template state:
entity_pos_t m_Range, m_BaseRange;
bool m_RevealShore;
static std::string GetSchema()
{
return
"<element name='Range'>"
"<data type='nonNegativeInteger'/>"
"</element>";
"</element>"
"<optional>"
"<element name='RevealShore'>"
"<data type='boolean'/>"
"</element>"
"</optional>";
}
virtual void Init(const CParamNode& paramNode)
{
m_BaseRange = m_Range = paramNode.GetChild("Range").ToFixed();
if (paramNode.GetChild("RevealShore").IsOk())
m_RevealShore = paramNode.GetChild("RevealShore").ToBool();
else
m_RevealShore = false;
}
virtual void Deinit()
@ -71,6 +83,20 @@ public:
{
switch (msg.GetType())
{
case MT_OwnershipChanged:
{
if (!m_RevealShore)
break;
const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
if (msgData.entity != GetEntityId())
break;
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
cmpRangeManager->RevealShore(msgData.from, false);
cmpRangeManager->RevealShore(msgData.to, true);
break;
}
case MT_ValueModification:
{
const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -96,6 +96,11 @@ public:
virtual const Grid<u16>& GetPassabilityGrid() = 0;
/**
* Get a grid representing the distance to the shore of the terrain tile.
*/
virtual Grid<u16> ComputeShoreGrid(bool expandOnWater = false) = 0;
/**
* Compute a tile-based path from the given point to the goal, and return the set of waypoints.
* The waypoints correspond to the centers of horizontally/vertically adjacent tiles

View File

@ -352,6 +352,14 @@ public:
*/
virtual void ExploreTerritories() = 0;
/**
* Reveal the shore for specified player p.
* This works like for entities: if RevealShore is called multiple times with enabled, it
* will be necessary to call it the same number of times with !enabled to make the shore
* fall back into the FoW.
*/
virtual void RevealShore(player_id_t p, bool enable) = 0;
/**
* Set whether the whole map should be made visible to the given player.
* If player is -1, the map will be made visible to all players.