diff --git a/binaries/data/mods/public/gui/session_new/session.xml b/binaries/data/mods/public/gui/session_new/session.xml
index 2c723b9c2d..12718c7120 100644
--- a/binaries/data/mods/public/gui/session_new/session.xml
+++ b/binaries/data/mods/public/gui/session_new/session.xml
@@ -77,7 +77,7 @@
/>
-
diff --git a/binaries/data/mods/public/maps/scenarios/Combat_demo.xml b/binaries/data/mods/public/maps/scenarios/Combat_demo.xml
index da92e38c22..c44309944f 100755
--- a/binaries/data/mods/public/maps/scenarios/Combat_demo.xml
+++ b/binaries/data/mods/public/maps/scenarios/Combat_demo.xml
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0e1d41bffabe5eee7d05ad583c0dfef8bde2dcf1107033660a76eaf04ada2f86
-size 10975
+oid sha256:f61f903eb1565d9bbe72ad5e05153d0bb1ae9eedc4c28f1155323adca626c619
+size 10995
diff --git a/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml b/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml
index b9bac2805a..9861444120 100755
--- a/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml
+++ b/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:03b18839389266b6cd660a99b987e71763df07f570b062404c8eae3e46adc055
-size 2683
+oid sha256:8ae3781db402ea2fb04996c998713e3b3b6744c1c4ffb25f48fd4ef30324542b
+size 2703
diff --git a/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml b/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml
index 2a108261a3..ec2334387c 100644
--- a/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml
+++ b/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:83896add80e4ae91e7d9508b7d3c88f8a156f7edf527592d405d59f7132e23f6
-size 18204
+oid sha256:5657b71b257c6445e7c4c89002a8a418d4b8e7148608fe42751880550f274cce
+size 18224
diff --git a/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml b/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml
index 3c2557db61..caf7196848 100644
--- a/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml
+++ b/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:db0294ad024f5a08bb6441328532e4177a3cecb5881355ab19a82898cfcc1154
-size 7674
+oid sha256:11768719c63f14b4450657c6c37b817e1962e734515e9329adbf191d4c39a54a
+size 7694
diff --git a/binaries/data/mods/public/maps/scenarios/Units_demo.xml b/binaries/data/mods/public/maps/scenarios/Units_demo.xml
index fd7c423c2a..ff9b3ee434 100644
--- a/binaries/data/mods/public/maps/scenarios/Units_demo.xml
+++ b/binaries/data/mods/public/maps/scenarios/Units_demo.xml
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4254e278f9379fd2fdcf8cdfc8aba3cdef56179755035c8a1295d0875a6981e7
-size 2257
+oid sha256:08e09ebf02494ac877823a08bb372879af40b0ac7ccfada6266de21585444c90
+size 2277
diff --git a/binaries/data/mods/public/simulation/components/Vision.js b/binaries/data/mods/public/simulation/components/Vision.js
deleted file mode 100644
index 1dd9329f85..0000000000
--- a/binaries/data/mods/public/simulation/components/Vision.js
+++ /dev/null
@@ -1,22 +0,0 @@
-function Vision() {}
-
-Vision.prototype.Schema =
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "";
-
-/*
- * TODO: this all needs to be designed and implemented
- */
-
-Vision.prototype.GetRange = function()
-{
- return +this.template.Range;
-};
-
-Engine.RegisterComponentType(IID_Vision, "Vision", Vision);
diff --git a/binaries/data/mods/public/simulation/components/interfaces/Vision.js b/binaries/data/mods/public/simulation/components/interfaces/Vision.js
deleted file mode 100644
index 9477507015..0000000000
--- a/binaries/data/mods/public/simulation/components/interfaces/Vision.js
+++ /dev/null
@@ -1 +0,0 @@
-Engine.RegisterInterface("Vision");
diff --git a/binaries/data/mods/public/simulation/helpers/Setup.js b/binaries/data/mods/public/simulation/helpers/Setup.js
index 95177b4d5a..b4dff7446c 100644
--- a/binaries/data/mods/public/simulation/helpers/Setup.js
+++ b/binaries/data/mods/public/simulation/helpers/Setup.js
@@ -12,6 +12,13 @@ function LoadMapSettings(settings)
cmpUnitAI.SetStance(settings.DefaultStance);
}
}
+
+ if (settings.RevealMap)
+ {
+ var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ if (cmpRangeManager)
+ cmpRangeManager.SetLosRevealAll(true);
+ }
}
Engine.RegisterGlobal("LoadMapSettings", LoadMapSettings);
diff --git a/binaries/data/mods/public/simulation/templates/template_gaia.xml b/binaries/data/mods/public/simulation/templates/template_gaia.xml
index 6b1eb349a0..9102795053 100644
--- a/binaries/data/mods/public/simulation/templates/template_gaia.xml
+++ b/binaries/data/mods/public/simulation/templates/template_gaia.xml
@@ -5,6 +5,7 @@
Gaia
+ 0
true
diff --git a/source/gui/MiniMap.cpp b/source/gui/MiniMap.cpp
index 266a4d8080..2357e6cbfc 100644
--- a/source/gui/MiniMap.cpp
+++ b/source/gui/MiniMap.cpp
@@ -39,6 +39,7 @@
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpMinimap.h"
+#include "simulation2/components/ICmpRangeManager.h"
bool g_GameRestarted = false;
@@ -246,9 +247,7 @@ void CMiniMap::Draw()
if(m_TerrainDirty)
RebuildTerrainTexture();
-// CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
-// if(losMgr->m_LOSSetting != LOS_SETTING_ALL_VISIBLE)
-// RebuildLOSTexture();
+ RebuildLOSTexture();
}
const float texCoordMax = ((float)m_MapSize - 1) / ((float)m_TextureSize);
@@ -366,16 +365,24 @@ void CMiniMap::Draw()
std::vector vertexArray;
vertexArray.reserve(ents.size());
+ CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY);
+ debug_assert(!cmpRangeManager.null());
+
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
MinimapUnitVertex v;
ICmpMinimap* cmpMinimap = static_cast(it->second);
- if (cmpMinimap->GetRenderData(v.r, v.g, v.b, v.x, v.y))
+ entity_pos_t posX, posZ;
+ if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
- v.a = 255;
- v.x = x + v.x*sx;
- v.y = y - v.y*sy;
- vertexArray.push_back(v);
+ ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetPlayerID());
+ if (vis != ICmpRangeManager::VIS_HIDDEN)
+ {
+ v.a = 255;
+ v.x = x + posX.ToFloat()*sx;
+ v.y = y - posZ.ToFloat()*sy;
+ vertexArray.push_back(v);
+ }
}
}
@@ -421,7 +428,7 @@ void CMiniMap::CreateTextures()
glGenTextures(1, &m_LOSTexture);
g_Renderer.BindTexture(0, m_LOSTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0);
- m_LOSData = new u8[(m_MapSize - 1) * (m_MapSize - 1)];
+ m_LOSData = new u8[m_MapSize * m_MapSize];
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
@@ -493,41 +500,39 @@ void CMiniMap::RebuildTerrainTexture()
void CMiniMap::RebuildLOSTexture()
{
- PROFILE_START("rebuild minimap: los");
+ PROFILE("rebuild minimap: los");
- ssize_t x = 0;
- ssize_t y = 0;
- ssize_t w = m_MapSize - 1;
- ssize_t h = m_MapSize - 1;
+ ssize_t w = m_MapSize;
+ ssize_t h = m_MapSize;
- memset(m_LOSData, 0, w*h);
+ CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
+ if (cmpRangeManager.null() || cmpRangeManager->GetLosRevealAll(g_Game->GetPlayerID()))
+ {
+ memset(m_LOSData, 0, w*h);
+ }
+ else
+ {
+ ICmpRangeManager::CLosQuerier los = cmpRangeManager->GetLosQuerier(g_Game->GetPlayerID());
- // TODO: ought to do something like:
-/*
- CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
- CPlayer* player = g_Game->GetLocalPlayer();
+ u8 *dataPtr = m_LOSData;
- for(ssize_t j = 0; j < h; j++)
+ for (ssize_t j = 0; j < h; j++)
{
- u8 *dataPtr = m_LOSData + ((y + j) * (m_MapSize - 1)) + x;
- for(ssize_t i = 0; i < w; i++)
+ for (ssize_t i = 0; i < w; i++)
{
- ELOSStatus status = losMgr->GetStatus(i, j, player);
- if(status == LOS_UNEXPLORED)
- *dataPtr++ = 0xff;
- else if(status == LOS_EXPLORED)
- *dataPtr++ = (u8) (0xff * 0.3f);
- else
+ if (los.IsVisible(i, j))
*dataPtr++ = 0;
+ else if (los.IsExplored(i, j))
+ *dataPtr++ = 76;
+ else
+ *dataPtr++ = 255;
}
}
-*/
+ }
// Upload the texture
g_Renderer.BindTexture(0, m_LOSTexture);
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, m_LOSData);
-
- PROFILE_END("rebuild minimap: los");
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, m_LOSData);
}
void CMiniMap::Destroy()
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
index 016c920864..670cdd988d 100644
--- a/source/gui/scripting/ScriptFunctions.cpp
+++ b/source/gui/scripting/ScriptFunctions.cpp
@@ -36,6 +36,7 @@
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpGuiInterface.h"
+#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/helpers/Selection.h"
@@ -119,7 +120,7 @@ void PostNetworkCommand(void* cbdata, CScriptVal cmd)
std::vector PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y)
{
- return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y);
+ return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID());
}
std::vector PickFriendlyEntitiesInRect(void* UNUSED(cbdata), int x0, int y0, int x1, int y1, int player)
@@ -304,6 +305,18 @@ CScriptVal LoadMapData(void* cbdata, std::wstring name)
return reader.GetScriptData(guiManager->GetScriptInterface()).get();
}
+void SetRevealMap(void* UNUSED(cbdata), bool enabled)
+{
+ if (!g_Game)
+ return;
+
+ CSimulation2* sim = g_Game->GetSimulation2();
+ debug_assert(sim);
+ CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY);
+ if (!cmpRangeManager.null())
+ cmpRangeManager->SetLosRevealAll(enabled);
+}
+
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
@@ -343,4 +356,5 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction("RestartInAtlas");
scriptInterface.RegisterFunction("AtlasIsAvailable");
scriptInterface.RegisterFunction("LoadMapData");
+ scriptInterface.RegisterFunction("SetRevealMap");
}
diff --git a/source/maths/Fixed.h b/source/maths/Fixed.h
index f195c031d3..5d8b8297ca 100644
--- a/source/maths/Fixed.h
+++ b/source/maths/Fixed.h
@@ -176,6 +176,11 @@ public:
return value >> fract_bits;
}
+ int ToInt_RoundToNearest() const // (ties to infinity)
+ {
+ return (value + fract_pow2/2) >> fract_bits;
+ }
+
/// Returns the shortest string such that FromString will parse to the correct value.
CStr8 ToString() const;
@@ -280,6 +285,14 @@ public:
return CFixed((T)t);
}
+ /**
+ * Multiply the value by itself. Might overflow.
+ */
+ CFixed Square() const
+ {
+ return (*this).Multiply(*this);
+ }
+
/**
* Compute this*m/d. Must not have d == 0. Won't overflow if the result can be represented as a CFixed.
*/
diff --git a/source/maths/tests/test_Fixed.h b/source/maths/tests/test_Fixed.h
index 9dca55646b..780a4a3e7e 100644
--- a/source/maths/tests/test_Fixed.h
+++ b/source/maths/tests/test_Fixed.h
@@ -189,6 +189,22 @@ public:
TS_ASSERT_EQUALS(fixed::FromFloat(-2.99f).ToInt_RoundToNegInfinity(), -3);
}
+ void test_RoundToNearest()
+ {
+ TS_ASSERT_EQUALS(fixed::FromFloat(10.f).ToInt_RoundToNearest(), 10);
+ TS_ASSERT_EQUALS(fixed::FromFloat(10.1f).ToInt_RoundToNearest(), 10);
+ TS_ASSERT_EQUALS(fixed::FromFloat(10.5f).ToInt_RoundToNearest(), 11);
+ TS_ASSERT_EQUALS(fixed::FromFloat(10.99f).ToInt_RoundToNearest(), 11);
+ TS_ASSERT_EQUALS(fixed::FromFloat(0.1f).ToInt_RoundToNearest(), 0);
+ TS_ASSERT_EQUALS(fixed::FromFloat(0.0f).ToInt_RoundToNearest(), 0);
+ TS_ASSERT_EQUALS(fixed::FromFloat(-0.1f).ToInt_RoundToNearest(), 0);
+ TS_ASSERT_EQUALS(fixed::FromFloat(-0.99f).ToInt_RoundToNearest(), -1);
+ TS_ASSERT_EQUALS(fixed::FromFloat(-1.0f).ToInt_RoundToNearest(), -1);
+ TS_ASSERT_EQUALS(fixed::FromFloat(-2.0f).ToInt_RoundToNearest(), -2);
+ TS_ASSERT_EQUALS(fixed::FromFloat(-2.5f).ToInt_RoundToNearest(), -2);
+ TS_ASSERT_EQUALS(fixed::FromFloat(-2.99f).ToInt_RoundToNearest(), -3);
+ }
+
// TODO: test all the arithmetic operators
void test_Mod()
diff --git a/source/renderer/PatchRData.cpp b/source/renderer/PatchRData.cpp
index 4d1664bf5a..ce7dd9ba6d 100644
--- a/source/renderer/PatchRData.cpp
+++ b/source/renderer/PatchRData.cpp
@@ -33,6 +33,7 @@
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "simulation2/Simulation2.h"
+#include "simulation2/components/ICmpRangeManager.h"
const ssize_t BlendOffsets[8][2] = {
{ 0, -1 },
@@ -431,47 +432,11 @@ void CPatchRData::Update()
ssize_t pz=m_Patch->m_Z;
CTerrain* terrain=m_Patch->m_Parent;
- ssize_t mapSize=terrain->GetVerticesPerSide();
ssize_t vsize=PATCH_SIZE+1;
SColor4ub baseColour = terrain->GetBaseColour();
- /*
- if (g_Game) // XXX: need to implement this for new sim system
- {
- CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
-
- // this is very similar to BuildVertices(), but just for color
- for (ssize_t j=0;j= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2)
- {
- ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer());
- if(s==LOS_EXPLORED && losMod.R > 178)
- losMod = SColor4ub(178, 178, 178, 255);
- else if(s==LOS_UNEXPLORED && losMod.R > 0)
- losMod = SColor4ub(0, 0, 0, 255);
- }
- }
-
- m_Vertices[v].m_LOSColor = losMod;
- }
- }
- }
- else
- */
+ CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
+ if (cmpRangeManager.null() || cmpRangeManager->GetLosRevealAll(g_Game->GetPlayerID()))
{
for (ssize_t j = 0; j < vsize; ++j)
{
@@ -481,7 +446,31 @@ void CPatchRData::Update()
m_Vertices[v].m_LOSColor = baseColour;
}
}
+ }
+ else
+ {
+ ICmpRangeManager::CLosQuerier los = cmpRangeManager->GetLosQuerier(g_Game->GetPlayerID());
+ // this is very similar to BuildVertices(), but just for color
+ for (ssize_t j = 0; j < vsize; j++)
+ {
+ for (ssize_t i = 0; i < vsize; i++)
+ {
+ ssize_t ix = px * PATCH_SIZE + i;
+ ssize_t iz = pz * PATCH_SIZE + j;
+ ssize_t v = (j * vsize) + i;
+
+ SColor4ub losMod;
+ if (los.IsVisible(ix, iz))
+ losMod = baseColour;
+ else if (los.IsExplored(ix, iz))
+ losMod = SColor4ub(178, 178, 178, 255);
+ else
+ losMod = SColor4ub(0, 0, 0, 255);
+
+ m_Vertices[v].m_LOSColor = losMod;
+ }
+ }
}
// upload base vertices into their vertex buffer
diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp
index fc7ab0fb67..f313123342 100644
--- a/source/renderer/TerrainRenderer.cpp
+++ b/source/renderer/TerrainRenderer.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2009 Wildfire Games.
+/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -38,6 +38,7 @@
#include "ps/World.h"
#include "simulation2/Simulation2.h"
+#include "simulation2/components/ICmpRangeManager.h"
#include "renderer/PatchRData.h"
#include "renderer/Renderer.h"
@@ -434,9 +435,11 @@ void TerrainRenderer::RenderWater()
}
}
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
-// int mapSize = terrain->GetVerticesPerSide();
-// CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
+ CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
+ debug_assert(!cmpRangeManager.null());
+ ICmpRangeManager::CLosQuerier los = cmpRangeManager->GetLosQuerier(g_Game->GetPlayerID());
+ bool losRevealAll = cmpRangeManager->GetLosRevealAll(g_Game->GetPlayerID());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
@@ -566,50 +569,35 @@ void TerrainRenderer::RenderWater()
// is any corner of the tile below the water height? if not, no point rendering it
bool shouldRender = false;
- for(int j=0; j<4; j++)
+ for (int j = 0; j < 4; j++)
{
float terrainHeight = terrain->GetVertexGroundLevel(x + DX[j], z + DZ[j]);
- if( terrainHeight < WaterMgr->m_WaterHeight )
+ if (terrainHeight < WaterMgr->m_WaterHeight)
{
shouldRender = true;
break;
}
}
- if(!shouldRender)
- {
+ if (!shouldRender)
continue;
- }
for (int j=0; j<4; j++)
{
- int ix = x + DX[j];
- int iz = z + DZ[j];
+ ssize_t ix = x + DX[j];
+ ssize_t iz = z + DZ[j];
float vertX = ix * CELL_SIZE;
float vertZ = iz * CELL_SIZE;
float terrainHeight = terrain->GetVertexGroundLevel(ix, iz);
- float losMod = 1.0f;
- /*
- if (false) // XXX: need to implement this for new sim system
- {
- for(size_t k=0; k<4; k++)
- {
- ssize_t tx = ix - DX[k];
- ssize_t tz = iz - DZ[k];
-
- if(tx >= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2)
- {
- ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer());
- if(s == LOS_EXPLORED && losMod > 0.7f)
- losMod = 0.7f;
- else if(s==LOS_UNEXPLORED && losMod > 0.0f)
- losMod = 0.0f;
- }
- }
- }
- */
+ float losMod;
+ if (losRevealAll || los.IsVisible(ix, iz))
+ losMod = 1.0f;
+ else if (los.IsExplored(ix, iz))
+ losMod = 0.7f;
+ else
+ losMod = 0.0f;
if (fancy)
{
@@ -672,5 +660,3 @@ void TerrainRenderer::RenderWater()
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
}
-
-
diff --git a/source/simulation2/MessageTypes.h b/source/simulation2/MessageTypes.h
index 61bc7bc949..4a131bb88c 100644
--- a/source/simulation2/MessageTypes.h
+++ b/source/simulation2/MessageTypes.h
@@ -115,6 +115,9 @@ public:
fixed turnLength;
};
+/**
+ * Prepare for rendering a new frame (set up model positions etc).
+ */
class CMessageInterpolate : public CMessage
{
public:
@@ -129,6 +132,10 @@ public:
float offset; // range [0, 1] (inclusive); fractional time of current frame between previous/next simulation turns
};
+/**
+ * Add renderable objects to the scene collector.
+ * Called after CMessageInterpolate.
+ */
class CMessageRenderSubmit : public CMessage
{
public:
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
index 05654de0eb..c12313cbbe 100644
--- a/source/simulation2/TypeList.h
+++ b/source/simulation2/TypeList.h
@@ -118,6 +118,9 @@ COMPONENT(Terrain)
INTERFACE(UnitMotion)
COMPONENT(UnitMotion) // must be after Obstruction
+INTERFACE(Vision)
+COMPONENT(Vision)
+
INTERFACE(Visual)
COMPONENT(VisualActor)
diff --git a/source/simulation2/components/CCmpMinimap.cpp b/source/simulation2/components/CCmpMinimap.cpp
index f32f6b43ba..5ae4d08c4c 100644
--- a/source/simulation2/components/CCmpMinimap.cpp
+++ b/source/simulation2/components/CCmpMinimap.cpp
@@ -160,7 +160,7 @@ public:
}
}
- virtual bool GetRenderData(u8& r, u8& g, u8& b, float& x, float& z)
+ virtual bool GetRenderData(u8& r, u8& g, u8& b, entity_pos_t& x, entity_pos_t& z)
{
if (!m_Active)
return false;
@@ -168,8 +168,8 @@ public:
r = m_R;
g = m_G;
b = m_B;
- x = m_X.ToFloat();
- z = m_Z.ToFloat();
+ x = m_X;
+ z = m_Z;
return true;
}
};
diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp
index d905373277..9c9fb39d50 100644
--- a/source/simulation2/components/CCmpRangeManager.cpp
+++ b/source/simulation2/components/CCmpRangeManager.cpp
@@ -21,6 +21,7 @@
#include "ICmpRangeManager.h"
#include "ICmpPosition.h"
+#include "ICmpVision.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/helpers/Spatial.h"
@@ -49,11 +50,11 @@ struct Query
/**
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
- * into a 31-bit mask for quick set-membership tests.
+ * into a 32-bit mask for quick set-membership tests.
*/
static u32 CalcOwnerMask(i32 owner)
{
- if (owner >= -1 && owner < 30)
+ if (owner >= -1 && owner < 31)
return 1 << (1+owner);
else
return 0; // owner was invalid
@@ -64,13 +65,15 @@ static u32 CalcOwnerMask(i32 owner)
*/
struct EntityData
{
- EntityData() : ownerMask(CalcOwnerMask(-1)), inWorld(0) { }
+ EntityData() : retainInFog(0), owner(-1), inWorld(0) { }
entity_pos_t x, z;
- u32 ownerMask : 31;
- u32 inWorld : 1;
+ entity_pos_t visionRange;
+ u8 retainInFog; // boolean
+ i8 owner;
+ u8 inWorld; // boolean
};
-cassert(sizeof(EntityData) == 12);
+cassert(sizeof(EntityData) == 16);
/**
* Functor for sorting entities by distance from a source point.
@@ -101,12 +104,13 @@ private:
};
/**
- * Basic range manager implementation.
+ * Range manager implementation.
* Maintains a list of all entities (and their positions and owners), which is used for
* queries.
*
- * TODO: Ideally this would use a quadtree or something for more efficient spatial queries,
- * since it's about O(n^2) in the total number of entities on the map.
+ * LOS implementation is based on the model described in GPG2.
+ * (TODO: would be nice to make it cleverer, so e.g. mountains and walls
+ * can block vision)
*/
class CCmpRangeManager : public ICmpRangeManager
{
@@ -131,10 +135,27 @@ public:
SpatialSubdivision m_Subdivision;
+ // Range query state:
tag_t m_QueryNext; // next allocated id
std::map m_Queries;
std::map m_EntityData;
+ // LOS state:
+
+ bool m_LosRevealAll;
+ ssize_t m_TerrainVerticesPerSide;
+
+ // 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
+ // of units in a very small area.
+ // (Note we use vertexes, not tiles, to better match the renderer.)
+ // Lazily constructed when it's needed, to save memory in smaller games.
+ std::vector > m_LosPlayerCounts;
+
+ // 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
+ std::vector m_LosState;
+ static const int MAX_LOS_PLAYER_ID = 16;
+
static std::string GetSchema()
{
return "";
@@ -150,6 +171,9 @@ public:
// Initialise with bogus values (these will get replaced when
// SetBounds is called)
ResetSubdivisions(entity_pos_t::FromInt(1), entity_pos_t::FromInt(1));
+
+ m_LosRevealAll = false;
+ m_TerrainVerticesPerSide = 0;
}
virtual void Deinit(const CSimContext& UNUSED(context))
@@ -189,6 +213,14 @@ public:
// use the default-constructed EntityData here
EntityData entdata;
+ // Store the LOS data, if any
+ CmpPtr cmpVision(GetSimContext(), ent);
+ if (!cmpVision.null())
+ {
+ entdata.visionRange = cmpVision->GetRange();
+ entdata.retainInFog = (cmpVision->GetRetainInFog() ? 1 : 0);
+ }
+
// Remember this entity
m_EntityData.insert(std::make_pair(ent, entdata));
@@ -208,9 +240,18 @@ public:
if (msgData.inWorld)
{
if (it->second.inWorld)
- m_Subdivision.Move(ent, CFixedVector2D(it->second.x, it->second.z), CFixedVector2D(msgData.x, msgData.z));
+ {
+ CFixedVector2D from(it->second.x, it->second.z);
+ CFixedVector2D to(msgData.x, msgData.z);
+ m_Subdivision.Move(ent, from, to);
+ LosMove(it->second.owner, it->second.visionRange, from, to);
+ }
else
- m_Subdivision.Add(ent, CFixedVector2D(msgData.x, msgData.z));
+ {
+ CFixedVector2D to(msgData.x, msgData.z);
+ m_Subdivision.Add(ent, to);
+ LosAdd(it->second.owner, it->second.visionRange, to);
+ }
it->second.inWorld = 1;
it->second.x = msgData.x;
@@ -219,7 +260,11 @@ public:
else
{
if (it->second.inWorld)
- m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z));
+ {
+ CFixedVector2D from(it->second.x, it->second.z);
+ m_Subdivision.Remove(ent, from);
+ LosRemove(it->second.owner, it->second.visionRange, from);
+ }
it->second.inWorld = 0;
it->second.x = entity_pos_t::Zero();
@@ -239,7 +284,14 @@ public:
if (it == m_EntityData.end())
break;
- it->second.ownerMask = CalcOwnerMask(msgData.to);
+ 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);
+ }
+
+ it->second.owner = msgData.to;
break;
}
@@ -276,10 +328,19 @@ public:
}
}
- virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1)
+ virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices)
{
debug_assert(x0.IsZero() && z0.IsZero()); // don't bother implementing non-zero offsets yet
ResetSubdivisions(x1, z1);
+
+ m_TerrainVerticesPerSide = vertices;
+ m_LosPlayerCounts.clear();
+ m_LosPlayerCounts.resize(MAX_LOS_PLAYER_ID+1);
+ m_LosState.clear();
+ m_LosState.resize(vertices*vertices);
+
+ for (std::map::iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
+ LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
}
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
@@ -482,7 +543,7 @@ private:
debug_assert(it != m_EntityData.end());
// Quick filter to ignore entities with the wrong owner
- if (!(it->second.ownerMask & q.ownersMask))
+ if (!(CalcOwnerMask(it->second.owner) & q.ownersMask))
continue;
// Restrict based on precise location
@@ -573,6 +634,158 @@ private:
for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
collector.Submit(&m_DebugOverlayLines[i]);
}
+
+
+ // LOS implementation:
+
+ virtual CLosQuerier GetLosQuerier(int player)
+ {
+ return CLosQuerier(player, m_LosState, m_TerrainVerticesPerSide);
+ }
+
+ virtual ELosVisibility GetLosVisibility(entity_id_t ent, int player)
+ {
+ // (We can't use m_EntityData since this needs to handle LOCAL entities too)
+
+ CmpPtr cmpPosition(GetSimContext(), ent);
+ if (cmpPosition.null() || !cmpPosition->IsInWorld())
+ return VIS_HIDDEN;
+
+ if (m_LosRevealAll)
+ return VIS_VISIBLE;
+
+ CFixedVector2D pos = cmpPosition->GetPosition2D();
+
+ CLosQuerier los(player, m_LosState, m_TerrainVerticesPerSide);
+
+ int i = (pos.X / (int)CELL_SIZE).ToInt_RoundToNearest();
+ int j = (pos.Y / (int)CELL_SIZE).ToInt_RoundToNearest();
+
+ if (los.IsVisible(i, j))
+ return VIS_VISIBLE;
+
+ if (los.IsExplored(i, j))
+ {
+ CmpPtr cmpVision(GetSimContext(), ent);
+ if (!cmpVision.null() && cmpVision->GetRetainInFog())
+ return VIS_FOGGED;
+ }
+
+ return VIS_HIDDEN;
+ }
+
+ virtual void SetLosRevealAll(bool enabled)
+ {
+ // Eventually we might want this to be a per-player flag (which is why
+ // GetLosRevealAll takes a player argument), but currently it's just a
+ // global setting since I can't quite work out where per-player would be useful
+
+ m_LosRevealAll = enabled;
+ }
+
+ virtual bool GetLosRevealAll(int UNUSED(player))
+ {
+ return m_LosRevealAll;
+ }
+
+ /**
+ * Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
+ * amount is +1 or -1.
+ */
+ inline void LosUpdateStripHelper(u8 owner, ssize_t i0, ssize_t i1, ssize_t j, int amount, std::vector& counts)
+ {
+ for (ssize_t i = i0; i <= i1; ++i)
+ {
+ ssize_t idx = j*m_TerrainVerticesPerSide + i;
+
+ // Increasing from zero to non-zero - move from unexplored/explored to visible+explored
+ if (counts[idx] == 0 && amount > 0)
+ {
+ m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
+ }
+
+ counts[idx] += amount;
+
+ // Decreasing from non-zero to zero - move from visible+explored to explored
+ if (counts[idx] == 0 && amount < 0)
+ {
+ m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
+ }
+ }
+ }
+
+ /**
+ * Update the LOS state of tiles within a given circular range.
+ * Assumes owner is in the valid range.
+ */
+ inline void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos, int amount)
+ {
+ if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
+ return;
+
+ PROFILE("LosUpdateHelper");
+
+ std::vector& counts = m_LosPlayerCounts.at(owner);
+
+ // Lazy initialisation of counts:
+ if (counts.empty())
+ counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
+
+ // Compute the circular region as a series of strips.
+ // Rather than quantise pos to vertexes, we do more precise sub-tile computations
+ // to get smoother behaviour as a unit moves rather than jumping a whole tile
+ // at once.
+
+ // Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
+ // (so that we never render the sharp edge of the map)
+ ssize_t j0 = ((pos.Y - visionRange)/(int)CELL_SIZE).ToInt_RoundToInfinity();
+ ssize_t j1 = ((pos.Y + visionRange)/(int)CELL_SIZE).ToInt_RoundToNegInfinity();
+ ssize_t j0clamp = std::max(j0, (ssize_t)1);
+ ssize_t j1clamp = std::min(j1, m_TerrainVerticesPerSide-2);
+
+ entity_pos_t xscale = pos.X / (int)CELL_SIZE;
+ entity_pos_t yscale = pos.Y / (int)CELL_SIZE;
+ entity_pos_t rsquared = (visionRange / (int)CELL_SIZE).Square();
+
+ for (ssize_t j = j0clamp; j <= j1clamp; ++j)
+ {
+ // Compute values such that (i - x)^2 + (j - y)^2 <= r^2
+ // (TODO: is this sqrt slow? can we optimise it?)
+ entity_pos_t di = (rsquared - (entity_pos_t::FromInt(j) - yscale).Square()).Sqrt();
+ ssize_t i0 = (xscale - di).ToInt_RoundToInfinity();
+ ssize_t i1 = (xscale + di).ToInt_RoundToNegInfinity();
+
+ ssize_t i0clamp = std::max(i0, (ssize_t)1);
+ ssize_t i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
+ LosUpdateStripHelper(owner, i0clamp, i1clamp, j, amount, counts);
+ }
+ }
+
+ void LosAdd(i8 owner, entity_pos_t visionRange, CFixedVector2D pos)
+ {
+ if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
+ return;
+
+ LosUpdateHelper(owner, visionRange, pos, 1);
+ }
+
+ void LosRemove(i8 owner, entity_pos_t visionRange, CFixedVector2D pos)
+ {
+ if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
+ return;
+
+ LosUpdateHelper(owner, visionRange, pos, -1);
+ }
+
+ void LosMove(i8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
+ {
+ if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
+ return;
+
+ // TODO: we could optimise this by only modifying tiles that changed
+ LosRemove(owner, visionRange, from);
+ LosAdd(owner, visionRange, to);
+ }
};
REGISTER_COMPONENT_TYPE(RangeManager)
diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp
index c562cbbdee..0fae9a8fdf 100644
--- a/source/simulation2/components/CCmpTemplateManager.cpp
+++ b/source/simulation2/components/CCmpTemplateManager.cpp
@@ -433,9 +433,15 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
CParamNode::LoadXMLString(out, "");
- // Corpses should include decay components and un-inactivate them
+ if (!corpse)
+ {
+ // Previews should always be visible in fog-of-war
+ CParamNode::LoadXMLString(out, "0true");
+ }
+
if (corpse)
{
+ // Corpses should include decay components and un-inactivate them
if (out.GetChild("Entity").GetChild("Decay").IsOk())
CParamNode::LoadXMLString(out, "");
}
@@ -459,6 +465,7 @@ void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode
permittedComponentTypes.insert("Decay");
permittedComponentTypes.insert("Cost");
permittedComponentTypes.insert("Sound");
+ permittedComponentTypes.insert("Vision");
CParamNode::LoadXMLString(out, "");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
@@ -478,4 +485,9 @@ void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode
// Don't provide population bonuses yet (but still do take up population cost)
if (out.GetChild("Entity").GetChild("Cost").IsOk())
CParamNode::LoadXMLString(out, "0");
+
+ // 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");
}
diff --git a/source/simulation2/components/CCmpTerrain.cpp b/source/simulation2/components/CCmpTerrain.cpp
index 8bb322552a..a5f8bdc883 100644
--- a/source/simulation2/components/CCmpTerrain.cpp
+++ b/source/simulation2/components/CCmpTerrain.cpp
@@ -94,7 +94,8 @@ public:
{
cmpRangeManager->SetBounds(entity_pos_t::Zero(), entity_pos_t::Zero(),
entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE),
- entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE));
+ entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE),
+ m_Terrain->GetVerticesPerSide());
}
}
diff --git a/source/simulation2/components/CCmpVision.cpp b/source/simulation2/components/CCmpVision.cpp
new file mode 100644
index 0000000000..0cd0022cc7
--- /dev/null
+++ b/source/simulation2/components/CCmpVision.cpp
@@ -0,0 +1,76 @@
+/* Copyright (C) 2010 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "simulation2/system/Component.h"
+#include "ICmpVision.h"
+
+class CCmpVision: public ICmpVision
+{
+public:
+ static void ClassInit(CComponentManager& componentManager)
+ {
+ }
+
+ DEFAULT_COMPONENT_ALLOCATOR(Vision)
+
+ entity_pos_t m_Range;
+ bool m_RetainInFog;
+
+ static std::string GetSchema()
+ {
+ return
+ ""
+ ""
+ ""
+ ""
+ ""
+ "";
+ }
+
+ virtual void Init(const CSimContext& UNUSED(context), const CParamNode& paramNode)
+ {
+ m_Range = paramNode.GetChild("Range").ToFixed();
+ m_RetainInFog = paramNode.GetChild("RetainInFog").ToBool();
+ }
+
+ virtual void Deinit(const CSimContext& UNUSED(context))
+ {
+ }
+
+ virtual void Serialize(ISerializer& UNUSED(serialize))
+ {
+ }
+
+ virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
+ {
+ Init(context, paramNode);
+ }
+
+ virtual entity_pos_t GetRange()
+ {
+ return m_Range;
+ }
+
+ virtual bool GetRetainInFog()
+ {
+ return m_RetainInFog;
+ }
+};
+
+REGISTER_COMPONENT_TYPE(Vision)
diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp
index d5ccbdb7ed..4bbcd5499f 100644
--- a/source/simulation2/components/CCmpVisualActor.cpp
+++ b/source/simulation2/components/CCmpVisualActor.cpp
@@ -21,6 +21,8 @@
#include "ICmpVisual.h"
#include "ICmpPosition.h"
+#include "ICmpRangeManager.h"
+#include "ICmpVision.h"
#include "simulation2/MessageTypes.h"
#include "graphics/Frustum.h"
@@ -52,7 +54,7 @@ public:
std::wstring m_ActorName;
CUnit* m_Unit;
- bool m_Hidden; // only valid between Interpolate and RenderSubmit
+ ICmpRangeManager::ELosVisibility m_Visibility; // only valid between Interpolate and RenderSubmit
// Current animation state
float m_AnimRunThreshold; // if non-zero this is the special walk/run mode
@@ -358,11 +360,15 @@ void CCmpVisualActor::Interpolate(float frameTime, float frameOffset)
// Disable rendering of the unit if it has no position
if (!cmpPosition->IsInWorld())
{
- m_Hidden = true;
+ m_Visibility = ICmpRangeManager::VIS_HIDDEN;
return;
}
- m_Hidden = false;
+ CmpPtr cmpRangeManager(GetSimContext(), SYSTEM_ENTITY);
+ m_Visibility = cmpRangeManager->GetLosVisibility(GetEntityId(), GetSimContext().GetCurrentDisplayedPlayer());
+
+ // Even if HIDDEN due to LOS, we need to set up the transforms
+ // so that projectiles will be launched from the right place
bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
@@ -377,11 +383,9 @@ void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& fr
if (m_Unit == NULL)
return;
- if (m_Hidden)
+ if (m_Visibility == ICmpRangeManager::VIS_HIDDEN)
return;
- // TODO: need to think about things like LOS here
-
CModel& model = m_Unit->GetModel();
model.ValidatePosition();
@@ -389,7 +393,10 @@ void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& fr
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetBounds()))
return;
- // TODO: model->SetShadingColor(CColor(1.0f, 1.0f, 1.0f, 1.0f) if visible, CColor(0.7f, 0.7f, 0.7f, 1.0f) if hidden in FOW)
+ if (m_Visibility == ICmpRangeManager::VIS_FOGGED)
+ model.SetShadingColor(CColor(0.7f, 0.7f, 0.7f, 1.0f));
+ else
+ model.SetShadingColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
collector.SubmitRecursive(&model);
}
diff --git a/source/simulation2/components/ICmpMinimap.h b/source/simulation2/components/ICmpMinimap.h
index 56a3cef765..e2ebf9b46b 100644
--- a/source/simulation2/components/ICmpMinimap.h
+++ b/source/simulation2/components/ICmpMinimap.h
@@ -20,6 +20,8 @@
#include "simulation2/system/Interface.h"
+#include "simulation2/helpers/Position.h"
+
/**
* Per-unit minimap data.
*/
@@ -31,7 +33,7 @@ public:
* If it should not be drawn, returns false; otherwise the arguments are set
* to the colour and world position.
*/
- virtual bool GetRenderData(u8& r, u8& g, u8& b, float& x, float& z) = 0;
+ virtual bool GetRenderData(u8& r, u8& g, u8& b, entity_pos_t& x, entity_pos_t& z) = 0;
DECLARE_INTERFACE_TYPE(Minimap)
};
diff --git a/source/simulation2/components/ICmpRangeManager.cpp b/source/simulation2/components/ICmpRangeManager.cpp
index 309248f642..1c461b31a9 100644
--- a/source/simulation2/components/ICmpRangeManager.cpp
+++ b/source/simulation2/components/ICmpRangeManager.cpp
@@ -29,4 +29,5 @@ DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableAct
DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("ResetActiveQuery", std::vector, ICmpRangeManager, ResetActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
+DEFINE_INTERFACE_METHOD_1("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, bool)
END_INTERFACE_WRAPPER(RangeManager)
diff --git a/source/simulation2/components/ICmpRangeManager.h b/source/simulation2/components/ICmpRangeManager.h
index e65ee934de..f0a9261e19 100644
--- a/source/simulation2/components/ICmpRangeManager.h
+++ b/source/simulation2/components/ICmpRangeManager.h
@@ -22,8 +22,14 @@
#include "simulation2/helpers/Position.h"
+#include "graphics/Terrain.h" // for CELL_SIZE
+
/**
- * Provides efficient range-based queries of the game world.
+ * Provides efficient range-based queries of the game world,
+ * and also LOS-based effects (fog of war).
+ *
+ * (These are somewhat distinct concepts but they share a lot of the implementation,
+ * so for efficiency they're combined into this class.)
*
* Possible use cases:
* - combat units need to detect targetable enemies entering LOS, so they can choose
@@ -67,8 +73,9 @@ public:
* Set the bounds of the world.
* Entities should not be outside the bounds (else efficiency will suffer).
* @param x0,z0,x1,z1 Coordinates of the corners of the world
+ * @param vertices Number of terrain vertices per side
*/
- virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) = 0;
+ virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices) = 0;
/**
* Execute a passive query.
@@ -123,6 +130,98 @@ public:
*/
virtual void SetDebugOverlay(bool enabled) = 0;
+ // LOS interface:
+
+ enum ELosState
+ {
+ LOS_UNEXPLORED = 0,
+ LOS_EXPLORED = 1,
+ LOS_VISIBLE = 2,
+ LOS_MASK = 3
+ };
+
+ enum ELosVisibility
+ {
+ VIS_HIDDEN,
+ VIS_FOGGED,
+ VIS_VISIBLE
+ };
+
+ /**
+ * Object providing efficient abstracted access to the LOS state.
+ * This depends on some implementation details of CCmpRangeManager.
+ *
+ * This *ignores* the GetLosRevealAll flag - callers should check that explicitly.
+ */
+ class CLosQuerier
+ {
+ private:
+ friend class CCmpRangeManager;
+
+ CLosQuerier(int player, const std::vector& data, ssize_t verticesPerSide) :
+ m_Data(data), m_VerticesPerSide(verticesPerSide)
+ {
+ if (player > 0 && player <= 16)
+ m_PlayerMask = LOS_MASK << (2*(player-1));
+ else
+ m_PlayerMask = 0;
+ }
+
+ public:
+ /**
+ * Returns whether the given vertex is visible (i.e. is within a unit's LOS).
+ * i and j must be in the range [0, verticesPerSide).
+ */
+ inline bool IsVisible(ssize_t i, ssize_t j)
+ {
+#ifndef NDEBUG
+ debug_assert(i >= 0 && j >= 0 && i < m_VerticesPerSide && j < m_VerticesPerSide);
+#endif
+ // Check high bit of each bit-pair
+ return (m_Data.at(j*m_VerticesPerSide + i) & m_PlayerMask) & 0xAAAAAAAAu;
+ }
+
+ /**
+ * Returns whether the given vertex is explored (i.e. was (or still is) within a unit's LOS).
+ * i and j must be in the range [0, verticesPerSide).
+ */
+ inline bool IsExplored(ssize_t i, ssize_t j)
+ {
+#ifndef NDEBUG
+ debug_assert(i >= 0 && j >= 0 && i < m_VerticesPerSide && j < m_VerticesPerSide);
+#endif
+ // Check low bit of each bit-pair
+ return (m_Data.at(j*m_VerticesPerSide + i) & m_PlayerMask) & 0x55555555u;
+ }
+
+ private:
+ u32 m_PlayerMask;
+ const std::vector& m_Data;
+ ssize_t m_VerticesPerSide;
+ };
+
+ /**
+ * Returns a CLosQuerier for checking whether vertex positions are visible to the given player.
+ */
+ virtual CLosQuerier GetLosQuerier(int player) = 0;
+
+ /**
+ * Returns the visibility status of the given entity, with respect to the given player.
+ * Returns VIS_HIDDEN if the entity doesn't exist or is not in the world.
+ * This respects the GetLosRevealAll flag.
+ */
+ virtual ELosVisibility GetLosVisibility(entity_id_t ent, int player) = 0;
+
+ /**
+ * Set globally whether the whole map should be made visible.
+ */
+ virtual void SetLosRevealAll(bool enabled) = 0;
+
+ /**
+ * Returns whether the whole map has been made visible to the given player.
+ */
+ virtual bool GetLosRevealAll(int player) = 0;
+
DECLARE_INTERFACE_TYPE(RangeManager)
};
diff --git a/source/simulation2/components/ICmpVision.cpp b/source/simulation2/components/ICmpVision.cpp
new file mode 100644
index 0000000000..dcd1604682
--- /dev/null
+++ b/source/simulation2/components/ICmpVision.cpp
@@ -0,0 +1,27 @@
+/* Copyright (C) 2010 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpVision.h"
+
+#include "simulation2/system/InterfaceScripted.h"
+
+BEGIN_INTERFACE_WRAPPER(Vision)
+DEFINE_INTERFACE_METHOD_0("GetRange", entity_pos_t, ICmpVision, GetRange)
+DEFINE_INTERFACE_METHOD_0("GetRetainInFog", bool, ICmpVision, GetRetainInFog)
+END_INTERFACE_WRAPPER(Vision)
diff --git a/source/simulation2/components/ICmpVision.h b/source/simulation2/components/ICmpVision.h
new file mode 100644
index 0000000000..2f4174cbd6
--- /dev/null
+++ b/source/simulation2/components/ICmpVision.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 2010 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPVISION
+#define INCLUDED_ICMPVISION
+
+#include "simulation2/system/Interface.h"
+
+#include "simulation2/helpers/Position.h"
+
+/**
+ * Vision (LOS etc) interface (typically implemented by a scripted component).
+ */
+class ICmpVision : public IComponent
+{
+public:
+ virtual entity_pos_t GetRange() = 0;
+
+ virtual bool GetRetainInFog() = 0;
+
+ DECLARE_INTERFACE_TYPE(Vision)
+};
+
+#endif // INCLUDED_ICMPVISION
diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp
index ef742eabc7..044a756bc9 100644
--- a/source/simulation2/helpers/Selection.cpp
+++ b/source/simulation2/helpers/Selection.cpp
@@ -22,14 +22,18 @@
#include "graphics/Camera.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpOwnership.h"
+#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpVisual.h"
-std::vector EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY)
+std::vector EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, int player)
{
CVector3D origin, dir;
camera.BuildCameraRay(screenX, screenY, origin, dir);
+ CmpPtr cmpRangeManager(simulation, SYSTEM_ENTITY);
+ debug_assert(!cmpRangeManager.null());
+
std::vector > hits; // (dist^2, entity) pairs
const CSimulation2::InterfaceList& ents = simulation.GetEntitiesWithInterface(IID_Selectable);
@@ -37,7 +41,9 @@ std::vector EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
{
entity_id_t ent = it->first;
- // TODO: ought to filter out units hidden by current player's LOS
+ // Ignore entities hidden by LOS
+ if (cmpRangeManager->GetLosVisibility(ent, player) == ICmpRangeManager::VIS_HIDDEN)
+ continue;
CmpPtr cmpVisual(simulation.GetSimContext(), ent);
if (cmpVisual.null())
@@ -81,6 +87,9 @@ std::vector EntitySelection::PickEntitiesInRect(CSimulation2& simul
std::vector hitEnts;
+ // (We don't bother doing LOS checks for these units, since we assume 'owner'
+ // will be the current local player and all their own units will always be visible)
+
const CSimulation2::InterfaceList& ents = simulation.GetEntitiesWithInterface(IID_Selectable);
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
diff --git a/source/simulation2/helpers/Selection.h b/source/simulation2/helpers/Selection.h
index 3bf631faf1..c2021a67b2 100644
--- a/source/simulation2/helpers/Selection.h
+++ b/source/simulation2/helpers/Selection.h
@@ -36,8 +36,9 @@ namespace EntitySelection
/**
* Finds all selectable entities under the given screen coordinates.
* Returns list ordered by closeness of picking, closest first.
+ * Restricted to entities in the LOS of @p player, but with any owner.
*/
-std::vector PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY);
+std::vector PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, int player);
/**
* Finds all selectable entities within the given screen coordinate rectangle,
diff --git a/source/simulation2/system/SimContext.cpp b/source/simulation2/system/SimContext.cpp
index df89692d05..f2e32a45e5 100644
--- a/source/simulation2/system/SimContext.cpp
+++ b/source/simulation2/system/SimContext.cpp
@@ -21,6 +21,8 @@
#include "ComponentManager.h"
+#include "ps/Game.h"
+
CSimContext::CSimContext() :
m_ComponentManager(NULL), m_UnitManager(NULL), m_Terrain(NULL)
{
@@ -63,3 +65,10 @@ ScriptInterface& CSimContext::GetScriptInterface() const
{
return GetComponentManager().GetScriptInterface();
}
+
+int CSimContext::GetCurrentDisplayedPlayer() const
+{
+ if (!g_Game)
+ return -1;
+ return g_Game->GetPlayerID();
+}
diff --git a/source/simulation2/system/SimContext.h b/source/simulation2/system/SimContext.h
index aa6f210fd5..f9214620cd 100644
--- a/source/simulation2/system/SimContext.h
+++ b/source/simulation2/system/SimContext.h
@@ -43,6 +43,12 @@ public:
ScriptInterface& GetScriptInterface() const;
+ /**
+ * Returns the player ID that the current display is being rendered for.
+ * Currently relies on g_Game being initialised (evil globals...)
+ */
+ int GetCurrentDisplayedPlayer() const;
+
private:
CComponentManager* m_ComponentManager;
CUnitManager* m_UnitManager;
diff --git a/source/simulation2/tests/test_CmpTemplateManager.h b/source/simulation2/tests/test_CmpTemplateManager.h
index efb16922e4..8352f8ca20 100644
--- a/source/simulation2/tests/test_CmpTemplateManager.h
+++ b/source/simulation2/tests/test_CmpTemplateManager.h
@@ -81,15 +81,26 @@ public:
const CParamNode* preview = tempMan->LoadTemplate(ent2, "preview|unit", -1);
TS_ASSERT(preview != NULL);
- TS_ASSERT_WSTR_EQUALS(preview->ToXML(), L"0uprightfalseexample");
+ TS_ASSERT_WSTR_EQUALS(preview->ToXML(),
+ L"0uprightfalse"
+ L"0true"
+ L"example");
const CParamNode* previewobstruct = tempMan->LoadTemplate(ent2, "preview|unitobstruct", -1);
TS_ASSERT(previewobstruct != NULL);
- TS_ASSERT_WSTR_EQUALS(previewobstruct->ToXML(), L"1.00uprightfalseexample");
+ TS_ASSERT_WSTR_EQUALS(previewobstruct->ToXML(),
+ L"1.0"
+ L""
+ L"0uprightfalse"
+ L"0true"
+ L"example");
const CParamNode* previewactor = tempMan->LoadTemplate(ent2, "preview|actor|example2", -1);
TS_ASSERT(previewactor != NULL);
- TS_ASSERT_WSTR_EQUALS(previewactor->ToXML(), L"0uprightfalseexample2");
+ TS_ASSERT_WSTR_EQUALS(previewactor->ToXML(),
+ L"0uprightfalse"
+ L"0true"
+ L"example2");
}
void test_LoadTemplate_scriptcache()
diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp
index ff8155526a..d2f236d275 100644
--- a/source/tools/atlas/GameInterface/ActorViewer.cpp
+++ b/source/tools/atlas/GameInterface/ActorViewer.cpp
@@ -40,6 +40,8 @@
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRangeManager.h"
+#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpUnitMotion.h"
#include "simulation2/components/ICmpVisual.h"
@@ -116,6 +118,15 @@ ActorViewer::ActorViewer()
// Start the simulation
m.Simulation2.LoadDefaultScripts();
m.Simulation2.ResetState();
+
+ // Tell the simulation we've already loaded the terrain
+ CmpPtr cmpTerrain(m.Simulation2, SYSTEM_ENTITY);
+ if (!cmpTerrain.null())
+ cmpTerrain->ReloadTerrain();
+
+ CmpPtr cmpRangeManager(m.Simulation2, SYSTEM_ENTITY);
+ if (!cmpRangeManager.null())
+ cmpRangeManager->SetLosRevealAll(true);
}
ActorViewer::~ActorViewer()
diff --git a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp
index 2aa6643a8a..2b632e8075 100644
--- a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp
+++ b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp
@@ -37,6 +37,7 @@
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRangeManager.h"
namespace
{
@@ -66,6 +67,11 @@ namespace
LDR_NonprogressiveLoad();
PSRETURN ret = g_Game->ReallyStartGame();
debug_assert(ret == PSRETURN_OK);
+
+ // Disable fog-of-war
+ CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
+ if (!cmpRangeManager.null())
+ cmpRangeManager->SetLosRevealAll(true);
}
}