Index: binaries/data/mods/public/simulation/templates/structures/ptol_lighthouse.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/structures/ptol_lighthouse.xml (revision 16544)
+++ binaries/data/mods/public/simulation/templates/structures/ptol_lighthouse.xml (working copy)
@@ -17,7 +17,7 @@
Lighthouse
Pharos
Lighthouse Town -City
- Build along the shore to reveal the shorelines over the entire map (Not implemented). Very large vision range: 180 meters.
+ Build along the shore to reveal the shorelines over the entire map. Very large vision range: 180 meters.
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.
structures/lighthouse.png
phase_town
@@ -41,6 +41,7 @@
180
+ true
structures/ptolemies/lighthouse.xml
Index: source/ps/TemplateLoader.cpp
===================================================================
--- source/ps/TemplateLoader.cpp (revision 16544)
+++ source/ps/TemplateLoader.cpp (working copy)
@@ -512,7 +512,12 @@
// 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, "0");
+ // Foundations should not have special vision capabilities either
+ if (out.GetChild("Entity").GetChild("Vision").GetChild("RevealShore").IsOk())
+ CParamNode::LoadXMLString(out, "false");
+ }
}
void CTemplateLoader::CopyConstructionSubset(CParamNode& out, const CParamNode& in)
Index: source/simulation2/components/CCmpPathfinder.cpp
===================================================================
--- source/simulation2/components/CCmpPathfinder.cpp (revision 16544)
+++ source/simulation2/components/CCmpPathfinder.cpp (working copy)
@@ -306,6 +306,121 @@
return *m_Grid;
}
+Grid CCmpPathfinder::ComputeShoreGrid(bool expandOnWater)
+{
+ PROFILE3("ComputeShoreGrid");
+
+ CmpPtr 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 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 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 cmpTerrain(GetSystemEntity());
@@ -369,113 +484,11 @@
// Obstructions or terrain changed - we need to recompute passability
// TODO: only bother recomputing the region that has actually changed
+ Grid shoreGrid = ComputeShoreGrid();
+
CmpPtr 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 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 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)
{
Index: source/simulation2/components/CCmpPathfinder_Common.h
===================================================================
--- source/simulation2/components/CCmpPathfinder_Common.h (revision 16544)
+++ source/simulation2/components/CCmpPathfinder_Common.h (working copy)
@@ -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 @@
virtual const Grid& GetPassabilityGrid();
+ virtual Grid 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);
Index: source/simulation2/components/CCmpRangeManager.cpp
===================================================================
--- source/simulation2/components/CCmpRangeManager.cpp (revision 16544)
+++ source/simulation2/components/CCmpRangeManager.cpp (working copy)
@@ -1807,6 +1807,39 @@
}
}
+ 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 cmpPathfinder(GetSystemEntity());
+ const Grid& shoreGrid = cmpPathfinder->ComputeShoreGrid(true);
+ ENSURE(shoreGrid.m_W == m_TerrainVerticesPerSide-1 && shoreGrid.m_H == m_TerrainVerticesPerSide-1);
+
+ std::vector& 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 @@
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,26 +1924,30 @@
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
i32 i = i0 + idx - idx0;
+ MarkVisibilityDirtyAroundTile(owner, i, 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;
- 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;
+ 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;
+ 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];
+ 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;
- }
- }
+ 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;
}
/**
Index: source/simulation2/components/CCmpVision.cpp
===================================================================
--- source/simulation2/components/CCmpVision.cpp (revision 16544)
+++ source/simulation2/components/CCmpVision.cpp (working copy)
@@ -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 @@
public:
static void ClassInit(CComponentManager& componentManager)
{
+ componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_ValueModification);
componentManager.SubscribeToMessageType(MT_Deserialized);
}
@@ -39,6 +40,7 @@
// Template state:
entity_pos_t m_Range, m_BaseRange;
+ bool m_RevealShore;
static std::string GetSchema()
{
@@ -45,12 +47,22 @@
return
""
""
- "";
+ ""
+ ""
+ ""
+ ""
+ ""
+ "";
}
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 @@
{
switch (msg.GetType())
{
+ case MT_OwnershipChanged:
+ {
+ if (!m_RevealShore)
+ break;
+
+ const CMessageOwnershipChanged& msgData = static_cast (msg);
+ if (msgData.entity != GetEntityId())
+ break;
+
+ CmpPtr cmpRangeManager(GetSystemEntity());
+ cmpRangeManager->RevealShore(msgData.from, false);
+ cmpRangeManager->RevealShore(msgData.to, true);
+ break;
+ }
case MT_ValueModification:
{
const CMessageValueModification& msgData = static_cast (msg);
Index: source/simulation2/components/ICmpPathfinder.h
===================================================================
--- source/simulation2/components/ICmpPathfinder.h (revision 16544)
+++ source/simulation2/components/ICmpPathfinder.h (working copy)
@@ -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
@@ -97,6 +97,11 @@
virtual const Grid& GetPassabilityGrid() = 0;
/**
+ * Get a grid representing the distance to the shore of the terrain tile.
+ */
+ virtual Grid 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
* along the path.
Index: source/simulation2/components/ICmpRangeManager.h
===================================================================
--- source/simulation2/components/ICmpRangeManager.h (revision 16544)
+++ source/simulation2/components/ICmpRangeManager.h (working copy)
@@ -353,6 +353,14 @@
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.
*/