diff --git a/binaries/data/mods/public/art/textures/ui/session/minimap_circle.png b/binaries/data/mods/public/art/textures/ui/session/minimap_circle.png
new file mode 100644
index 0000000000..2ecb105250
--- /dev/null
+++ b/binaries/data/mods/public/art/textures/ui/session/minimap_circle.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:914b07d2322896280e1689028ddf4f4d236d9d4876f14415bec9143176487f4e
+size 9894
diff --git a/binaries/data/mods/public/gui/session_new/session.xml b/binaries/data/mods/public/gui/session_new/session.xml
index e8bcb7c5ad..57ee21bd4c 100644
--- a/binaries/data/mods/public/gui/session_new/session.xml
+++ b/binaries/data/mods/public/gui/session_new/session.xml
@@ -402,11 +402,13 @@
-
+
+
diff --git a/binaries/data/mods/public/maps/scenarios/Hellenised_Egypt.xml b/binaries/data/mods/public/maps/scenarios/Hellenised_Egypt.xml
index ce9c1583a5..8c6035e2f4 100644
--- a/binaries/data/mods/public/maps/scenarios/Hellenised_Egypt.xml
+++ b/binaries/data/mods/public/maps/scenarios/Hellenised_Egypt.xml
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3db3c590378c4d11150bf1241e0c4a8475f3b5761c3e833e782a2a984e663aad
-size 578844
+oid sha256:c83270ab97899b6e859af6c03f44d87712d86d7cfbb29ad6f6c61b9f4a2179f8
+size 578866
diff --git a/binaries/data/mods/public/simulation/helpers/Setup.js b/binaries/data/mods/public/simulation/helpers/Setup.js
index 91ed181d48..b549732880 100644
--- a/binaries/data/mods/public/simulation/helpers/Setup.js
+++ b/binaries/data/mods/public/simulation/helpers/Setup.js
@@ -19,10 +19,17 @@ function LoadMapSettings(settings)
if (cmpRangeManager)
cmpRangeManager.SetLosRevealAll(true);
}
+
+ if (settings.CircularMap)
+ {
+ var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ if (cmpRangeManager)
+ cmpRangeManager.SetLosCircular(true);
+ }
var cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
if (settings.GameType)
- {
+ {
cmpEndGameManager.SetGameType(settings.GameType);
}
cmpEndGameManager.Start();
diff --git a/source/gui/MiniMap.cpp b/source/gui/MiniMap.cpp
index 627fa6de7a..abe8171926 100644
--- a/source/gui/MiniMap.cpp
+++ b/source/gui/MiniMap.cpp
@@ -56,6 +56,7 @@ CMiniMap::CMiniMap()
m_LOSTexture(0), m_TerrainDirty(true)
{
AddSetting(GUIST_CColor, "fov_wedge_color");
+ AddSetting(GUIST_bool, "circular");
AddSetting(GUIST_CStr, "tooltip");
AddSetting(GUIST_CStr, "tooltip_style");
m_Clicking = false;
@@ -125,26 +126,51 @@ void CMiniMap::HandleMessage(const SGUIMessage &Message)
} // switch
}
+void CMiniMap::GetMouseWorldCoordinates(float& x, float& z)
+{
+ // Determine X and Z according to proportion of mouse position and minimap
+
+ CPos mousePos = GetMousePos();
+
+ float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth();
+ float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight();
+
+ float angle = GetAngle();
+
+ x = CELL_SIZE * m_MapSize * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5) + 0.5);
+ z = CELL_SIZE * m_MapSize * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5) + 0.5);
+}
+
void CMiniMap::SetCameraPos()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
- CPos mousePos = GetMousePos();
- target.X = CELL_SIZE * m_MapSize * ((mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth());
- target.Z = CELL_SIZE * m_MapSize * ((m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight());
+ GetMouseWorldCoordinates(target.X, target.Z);
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
+float CMiniMap::GetAngle()
+{
+ bool circular;
+ GUI::GetSetting(this, "circular", circular);
+
+ // If this is a circular map, rotate it to match the camera angle
+ if (circular)
+ {
+ CVector3D cameraIn = m_Camera->m_Orientation.GetIn();
+ return -atan2(cameraIn.X, cameraIn.Z);
+ }
+
+ // Otherwise there's no rotation
+ return 0.f;
+}
+
void CMiniMap::FireWorldClickEvent(int button, int clicks)
{
- // Determine X and Z according to proportion of mouse position and minimap
- CPos MousePos = GetMousePos();
- float x = CELL_SIZE * m_MapSize *
- ((MousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth());
- float z = CELL_SIZE * m_MapSize *
- ((m_CachedActualSize.bottom - MousePos.y) / m_CachedActualSize.GetHeight());
+ float x, z;
+ GetMouseWorldCoordinates(x, z);
CScriptValRooted coords;
g_ScriptingHost.GetScriptInterface().Eval("({})", coords);
@@ -194,10 +220,10 @@ void CMiniMap::DrawViewRect()
// Draw the viewing rectangle with the ScEd's conversion algorithm
glBegin(GL_LINE_LOOP);
- glVertex2f(x+ViewRect[0][0], y-ViewRect[0][1]);
- glVertex2f(x+ViewRect[1][0], y-ViewRect[1][1]);
- glVertex2f(x+ViewRect[2][0], y-ViewRect[2][1]);
- glVertex2f(x+ViewRect[3][0], y-ViewRect[3][1]);
+ glVertex2f(ViewRect[0][0], -ViewRect[0][1]);
+ glVertex2f(ViewRect[1][0], -ViewRect[1][1]);
+ glVertex2f(ViewRect[2][0], -ViewRect[2][1]);
+ glVertex2f(ViewRect[3][0], -ViewRect[3][1]);
glEnd();
// restore state
@@ -212,6 +238,25 @@ struct MinimapUnitVertex
float x, y;
};
+void CMiniMap::DrawTexture(float coordMax, float angle, float x, float y, float x2, float y2, float z)
+{
+ // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m)
+ const float s = sin(angle);
+ const float c = cos(angle);
+ const float m = coordMax / 2.f;
+
+ glBegin(GL_QUADS);
+ glTexCoord2f(m*(-c + s + 1.f), m*(-c + -s + 1.f));
+ glVertex3f(x, y, z);
+ glTexCoord2f(m*(c + s + 1.f), m*(-c + s + 1.f));
+ glVertex3f(x2, y, z);
+ glTexCoord2f(m*(c + -s + 1.f), m*(c + s + 1.f));
+ glVertex3f(x2, y2, z);
+ glTexCoord2f(m*(-c + -s + 1.f), m*(c + -s + 1.f));
+ glVertex3f(x, y2, z);
+ glEnd();
+}
+
void CMiniMap::Draw()
{
PROFILE("minimap");
@@ -220,7 +265,7 @@ void CMiniMap::Draw()
// happens when the game is started, so abort until then.
if(!(GetGUI() && g_Game && g_Game->IsGameStarted()))
return;
-
+
glDisable(GL_DEPTH_TEST);
// Set our globals in case they hadn't been set before
@@ -252,27 +297,20 @@ void CMiniMap::Draw()
RebuildLOSTexture();
}
- const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize;
- const float losTexCoordMax = (float)(m_LOSMapSize - 1) / (float)m_LOSTextureSize;
const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top;
const float z = GetBufferedZ();
+ const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize;
+ const float losTexCoordMax = (float)(m_LOSMapSize - 1) / (float)m_LOSTextureSize;
+
+ const float angle = GetAngle();
// Draw the main textured quad
g_Renderer.BindTexture(0, m_TerrainTexture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glBegin(GL_QUADS);
- glTexCoord2f(0.0f, 0.0f);
- glVertex3f(x, y, z);
- glTexCoord2f(texCoordMax, 0.0f);
- glVertex3f(x2, y, z);
- glTexCoord2f(texCoordMax, texCoordMax);
- glVertex3f(x2, y2, z);
- glTexCoord2f(0.0f, texCoordMax);
- glVertex3f(x, y2, z);
- glEnd();
+ DrawTexture(texCoordMax, angle, x, y, x2, y2, z);
/* // TODO: reimplement with new sim system
// Shade territories by player
@@ -340,19 +378,18 @@ void CMiniMap::Draw()
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glBegin(GL_QUADS);
glColor3f(0.0f, 0.0f, 0.0f);
- glTexCoord2f(0.0f, 0.0f);
- glVertex3f(x, y, z);
- glTexCoord2f(losTexCoordMax, 0.0f);
- glVertex3f(x2, y, z);
- glTexCoord2f(losTexCoordMax, losTexCoordMax);
- glVertex3f(x2, y2, z);
- glTexCoord2f(0.0f, losTexCoordMax);
- glVertex3f(x, y2, z);
- glEnd();
+ DrawTexture(losTexCoordMax, angle, x, y, x2, y2, z);
glDisable(GL_BLEND);
+ // Set up the matrix for drawing points and lines
+ glPushMatrix();
+ glTranslatef(x, y, z);
+ // Rotate around the center of the map
+ glTranslatef((x2-x)/2.f, (y2-y)/2.f, 0.f);
+ glRotatef(angle * 180.f/M_PI, 0.f, 0.f, 1.f);
+ glTranslatef(-(x2-x)/2.f, -(y2-y)/2.f, 0.f);
+
PROFILE_START("minimap units");
// Don't enable GL_POINT_SMOOTH because it's far too slow
@@ -382,8 +419,8 @@ void CMiniMap::Draw()
if (vis != ICmpRangeManager::VIS_HIDDEN)
{
v.a = 255;
- v.x = x + posX.ToFloat()*sx;
- v.y = y - posZ.ToFloat()*sy;
+ v.x = posX.ToFloat()*sx;
+ v.y = -posZ.ToFloat()*sy;
vertexArray.push_back(v);
}
}
@@ -391,22 +428,19 @@ void CMiniMap::Draw()
if (!vertexArray.empty())
{
- glPushMatrix();
- glTranslatef(0, 0, z);
-
glInterleavedArrays(GL_C4UB_V2F, sizeof(MinimapUnitVertex), &vertexArray[0]);
glDrawArrays(GL_POINTS, 0, (GLsizei)vertexArray.size());
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
-
- glPopMatrix();
}
PROFILE_END("minimap units");
DrawViewRect();
+ glPopMatrix();
+
// Reset everything back to normal
glPointSize(1.0f);
glEnable(GL_TEXTURE_2D);
diff --git a/source/gui/MiniMap.h b/source/gui/MiniMap.h
index f3153a6869..a12bc87203 100644
--- a/source/gui/MiniMap.h
+++ b/source/gui/MiniMap.h
@@ -82,7 +82,13 @@ protected:
GLsizei m_TextureSize;
GLsizei m_LOSTextureSize;
- void DrawViewRect(); // split out of Draw
+ void DrawTexture(float coordMax, float angle, float x, float y, float x2, float y2, float z);
+
+ void DrawViewRect();
+
+ void GetMouseWorldCoordinates(float& x, float& z);
+
+ float GetAngle();
};
#endif
diff --git a/source/renderer/PatchRData.cpp b/source/renderer/PatchRData.cpp
index d89419cf40..d6ea4a8133 100644
--- a/source/renderer/PatchRData.cpp
+++ b/source/renderer/PatchRData.cpp
@@ -436,7 +436,7 @@ void CPatchRData::Update()
SColor4ub baseColour = terrain->GetBaseColour();
CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
- if (cmpRangeManager.null() || cmpRangeManager->GetLosRevealAll(g_Game->GetPlayerID()))
+ if (cmpRangeManager.null())
{
for (ssize_t j = 0; j < vsize; ++j)
{
diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp
index 7bc86be6a4..72bac93d9c 100644
--- a/source/simulation2/components/CCmpRangeManager.cpp
+++ b/source/simulation2/components/CCmpRangeManager.cpp
@@ -184,6 +184,7 @@ public:
// LOS state:
bool m_LosRevealAll;
+ bool m_LosCircular;
i32 m_TerrainVerticesPerSide;
// Counts of units seeing vertex, per vertex, per player (starting with player 0).
@@ -197,6 +198,10 @@ public:
std::vector m_LosState;
static const int MAX_LOS_PLAYER_ID = 16;
+ // Special static visibility data for the "reveal whole map" mode
+ // (TODO: this is usually a waste of memory)
+ std::vector m_LosStateRevealed;
+
static std::string GetSchema()
{
return "";
@@ -216,6 +221,7 @@ public:
ResetSubdivisions(entity_pos_t::FromInt(1), entity_pos_t::FromInt(1));
m_LosRevealAll = false;
+ m_LosCircular = false;
m_TerrainVerticesPerSide = 0;
}
@@ -236,6 +242,7 @@ public:
SerializeMap()(serialize, "entity data", m_EntityData);
serialize.Bool("los reveal all", m_LosRevealAll);
+ serialize.Bool("los circular", m_LosCircular);
serialize.NumberI32_Unbounded("terrain verts per side", m_TerrainVerticesPerSide);
// We don't serialize m_Subdivision, m_LosPlayerCounts, m_LosState
@@ -254,7 +261,7 @@ public:
SerializeCommon(deserialize);
// Reinitialise subdivisions and LOS data
- SetBounds(m_WorldX0, m_WorldZ0, m_WorldX1, m_WorldZ1, m_TerrainVerticesPerSide);
+ ResetDerivedData();
}
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
@@ -403,16 +410,28 @@ public:
m_WorldZ1 = z1;
m_TerrainVerticesPerSide = vertices;
- debug_assert(x0.IsZero() && z0.IsZero()); // don't bother implementing non-zero offsets yet
- ResetSubdivisions(x1, z1);
+ ResetDerivedData();
+ }
+
+ // Reinitialise subdivisions and LOS data, based on entity data
+ void ResetDerivedData()
+ {
+ debug_assert(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet
+ ResetSubdivisions(m_WorldX1, m_WorldZ1);
m_LosPlayerCounts.clear();
m_LosPlayerCounts.resize(MAX_LOS_PLAYER_ID+1);
m_LosState.clear();
- m_LosState.resize(vertices*vertices);
+ m_LosState.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
+ m_LosStateRevealed.clear();
+ m_LosStateRevealed.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
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));
+
+ for (ssize_t j = 0; j < m_TerrainVerticesPerSide; ++j)
+ for (ssize_t i = 0; i < m_TerrainVerticesPerSide; ++i)
+ m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = LosIsOffWorld(i, j) ? 0 : 0xFFFFFFFFu;
}
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
@@ -728,7 +747,10 @@ private:
virtual CLosQuerier GetLosQuerier(int player)
{
- return CLosQuerier(player, m_LosState, m_TerrainVerticesPerSide);
+ if (m_LosRevealAll)
+ return CLosQuerier(player, m_LosStateRevealed, m_TerrainVerticesPerSide);
+ else
+ return CLosQuerier(player, m_LosState, m_TerrainVerticesPerSide);
}
virtual ELosVisibility GetLosVisibility(entity_id_t ent, int player)
@@ -740,19 +762,24 @@ private:
if (cmpPosition.null() || !cmpPosition->IsInWorld())
return VIS_HIDDEN;
- // Global flag makes all positioned entities visible
- if (m_LosRevealAll)
- return VIS_VISIBLE;
-
- // Visible if within a visible region
-
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();
+ // Global flag makes all positioned entities visible
+ if (m_LosRevealAll)
+ {
+ if (LosIsOffWorld(i, j))
+ return VIS_HIDDEN;
+ else
+ return VIS_VISIBLE;
+ }
+
+ // Visible if within a visible region
+
+ CLosQuerier los(player, m_LosState, m_TerrainVerticesPerSide);
+
if (los.IsVisible(i, j))
return VIS_VISIBLE;
@@ -782,6 +809,35 @@ private:
return m_LosRevealAll;
}
+ virtual void SetLosCircular(bool enabled)
+ {
+ m_LosCircular = enabled;
+
+ ResetDerivedData();
+ }
+
+ /**
+ * Returns whether the given vertex is outside the normal bounds of the world
+ * (i.e. outside the range of a circular map)
+ */
+ inline bool LosIsOffWorld(ssize_t i, ssize_t j)
+ {
+ if (m_LosCircular)
+ {
+ // With a circular map, vertex is off-world if hypot(i - size/2, j - size/2) > size/2:
+
+ ssize_t dist2 = (i - m_TerrainVerticesPerSide/2)*(i - m_TerrainVerticesPerSide/2)
+ + (j - m_TerrainVerticesPerSide/2)*(j - m_TerrainVerticesPerSide/2);
+
+ if (dist2 >= m_TerrainVerticesPerSide*m_TerrainVerticesPerSide/4)
+ return true;
+ }
+
+ // With a square map, nothing is off-world
+
+ return false;
+ }
+
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
* amount is +1 or -1.
@@ -795,7 +851,8 @@ private:
// 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)));
+ if (!LosIsOffWorld(i, j))
+ m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
}
counts[idx] += amount;
@@ -803,6 +860,7 @@ private:
// Decreasing from non-zero to zero - move from visible+explored to explored
if (counts[idx] == 0 && amount < 0)
{
+ // (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
}
}
diff --git a/source/simulation2/components/ICmpRangeManager.cpp b/source/simulation2/components/ICmpRangeManager.cpp
index d5303dd0b9..0e328b3579 100644
--- a/source/simulation2/components/ICmpRangeManager.cpp
+++ b/source/simulation2/components/ICmpRangeManager.cpp
@@ -44,4 +44,5 @@ DEFINE_INTERFACE_METHOD_1("GetEntitiesByPlayer", std::vector, ICmpR
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
DEFINE_INTERFACE_METHOD_1("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, bool)
DEFINE_INTERFACE_METHOD_2("GetLosVisibility", std::string, ICmpRangeManager, GetLosVisibility_wrapper, entity_id_t, int)
+DEFINE_INTERFACE_METHOD_1("SetLosCircular", void, ICmpRangeManager, SetLosCircular, bool)
END_INTERFACE_WRAPPER(RangeManager)
diff --git a/source/simulation2/components/ICmpRangeManager.h b/source/simulation2/components/ICmpRangeManager.h
index e2946a3887..8e49fa3f69 100644
--- a/source/simulation2/components/ICmpRangeManager.h
+++ b/source/simulation2/components/ICmpRangeManager.h
@@ -244,6 +244,11 @@ public:
*/
virtual bool GetLosRevealAll(int player) = 0;
+ /**
+ * Set the LOS to be restricted to a circular map.
+ */
+ virtual void SetLosCircular(bool enabled) = 0;
+
DECLARE_INTERFACE_TYPE(RangeManager)
};