local LIB_IDENTIFIER = "LibMapPing" local lib = LibStub:NewLibrary("LibMapPing", 1) if not lib then return -- already loaded and no upgrade necessary end local g_mapPinManager local function Log(message, ...) df("[%s] %s", LIB_IDENTIFIER, message:format(...)) end local MAP_PIN_TYPE_PLAYER_WAYPOINT = MAP_PIN_TYPE_PLAYER_WAYPOINT local MAP_PIN_TYPE_PING = MAP_PIN_TYPE_PING local MAP_PIN_TYPE_RALLY_POINT = MAP_PIN_TYPE_RALLY_POINT local MAP_PIN_TAG_PLAYER_WAYPOINT = "waypoint" local MAP_PIN_TAG_RALLY_POINT = "rally" local PING_CATEGORY = "pings" local MAP_PIN_TAG = { [MAP_PIN_TYPE_PLAYER_WAYPOINT] = MAP_PIN_TAG_PLAYER_WAYPOINT, --[MAP_PIN_TYPE_PING] = group pings have individual tags for each member [MAP_PIN_TYPE_RALLY_POINT] = MAP_PIN_TAG_RALLY_POINT, } local GET_MAP_PING_FUNCTION = { [MAP_PIN_TYPE_PLAYER_WAYPOINT] = GetMapPlayerWaypoint, [MAP_PIN_TYPE_PING] = GetMapPing, [MAP_PIN_TYPE_RALLY_POINT] = GetMapRallyPoint, } local REMOVE_MAP_PING_FUNCTION = { [MAP_PIN_TYPE_PLAYER_WAYPOINT] = RemovePlayerWaypoint, [MAP_PIN_TYPE_PING] = function() -- there is no such function for group pings, but we can set it to 0, 0 which effectively hides it PingMap(MAP_PIN_TYPE_PING, MAP_TYPE_LOCATION_CENTERED, 0, 0) end, [MAP_PIN_TYPE_RALLY_POINT] = RemoveRallyPoint, } lib.mutePing = {} lib.suppressPing = {} lib.isPingSet = {} local function GetKey(pingType, pingTag) if(pingType == MAP_PIN_TYPE_PLAYER_WAYPOINT) then pingTag = MAP_PIN_TAG_PLAYER_WAYPOINT elseif(pingType == MAP_PIN_TYPE_RALLY_POINT) then pingTag = MAP_PIN_TAG_RALLY_POINT end return string.format("%d_%s", pingType, pingTag) end function GetMapPlayerWaypoint() if(lib:IsPingSuppressed(MAP_PIN_TYPE_PLAYER_WAYPOINT, MAP_PIN_TAG_PLAYER_WAYPOINT)) then return 0, 0 end return GET_MAP_PING_FUNCTION[MAP_PIN_TYPE_PLAYER_WAYPOINT]() end function GetMapPing(pingTag) if(lib:IsPingSuppressed(MAP_PIN_TYPE_PING, pingTag)) then return 0, 0 end return GET_MAP_PING_FUNCTION[MAP_PIN_TYPE_PING](pingTag) end function GetMapRallyPoint() if(lib:IsPingSuppressed(MAP_PIN_TYPE_RALLY_POINT, MAP_PIN_TAG_RALLY_POINT)) then return 0, 0 end return GET_MAP_PING_FUNCTION[MAP_PIN_TYPE_RALLY_POINT]() end function lib:SetMapPing(pingType, mapType, x, y) PingMap(pingType, mapType, x, y) end function lib:RemoveMapPing(pingType) if(REMOVE_MAP_PING_FUNCTION[pingType]) then REMOVE_MAP_PING_FUNCTION[pingType]() end end function lib:GetMapPing(pingType, pingTag) local x, y = 0, 0 if(GET_MAP_PING_FUNCTION[pingType]) then x, y = GET_MAP_PING_FUNCTION[pingType](pingTag) end return x, y end function lib:HasMapPing(pingType, pingTag) -- TODO: this should return true immediately after calling set, but false inside the map ping event when a ping was set pingTag = pingTag or MAP_PIN_TAG[pingType] if(not pingTag) then Log("No pingTag specified for HasMapPing") return false end local key = GetKey(pingType, pingTag) return (lib.isPingSet[key] == true) end function lib:RefreshMapPin(pingType, pingTag) if(not g_mapPinManager) then Log("PinManager not available. Using ZO_WorldMap_UpdateMap instead.") ZO_WorldMap_UpdateMap() return true end pingTag = pingTag or MAP_PIN_TAG[pingType] if(not pingTag) then Log("No pingTag specified for RefreshMapPing") return false end g_mapPinManager:RemovePins(PING_CATEGORY, pingType, pingTag) local x, y = lib:GetMapPing(pingType, pingTag) if(lib:IsPositionOnMap(x, y)) then g_mapPinManager:CreatePin(pingType, pingTag, x, y) return true end return false end function lib:IsPositionOnMap(x, y) return not (x < 0 or y < 0 or x > 1 or y > 1 or (x == 0 and y == 0)) end function lib:MutePing(pingType, pingTag) local key = GetKey(pingType, pingTag) local mute = lib.mutePing[key] or 0 lib.mutePing[key] = mute + 1 end function lib:UnmutePing(pingType, pingTag) local key = GetKey(pingType, pingTag) local mute = (lib.mutePing[key] or 0) - 1 if(mute < 0) then mute = 0 end lib.mutePing[key] = mute end function lib:IsPingMuted(pingType, pingTag) local key = GetKey(pingType, pingTag) return lib.mutePing[key] and lib.mutePing[key] > 0 end function lib:SuppressPing(pingType, pingTag) local key = GetKey(pingType, pingTag) local suppress = lib.suppressPing[key] or 0 lib.suppressPing[key] = suppress + 1 end function lib:UnsuppressPing(pingType, pingTag) local key = GetKey(pingType, pingTag) local suppress = (lib.suppressPing[key] or 0) - 1 if(suppress < 0) then suppress = 0 end lib.suppressPing[key] = suppress end function lib:IsPingSuppressed(pingType, pingTag) local key = GetKey(pingType, pingTag) return lib.suppressPing[key] and lib.suppressPing[key] > 0 end local function InterceptMapPinManager() if (g_mapPinManager) then return end local orgRefreshCustomPins = ZO_WorldMapPins.RefreshCustomPins function ZO_WorldMapPins:RefreshCustomPins() g_mapPinManager = self end ZO_WorldMap_RefreshCustomPinsOfType() ZO_WorldMapPins.RefreshCustomPins = orgRefreshCustomPins end InterceptMapPinManager() -- TODO keep an eye on worldmap.lua for changes local function HandleMapPing(eventCode, pingEventType, pingType, pingTag, x, y, isPingOwner) if(pingEventType == PING_EVENT_ADDED) then lib.cm:FireCallbacks("BeforePingAdded", pingType, pingTag, x, y, isPingOwner) lib.isPingSet[GetKey(pingType, pingTag)] = true g_mapPinManager:RemovePins(PING_CATEGORY, pingType, pingTag) if(not lib:IsPingSuppressed(pingType, pingTag)) then g_mapPinManager:CreatePin(pingType, pingTag, x, y) if(isPingOwner and not lib:IsPingMuted(pingType, pingTag)) then PlaySound(SOUNDS.MAP_PING) end end lib.cm:FireCallbacks("AfterPingAdded", pingType, pingTag, x, y, isPingOwner) elseif(pingEventType == PING_EVENT_REMOVED) then lib.cm:FireCallbacks("BeforePingRemoved", pingType, pingTag, x, y, isPingOwner) lib.isPingSet[GetKey(pingType, pingTag)] = false g_mapPinManager:RemovePins(PING_CATEGORY, pingType, pingTag) if (isPingOwner and not lib:IsPingSuppressed(pingType, pingTag) and not lib:IsPingMuted(pingType, pingTag)) then PlaySound(SOUNDS.MAP_PING_REMOVE) end lib.cm:FireCallbacks("AfterPingRemoved", pingType, pingTag, x, y, isPingOwner) end end EVENT_MANAGER:UnregisterForEvent(LIB_IDENTIFIER, EVENT_ADD_ON_LOADED) EVENT_MANAGER:RegisterForEvent(LIB_IDENTIFIER, EVENT_ADD_ON_LOADED, function(_, addonName) if(addonName == "ZO_Ingame") then EVENT_MANAGER:UnregisterForEvent(LIB_IDENTIFIER, EVENT_ADD_ON_LOADED) -- don't let worldmap do anything as we manage it instead EVENT_MANAGER:UnregisterForEvent("ZO_WorldMap", EVENT_MAP_PING) EVENT_MANAGER:RegisterForEvent(LIB_IDENTIFIER, EVENT_MAP_PING, HandleMapPing) end end) lib.cm = ZO_CallbackObject:New() function lib:RegisterCallback(eventName, callback) lib.cm:RegisterCallback(eventName, callback) end function lib:UnregisterCallback(eventName, callback) lib.cm:UnregisterCallback(eventName, callback) end