forked from mirrors/0ad
Map flares
Add flaring on the map and resize the minimap buttons. Target marker and button by Stan. Reviewed/Commented by wraitii, elexis, vladislavbelov Refs: #3491 Refs: #57 Differential Revision: https://code.wildfiregames.com/D1751 This was SVN commit r25691.
This commit is contained in:
@@ -341,6 +341,8 @@ 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
|
||||
toggledefaultformation = "" ; Switch between null default formation and the last default formation used (defaults to "box")
|
||||
flare = K ; Modifier to send a flare to your allies
|
||||
flareactivate = "" ; Modifier to activate the mode to send a flare to your allies
|
||||
; Overlays
|
||||
showstatusbars = Tab ; Toggle display of status bars
|
||||
devcommands.toggle = "Alt+D" ; Toggle developer commands panel
|
||||
|
||||
@@ -230,6 +230,31 @@
|
||||
<ref name="bool"/>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="flare_texture_count">
|
||||
<data type="nonNegativeInteger"/>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="flare_render_size">
|
||||
<data type="nonNegativeInteger"/>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="flare_animation_speed">
|
||||
<data type="nonNegativeInteger"/>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="flare_interleave">
|
||||
<ref name="bool"/>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="flare_lifetime_seconds">
|
||||
<data type="nonNegativeInteger"/>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="font"/>
|
||||
</optional>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<actor version="1">
|
||||
<castshadow/>
|
||||
<float/>
|
||||
<group>
|
||||
<variant frequency="100" name="Flare Target Marker">
|
||||
<animations>
|
||||
<animation file="other/flare_target_marker_idle.dae" name="Idle" speed="100"/>
|
||||
</animations>
|
||||
<mesh>skeletal/flare_target_marker.dae</mesh>
|
||||
<textures>
|
||||
<texture file="skeletal/flare_target_marker_diffuse.png" name="baseTex"/>
|
||||
<texture file="skeletal/flare_target_marker_spec.png" name="specTex"/>
|
||||
</textures>
|
||||
</variant>
|
||||
</group>
|
||||
<material>player_trans_spec.xml</material>
|
||||
</actor>
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<skeletons>
|
||||
<standard_skeleton title="flare-marker-armature" id="flare-marker-armature">
|
||||
<bone name="base">
|
||||
<bone name="ping-marker-ball" />
|
||||
<bone name="ping-marker-body" />
|
||||
</bone>
|
||||
<bone name="target marker" />
|
||||
</standard_skeleton>
|
||||
<skeleton title="flare-marker-armature" target="flare-marker-armature">
|
||||
<identifier>
|
||||
<root>base</root>
|
||||
</identifier>
|
||||
<bone name="base">
|
||||
<target>base</target>
|
||||
<bone name="ping-marker-ball">
|
||||
<target>ping-marker-ball</target>
|
||||
</bone>
|
||||
<bone name="ping-marker-body">
|
||||
<target>ping-marker-body</target>
|
||||
</bone>
|
||||
</bone>
|
||||
<bone name="target marker">
|
||||
<target>target marker</target>
|
||||
</bone>
|
||||
</skeleton>
|
||||
</skeletons>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
1 1
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -115,6 +115,7 @@ You may change hotkeys in [font="sans-bold-14"]Options > Hotkeys[font="sans-14"]
|
||||
hotkey.session.attackmove + Right Click with unit(s) selected – Attack move (by default all enemy units and structures along the way are targeted)
|
||||
hotkey.session.attackmoveUnit + Right Click with unit(s) selected – Attack move, only units along the way are targeted
|
||||
hotkey.session.snaptoedges + Mouse Move near structures – Align the new structure with an existing nearby structure
|
||||
hotkey.session.flare + Right Click – Send a flare to your allies
|
||||
|
||||
[font="sans-bold-14"]Overlays[font="sans-14"]
|
||||
hotkey.session.gui.toggle – Toggle the GUI
|
||||
|
||||
@@ -32,6 +32,7 @@ const INPUT_BUILDING_WALL_CLICK = 8;
|
||||
const INPUT_BUILDING_WALL_PATHING = 9;
|
||||
const INPUT_UNIT_POSITION_START = 10;
|
||||
const INPUT_UNIT_POSITION = 11;
|
||||
const INPUT_FLARE = 12;
|
||||
|
||||
var inputState = INPUT_NORMAL;
|
||||
|
||||
@@ -79,6 +80,16 @@ var g_DragStart;
|
||||
*/
|
||||
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 = 5000;
|
||||
|
||||
// Same double-click behaviour for hotkey presses.
|
||||
const doublePressTime = 500;
|
||||
var doublePressTimer = 0;
|
||||
@@ -89,7 +100,12 @@ function updateCursorAndTooltip()
|
||||
let cursorSet = false;
|
||||
let tooltipSet = false;
|
||||
let informationTooltip = Engine.GetGUIObjectByName("informationTooltip");
|
||||
if (!mouseIsOverObject && (inputState == INPUT_NORMAL || inputState == INPUT_PRESELECTEDACTION) || g_MiniMapPanel.isMouseOverMiniMap())
|
||||
if (inputState == INPUT_FLARE || inputState == INPUT_NORMAL && Engine.HotkeyIsPressed("session.flare") && !g_IsObserver)
|
||||
{
|
||||
Engine.SetCursor("action-flare");
|
||||
cursorSet = true;
|
||||
}
|
||||
else if (!mouseIsOverObject && (inputState == INPUT_NORMAL || inputState == INPUT_PRESELECTEDACTION) || g_MiniMapPanel.isMouseOverMiniMap())
|
||||
{
|
||||
let action = determineAction(mouseX, mouseY, g_MiniMapPanel.isMouseOverMiniMap());
|
||||
if (action)
|
||||
@@ -810,6 +826,11 @@ function handleInputAfterGui(ev)
|
||||
return false;
|
||||
|
||||
case "mousebuttondown":
|
||||
if (Engine.HotkeyIsPressed("session.flare") && controlsPlayer(g_ViewedPlayer))
|
||||
{
|
||||
triggerFlareAction(Engine.GetTerrainAtScreenPoint(ev.x, ev.y));
|
||||
return true;
|
||||
}
|
||||
if (ev.button == SDL_BUTTON_LEFT)
|
||||
{
|
||||
g_DragStart = new Vector2D(ev.x, ev.y);
|
||||
@@ -1111,6 +1132,21 @@ function handleInputAfterGui(ev)
|
||||
|
||||
}
|
||||
break;
|
||||
case INPUT_FLARE:
|
||||
if (ev.type == "mousebuttondown")
|
||||
{
|
||||
if (ev.button == SDL_BUTTON_LEFT && controlsPlayer(g_ViewedPlayer))
|
||||
{
|
||||
triggerFlareAction(Engine.GetTerrainAtScreenPoint(ev.x, ev.y));
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
else if (ev.button == SDL_BUTTON_RIGHT)
|
||||
{
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1224,6 +1260,21 @@ function positionUnitsFreehandSelectionMouseUp(ev)
|
||||
return true;
|
||||
}
|
||||
|
||||
function triggerFlareAction(target)
|
||||
{
|
||||
let now = Date.now();
|
||||
if (g_LastFlareTime && now < g_LastFlareTime + g_FlareCooldown)
|
||||
return;
|
||||
|
||||
g_LastFlareTime = now;
|
||||
displayFlare(target, Engine.GetPlayerID());
|
||||
Engine.PlayUISound(g_FlareSound, false);
|
||||
Engine.PostNetworkCommand({
|
||||
"type": "map-flare",
|
||||
"target": target
|
||||
});
|
||||
}
|
||||
|
||||
function handleUnitAction(target, action)
|
||||
{
|
||||
if (!g_UnitActions[action.type] || !g_UnitActions[action.type].execute)
|
||||
|
||||
@@ -8,6 +8,22 @@ var g_TutorialMessages = [];
|
||||
*/
|
||||
var g_TutorialNewMessageTags = { "color": "255 226 149" };
|
||||
|
||||
/**
|
||||
* The number of seconds we observe for rate limiting flares.
|
||||
*/
|
||||
var g_FlareRateLimitScope = 10;
|
||||
|
||||
/**
|
||||
* The maximum allowed number of flares within g_FlareRateLimitScope seconds.
|
||||
* This should be a bit larger than the number of flares that can be sent in theory by using the GUI.
|
||||
*/
|
||||
var g_FlareRateLimitMaximumFlares = 16;
|
||||
|
||||
/**
|
||||
* Contains the arrival timestamps the flares of the last g_FlareRateLimitScope seconds.
|
||||
*/
|
||||
var g_FlareRateLimitLastTimes = [];
|
||||
|
||||
/**
|
||||
* These handlers are called everytime a client joins or disconnects.
|
||||
*/
|
||||
@@ -267,6 +283,27 @@ var g_NotificationsTypes =
|
||||
}
|
||||
|
||||
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()])
|
||||
{
|
||||
let now = Date.now();
|
||||
if (g_FlareRateLimitLastTimes.length)
|
||||
{
|
||||
g_FlareRateLimitLastTimes = g_FlareRateLimitLastTimes.filter(t => now - t < g_FlareRateLimitScope * 1000);
|
||||
if (g_FlareRateLimitLastTimes.length >= g_FlareRateLimitMaximumFlares)
|
||||
{
|
||||
warn("Received too many flares. Dropping a flare request by '" + g_Players[player].name + "'.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
g_FlareRateLimitLastTimes.push(now);
|
||||
|
||||
displayFlare(notification.target, player);
|
||||
Engine.PlayUISound(g_FlareSound, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ class MiniMap
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -16,9 +17,15 @@ class MiniMap
|
||||
{
|
||||
// Partly duplicated from handleInputAfterGui(), but with the input being
|
||||
// world coordinates instead of screen coordinates.
|
||||
if (inputState == INPUT_NORMAL && controlsPlayer(g_ViewedPlayer) && Engine.HotkeyIsPressed("session.flare"))
|
||||
{
|
||||
triggerFlareAction(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (button == SDL_BUTTON_LEFT)
|
||||
{
|
||||
if (inputState != INPUT_PRESELECTEDACTION || preSelectedAction == ACTION_NONE)
|
||||
if (inputState != INPUT_FLARE && (inputState != INPUT_PRESELECTEDACTION || preSelectedAction == ACTION_NONE))
|
||||
return false;
|
||||
}
|
||||
else if (button == SDL_BUTTON_RIGHT)
|
||||
@@ -29,6 +36,11 @@ class MiniMap
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
else if (inputState == INPUT_FLARE)
|
||||
{
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
else if (inputState != INPUT_NORMAL)
|
||||
return false;
|
||||
}
|
||||
@@ -39,6 +51,13 @@ class MiniMap
|
||||
if (!controlsPlayer(g_ViewedPlayer))
|
||||
return false;
|
||||
|
||||
if (inputState == INPUT_FLARE && button == SDL_BUTTON_LEFT)
|
||||
{
|
||||
triggerFlareAction(target);
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
let action = determineAction(undefined, undefined, true);
|
||||
if (!action)
|
||||
return false;
|
||||
@@ -64,4 +83,9 @@ class MiniMap
|
||||
{
|
||||
return this.mouseIsOverMiniMap;
|
||||
}
|
||||
|
||||
flare(target, playerID)
|
||||
{
|
||||
return this.miniMap.flare({ "x": target.x, "y": target.z }, g_DiplomacyColors.getPlayerColor(playerID));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<!-- Idle Worker Button -->
|
||||
<object name="idleWorkerButton"
|
||||
type="button"
|
||||
size="50%-5 50%-5 100%-5 100%-5"
|
||||
size="100%-119 100%-120 100%-4 100%-5"
|
||||
tooltip_style="sessionToolTip"
|
||||
hotkey="selection.idleworker"
|
||||
sprite="stretched:session/minimap-idle.png"
|
||||
@@ -28,11 +28,33 @@
|
||||
<!-- Diplomacy Colors Button -->
|
||||
<object name="diplomacyColorsButton"
|
||||
type="button"
|
||||
size="5 50%-5 50%+5 100%-5"
|
||||
size="4 100%-120 119 100%-5"
|
||||
tooltip_style="sessionToolTip"
|
||||
hotkey="session.diplomacycolors"
|
||||
/>
|
||||
|
||||
<!-- Flare Button -->
|
||||
<object
|
||||
name="flareButton"
|
||||
type="button"
|
||||
size="3 3 118 118"
|
||||
tooltip_style="sessionToolTip"
|
||||
hotkey="session.flareactivate"
|
||||
sprite="stretched:session/minimap-flare.png"
|
||||
sprite_over="stretched:session/minimap-flare-highlight.png"
|
||||
mouse_event_mask="texture:session/minimap-flare.png"
|
||||
/>
|
||||
|
||||
<!-- MiniMap -->
|
||||
<object name="minimap" size="8 8 100%-8 100%-8" type="minimap" mask="true"/>
|
||||
<object
|
||||
name="minimap"
|
||||
size="8 8 100%-8 100%-8"
|
||||
type="minimap"
|
||||
mask="true"
|
||||
flare_texture_count="16"
|
||||
flare_render_size="32"
|
||||
flare_animation_speed="7"
|
||||
flare_interleave="true"
|
||||
flare_lifetime_seconds="6"
|
||||
/>
|
||||
</object>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
if (g_IsObserver)
|
||||
return;
|
||||
if (inputState == INPUT_NORMAL)
|
||||
inputState = INPUT_FLARE;
|
||||
}
|
||||
}
|
||||
|
||||
MiniMapFlareButton.prototype.Tooltip = markForTranslation("Send a flare to your allies");
|
||||
@@ -7,9 +7,15 @@ class MiniMapPanel
|
||||
{
|
||||
this.diplomacyColorsButton = new MiniMapDiplomacyColorsButton(diplomacyColors);
|
||||
this.idleWorkerButton = new MiniMapIdleWorkerButton(playerViewControl, idleWorkerClasses);
|
||||
this.flareButton = new MiniMapFlareButton();
|
||||
this.miniMap = new MiniMap();
|
||||
}
|
||||
|
||||
flare(target, playerID)
|
||||
{
|
||||
return this.miniMap.flare(target, playerID);
|
||||
}
|
||||
|
||||
isMouseOverMiniMap()
|
||||
{
|
||||
return this.miniMap.isMouseOverMiniMap();
|
||||
|
||||
@@ -3,9 +3,15 @@
|
||||
* given a command type.
|
||||
*/
|
||||
var g_TargetMarker = {
|
||||
"move": "special/target_marker"
|
||||
"move": "special/target_marker",
|
||||
"map_flare": "special/flare_target_marker"
|
||||
};
|
||||
|
||||
/**
|
||||
* Sound we play when displaying a flare.
|
||||
*/
|
||||
var g_FlareSound = "audio/interface/alarm/alarmally_1.ogg";
|
||||
|
||||
/**
|
||||
* Which enemy entity types will be attacked on sight when patroling.
|
||||
*/
|
||||
@@ -1828,6 +1834,17 @@ function DrawTargetMarker(target)
|
||||
});
|
||||
}
|
||||
|
||||
function displayFlare(target, playerID)
|
||||
{
|
||||
Engine.GuiInterfaceCall("AddTargetMarker", {
|
||||
"template": g_TargetMarker.map_flare,
|
||||
"x": target.x,
|
||||
"z": target.z,
|
||||
"owner": playerID
|
||||
});
|
||||
g_MiniMapPanel.flare(target, playerID);
|
||||
}
|
||||
|
||||
function getCommandInfo(command, entStates)
|
||||
{
|
||||
return entStates && g_EntityCommands[command] &&
|
||||
|
||||
@@ -1116,7 +1116,9 @@ GuiInterface.prototype.AddTargetMarker = function(player, cmd)
|
||||
let ent = Engine.AddLocalEntity(cmd.template);
|
||||
if (!ent)
|
||||
return;
|
||||
|
||||
let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
||||
if (cmpOwnership)
|
||||
cmpOwnership.SetOwner(cmd.owner);
|
||||
let cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
cmpPosition.JumpTo(cmd.x, cmd.z);
|
||||
};
|
||||
|
||||
@@ -878,6 +878,16 @@ var g_Commands = {
|
||||
}
|
||||
},
|
||||
|
||||
"map-flare": function(player, cmd, data)
|
||||
{
|
||||
let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
||||
cmpGuiInterface.PushNotification({
|
||||
"type": "map-flare",
|
||||
"players": [player],
|
||||
"target": cmd.target
|
||||
});
|
||||
},
|
||||
|
||||
"autoqueue-on": function(player, cmd, data)
|
||||
{
|
||||
for (let ent of data.entities)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity parent="special/actor">
|
||||
<Decay>
|
||||
<Active>true</Active>
|
||||
<SinkingAnim>false</SinkingAnim>
|
||||
<DelayTime>6</DelayTime>
|
||||
<SinkRate>10000</SinkRate>
|
||||
<SinkAccel>0</SinkAccel>
|
||||
</Decay>
|
||||
<Ownership/>
|
||||
<Position>
|
||||
<Floating>true</Floating>
|
||||
</Position>
|
||||
<VisualActor>
|
||||
<Actor>special/flare_target_marker.xml</Actor>
|
||||
</VisualActor>
|
||||
</Entity>
|
||||
@@ -40,6 +40,7 @@ void CGUI::AddObjectTypes()
|
||||
m_ProxyData.insert(JSI_GUIProxy<IGUIObject>::CreateData(*m_ScriptInterface));
|
||||
m_ProxyData.insert(JSI_GUIProxy<CText>::CreateData(*m_ScriptInterface));
|
||||
m_ProxyData.insert(JSI_GUIProxy<CList>::CreateData(*m_ScriptInterface));
|
||||
m_ProxyData.insert(JSI_GUIProxy<CMiniMap>::CreateData(*m_ScriptInterface));
|
||||
m_ProxyData.insert(JSI_GUIProxy<CButton>::CreateData(*m_ScriptInterface));
|
||||
|
||||
AddObjectType("button", &CButton::ConstructObject);
|
||||
|
||||
@@ -37,6 +37,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/CStrInternStatic.h"
|
||||
#include "ps/Filesystem.h"
|
||||
@@ -141,7 +142,10 @@ CMiniMap::CMiniMap(CGUI& pGUI) :
|
||||
IGUIObject(pGUI),
|
||||
m_MapSize(0), m_MapScale(1.f),
|
||||
m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW), m_Mask(this, "mask", false),
|
||||
m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false)
|
||||
m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false),
|
||||
m_FlareTextureCount(this, "flare_texture_count", 0), m_FlareRenderSize(this, "flare_render_size", 0),
|
||||
m_FlareAnimationSpeed(this, "flare_animation_speed", 0), m_FlareInterleave(this, "flare_interleave", false),
|
||||
m_FlareLifetimeSeconds(this, "flare_lifetime_seconds", 0)
|
||||
{
|
||||
m_Clicking = false;
|
||||
m_MouseHovering = false;
|
||||
@@ -201,6 +205,13 @@ void CMiniMap::HandleMessage(SGUIMessage& Message)
|
||||
IGUIObject::HandleMessage(Message);
|
||||
switch (Message.type)
|
||||
{
|
||||
case GUIM_LOAD:
|
||||
RecreateFlareTextures();
|
||||
break;
|
||||
case GUIM_SETTINGS_UPDATED:
|
||||
if (Message.value == "flare_texture_count")
|
||||
RecreateFlareTextures();
|
||||
break;
|
||||
case GUIM_MOUSE_PRESS_LEFT:
|
||||
if (m_MouseHovering)
|
||||
{
|
||||
@@ -248,6 +259,24 @@ void CMiniMap::HandleMessage(SGUIMessage& Message)
|
||||
}
|
||||
}
|
||||
|
||||
void CMiniMap::RecreateFlareTextures()
|
||||
{
|
||||
// Catch invalid values.
|
||||
if (m_FlareTextureCount > 99)
|
||||
{
|
||||
LOGERROR("Invalid value for flare texture count. Valid range is 0-99.");
|
||||
return;
|
||||
}
|
||||
const CStr numberingFormat = "%02u";
|
||||
m_FlareTextures.clear();
|
||||
m_FlareTextures.reserve(m_FlareTextureCount);
|
||||
for (u32 i = 0; i < m_FlareTextureCount; ++i)
|
||||
{
|
||||
CTextureProperties textureProps(L"art/textures/animated/minimap-flare/frame" + CStr(fmt::sprintf(numberingFormat, i)).FromUTF8() + L".png");
|
||||
m_FlareTextures.emplace_back(g_Renderer.GetTextureManager().CreateTexture(textureProps));
|
||||
}
|
||||
}
|
||||
|
||||
bool CMiniMap::IsMouseOver() const
|
||||
{
|
||||
// Get the mouse position.
|
||||
@@ -369,6 +398,41 @@ void CMiniMap::DrawViewRect(const CMatrix3D& transform) const
|
||||
glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
void CMiniMap::DrawFlare(CCanvas2D& canvas, const MapFlare& flare, double currentTime) const
|
||||
{
|
||||
if (!m_FlareTextures.size())
|
||||
return;
|
||||
|
||||
// Coordinates with 0,0 in the middle of the minimap and +-0.5 as max.
|
||||
const float invTileMapSize = 1.0f / static_cast<float>(TERRAIN_TILE_SIZE * m_MapSize);
|
||||
const float relativeX = (flare.pos.X * invTileMapSize - 0.5) / m_MapScale;
|
||||
const float relativeY = (flare.pos.Y * invTileMapSize - 0.5) / m_MapScale;
|
||||
|
||||
// Rotate coordinates.
|
||||
const float angle = GetAngle();
|
||||
const float rotatedX = cos(angle) * relativeX + sin(angle) * relativeY;
|
||||
const float rotatedY = -sin(angle) * relativeX + cos(angle) * relativeY;
|
||||
// Calculate coordinates in gui space.
|
||||
const float cx = m_CachedActualSize.left + (0.5f + rotatedX) * m_CachedActualSize.GetWidth();
|
||||
const float cy = m_CachedActualSize.bottom - (0.5f + rotatedY) * m_CachedActualSize.GetHeight();
|
||||
|
||||
const CRect destination(cx-m_FlareRenderSize, cy-m_FlareRenderSize, cx+m_FlareRenderSize, cy+m_FlareRenderSize);
|
||||
|
||||
const u32 flooredStep = floor((currentTime - flare.time) * m_FlareAnimationSpeed);
|
||||
|
||||
CTexturePtr texture = m_FlareTextures[flooredStep % m_FlareTextures.size()];
|
||||
// TODO: Only draw inside the minimap circle.
|
||||
canvas.DrawTexture(texture, destination, CRect(0, 0, texture->GetWidth(), texture->GetHeight()), flare.color, CColor(0.0f, 0.0f, 0.0f, 0.0f), 0.0f);
|
||||
|
||||
// Draw a second circle if the first has reached half of the animation
|
||||
if (m_FlareInterleave && flooredStep >= m_FlareTextures.size() / 2)
|
||||
{
|
||||
texture = m_FlareTextures[(flooredStep - m_FlareTextures.size() / 2) % m_FlareTextures.size()];
|
||||
// TODO: Only draw inside the minimap circle.
|
||||
canvas.DrawTexture(texture, destination, CRect(0, 0, texture->GetWidth(), texture->GetHeight()), flare.color, CColor(0.0f, 0.0f, 0.0f, 0.0f), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
struct MinimapUnitVertex
|
||||
{
|
||||
// This struct is copyable for convenience and because to move is to copy for primitives.
|
||||
@@ -521,7 +585,7 @@ void CMiniMap::Draw(CCanvas2D& canvas)
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
PROFILE_START("minimap units");
|
||||
PROFILE_START("minimap units and flares");
|
||||
|
||||
CShaderDefines pointDefines;
|
||||
pointDefines.Add(str_MINIMAP_POINT, str_1);
|
||||
@@ -641,5 +705,23 @@ void CMiniMap::Draw(CCanvas2D& canvas)
|
||||
|
||||
DrawViewRect(unitMatrix);
|
||||
|
||||
PROFILE_END("minimap units");
|
||||
while (!m_MapFlares.empty() && m_FlareLifetimeSeconds + m_MapFlares.front().time < cur_time)
|
||||
m_MapFlares.pop_front();
|
||||
|
||||
for (const MapFlare& flare : m_MapFlares)
|
||||
DrawFlare(canvas, flare, cur_time);
|
||||
|
||||
PROFILE_END("minimap units and flares");
|
||||
}
|
||||
|
||||
bool CMiniMap::Flare(const CVector2D& pos, const CStr& colorStr)
|
||||
{
|
||||
CColor color;
|
||||
if (!color.ParseString(colorStr))
|
||||
{
|
||||
LOGERROR("CMiniMap::Flare: Couldn't parse color string");
|
||||
return false;
|
||||
}
|
||||
m_MapFlares.push_back({ pos, color, timer_Time() });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,15 @@
|
||||
#ifndef INCLUDED_MINIMAP
|
||||
#define INCLUDED_MINIMAP
|
||||
|
||||
#include "graphics/Color.h"
|
||||
#include "graphics/Texture.h"
|
||||
#include "gui/ObjectBases/IGUIObject.h"
|
||||
#include "maths/Vector2D.h"
|
||||
#include "renderer/VertexArray.h"
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
class CCamera;
|
||||
class CMatrix3D;
|
||||
class CTerrain;
|
||||
@@ -28,13 +34,25 @@ class CTerrain;
|
||||
class CMiniMap : public IGUIObject
|
||||
{
|
||||
GUI_OBJECT(CMiniMap)
|
||||
|
||||
public:
|
||||
CMiniMap(CGUI& pGUI);
|
||||
virtual ~CMiniMap();
|
||||
|
||||
bool Flare(const CVector2D& pos, const CStr& colorStr);
|
||||
|
||||
protected:
|
||||
struct MapFlare
|
||||
{
|
||||
CVector2D pos;
|
||||
CColor color;
|
||||
double time;
|
||||
};
|
||||
|
||||
virtual void Draw(CCanvas2D& canvas);
|
||||
|
||||
virtual void CreateJSObject();
|
||||
|
||||
/**
|
||||
* @see IGUIObject#HandleMessage()
|
||||
*/
|
||||
@@ -57,6 +75,16 @@ private:
|
||||
// Whether or not the mouse is currently down
|
||||
bool m_Clicking;
|
||||
|
||||
std::deque<MapFlare> m_MapFlares;
|
||||
|
||||
std::vector<CTexturePtr> m_FlareTextures;
|
||||
|
||||
CGUISimpleSetting<u32> m_FlareTextureCount;
|
||||
CGUISimpleSetting<u32> m_FlareRenderSize;
|
||||
CGUISimpleSetting<u32> m_FlareAnimationSpeed;
|
||||
CGUISimpleSetting<bool> m_FlareInterleave;
|
||||
CGUISimpleSetting<u32> m_FlareLifetimeSeconds;
|
||||
|
||||
// Whether to draw a black square around and under the minimap.
|
||||
CGUISimpleSetting<bool> m_Mask;
|
||||
|
||||
@@ -66,8 +94,12 @@ private:
|
||||
// 1.f if map is circular or 1.414f if square (to shrink it inside the circle)
|
||||
float m_MapScale;
|
||||
|
||||
void RecreateFlareTextures();
|
||||
|
||||
void DrawViewRect(const CMatrix3D& transform) const;
|
||||
|
||||
void DrawFlare(CCanvas2D& canvas, const MapFlare& flare, double curentTime) const;
|
||||
|
||||
void GetMouseWorldCoordinates(float& x, float& z) const;
|
||||
|
||||
float GetAngle() const;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "gui/ObjectBases/IGUIObject.h"
|
||||
#include "gui/ObjectTypes/CButton.h"
|
||||
#include "gui/ObjectTypes/CList.h"
|
||||
#include "gui/ObjectTypes/CMiniMap.h"
|
||||
#include "gui/ObjectTypes/CText.h"
|
||||
|
||||
// Called for every specialization - adds the common interface.
|
||||
@@ -58,3 +59,10 @@ template<> void JSI_GUIProxy<CList>::CreateFunctions(const ScriptRequest& rq, GU
|
||||
CreateFunction<static_cast<void(CList::*)(const CGUIString&)>(&CList::AddItem)>(rq, cache, "addItem");
|
||||
}
|
||||
DECLARE_GUIPROXY(CList);
|
||||
|
||||
// CMiniMap
|
||||
template<> void JSI_GUIProxy<CMiniMap>::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
|
||||
{
|
||||
CreateFunction<&CMiniMap::Flare>(rq, cache, "flare");
|
||||
}
|
||||
DECLARE_GUIPROXY(CMiniMap);
|
||||
|
||||
Reference in New Issue
Block a user