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/minimap/MiniMap.xml =================================================================== --- binaries/data/mods/public/gui/session/minimap/MiniMap.xml +++ binaries/data/mods/public/gui/session/minimap/MiniMap.xml @@ -6,7 +6,7 @@ sprite="mapPanel" > - + , 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; 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) { + AddSetting("ping_x"); + AddSetting("ping_z"); + AddSetting("ping_color"); + AddSetting("ping"); + m_Clicking = false; m_MouseHovering = false; @@ -171,10 +176,16 @@ 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) @@ -184,7 +195,24 @@ case GUIM_MOUSE_WHEEL_UP: Message.Skip(); break; - + case GUIM_SETTINGS_UPDATED: + if (Message.value == "ping") + { + bool doPing = false; + GUI::GetSetting(this, "ping", doPing); + if (doPing) + { + GUI::SetSetting(this, "ping", false); + float pingX; + GUI::GetSetting(this, "ping_x", pingX); + float pingZ; + GUI::GetSetting(this, "ping_z", pingZ); + CColor pingColor; + GUI::GetSetting(this, "ping_color", pingColor); + m_MapPings.push_back({GetMapCoordinates(pingX, pingZ), pingColor, timer_Time()}); + } + } + break; default: break; } @@ -221,6 +249,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 +274,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); @@ -245,18 +282,20 @@ float x, z; GetMouseWorldCoordinates(x, z); - JS::RootedValue coords(cx); - ScriptInterface::CreateObject(cx, &coords, "x", x, "z", z); - - JS::AutoValueVector paramData(cx); - paramData.append(coords); - - ScriptEvent(EventNameWorldClick, paramData); + JS::RootedValue target(cx); + g_GUI->GetActiveGUI()->GetScriptInterface()->Eval("({})", &target); + g_GUI->GetActiveGUI()->GetScriptInterface()->SetProperty(target, "x", x, false); + g_GUI->GetActiveGUI()->GetScriptInterface()->SetProperty(target, "z", z, false); + JS::RootedValue data(cx); + g_GUI->GetActiveGUI()->GetScriptInterface()->Eval("({})", &data); + g_GUI->GetActiveGUI()->GetScriptInterface()->SetProperty(data, "target", target, false); + g_GUI->GetActiveGUI()->GetScriptInterface()->SetProperty(data, "type", eventType, false); + ScriptEvent("worldclick", data); } // 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 +310,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 +351,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 +682,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