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 @@ -78,6 +78,11 @@ */ var clickedEntity = INVALID_ENTITY; +/** + * Store the last time the ping functionnality was used. + */ +var g_LastMapPingTime; + // Same double-click behaviour for hotkey presses const doublePressTime = 500; var doublePressTimer = 0; @@ -1218,7 +1223,7 @@ // Add target markers with a minimum distance of 5 to each other. let entitiesBetweenMarker = Math.ceil(5 / distanceBetweenEnts); for (let i = 0; i < entityDistribution.length; i += entitiesBetweenMarker) - DrawTargetMarker({ "x": entityDistribution[i].x, "z": entityDistribution[i].y }); + drawTargetMarker({ "x": entityDistribution[i].x, "z": entityDistribution[i].y }); Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", 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 @@ -249,7 +249,7 @@ setCameraFollow(cmd.entities[0]); if (["walk", "attack-walk", "patrol"].indexOf(cmd.type) != -1) - DrawTargetMarker(cmd); + drawTargetMarker(cmd); // Select units affected by that command let selection = []; @@ -271,6 +271,12 @@ } global.music.setLocked(notification.lock); + }, + "map-ping": function(notification, player) + { + // Don't display for the player that did the ping because he will see it immediately + if (player != Engine.GetPlayerID() && g_Players[player].isMutualAlly[Engine.GetPlayerID()]) + displayMapPing(notification.target, player); } }; 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 @@ -9,18 +9,38 @@ Engine.GetGUIObjectByName("minimap").onWorldClick = this.onWorldClick.bind(this); } - onWorldClick(target) + onWorldClick(data) { - if (!controlsPlayer(g_ViewedPlayer)) - return; + switch (data.type) + { + case "mouserightrelease": + case "mouserightdoubleclick": + { + // Partly duplicated from handleInputAfterGui(), but with the input being + // world coordinates instead of screen coordinates. - // Partly duplicated from handleInputAfterGui(), but with the input being - // world coordinates instead of screen coordinates. + if (inputState != INPUT_NORMAL) + return false; - if (inputState != INPUT_NORMAL) - return false; - - let action = determineAction(undefined, undefined, true); - return action && handleUnitAction(target, action); + let action = determineAction(undefined, undefined, true); + return action && handleUnitAction(data.target, action); + } + case "mousemiddlerelease": + case "mousemiddledoubleclick": + { + let now = Date.now(); + if (!g_LastMapPingTime || now > g_LastMapPingTime + 1000) { + g_LastMapPingTime = now; + displayMapPing(data.target, Engine.GetPlayerID()); + Engine.PostNetworkCommand({ + "type": "map-ping", + "target": data.target + }); + return true; + } + break; + } + } + return false; } } 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_ping": "special/target_marker" }; /** @@ -55,7 +57,7 @@ "queued": queued }); - DrawTargetMarker(target); + drawTargetMarker(target); Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", @@ -106,7 +108,7 @@ "queued": queued }); - DrawTargetMarker(target); + drawTargetMarker(target); Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", @@ -253,7 +255,7 @@ "allowCapture": false }); - DrawTargetMarker(target); + drawTargetMarker(target); Engine.GuiInterfaceCall("PlaySound", { "name": "order_patrol", "entity": selection[0] }); return true; @@ -1561,7 +1563,7 @@ return false; } -function DrawTargetMarker(target) +function drawTargetMarker(target) { Engine.GuiInterfaceCall("AddTargetMarker", { "template": g_TargetMarker.move, @@ -1570,6 +1572,25 @@ }); } +function displayMapPing(target, playerID) +{ + Engine.GuiInterfaceCall("AddTargetMarker", { + "template": g_TargetMarker.map_ping, + "x": target.x, + "z": target.z + }); + + //TODO: is there a better way to call the ping function of the minimap? + let minimap = Engine.GetGUIObjectByName("minimap"); + minimap.ping_x = target.x; + minimap.ping_z = target.z; + minimap.ping_color = rgbToGuiColor(g_DisplayedPlayerColors[playerID]); + minimap.ping = true; + + // TODO: Create a new sound and play it here + Engine.PlayUISound("audio/interface/ui/chat_alert.ogg", false); +} + function findGatherType(gatherer, supply) { if (!gatherer.resourceGatherRates || !supply) 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,15 @@ cmpResourceDropsite.SetSharing(cmd.shared); } }, + "map-ping": function(player, cmd, data) + { + let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + cmpGuiInterface.PushNotification({ + "type": "map-ping", + "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 @@ -22,6 +22,14 @@ #include "gui/ObjectBases/IGUIObject.h" #include "renderer/VertexArray.h" +//typedef std::pair, double> MapPingObj; +struct MapPingObj +{ + CPos pos; + CColor color; + double time; +}; + class CCamera; class CMatrix3D; class CTerrain; @@ -56,7 +64,7 @@ void SetCameraPos(); - void FireWorldClickEvent(int button, int clicks); + void FireWorldClickEvent(const CStr& eventType); static const CStr EventNameWorldClick; @@ -68,6 +76,8 @@ //Whether or not the mouse is currently down bool m_Clicking; + std::deque m_MapPings; + // minimap texture handles GLuint m_TerrainTexture; @@ -95,10 +105,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 DrawPing(const CMatrix3D& transform, const MapPingObj& ping, double cur_time); void GetMouseWorldCoordinates(float& x, float& z) const; + CPos GetMapCoordinates(float x, float z) const; + float GetAngle() const; VertexIndexArray m_IndexArray; @@ -112,6 +126,11 @@ double m_HalfBlinkDuration; double m_NextBlinkTime; bool m_BlinkState; + + float m_PingZ; + float m_PingX; + bool m_Ping; + CGUIColor m_PingColor; }; #endif // INCLUDED_MINIMAP Index: source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- source/gui/ObjectTypes/CMiniMap.cpp +++ source/gui/ObjectTypes/CMiniMap.cpp @@ -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 @@ -72,6 +72,11 @@ m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW), m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0) { + RegisterSetting("ping_x", m_PingX); + RegisterSetting("ping_z", m_PingZ); + RegisterSetting("ping_color", m_PingColor); + RegisterSetting("ping", m_Ping); + m_Clicking = false; m_MouseHovering = false; @@ -171,11 +176,17 @@ m_MouseHovering = false; break; case GUIM_MOUSE_RELEASE_RIGHT: - CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); + FireWorldClickEvent("mouserightrelease"); break; case GUIM_MOUSE_DBLCLICK_RIGHT: - CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); + FireWorldClickEvent("mouserightdoubleclick"); break; + //case GUIM_MOUSE_RELEASE_MIDDLE: + // FireWorldClickEvent("mousemiddlerelease"); + // break; + //case GUIM_MOUSE_DBLCLICK_MIDDLE: + // FireWorldClickEvent("mousemiddledoubleclick"); + // break; case GUIM_MOUSE_MOTION: if (m_MouseHovering && m_Clicking) SetCameraPos(); @@ -184,7 +195,17 @@ case GUIM_MOUSE_WHEEL_UP: Message.Skip(); break; - + case GUIM_SETTINGS_UPDATED: + if (Message.value == "ping") + { + bool doPing = GetSetting("ping"); + if (doPing) + { + SetSetting("ping", false, false); + m_MapPings.push_back({GetMapCoordinates(m_PingX, m_PingZ), m_PingColor, timer_Time()}); + } + } + break; default: break; } @@ -221,6 +242,15 @@ 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(); @@ -237,7 +267,7 @@ return -atan2(cameraIn.X, cameraIn.Z); } -void CMiniMap::FireWorldClickEvent(int UNUSED(button), int UNUSED(clicks)) +void CMiniMap::FireWorldClickEvent(const CStr& eventType) { JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); @@ -248,15 +278,18 @@ JS::RootedValue coords(cx); ScriptInterface::CreateObject(cx, &coords, "x", x, "z", z); + JS::RootedValue data(cx); + ScriptInterface::CreateObject(cx, &data, "target", coords, "type", eventType); + JS::AutoValueVector paramData(cx); - paramData.append(coords); + paramData.append(data); ScriptEvent(EventNameWorldClick, paramData); } // 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 @@ -271,19 +304,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. @@ -315,6 +345,47 @@ glDisable(GL_SCISSOR_TEST); } +// TODO: Hacky and ugly copypasta +void CMiniMap::DrawPing(const CMatrix3D& transform, const MapPingObj& ping, double cur_time) +{ + float step = std::fmod((cur_time - ping.time) * 10, 10); + float viewVerts[] = { + ping.pos.x + step, -ping.pos.y-step, + ping.pos.x - step, -ping.pos.y-step, + ping.pos.x - step, -ping.pos.y+step, + ping.pos.x + step, -ping.pos.y+step + }; + const float width = m_CachedActualSize.GetWidth(); + const float height = m_CachedActualSize.GetHeight(); + // Enable Scissoring to restrict the rectangle to only the minimap. + glScissor( + m_CachedActualSize.left * g_GuiScale, + g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale, + width * g_GuiScale, + height * g_GuiScale); + glEnable(GL_SCISSOR_TEST); + glLineWidth(2.0f); + + CShaderDefines lineDefines; + lineDefines.Add(str_MINIMAP_LINE, str_1); + CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines); + tech->BeginPass(); + CShaderProgramPtr shader = tech->GetShader(); + shader->Uniform(str_transform, transform); + shader->Uniform(str_color, ping.color.r, ping.color.g, ping.color.b, ping.color.a); + + shader->VertexPointer(2, GL_FLOAT, 0, viewVerts); + shader->AssertPointersBound(); + + if (!g_Renderer.m_SkipSubmit) + glDrawArrays(GL_LINE_LOOP, 0, 4); + + tech->EndPass(); + + glLineWidth(1.0f); + glDisable(GL_SCISSOR_TEST); +} + struct MinimapUnitVertex { // This struct is copyable for convenience and because to move is to copy for primitives. @@ -605,6 +676,12 @@ DrawViewRect(unitMatrix); + while (!m_MapPings.empty() && 3 + m_MapPings.front().time < cur_time) + m_MapPings.pop_front(); + + for (const MapPingObj& ping : m_MapPings) + DrawPing(unitMatrix, ping, cur_time); + PROFILE_END("minimap units"); // Reset depth mask