Compare with Previous | Blame | View Log
-- LibGPS2 & its files © sirinsidiator --
-- Distributed under The Artistic License 2.0 (see LICENSE) --
------------------------------------------------------------------
local LIB_NAME = "LibGPS2"
local lib = LibStub:NewLibrary(LIB_NAME, 16)
if not lib then
return
-- already loaded and no upgrade necessary
end
local LMP = LibStub("LibMapPing", true)
if(not LMP) then
error(string.format("[%s] Cannot load without LibMapPing", LIB_NAME))
end
local DUMMY_PIN_TYPE = LIB_NAME .. "DummyPin"
local LIB_IDENTIFIER_FINALIZE = LIB_NAME .. "_Finalize"
lib.LIB_EVENT_STATE_CHANGED = "OnLibGPS2MeasurementChanged"
local LOG_WARNING = "Warning"
local LOG_NOTICE = "Notice"
local LOG_DEBUG = "Debug"
local POSITION_MIN = 0.085
local POSITION_MAX = 0.915
local TAMRIEL_MAP_INDEX = 1
local rootMaps = lib.rootMaps or {}
lib.rootMaps = rootMaps
--lib.debugMode = 1 -- TODO
lib.mapMeasurements = lib.mapMeasurements or {}
local mapMeasurements = lib.mapMeasurements
lib.mapStack = lib.mapStack or {}
local mapStack = lib.mapStack
lib.suppressCount = lib.suppressCount or 0
local MAP_PIN_TYPE_PLAYER_WAYPOINT = MAP_PIN_TYPE_PLAYER_WAYPOINT
local currentWaypointX, currentWaypointY, currentWaypointMapId = 0, 0, nil
local needWaypointRestore = false
local orgSetMapToMapListIndex = nil
local orgSetMapToPlayerLocation = nil
local orgSetMapFloor = nil
local orgProcessMapClick = nil
local orgFunctions = {}
local measuring = false
SLASH_COMMANDS["/libgpsdebug"] = function(value)
lib.debugMode = (tonumber(value) == 1)
df("[%s] debug mode %s", LIB_NAME, lib.debugMode and "enabled" or "disabled")
end
local function LogMessage(type, message, ...)
if not lib.debugMode then return end
df("[%s] %s: %s", LIB_NAME, type, zo_strjoin(" ", message, ...))
end
local function GetAddon()
local addOn
local function errornous() addOn = 'a' + 1 end
local function errorHandler(err) addOn = string.match(err, "'GetAddon'.+user:/AddOns/(.-:.-):") end
xpcall(errornous, errorHandler)
return addOn
end
local function FinalizeMeasurement()
EVENT_MANAGER:UnregisterForUpdate(LIB_IDENTIFIER_FINALIZE)
while lib.suppressCount > 0 do
LMP:UnsuppressPing(MAP_PIN_TYPE_PLAYER_WAYPOINT)
lib.suppressCount = lib.suppressCount - 1
end
if needWaypointRestore then
LogMessage(LOG_DEBUG, "Update waypoint pin", LMP:GetMapPing(MAP_PIN_TYPE_PLAYER_WAYPOINT))
LMP:RefreshMapPin(MAP_PIN_TYPE_PLAYER_WAYPOINT)
needWaypointRestore = false
end
measuring = false
CALLBACK_MANAGER:FireCallbacks(lib.LIB_EVENT_STATE_CHANGED, measuring)
end
local function HandlePingEvent(pingType, pingTag, x, y, isPingOwner)
if(not isPingOwner or pingType ~= MAP_PIN_TYPE_PLAYER_WAYPOINT or not measuring) then return end
-- we delay our handler until all events have been fired and so that other addons can react to it first in case they use IsMeasuring
EVENT_MANAGER:UnregisterForUpdate(LIB_IDENTIFIER_FINALIZE)
EVENT_MANAGER:RegisterForUpdate(LIB_IDENTIFIER_FINALIZE, 0, FinalizeMeasurement)
end
local function GetPlayerPosition()
return GetMapPlayerPosition("player")
end
local function GetPlayerWaypoint()
return LMP:GetMapPing(MAP_PIN_TYPE_PLAYER_WAYPOINT)
end
local function SetMeasurementWaypoint(x, y)
-- this waypoint stays invisible for others
lib.suppressCount = lib.suppressCount + 1
LMP:SuppressPing(MAP_PIN_TYPE_PLAYER_WAYPOINT)
LMP:SetMapPing(MAP_PIN_TYPE_PLAYER_WAYPOINT, MAP_TYPE_LOCATION_CENTERED, x, y)
end
local function SetPlayerWaypoint(x, y)
LMP:SetMapPing(MAP_PIN_TYPE_PLAYER_WAYPOINT, MAP_TYPE_LOCATION_CENTERED, x, y)
end
local function RemovePlayerWaypoint()
LMP:RemoveMapPing(MAP_PIN_TYPE_PLAYER_WAYPOINT)
end
local function GetReferencePoints()
local x1, y1 = GetPlayerPosition()
local x2, y2 = GetPlayerWaypoint()
return x1, y1, x2, y2
end
local function IsMapMeasured(mapId)
return (mapMeasurements[mapId or GetMapTileTexture()] ~= nil)
end
local function StoreTamrielMapMeasurements()
-- no need to actually measure the world map
if (orgSetMapToMapListIndex(TAMRIEL_MAP_INDEX) ~= SET_MAP_RESULT_FAILED) then
local measurement = {
scaleX = 1,
scaleY = 1,
offsetX = 0,
offsetY = 0,
mapIndex = TAMRIEL_MAP_INDEX,
zoneIndex = GetCurrentMapZoneIndex()
}
mapMeasurements[GetMapTileTexture()] = measurement
rootMaps[TAMRIEL_MAP_INDEX] = measurement
return true
end
return false
end
local function CalculateMeasurements(mapId, localX, localY)
-- select the map corner farthest from the player position
local wpX, wpY = POSITION_MIN, POSITION_MIN
-- on some maps we cannot set the waypoint to the map border (e.g. Aurdion)
-- Opposite corner:
if (localX < 0.5) then wpX = POSITION_MAX end
if (localY < 0.5) then wpY = POSITION_MAX end
SetMeasurementWaypoint(wpX, wpY)
-- add local points to seen maps
local measurementPositions = {}
table.insert(measurementPositions, { mapId = mapId, pX = localX, pY = localY, wpX = wpX, wpY = wpY })
-- switch to zone map in order to get the mapIndex for the current location
local x1, y1, x2, y2
while not(GetMapType() == MAPTYPE_ZONE and GetMapContentType() ~= MAP_CONTENT_DUNGEON) do
if (MapZoomOut() ~= SET_MAP_RESULT_MAP_CHANGED) then break end
-- collect measurements for all maps we come through on our way to the zone map
x1, y1, x2, y2 = GetReferencePoints()
table.insert(measurementPositions, { mapId = GetMapTileTexture(), pX = x1, pY = y1, wpX = x2, wpY = y2 })
end
-- some non-zone maps like Eyevea zoom directly to the Tamriel map
local mapIndex = GetCurrentMapIndex() or TAMRIEL_MAP_INDEX
local zoneIndex = GetCurrentMapZoneIndex()
-- switch to world map so we can calculate the global map scale and offset
if orgSetMapToMapListIndex(TAMRIEL_MAP_INDEX) == SET_MAP_RESULT_FAILED then
-- failed to switch to the world map
LogMessage(LOG_NOTICE, "Could not switch to world map")
return
end
-- get the two reference points on the world map
x1, y1, x2, y2 = GetReferencePoints()
-- calculate scale and offset for all maps that we saw
local scaleX, scaleY, offsetX, offsetY
for _, m in ipairs(measurementPositions) do
if (mapMeasurements[m.mapId]) then break end -- we always go up in the hierarchy so we can stop once a measurement already exists
LogMessage(LOG_DEBUG, "Store map measurement for", m.mapId:sub(10, -7))
scaleX = (x2 - x1) / (m.wpX - m.pX)
scaleY = (y2 - y1) / (m.wpY - m.pY)
offsetX = x1 - m.pX * scaleX
offsetY = y1 - m.pY * scaleY
if (math.abs(scaleX - scaleY) > 1e-3) then
LogMessage(LOG_WARNING, "Current map measurement might be wrong", m.mapId:sub(10, -7), mapIndex, m.pX, m.pY, m.wpX, m.wpY, x1, y1, x2, y2, offsetX, offsetY, scaleX, scaleY)
end
-- store measurements
mapMeasurements[m.mapId] = {
scaleX = scaleX,
scaleY = scaleY,
offsetX = offsetX,
offsetY = offsetY,
mapIndex = mapIndex,
zoneIndex = zoneIndex
}
end
return mapIndex
end
local function StoreCurrentWaypoint()
currentWaypointX, currentWaypointY = GetPlayerWaypoint()
currentWaypointMapId = GetMapTileTexture()
end
local function ClearCurrentWaypoint()
currentWaypointX, currentWaypointY = 0, 0, nil
end
local function GetExtraMapMeasurement(extraMapIndex)
-- switch to the map
orgSetMapToMapListIndex(extraMapIndex)
local extraMapId = GetMapTileTexture()
if(not IsMapMeasured(extraMapId)) then
-- calculate the measurements of map without worrying about the waypoint
local mapIndex = CalculateMeasurements(extraMapId, GetPlayerPosition())
if (mapIndex ~= extraMapIndex) then
local name = GetMapInfo(extraMapIndex)
name = zo_strformat("<<C:1>>", name)
LogMessage(LOG_WARNING, "CalculateMeasurements returned different index while measuring ", name, " map. expected:", extraMapIndex, "actual:", mapIndex)
if (not IsMapMeasured(extraMapId)) then
LogMessage(LOG_WARNING, "Failed to measure ", name, " map.")
return
end
end
end
return mapMeasurements[extraMapId]
end
local function RestoreCurrentWaypoint()
if(not currentWaypointMapId) then
LogMessage(LOG_DEBUG, "Called RestoreCurrentWaypoint without calling StoreCurrentWaypoint.")
return
end
local wasSet = false
if (currentWaypointX ~= 0 or currentWaypointY ~= 0) then
-- calculate waypoint position on the worldmap
local measurements = mapMeasurements[currentWaypointMapId]
local x = currentWaypointX * measurements.scaleX + measurements.offsetX
local y = currentWaypointY * measurements.scaleY + measurements.offsetY
for rootMapIndex, measurements in pairs(rootMaps) do
if not measurements then
measurements = GetExtraMapMeasurement(rootMapIndex)
rootMaps[rootMapIndex] = measurements
end
if(measurements) then
if(x > measurements.offsetX and x < (measurements.offsetX + measurements.scaleX) and
y > measurements.offsetY and y < (measurements.offsetY + measurements.scaleY)) then
if(orgSetMapToMapListIndex(rootMapIndex) ~= SET_MAP_RESULT_FAILED) then
-- calculate waypoint coodinates within root map
x = (x - measurements.offsetX) / measurements.scaleX
y = (y - measurements.offsetY) / measurements.scaleY
SetPlayerWaypoint(x, y)
wasSet = true
break
end
end
end
end
if (not wasSet) then
LogMessage(LOG_DEBUG, "Cannot reset waypoint because it was outside of our reach")
end
end
if(wasSet) then
LogMessage(LOG_DEBUG, "Waypoint was restored, request pin update")
needWaypointRestore = true -- we need to update the pin on the worldmap afterwards
else
RemovePlayerWaypoint()
end
ClearCurrentWaypoint()
end
local function ConnectToWorldMap()
lib.panAndZoom = ZO_WorldMap_GetPanAndZoom()
lib.mapPinManager = ZO_WorldMap_GetPinManager()
if (_G[DUMMY_PIN_TYPE]) then return end
ZO_WorldMap_AddCustomPin(DUMMY_PIN_TYPE, function(pinManager) end , nil, { level = 0, size = 0, texture = "" })
ZO_WorldMap_SetCustomPinEnabled(_G[DUMMY_PIN_TYPE], false)
end
local function HookSetMapToFunction(funcName)
local orgFunction = _G[funcName]
orgFunctions[funcName] = orgFunction
local function NewFunction(...)
local result = orgFunction(...)
if(result ~= SET_MAP_RESULT_MAP_FAILED and not IsMapMeasured()) then
LogMessage(LOG_DEBUG, funcName)
local success, mapResult = lib:CalculateMapMeasurements(false)
if(mapResult ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED) then
result = mapResult
end
orgFunction(...)
end
-- All stuff is done before anyone triggers an "OnWorldMapChanged" event due to this result
return result
end
_G[funcName] = NewFunction
end
local function HookSetMapToPlayerLocation()
orgSetMapToPlayerLocation = SetMapToPlayerLocation
orgFunctions["SetMapToPlayerLocation"] = orgSetMapToPlayerLocation
local function NewSetMapToPlayerLocation(...)
if not DoesUnitExist("player") then return SET_MAP_RESULT_MAP_FAILED end
local result = orgSetMapToPlayerLocation(...)
if(result ~= SET_MAP_RESULT_MAP_FAILED and not IsMapMeasured()) then
LogMessage(LOG_DEBUG, "SetMapToPlayerLocation")
local success, mapResult = lib:CalculateMapMeasurements(false)
if(mapResult ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED) then
result = mapResult
end
orgSetMapToPlayerLocation(...)
end
-- All stuff is done before anyone triggers an "OnWorldMapChanged" event due to this result
return result
end
SetMapToPlayerLocation = NewSetMapToPlayerLocation
end
local function HookSetMapToMapListIndex()
orgSetMapToMapListIndex = SetMapToMapListIndex
orgFunctions["SetMapToMapListIndex"] = orgSetMapToMapListIndex
local function NewSetMapToMapListIndex(mapIndex)
local result = orgSetMapToMapListIndex(mapIndex)
if(result ~= SET_MAP_RESULT_MAP_FAILED and not IsMapMeasured()) then
LogMessage(LOG_DEBUG, "SetMapToMapListIndex")
local success, mapResult = lib:CalculateMapMeasurements(false)
if(mapResult ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED) then
result = mapResult
end
orgSetMapToMapListIndex(mapIndex)
end
-- All stuff is done before anyone triggers an "OnWorldMapChanged" event due to this result
return result
end
SetMapToMapListIndex = NewSetMapToMapListIndex
end
local function HookProcessMapClick()
orgProcessMapClick = ProcessMapClick
orgFunctions["ProcessMapClick"] = orgProcessMapClick
local function NewProcessMapClick(...)
local result = orgProcessMapClick(...)
if(result ~= SET_MAP_RESULT_MAP_FAILED and not IsMapMeasured()) then
LogMessage(LOG_DEBUG, "ProcessMapClick")
local success, mapResult = lib:CalculateMapMeasurements(true)
if(mapResult ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED) then
result = mapResult
end
-- Returning is done via clicking already
end
return result
end
ProcessMapClick = NewProcessMapClick
end
local function HookSetMapFloor()
orgSetMapFloor = SetMapFloor
orgFunctions["SetMapFloor"] = orgSetMapFloor
local function NewSetMapFloor(...)
local result = orgSetMapFloor(...)
if result ~= SET_MAP_RESULT_MAP_FAILED and not IsMapMeasured() then
LogMessage(LOG_DEBUG, "SetMapFloor")
local success, mapResult = lib:CalculateMapMeasurements(true)
if(mapResult ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED) then
result = mapResult
end
orgSetMapFloor(...)
end
return result
end
SetMapFloor = NewSetMapFloor
end
local function Initialize() -- wait until we have defined all functions
--- Unregister handler from older libGPS ( < 3)
EVENT_MANAGER:UnregisterForEvent("LibGPS2_SaveWaypoint", EVENT_PLAYER_DEACTIVATED)
EVENT_MANAGER:UnregisterForEvent("LibGPS2_RestoreWaypoint", EVENT_PLAYER_ACTIVATED)
--- Unregister handler from older libGPS ( <= 5.1)
EVENT_MANAGER:UnregisterForEvent(LIB_NAME .. "_Init", EVENT_PLAYER_ACTIVATED)
--- Unregister handler from older libGPS, as it is now managed by LibMapPing ( >= 6)
EVENT_MANAGER:UnregisterForEvent(LIB_NAME .. "_UnmuteMapPing", EVENT_MAP_PING)
if (lib.Unload) then
-- Undo action from older libGPS ( >= 5.2)
lib:Unload()
if (lib.suppressCount > 0) then
if lib.debugMode then zo_callLater(function() LogMessage(LOG_WARNING, "There is a measure in progress before loading is completed.") end, 2000) end
FinalizeMeasurement()
end
end
--- Register new Unload
function lib:Unload()
for funcName, func in pairs(orgFunctions) do
_G[funcName] = func
end
LMP:UnregisterCallback("AfterPingAdded", HandlePingEvent)
LMP:UnregisterCallback("AfterPingRemoved", HandlePingEvent)
rootMaps, mapMeasurements, mapStack = nil, nil, nil
end
ConnectToWorldMap()
HookSetMapToFunction("SetMapToQuestCondition")
HookSetMapToFunction("SetMapToQuestStepEnding")
HookSetMapToFunction("SetMapToQuestZone")
HookSetMapToPlayerLocation()
HookSetMapToMapListIndex()
HookProcessMapClick()
HookSetMapFloor()
StoreTamrielMapMeasurements()
local function addRootMap(zoneId)
local mapIndex = GetMapIndexByZoneId(zoneId)
if mapIndex then rootMaps[mapIndex] = false end
end
addRootMap(347) -- Coldhabour
addRootMap(980) -- Clockwork City
addRootMap(1027) -- Artaeum
-- Any future extra dimension map here
SetMapToPlayerLocation() -- initial measurement so we can get back to where we are currently
LMP:RegisterCallback("AfterPingAdded", HandlePingEvent)
LMP:RegisterCallback("AfterPingRemoved", HandlePingEvent)
end
------------------------ public functions ----------------------
--- Returns true as long as the player exists.
function lib:IsReady()
return DoesUnitExist("player")
end
--- Returns true if the library is currently doing any measurements.
function lib:IsMeasuring()
return measuring
end
--- Removes all cached measurement values.
function lib:ClearMapMeasurements()
mapMeasurements = { }
end
--- Removes the cached measurement values for the map that is currently active.
function lib:ClearCurrentMapMeasurements()
local mapId = GetMapTileTexture()
mapMeasurements[mapId] = nil
end
--- Returns a table with the measurement values for the active map or nil if the measurements could not be calculated for some reason.
--- The table contains scaleX, scaleY, offsetX, offsetY and mapIndex.
--- scaleX and scaleY are the dimensions of the active map on the Tamriel map.
--- offsetX and offsetY are the offset of the top left corner on the Tamriel map.
--- mapIndex is the mapIndex of the parent zone of the current map.
function lib:GetCurrentMapMeasurements()
local mapId = GetMapTileTexture()
if (not mapMeasurements[mapId]) then
-- try to calculate the measurements if they are not yet available
lib:CalculateMapMeasurements()
end
return mapMeasurements[mapId]
end
--- Returns the mapIndex and zoneIndex of the parent zone for the currently set map.
--- return[1] number - The mapIndex of the parent zone
--- return[2] number - The zoneIndex of the parent zone
function lib:GetCurrentMapParentZoneIndices()
local measurements = lib:GetCurrentMapMeasurements()
local mapIndex = measurements.mapIndex
if(not measurements.zoneIndex) then
lib:PushCurrentMap()
SetMapToMapListIndex(mapIndex)
measurements.zoneIndex = GetCurrentMapZoneIndex()
lib:PopCurrentMap()
end
local zoneIndex = measurements.zoneIndex
return mapIndex, zoneIndex
end
--- Calculates the measurements for the current map and all parent maps.
--- This method does nothing if there is already a cached measurement for the active map.
--- return[1] boolean - True, if a valid measurement was calculated
--- return[2] SetMapResultCode - Specifies if the map has changed or failed during measurement (independent of the actual result of the measurement)
function lib:CalculateMapMeasurements(returnToInitialMap)
-- cosmic map cannot be measured (GetMapPlayerWaypoint returns 0,0)
if (GetMapType() == MAPTYPE_COSMIC) then return false, SET_MAP_RESULT_CURRENT_MAP_UNCHANGED end
-- no need to take measurements more than once
local mapId = GetMapTileTexture()
if (mapMeasurements[mapId] or mapId == "") then return false end
if (lib.debugMode) then
LogMessage("Called from", GetAddon(), "for", mapId)
end
-- get the player position on the current map
local localX, localY = GetPlayerPosition()
if (localX == 0 and localY == 0) then
-- cannot take measurements while player position is not initialized
return false, SET_MAP_RESULT_CURRENT_MAP_UNCHANGED
end
returnToInitialMap = (returnToInitialMap ~= false)
measuring = true
CALLBACK_MANAGER:FireCallbacks(lib.LIB_EVENT_STATE_CHANGED, measuring)
-- check some facts about the current map, so we can reset it later
-- local oldMapIsZoneMap, oldMapFloor, oldMapFloorCount
if returnToInitialMap then
lib:PushCurrentMap()
end
local hasWaypoint = LMP:HasMapPing(MAP_PIN_TYPE_PLAYER_WAYPOINT)
if(hasWaypoint) then StoreCurrentWaypoint() end
local mapIndex = CalculateMeasurements(mapId, localX, localY)
-- Until now, the waypoint was abused. Now the waypoint must be restored or removed again (not from Lua only).
if(hasWaypoint) then
RestoreCurrentWaypoint()
else
RemovePlayerWaypoint()
end
if (returnToInitialMap) then
local result = lib:PopCurrentMap()
return true, result
end
return true, (mapId == GetMapTileTexture()) and SET_MAP_RESULT_CURRENT_MAP_UNCHANGED or SET_MAP_RESULT_MAP_CHANGED
end
--- Converts the given map coordinates on the current map into coordinates on the Tamriel map.
--- Returns x and y on the world map and the mapIndex of the parent zone
--- or nil if the measurements of the active map are not available.
function lib:LocalToGlobal(x, y)
local measurements = lib:GetCurrentMapMeasurements()
if (measurements) then
x = x * measurements.scaleX + measurements.offsetX
y = y * measurements.scaleY + measurements.offsetY
return x, y, measurements.mapIndex
end
end
--- Converts the given global coordinates into a position on the active map.
--- Returns x and y on the current map or nil if the measurements of the active map are not available.
function lib:GlobalToLocal(x, y)
local measurements = lib:GetCurrentMapMeasurements()
if (measurements) then
x = (x - measurements.offsetX) / measurements.scaleX
y = (y - measurements.offsetY) / measurements.scaleY
return x, y
end
end
--- Converts the given map coordinates on the specified zone map into coordinates on the Tamriel map.
--- This method is useful if you want to convert global positions from the old LibGPS version into the new format.
--- Returns x and y on the world map and the mapIndex of the parent zone
--- or nil if the measurements of the zone map are not available.
function lib:ZoneToGlobal(mapIndex, x, y)
lib:GetCurrentMapMeasurements()
-- measurement done in here:
SetMapToMapListIndex(mapIndex)
x, y, mapIndex = lib:LocalToGlobal(x, y)
return x, y, mapIndex
end
--- This function zooms and pans to the specified position on the active map.
function lib:PanToMapPosition(x, y)
-- if we don't have access to the mapPinManager we cannot do anything
if (not self.mapPinManager) then return end
local mapPinManager = self.mapPinManager
-- create dummy pin
local pin = mapPinManager:CreatePin(_G[DUMMY_PIN_TYPE], "libgpsdummy", x, y)
self.panAndZoom:PanToPin(pin)
-- cleanup
mapPinManager:RemovePins(DUMMY_PIN_TYPE)
end
local function FakeZO_WorldMap_IsMapChangingAllowed() return true end
local function FakeSetMapToMapListIndex() return SET_MAP_RESULT_MAP_CHANGED end
local FakeCALLBACK_MANAGER = { FireCallbacks = function() end }
--- This function sets the current map as player chosen so it won't switch back to the previous map.
function lib:SetPlayerChoseCurrentMap()
-- replace the original functions
local oldIsChangingAllowed = ZO_WorldMap_IsMapChangingAllowed
ZO_WorldMap_IsMapChangingAllowed = FakeZO_WorldMap_IsMapChangingAllowed
local oldSetMapToMapListIndex = SetMapToMapListIndex
SetMapToMapListIndex = FakeSetMapToMapListIndex
local oldCALLBACK_MANAGER = CALLBACK_MANAGER
CALLBACK_MANAGER = FakeCALLBACK_MANAGER
-- make our rigged call to set the player chosen flag
ZO_WorldMap_SetMapByIndex()
-- cleanup
ZO_WorldMap_IsMapChangingAllowed = oldIsChangingAllowed
SetMapToMapListIndex = oldSetMapToMapListIndex
CALLBACK_MANAGER = oldCALLBACK_MANAGER
end
--- Sets the best matching root map: Tamriel, Cold Harbour or Clockwork City and what ever will come.
--- Returns SET_MAP_RESULT_FAILED, SET_MAP_RESULT_MAP_CHANGED depending on the result of the API calls.
function lib:SetMapToRootMap(x, y)
local result = SET_MAP_RESULT_FAILED
for rootMapIndex, measurements in pairs(rootMaps) do
if (not measurements) then
measurements = GetExtraMapMeasurement(rootMapIndex)
rootMaps[rootMapIndex] = measurements
result = SET_MAP_RESULT_MAP_CHANGED
end
if (measurements) then
if (x > measurements.offsetX and x < (measurements.offsetX + measurements.scaleX) and
y > measurements.offsetY and y < (measurements.offsetY + measurements.scaleY)) then
if (orgSetMapToMapListIndex(rootMapIndex) ~= SET_MAP_RESULT_FAILED) then
return SET_MAP_RESULT_MAP_CHANGED
end
end
end
end
return result
end
--- Repeatedly calls ProcessMapClick on the given global position starting on the Tamriel map until nothing more would happen.
--- Returns SET_MAP_RESULT_FAILED, SET_MAP_RESULT_MAP_CHANGED or SET_MAP_RESULT_CURRENT_MAP_UNCHANGED depending on the result of the API calls.
function lib:MapZoomInMax(x, y)
local result = lib:SetMapToRootMap(x, y)
if (result ~= SET_MAP_RESULT_FAILED) then
local localX, localY = lib:GlobalToLocal(x, y)
while WouldProcessMapClick(localX, localY) do
result = orgProcessMapClick(localX, localY)
if (result == SET_MAP_RESULT_FAILED) then break end
localX, localY = lib:GlobalToLocal(x, y)
end
end
return result
end
local SetCurrentZoom, GetCurrentZoom -- TODO remove
if(GetAPIVersion() >= 100025) then
function SetCurrentZoom(zoom)
return lib.panAndZoom:SetCurrentNormalizedZoom(zoom)
end
function GetCurrentZoom()
return lib.panAndZoom:GetCurrentNormalizedZoom()
end
else
function GetCurrentZoom()
return lib.panAndZoom:GetCurrentZoom()
end
function SetCurrentZoom(zoom)
return lib.panAndZoom:SetCurrentZoom(zoom)
end
end
--- Stores information about how we can back to this map on a stack.
-- There is no panAndZoom:GetCurrentOffset(), yet
local function CalculateContainerAnchorOffsets()
local containerCenterX, containerCenterY = ZO_WorldMapContainer:GetCenter()
local scrollCenterX, scrollCenterY = ZO_WorldMapScroll:GetCenter()
return containerCenterX - scrollCenterX, containerCenterY - scrollCenterY
end
function lib:PushCurrentMap()
local wasPlayerLocation, targetMapTileTexture, currentMapFloor, currentMapFloorCount, currentMapIndex, zoom, offsetX, offsetY
currentMapIndex = GetCurrentMapIndex()
wasPlayerLocation = DoesCurrentMapMatchMapForPlayerLocation()
targetMapTileTexture = GetMapTileTexture()
currentMapFloor, currentMapFloorCount = GetMapFloorInfo()
zoom = GetCurrentZoom()
offsetX, offsetY = CalculateContainerAnchorOffsets()
mapStack[#mapStack + 1] = { wasPlayerLocation, targetMapTileTexture, currentMapFloor, currentMapFloorCount, currentMapIndex, zoom, offsetX, offsetY }
end
--- Switches to the map that was put on the stack last.
--- Returns SET_MAP_RESULT_FAILED, SET_MAP_RESULT_MAP_CHANGED or SET_MAP_RESULT_CURRENT_MAP_UNCHANGED depending on the result of the API calls.
function lib:PopCurrentMap()
local result = SET_MAP_RESULT_FAILED
local data = table.remove(mapStack, #mapStack)
if(not data) then
LogMessage(LOG_DEBUG, "PopCurrentMap failed. No data on map stack.")
return result
end
local wasPlayerLocation, targetMapTileTexture, currentMapFloor, currentMapFloorCount, currentMapIndex, zoom, offsetX, offsetY = unpack(data)
local currentTileTexture = GetMapTileTexture()
if(currentTileTexture ~= targetMapTileTexture) then
if(wasPlayerLocation) then
result = orgSetMapToPlayerLocation()
elseif(currentMapIndex ~= nil and currentMapIndex > 0) then -- set to a zone map
result = orgSetMapToMapListIndex(currentMapIndex)
else -- here is where it gets tricky
local target = mapMeasurements[targetMapTileTexture]
if(not target) then -- always just return to player map if we cannot restore the previous map.
LogMessage(LOG_DEBUG, string.format("No measurement for \"%s\". Returning to player location.", targetMapTileTexture))
return orgSetMapToPlayerLocation()
end
-- switch to the parent zone
if(target.mapIndex == TAMRIEL_MAP_INDEX) then -- zone map has no mapIndex (e.g. Eyevea or Hew's Bane on first PTS patch for update 9)
-- switch to the tamriel map just in case
result = orgSetMapToMapListIndex(TAMRIEL_MAP_INDEX)
if(result == SET_MAP_RESULT_FAILED) then return result end
-- get global coordinates of target map center
local x = target.offsetX + (target.scaleX / 2)
local y = target.offsetY + (target.scaleY / 2)
if(not WouldProcessMapClick(x, y)) then
LogMessage(LOG_DEBUG, string.format("Cannot process click at %s/%s on map \"%s\" in order to get to \"%s\". Returning to player location instead.", tostring(x), tostring(y), GetMapTileTexture(), targetMapTileTexture))
return orgSetMapToPlayerLocation()
end
result = orgProcessMapClick(x, y)
if(result == SET_MAP_RESULT_FAILED) then return result end
else
result = orgSetMapToMapListIndex(target.mapIndex)
if(result == SET_MAP_RESULT_FAILED) then return result end
end
-- switch to the sub zone
currentTileTexture = GetMapTileTexture()
if(currentTileTexture ~= targetMapTileTexture) then
-- determine where on the zone map we have to click to get to the sub zone map
-- get global coordinates of target map center
local x = target.offsetX + (target.scaleX / 2)
local y = target.offsetY + (target.scaleY / 2)
-- transform to local coordinates
local current = mapMeasurements[currentTileTexture]
if(not current) then
LogMessage(LOG_DEBUG, string.format("No measurement for \"%s\". Returning to player location.", currentTileTexture))
return orgSetMapToPlayerLocation()
end
x = (x - current.offsetX) / current.scaleX
y = (y - current.offsetY) / current.scaleY
if(not WouldProcessMapClick(x, y)) then
LogMessage(LOG_DEBUG, string.format("Cannot process click at %s/%s on map \"%s\" in order to get to \"%s\". Returning to player location instead.", tostring(x), tostring(y), GetMapTileTexture(), targetMapTileTexture))
return orgSetMapToPlayerLocation()
end
result = orgProcessMapClick(x, y)
if(result == SET_MAP_RESULT_FAILED) then return result end
end
-- switch to the correct floor (e.g. Elden Root)
if (currentMapFloorCount > 0) then
result = orgSetMapFloor(currentMapFloor)
end
if (result ~= SET_MAP_RESULT_FAILED) then
SetCurrentZoom(zoom)
lib.panAndZoom:SetCurrentOffset(offsetX, offsetY)
end
end
else
result = SET_MAP_RESULT_CURRENT_MAP_UNCHANGED
end
return result
end
Initialize()