HarvestRoute = {} HarvestRoute.name = "HarvestRoute" HarvestRoute.displayName = 'HarvestRoute for Harvestmap' HarvestRoute.version = "1.0.5" HarvestRoute.author = "generic" HarvestRoute.settingsVersion = 2 HarvestRoute.savedVars = {} HarvestRoute.defaultSettings = { trackerRange = 5, enableTrackerWindow = true, showTrackerWindow = false } local LibAddonMenu2 = LibAddonMenu2 local Farm = Harvest.farm local Editor = Farm.editor local Path = Harvest.path local MapCache = nil local Helper = Farm.helper local atan2 = math.atan2 local zo_round = _G["zo_round"] local GetPlayerCameraHeading = _G["GetPlayerCameraHeading"] local GetMapPlayerPosition = _G["GetMapPlayerPosition"] local HarvestRouteButton = nil local isActive = false local isTrackerVisible = false local currentZoneId = nil local lastNodeId = nil local lastNodeAddedToPath = nil local lastPathIndex = nil local closesPathNodeId = nil local closestOutOfPathId = nil local pathNodeId = nil local pathNodes = {} local playerX = 0 local playerY = 0 local playerZ = 0 local function initialize( eventCode, addOnName ) if ( addOnName ~= HarvestRoute.name ) then return end HarvestRoute.savedVars = ZO_SavedVars:NewAccountWide("HarvestRouteVars", HarvestRoute.settingsVersion, nil, HarvestRoute.defaultSettings) local padding = 30 local control = WINDOW_MANAGER:CreateControlFromVirtual('HarvestRouteButton', HarvestFarmGenerator, "ZO_DefaultButton") control:SetText(HarvestRoute.GetLocalization( "buttonstarttracker" )) control:SetAnchor(TOPLEFT, Harvest.farm.generator.generatorBar , BOTTOMLEFT, 0, 14) control:SetWidth(HarvestFarmGenerator:GetWidth() - padding) control:SetClickSound("Click") control:SetHandler("OnClicked", HarvestRoute.OnButton ) HarvestRouteButton = control local lastControl = control local control = WINDOW_MANAGER:CreateControl(nil, HarvestFarmGenerator, CT_LABEL) control:SetFont("ZoFontGame") control:SetText(HarvestRoute.GetLocalization( "tourtrackerdescription" )) control:SetAnchor(TOPLEFT, lastControl, BOTTOMLEFT, 0, 10) control:SetWidth(HarvestFarmGenerator:GetWidth() - padding) HarvestRouteTrackerPathInfoTitle:SetText( HarvestRoute.GetLocalization( "pathinfotitle" ) ) HarvestRouteTrackerNearestNodeTitle:SetText( HarvestRoute.GetLocalization( "nearestnodetitle" )) HarvestRouteTrackerNearestNodeTitle:SetHandler("OnMouseEnter", function(self) ZO_Tooltips_ShowTextTooltip(self, TOP, HarvestRoute.GetLocalization( "nearestnodetooltip" )) end) HarvestRouteTrackerNearestNodeTitle:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end) HarvestRouteTrackerLastPathNodeTitle:SetText( HarvestRoute.GetLocalization( "lastpathnodetitle" )) HarvestRouteTrackerLastPathNodeTitle:SetHandler("OnMouseEnter", function(self) ZO_Tooltips_ShowTextTooltip(self, TOP, HarvestRoute.GetLocalization( "lastpathnodetooltip" )) end) HarvestRouteTrackerLastPathNodeTitle:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end) HarvestRouteTrackerButton:SetText( HarvestRoute.GetLocalization( "buttonstarttracker" ) ) HarvestRouteTrackerNearestArrow:SetColor( 0.5, 0.75, 0.5, 0.75) HarvestRouteTrackerPathArrow:SetColor( 0.25, 0.25, 0.25, 0.75) HarvestRoute.InitializeSettingsMenu() EVENT_MANAGER:RegisterForEvent("HarvestRoutePlayerActivated", EVENT_PLAYER_ACTIVATED, HarvestRoute.OnPlayerLoaded ) Harvest.callbackManager:RegisterForEvent(Harvest.events.TOUR_CHANGED, HarvestRoute.checkPath ) if HarvestRoute.savedVars.enableTrackerWindow and HarvestRoute.savedVars.showTrackerWindow then HarvestRoute:SetTrackerHidden( false ) HarvestRoute:InitTracker() HarvestRoute:UpdateTracker() end end local function TrackerWindowDisabled() return not HarvestRoute.savedVars.enableTrackerWindow end function HarvestRoute:InitializeSettingsMenu() local panel = { type = "panel", name = HarvestRoute.name, displayName = HarvestRoute.displayName, author = HarvestRoute.author, version = HarvestRoute.version, registerForRefresh = true, registerForDefaults = true, } local options = { { type = "description", text = HarvestRoute.GetLocalization("addonsettingstext"), }, { type = "checkbox", name = HarvestRoute.GetLocalization("enabletrackerwindow"), tooltip = HarvestRoute.GetLocalization("enabletrackerwindowtooltip"), getFunc = function () return HarvestRoute.savedVars.enableTrackerWindow end, setFunc = function (value) HarvestRoute.savedVars.enableTrackerWindow = value if not value then HarvestRouteTracker:SetHidden( true ) elseif HarvestRoute.savedVars.alwaysShowTracker then HarvestRouteTracker:SetHidden( false ) end end, default = HarvestRoute.defaultSettings.enableTrackerWindow, }, { type = "checkbox", name = HarvestRoute.GetLocalization("showtrackerwindow"), tooltip = HarvestRoute.GetLocalization("showtrackerwindowtooltip"), getFunc = function () return HarvestRoute.savedVars.showTrackerWindow end, setFunc = function (value) HarvestRoute.savedVars.showTrackerWindow = value if value then HarvestRoute:SetTrackerHidden( false ) HarvestRoute:InitTracker() HarvestRoute:UpdateTracker() end end, default = HarvestRoute.defaultSettings.showTrackerWindow, disabled = TrackerWindowDisabled, }, { type = "slider", name = HarvestRoute.GetLocalization("routerangemultiplier"), tooltip = HarvestRoute.GetLocalization("routerangemultipliertooltip"), min = 3, max = 20, getFunc = function () return HarvestRoute.savedVars.trackerRange end, setFunc = function (value) HarvestRoute.savedVars.trackerRange = value end, default = HarvestRoute.defaultSettings.trackerRange, } } LibAddonMenu2:RegisterAddonPanel(HarvestRoute.name.."OptionsMenu", panel) LibAddonMenu2:RegisterOptionControls(HarvestRoute.name.."OptionsMenu", options) end function HarvestRoute:OnButton() if isActive then isActive = false HarvestRoute:StopTracker() else isActive = true HarvestRoute:StartTracker() end HarvestRoute:UpdateTracker() end function HarvestRoute:StartTracker() EVENT_MANAGER:RegisterForUpdate("HarvestRouteUpdatePosition", 50, HarvestRoute.OnUpdate) local mapMetaData = Harvest.mapTools:GetViewedMapMetaData() MapCache = Harvest.Data:GetMapCache( mapMetaData ) currentZoneId = GetUnitZoneIndex("player") HarvestRoute:SetTrackerHidden(false) HarvestRoute:InitTracker() end function HarvestRoute:StopTracker() EVENT_MANAGER:UnregisterForUpdate("HarvestRouteUpdatePosition") HarvestRoute:InitTracker() end function HarvestRoute:OnPlayerLoaded() local newZoneId = GetUnitZoneIndex("player") if currentZoneId and currentZoneId == newZoneId then return end HarvestRoute:debug('zone switched') lastNodeId = nil pathNodeId = nil lastPathIndex = nill pathNodes = {} currentZoneId = newZoneId if isActive then HarvestRoute:OnButton() end if HarvestRoute.savedVars.enableTrackerWindow then if HarvestRoute.savedVars.showTrackerWindow or isTrackerVisible then HarvestRoute:SetTrackerHidden( false ) HarvestRoute:UpdateTracker() end end end -- reset pathnodes when path has changed from outside, and either last node or last path node are invalid function HarvestRoute:checkPath(event, path) local validpath = false local validnode = false if lastNodeId and MapCache then -- does the nodeId still exist? if MapCache.pinTypeId[lastNodeId] then validnode = true -- check if our path information still works if pathNodeId and Farm.path then local index = Farm.path:GetIndex(pathNodeId) if index and (index == lastPathIndex) then validpath = true end end end end if not validnode then HarvestRoute:debug('last node invalid') lastNodeId = nil end if not validpath then HarvestRoute:debug('last path index invalid') lastNodeId = nil pathNodeId = nil lastPathIndex = nil pathNodes = {} end HarvestRoute:UpdateTracker() end function HarvestRoute:OnUpdate (time) if isActive then playerX, playerY, playerZ = Harvest.GetPlayer3DPosition() local nodeId, nodeDistance, nodePinType = HarvestRoute:GetClosestNode() if nodeId then if nodePinType ~= Harvest.UNKNOWN and nodeId ~= lastNodeId and (nodeDistance < HarvestRoute.savedVars.trackerRange) then HarvestRoute:debug('node in range: '..nodeId) local x, y = MapCache:GetLocal(nodeId) if Farm.path then local index = Farm.path:GetIndex(nodeId) if index then -- check if we are far enough away from the last inserted node before switching local switchToPathNode = true if lastNodeAddedToPath then dx = MapCache.worldX[nodeId] - MapCache.worldX[lastNodeAddedToPath] dy = MapCache.worldY[nodeId] - MapCache.worldY[lastNodeAddedToPath] distance = dx * dx + dy * dy checkDistance = HarvestRoute.savedVars.trackerRange * HarvestRoute.savedVars.trackerRange if (checkDistance > distance) then switchToPathNode = false end end if switchToPathNode then HarvestRoute:debug('path node '..nodeId..' ') pathNodeId = nodeId lastPathIndex = index end else if pathNodeId then HarvestRoute:debug('insert '..nodeId..' after path node '..pathNodeId..' ') Farm.path:InsertAfter(pathNodeId, nodeId) pathNodeId = nodeId lastNodeAddedToPath = nodeId lastPathIndex = Farm.path:GetIndex(nodeId) Harvest.farm.display:Refresh(Farm.path, Harvest.farm.display.selectedPins, Harvest.farm.display.selectedMapCache) Harvest.farm.editor.statusLabel:SetText(Harvest.farm.editor.textConstructor()) -- if we are farming, update the next pin so we can actually finish the loop if we want to if Farm.helper:IsRunning() then Farm.helper.nextPathIndex = lastPathIndex Farm.helper:UpdateToNextTarget() end end end else --create a new path pathNodes[#pathNodes + 1] = nodeId HarvestRoute:debug('added '..nodeId..' as path node '..#pathNodes) if #pathNodes > 2 then path = Harvest.path:New(MapCache) path:GenerateFromNodeIds(pathNodes, 1, #pathNodes) Farm:SetPath(path) lastPathIndex = Farm.path:GetIndex(nodeId) pathNodeId = nodeId pathNodes = {} end end lastNodeId = nodeId end --nodeDistance HarvestRoute:UpdateTracker() end --nodeId end --isActive end -- get the closest node to the player, using HarvestMap MapCache Divisions (and only check 4 divisions, from -50 to +50 from current position) function HarvestRoute:GetClosestNode() local bestNodeId = nil if not MapCache then --yeah, no cache no cookies. return nil, nil end local halfDivisionInMeters = zo_floor(MapCache.DivisionWidthInMeters / 2) --should be 50 most of the time, but lets keep it dynamic local numDivisions = MapCache.numDivisions local totalNumDivisions = MapCache.TotalNumDivisions local half_before_x = playerX - halfDivisionInMeters local half_before_y = playerY - halfDivisionInMeters local index = (zo_floor(half_before_x / MapCache.DivisionWidthInMeters) + zo_floor(half_before_y / MapCache.DivisionWidthInMeters) * numDivisions) % MapCache.TotalNumDivisions local bestDistance = halfDivisionInMeters * halfDivisionInMeters local bestPathDistance = bestDistance local bestOutOfPathDistance = bestDistance local divisions = MapCache.divisions -- check all pintypes, and the 4 divisions that contain the 100 x 100 square around the player if divisions then for _, pinTypeId in pairs(Harvest.PINTYPES) do local pinDivision = MapCache.divisions[pinTypeId] if pinDivision then for i = index, index + 1 do for j = 0, 1 do division = pinDivision[(i + j * numDivisions) % totalNumDivisions] or {} if division then -- check all node in current division, if there are closer nodes to the player for _, nodeId in pairs(division) do if MapCache.worldX then dx = MapCache.worldX[nodeId] - playerX dy = MapCache.worldY[nodeId] - playerY distance = dx * dx + dy * dy if distance < bestDistance then bestNodeId = nodeId bestNodePinType = pinTypeId bestDistance = distance end inPath = nil if Farm.path then inPath = Farm.path:GetIndex(nodeId) end if inPath then if distance < bestPathDistance then bestPathDistance = distance closesPathNodeId = nodeId end else if distance < bestOutOfPathDistance then bestOutOfPathDistance = distance closestOutOfPathId = nodeId end end end end end end -- for j end -- for i end -- if pinDivision end --for pinType if bestPathDistance == (halfDivisionInMeters * halfDivisionInMeters) then closesPathNodeId = nil end if bestOutOfPathDistance == (halfDivisionInMeters * halfDivisionInMeters) then closestOutOfPathId = nodeId end if bestNodeId then realDistance = bestDistance ^ 0.5 return bestNodeId, realDistance, bestNodePinType end end return nil, nil end -- UI element management function HarvestRoute:SetTrackerHidden(value) isTrackerVisible = false if not HarvestRoute.savedVars.enableTrackerWindow then value = true end HarvestRouteTracker:SetHidden(value) isTrackerVisible = not value end function HarvestRoute:GetNodeDescription(nodeId) local description = HarvestRoute.GetLocalization( "nodeinfounknown" ) local angle = nil if MapCache and MapCache.worldX and MapCache.worldX[nodeId] then local x, y, z = Harvest.GetPlayer3DPosition() local targetX = MapCache.worldX[nodeId] local targetY = MapCache.worldY[nodeId] local dx = playerX - targetX local dy = playerY - targetY local distanceInMeters = (dx * dx + dy * dy)^0.5 nodeX = zo_round( MapCache.worldX[nodeId] * 100 ) / 100 nodeY = zo_round( MapCache.worldY[nodeId] * 100 ) / 100 pinType = MapCache.pinTypeId[nodeId] node = Harvest.GetLocalization( "pintype" ..pinType ) description = zo_strformat( HarvestRoute.GetLocalization( "nodeinfo" ), node, distanceInMeters) angle = -atan2(dx, dy) end return description, angle end function HarvestRoute:InitTracker() if isActive then HarvestRouteButton:SetText(HarvestRoute.GetLocalization( "buttonstoptracker" )) HarvestRouteTrackerButton:SetText(HarvestRoute.GetLocalization( "buttonstoptracker" )) HarvestRouteTrackerActive:SetText( HarvestRoute.GetLocalization( "trackeractive" ) ) HarvestRouteTrackerNearestNodeTitle:SetHidden( false ) HarvestRouteTrackerNearestNode:SetHidden( false ) else HarvestRouteButton:SetText(HarvestRoute.GetLocalization( "buttonstarttracker" )) HarvestRouteTrackerButton:SetText(HarvestRoute.GetLocalization( "buttonstarttracker" )) HarvestRouteTrackerActive:SetText( HarvestRoute.GetLocalization( "trackerinactive" ) ) HarvestRouteTrackerNearestNodeTitle:SetHidden( true ) HarvestRouteTrackerNearestNode:SetHidden( true ) HarvestRouteTrackerNearestArrow:SetHidden( true ) HarvestRouteTrackerLastPathNodeTitle:SetHidden( true ) HarvestRouteTrackerLastPathNode:SetHidden( true ) HarvestRouteTrackerPathArrow:SetHidden( true ) end end function HarvestRoute:UpdateTracker() HarvestRoute:debug('UpdateTracker') if not HarvestRoute.savedVars.enableTrackerWindow then if isTrackerVisible then HarvestRoute:SetTrackerHidden( true ) end return end if not isTrackerVisible then HarvestRoute:debug('tracker not visible') return end if isActive then nearestText, nearestAngle = HarvestRoute:GetNodeDescription(closestOutOfPathId) HarvestRouteTrackerNearestNode:SetText( nearestText ) if nearestAngle then HarvestRouteTrackerNearestArrow:SetHidden(false) HarvestRouteTrackerNearestArrow:SetTextureRotation(-(nearestAngle + GetPlayerCameraHeading()), 0.5, 0.5) else HarvestRouteTrackerNearestArrow:SetHidden(true) end end if Farm.path then num = Farm.path.numNodes length = zo_round(Farm.path.length * 10) / 10 infotext = zo_strformat( HarvestRoute.GetLocalization( "pathinfo" ), num, length) HarvestRouteTrackerPathInfo:SetText( infotext ) if isActive then HarvestRouteTrackerLastPathNodeTitle:SetHidden( false ) HarvestRouteTrackerLastPathNode:SetHidden( false ) pathText, pathAngle = HarvestRoute:GetNodeDescription(pathNodeId) HarvestRouteTrackerLastPathNode:SetText( pathText ) if pathAngle then HarvestRouteTrackerPathArrow:SetHidden(false) HarvestRouteTrackerPathArrow:SetTextureRotation(-(pathAngle + GetPlayerCameraHeading()), 0.5, 0.5) else HarvestRouteTrackerPathArrow:SetHidden(true) end end else HarvestRouteTrackerPathInfo:SetText( HarvestRoute.GetLocalization( "pathinfomissing" ) ) HarvestRouteTrackerLastPathNodeTitle:SetHidden( true ) HarvestRouteTrackerLastPathNode:SetHidden( true ) HarvestRouteTrackerPathArrow:SetHidden( true ) end end function HarvestRoute:debug(text) --CHAT_SYSTEM:AddMessage(text) end EVENT_MANAGER:RegisterForEvent("HarvestRoute", EVENT_ADD_ON_LOADED, initialize)