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 @@ handleMinimapEvent(arguments[0]); - + + 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) };