Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg
+++ binaries/data/config/default.cfg
@@ -305,6 +305,7 @@
rotate.cw = RightBracket ; Rotate building placement preview clockwise
rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise
snaptoedges = Ctrl ; Modifier to align new structures with nearby existing structure
+flare = K ; Modifier to send a flare to your allies
; Overlays
showstatusbars = Tab ; Toggle display of status bars
devcommands.toggle = "Alt+D" ; Toggle developer commands panel
Index: binaries/data/mods/public/art/textures/cursors/action-flare.txt
===================================================================
--- /dev/null
+++ binaries/data/mods/public/art/textures/cursors/action-flare.txt
@@ -0,0 +1 @@
+1 1
Index: binaries/data/mods/public/gui/manual/intro.txt
===================================================================
--- binaries/data/mods/public/gui/manual/intro.txt
+++ binaries/data/mods/public/gui/manual/intro.txt
@@ -116,6 +116,7 @@
• Otherwise – Attack move (by default all enemy units and structures along the way are targeted)
Ctrl + Q + Right Click with unit(s) selected – Attack move, only units along the way are targeted
Ctrl + Mouse Move near structures – Align the new structure with an existing nearby structure
+ K + Right Click – Send a flare to your allies
[font="sans-bold-14"]Overlays[font="sans-14"]
Alt + G – Toggle the GUI
Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js
+++ binaries/data/mods/public/gui/session/input.js
@@ -17,6 +17,7 @@
const ACTION_REPAIR = 2;
const ACTION_GUARD = 3;
const ACTION_PATROL = 4;
+const ACTION_FLARE = 5;
var preSelectedAction = ACTION_NONE;
const INPUT_NORMAL = 0;
@@ -78,6 +79,16 @@
*/
var clickedEntity = INVALID_ENTITY;
+/**
+ * Store the last time the flare functionality was used to prevent overusage.
+ */
+var g_LastFlareTime;
+
+/**
+ * The duration in ms for which we disable flaring after each flare to prevent overusage.
+ */
+const g_FlareCooldown = 1000;
+
// Same double-click behaviour for hotkey presses
const doublePressTime = 500;
var doublePressTimer = 0;
@@ -208,6 +219,10 @@
*/
function determineAction(x, y, fromMiniMap)
{
+ let r = g_MiniMapPanel.preSelectedActionCheck() || g_MiniMapPanel.hotkeyActionCheck();
+ if (r)
+ return r;
+
let selection = g_Selection.toList();
if (!selection.length)
{
@@ -244,7 +259,7 @@
for (let action of g_UnitActionsSortedKeys)
if (g_UnitActions[action].preSelectedActionCheck)
{
- let r = g_UnitActions[action].preSelectedActionCheck(target, selection);
+ r = g_UnitActions[action].preSelectedActionCheck(target, selection);
if (r)
return r;
}
@@ -1248,6 +1263,20 @@
function handleUnitAction(target, action)
{
+ if (action.type == "flare")
+ {
+ let now = Date.now();
+ if (g_LastFlareTime && now < g_LastFlareTime + g_FlareCooldown)
+ return false;
+
+ g_LastFlareTime = now;
+ displayFlare(target, Engine.GetPlayerID());
+ Engine.PostNetworkCommand({
+ "type": "map-flare",
+ "target": target
+ });
+ return true;
+ }
if (!g_UnitActions[action.type] || !g_UnitActions[action.type].execute)
{
error("Invalid action.type " + action.type);
Index: binaries/data/mods/public/gui/session/messages.js
===================================================================
--- binaries/data/mods/public/gui/session/messages.js
+++ binaries/data/mods/public/gui/session/messages.js
@@ -265,6 +265,17 @@
}
global.music.setLocked(notification.lock);
+ },
+ "map-flare": function(notification, player)
+ {
+ // Don't display for the player that did the flare because they will see it immediately
+ if (player != Engine.GetPlayerID() && g_Players[player].isMutualAlly[Engine.GetPlayerID()])
+ {
+ displayFlare(notification.target, player);
+
+ // TODO: Create a new sound and play it here
+ Engine.PlayUISound("audio/interface/alarm/alarmally_1.ogg", false);
+ }
}
};
Index: binaries/data/mods/public/gui/session/minimap/MiniMap.js
===================================================================
--- binaries/data/mods/public/gui/session/minimap/MiniMap.js
+++ binaries/data/mods/public/gui/session/minimap/MiniMap.js
@@ -6,9 +6,10 @@
{
constructor()
{
- Engine.GetGUIObjectByName("minimap").onWorldClick = this.onWorldClick.bind(this);
- Engine.GetGUIObjectByName("minimap").onMouseEnter = this.onMouseEnter.bind(this);
- Engine.GetGUIObjectByName("minimap").onMouseLeave = this.onMouseLeave.bind(this);
+ this.miniMap = Engine.GetGUIObjectByName("minimap");
+ this.miniMap.onWorldClick = this.onWorldClick.bind(this);
+ this.miniMap.onMouseEnter = this.onMouseEnter.bind(this);
+ this.miniMap.onMouseLeave = this.onMouseLeave.bind(this);
this.mouseIsOverMiniMap = false;
}
@@ -64,4 +65,9 @@
{
return this.mouseIsOverMiniMap;
}
+
+ flare(target, playerID)
+ {
+ return this.miniMap.flare([target.x, target.z], g_DiplomacyColors.getPlayerColor(playerID));
+ }
}
Index: binaries/data/mods/public/gui/session/minimap/MiniMap.xml
===================================================================
--- binaries/data/mods/public/gui/session/minimap/MiniMap.xml
+++ binaries/data/mods/public/gui/session/minimap/MiniMap.xml
@@ -36,4 +36,14 @@
tooltip_style="sessionToolTip"
hotkey="session.diplomacycolors"
/>
+
+
+
Index: binaries/data/mods/public/gui/session/minimap/MiniMapFlareButton.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/session/minimap/MiniMapFlareButton.js
@@ -0,0 +1,50 @@
+/**
+ * If the button that this class manages is pressed, an idle unit having one of the given classes is selected.
+ */
+class MiniMapFlareButton
+{
+ constructor()
+ {
+ this.flareButton = Engine.GetGUIObjectByName("flareButton");
+ this.flareButton.onPress = this.onPress.bind(this);
+ registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
+ }
+
+ onHotkeyChange()
+ {
+ this.flareButton.tooltip =
+ colorizeHotkey("%(hotkey)s" + " ", "session.flare") +
+ translate(this.Tooltip);
+ }
+
+ onPress()
+ {
+ inputState = INPUT_PRESELECTEDACTION;
+ preSelectedAction = ACTION_FLARE;
+ }
+
+ getAction(target)
+ {
+ return {
+ "type": "flare",
+ "cursor": "action-flare",
+ "target": target
+ };
+ }
+
+ hotkeyActionCheck(target, selection)
+ {
+ if (!Engine.HotkeyIsPressed("session.flare"))
+ return false;
+ return this.getAction(target);
+ }
+
+ preSelectedActionCheck(target, selection)
+ {
+ if (preSelectedAction != ACTION_FLARE)
+ return false;
+ return this.getAction(target);
+ }
+}
+
+MiniMapFlareButton.prototype.Tooltip = markForTranslation("Send a flare to your allies");
Index: binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js
===================================================================
--- binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js
+++ binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js
@@ -7,9 +7,25 @@
{
this.diplomacyColorsButton = new MiniMapDiplomacyColorsButton(diplomacyColors);
this.idleWorkerButton = new MiniMapIdleWorkerButton(playerViewControl, idleWorkerClasses);
+ this.flareButton = new MiniMapFlareButton();
this.miniMap = new MiniMap();
}
+ hotkeyActionCheck(target, selection)
+ {
+ return this.flareButton.hotkeyActionCheck(target, selection);
+ }
+
+ preSelectedActionCheck(target, selection)
+ {
+ return this.flareButton.preSelectedActionCheck(target, selection);
+ }
+
+ flare(target, playerID)
+ {
+ return this.miniMap.flare(target, playerID);
+ }
+
isMouseOverMiniMap()
{
return this.miniMap.isMouseOverMiniMap();
Index: binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js
+++ binaries/data/mods/public/gui/session/unit_actions.js
@@ -3,7 +3,9 @@
* given a command type.
*/
var g_TargetMarker = {
- "move": "special/target_marker"
+ "move": "special/target_marker",
+ // TODO: Add proper marker
+ "map_flare": "special/target_marker"
};
/**
@@ -1585,6 +1587,16 @@
});
}
+function displayFlare(target, playerID)
+{
+ Engine.GuiInterfaceCall("AddTargetMarker", {
+ "template": g_TargetMarker.map_flare,
+ "x": target.x,
+ "z": target.z
+ });
+ g_MiniMapPanel.flare(target, playerID)
+}
+
function findGatherType(gatherer, supply)
{
if (!gatherer.resourceGatherRates || !supply)
Index: binaries/data/mods/public/shaders/glsl/minimap.fs
===================================================================
--- binaries/data/mods/public/shaders/glsl/minimap.fs
+++ binaries/data/mods/public/shaders/glsl/minimap.fs
@@ -1,6 +1,6 @@
#version 110
-#if MINIMAP_BASE || MINIMAP_LOS
+#if MINIMAP_BASE || MINIMAP_LOS || MINIMAP_FLARE
uniform sampler2D baseTex;
varying vec2 v_tex;
#endif
@@ -9,7 +9,7 @@
varying vec3 color;
#endif
-#if MINIMAP_LINE
+#if MINIMAP_LINE || MINIMAP_FLARE
uniform vec4 color;
#endif
@@ -27,6 +27,10 @@
gl_FragColor = vec4(color, 1.0);
#endif
+ #if MINIMAP_FLARE
+ gl_FragColor = texture2D(baseTex, v_tex) * color;
+ #endif
+
#if MINIMAP_LINE
gl_FragColor = color;
#endif
Index: binaries/data/mods/public/shaders/glsl/minimap.vs
===================================================================
--- binaries/data/mods/public/shaders/glsl/minimap.vs
+++ binaries/data/mods/public/shaders/glsl/minimap.vs
@@ -4,7 +4,7 @@
uniform mat4 textureTransform;
uniform float pointSize;
-#if MINIMAP_BASE || MINIMAP_LOS
+#if MINIMAP_BASE || MINIMAP_LOS || MINIMAP_FLARE
attribute vec3 a_vertex;
attribute vec2 a_uv0;
varying vec2 v_tex;
@@ -22,7 +22,7 @@
void main()
{
- #if MINIMAP_BASE || MINIMAP_LOS
+ #if MINIMAP_BASE || MINIMAP_LOS || MINIMAP_FLARE
gl_Position = transform * vec4(a_vertex, 1.0);
v_tex = (textureTransform * vec4(a_uv0, 0.0, 1.0)).xy;
#endif
Index: binaries/data/mods/public/shaders/glsl/minimap.xml
===================================================================
--- binaries/data/mods/public/shaders/glsl/minimap.xml
+++ binaries/data/mods/public/shaders/glsl/minimap.xml
@@ -6,7 +6,7 @@
-
+
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js
+++ binaries/data/mods/public/simulation/helpers/Commands.js
@@ -845,6 +845,16 @@
cmpResourceDropsite.SetSharing(cmd.shared);
}
},
+
+ "map-flare": function(player, cmd, data)
+ {
+ let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ cmpGuiInterface.PushNotification({
+ "type": "map-flare",
+ "players": [player],
+ "target": cmd.target
+ });
+ },
};
/**
Index: source/gui/ObjectTypes/CMiniMap.h
===================================================================
--- source/gui/ObjectTypes/CMiniMap.h
+++ source/gui/ObjectTypes/CMiniMap.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2019 Wildfire Games.
+/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#define INCLUDED_MINIMAP
#include "graphics/ShaderProgramPtr.h"
+#include "graphics/Texture.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "renderer/VertexArray.h"
@@ -26,6 +27,13 @@
class CMatrix3D;
class CTerrain;
+struct MapFlareObj
+{
+ CPos pos;
+ CColor color;
+ double time;
+};
+
class CMiniMap : public IGUIObject
{
GUI_OBJECT(CMiniMap)
@@ -41,12 +49,22 @@
protected:
virtual void Draw();
+ virtual void RegisterScriptFunctions();
+
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
+ * Script accessors to this GUI object.
+ */
+ static JSFunctionSpec JSI_methods[];
+
+ static bool FlareProxy(JSContext* cx, uint argc, JS::Value* vp);
+ bool Flare(JSContext* cx, uint argc, JS::Value* vp);
+
+ /**
* @see IGUIObject#IsMouseOver()
*/
virtual bool IsMouseOver() const;
@@ -74,9 +92,13 @@
//Whether or not the mouse is currently down
bool m_Clicking;
+ std::deque m_MapFlares;
+
// minimap texture handles
GLuint m_TerrainTexture;
+ CTexturePtr m_FlareTextures[16];
+
// texture data
u32* m_TerrainData;
@@ -101,10 +123,14 @@
void DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const;
- void DrawViewRect(CMatrix3D transform) const;
+ void DrawViewRect(const CMatrix3D& transform) const;
+
+ void DrawFlare(CShaderProgramPtr shader, const MapFlareObj& flare, double cur_time) const;
void GetMouseWorldCoordinates(float& x, float& z) const;
+ CPos GetMapCoordinates(float x, float z) const;
+
float GetAngle() const;
VertexIndexArray m_IndexArray;
Index: source/gui/ObjectTypes/CMiniMap.cpp
===================================================================
--- source/gui/ObjectTypes/CMiniMap.cpp
+++ source/gui/ObjectTypes/CMiniMap.cpp
@@ -33,6 +33,7 @@
#include "lib/external_libraries/libsdl.h"
#include "lib/ogl.h"
#include "lib/timer.h"
+#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
@@ -128,6 +129,12 @@
CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
}
m_HalfBlinkDuration = blinkDuration/2;
+
+ for (int i = 0; i < 16; ++i)
+ {
+ CTextureProperties textureProps(L"art/textures/animated/minimap-flare/frame" + CStrW::FromInt(i) + L".png");
+ m_FlareTextures[i] = g_Renderer.GetTextureManager().CreateTexture(textureProps);
+ }
}
CMiniMap::~CMiniMap()
@@ -218,6 +225,14 @@
z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5);
}
+CPos CMiniMap::GetMapCoordinates(float x, float z) const
+{
+ const float width = m_CachedActualSize.GetWidth();
+ const float height = m_CachedActualSize.GetHeight();
+ const float invTileMapSize = 1.0f / (TERRAIN_TILE_SIZE * m_MapSize);
+ return CPos(width * x * invTileMapSize, height * z * invTileMapSize);
+}
+
void CMiniMap::SetCameraPos()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
@@ -257,14 +272,13 @@
// This sets up and draws the rectangle on the minimap
// which represents the view of the camera in the world.
-void CMiniMap::DrawViewRect(CMatrix3D transform) const
+void CMiniMap::DrawViewRect(const CMatrix3D& transform) const
{
// Compute the camera frustum intersected with a fixed-height plane.
// Use the water height as a fixed base height, which should be the lowest we can go
float h = g_Renderer.GetWaterManager()->m_WaterHeight;
const float width = m_CachedActualSize.GetWidth();
const float height = m_CachedActualSize.GetHeight();
- const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize);
CVector3D hitPt[4];
hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h);
@@ -272,19 +286,16 @@
hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h);
hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h);
- float ViewRect[4][2];
+ CPos ViewRect[4];
for (int i = 0; i < 4; ++i)
- {
// convert to minimap space
- ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize);
- ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize);
- }
+ ViewRect[i] = GetMapCoordinates(hitPt[i].X, hitPt[i].Z);
float viewVerts[] = {
- ViewRect[0][0], -ViewRect[0][1],
- ViewRect[1][0], -ViewRect[1][1],
- ViewRect[2][0], -ViewRect[2][1],
- ViewRect[3][0], -ViewRect[3][1]
+ ViewRect[0].x, -ViewRect[0].y,
+ ViewRect[1].x, -ViewRect[1].y,
+ ViewRect[2].x, -ViewRect[2].y,
+ ViewRect[3].x, -ViewRect[3].y
};
// Enable Scissoring to restrict the rectangle to only the minimap.
@@ -316,6 +327,54 @@
glDisable(GL_SCISSOR_TEST);
}
+void CMiniMap::DrawFlare(CShaderProgramPtr shader, const MapFlareObj& flare, double cur_time) const
+{
+ float step = 35;//std::fmod((cur_time - flare.time) * 15, 35);
+ float x = flare.pos.x + step;
+ float y = -flare.pos.y-step;
+ float x2 = flare.pos.x - step;
+ float y2 = -flare.pos.y+step;
+ float z = 0;
+
+ float quadTex[] = {
+ 0, 1,
+ 1, 1,
+ 1, 0,
+
+ 1, 0,
+ 0, 0,
+ 0, 1
+ };
+ float quadVerts[] = {
+ x, y, z,
+ x2, y, z,
+ x2, y2, z,
+
+ x2, y2, z,
+ x, y2, z,
+ x, y, z
+ };
+
+ int flooredStep = floor((cur_time - flare.time) * 16);
+
+ shader->Uniform(str_color, flare.color);
+ shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
+ shader->VertexPointer(3, GL_FLOAT, 0, quadVerts);
+ shader->AssertPointersBound();
+
+ if (g_Renderer.m_SkipSubmit)
+ return;
+
+ shader->BindTexture(str_baseTex, m_FlareTextures[flooredStep % 16]);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+
+ if (flooredStep >= 8)
+ {
+ shader->BindTexture(str_baseTex, m_FlareTextures[(flooredStep - 8) % 16]);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ }
+}
+
struct MinimapUnitVertex
{
// This struct is copyable for convenience and because to move is to copy for primitives.
@@ -340,7 +399,6 @@
++attrPos;
}
-
void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const
{
// Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m)
@@ -484,7 +542,7 @@
glDisable(GL_BLEND);
- PROFILE_START("minimap units");
+ PROFILE_START("minimap units and flares");
CShaderDefines pointDefines;
pointDefines.Add(str_MINIMAP_POINT, str_1);
@@ -606,7 +664,35 @@
DrawViewRect(unitMatrix);
- PROFILE_END("minimap units");
+ while (!m_MapFlares.empty() && 6 + m_MapFlares.front().time < cur_time)
+ m_MapFlares.pop_front();
+
+ glEnable(GL_BLEND);
+
+ const float width = m_CachedActualSize.GetWidth();
+ const float height = m_CachedActualSize.GetHeight();
+ glScissor(
+ m_CachedActualSize.left * g_GuiScale,
+ g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale,
+ width * g_GuiScale,
+ height * g_GuiScale);
+ glEnable(GL_SCISSOR_TEST);
+
+ CShaderDefines flareDefines;
+ flareDefines.Add(str_MINIMAP_FLARE, str_1);
+ tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), flareDefines);
+ tech->BeginPass();
+ shader = tech->GetShader();
+
+ shader->Uniform(str_transform, unitMatrix);
+ shader->Uniform(str_textureTransform, baseTextureTransform);
+ for (const MapFlareObj& flare : m_MapFlares)
+ DrawFlare(shader, flare, cur_time);
+ tech->EndPass();
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_BLEND);
+
+ PROFILE_END("minimap units and flares");
// Reset depth mask
glDepthMask(1);
@@ -702,6 +788,59 @@
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData);
}
+JSFunctionSpec CMiniMap::JSI_methods[] =
+{
+ JS_FN("flare", CMiniMap::FlareProxy, 2, 0),
+ JS_FS_END
+};
+
+void CMiniMap::RegisterScriptFunctions()
+{
+ JSContext* cx = m_pGUI.GetScriptInterface()->GetContext();
+ JSAutoRequest rq(cx);
+ JS_DefineFunctions(cx, m_JSObject, JSI_methods);
+}
+
+bool CMiniMap::FlareProxy(JSContext* cx, uint argc, JS::Value* vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ CMiniMap* thisObj = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class);
+ if (!thisObj)
+ {
+ JSAutoRequest rq(cx);
+ JS_ReportError(cx, "This is not a CMiniMap object!");
+ return false;
+ }
+
+ bool ret = thisObj->Flare(cx, argc, vp);
+
+ args.rval().set(JS::BooleanValue(ret));
+ return true;
+}
+
+bool CMiniMap::Flare(JSContext* cx, uint argc, JS::Value* vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ bool ret = false;
+ if (args.length() != 2)
+ LOGERROR("CMiniMap::Flare: Got %d arguments, but expected 2.", args.length());
+ else
+ {
+ JSAutoRequest rq(cx);
+ CVector2D pos;
+ CStr colorStr;
+ CColor color;
+ if (ScriptInterface::FromJSVal(cx, args.get(0), pos) &&
+ ScriptInterface::FromJSVal(cx, args.get(1), colorStr) &&
+ color.ParseString(colorStr))
+ {
+ m_MapFlares.push_back({GetMapCoordinates(pos.X, pos.Y), color, timer_Time()});
+ ret = true;
+ }
+ }
+ return ret;
+}
+
void CMiniMap::Destroy()
{
if (m_TerrainTexture)
Index: source/ps/CStrInternStatic.h
===================================================================
--- source/ps/CStrInternStatic.h
+++ source/ps/CStrInternStatic.h
@@ -42,6 +42,7 @@
X(MINIMAP_LINE)
X(MINIMAP_LOS)
X(MINIMAP_POINT)
+X(MINIMAP_FLARE)
X(MODE_SHADOWCAST)
X(MODE_SILHOUETTEDISPLAY)
X(MODE_SILHOUETTEOCCLUDER)