1.0.4 added tracker window and fixed settings save

generic [05-11-22 - 11:14]
1.0.4 added tracker window and fixed settings save
Filename
HarvestRoute.lua
HarvestRoute.txt
Localization/de.lua
Localization/default.lua
Tracker.xml
diff --git a/HarvestRoute.lua b/HarvestRoute.lua
index 904f714..3df3df1 100644
--- a/HarvestRoute.lua
+++ b/HarvestRoute.lua
@@ -1,12 +1,15 @@
 HarvestRoute = {}
 HarvestRoute.name = "HarvestRoute"
 HarvestRoute.displayName = 'HarvestRoute for Harvestmap'
-HarvestRoute.version = "1.0.3"
+HarvestRoute.version = "1.0.4"
 HarvestRoute.author = "generic"
-HarvestRoute.settingsVersion = 1
+HarvestRoute.settingsVersion = 2
+HarvestRoute.savedVars = {}

 HarvestRoute.defaultSettings = {
-  trackerRange = 5
+  trackerRange = 5,
+  enableTrackerWindow = true,
+  showTrackerWindow = false
 }

 local LibAddonMenu2 = LibAddonMenu2
@@ -17,23 +20,39 @@ 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 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, 10)
+    control:SetAnchor(TOPLEFT, Harvest.farm.generator.generatorBar , BOTTOMLEFT, 0, 14)
     control:SetWidth(HarvestFarmGenerator:GetWidth() - padding)
     control:SetClickSound("Click")
     control:SetHandler("OnClicked", HarvestRoute.OnButton )
@@ -43,17 +62,48 @@ local function initialize( eventCode, addOnName )
     local control = WINDOW_MANAGER:CreateControl(nil, HarvestFarmGenerator, CT_LABEL)
     control:SetFont("ZoFontGame")
     control:SetText(HarvestRoute.GetLocalization( "tourtrackerdescription" ))
-    control:SetAnchor(TOPLEFT, lastControl, BOTTOMLEFT, 0, 20)
+    control:SetAnchor(TOPLEFT, lastControl, BOTTOMLEFT, 0, 10)
     control:SetWidth(HarvestFarmGenerator:GetWidth() - padding)
-    HarvestRoute.savedVars = ZO_SavedVars:NewAccountWide("HarvestRoute", HarvestRoute.settingsVersion, nil, HarvestRoute.defaultSettings)
+
+    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",
@@ -70,6 +120,36 @@ function HarvestRoute:InitializeSettingsMenu()
           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"),
@@ -84,21 +164,32 @@ function HarvestRoute:InitializeSettingsMenu()
     LibAddonMenu2:RegisterOptionControls(HarvestRoute.name.."OptionsMenu", options)
 end

-
-
 function HarvestRoute:OnButton()
     if isActive then
-      EVENT_MANAGER:UnregisterForUpdate("HarvestRouteUpdatePosition")
-      HarvestRouteButton:SetText(HarvestRoute.GetLocalization( "buttonstarttracker" ))
       isActive = false
+      HarvestRoute:StopTracker()
     else
+      isActive = true
+      HarvestRoute:StartTracker()
+    end
+    HarvestRoute:UpdateTracker()
+end
+
+function HarvestRoute:StartTracker()
       EVENT_MANAGER:RegisterForUpdate("HarvestRouteUpdatePosition", 50, HarvestRoute.OnUpdate)
-      HarvestRouteButton:SetText(HarvestRoute.GetLocalization( "buttonstoptracker" ))
+
+
       local mapMetaData = Harvest.mapTools:GetViewedMapMetaData()
       MapCache = Harvest.Data:GetMapCache( mapMetaData )
-      isActive = true
       currentZoneId = GetUnitZoneIndex("player")
-    end
+
+      HarvestRoute:SetTrackerHidden(false)
+      HarvestRoute:InitTracker()
+end
+
+function HarvestRoute:StopTracker()
+      EVENT_MANAGER:UnregisterForUpdate("HarvestRouteUpdatePosition")
+      HarvestRoute:InitTracker()
 end

 function HarvestRoute:OnPlayerLoaded()
@@ -106,6 +197,7 @@ function HarvestRoute:OnPlayerLoaded()
       if currentZoneId and currentZoneId == newZoneId then
         return
       end
+      HarvestRoute:debug('zone switched')
       lastNodeId = nil
       pathNodeId = nil
       lastPathIndex = nill
@@ -114,6 +206,12 @@ function HarvestRoute:OnPlayerLoaded()
       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
@@ -134,72 +232,88 @@ function HarvestRoute:checkPath(event, path)
     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
-      local nodeId = HarvestRoute:GetClosestNode()
-      if nodeId and nodeId ~= lastNodeId then
-        lastNodeId = nodeId
-        local x, y = MapCache:GetLocal(nodeId)
-        if Farm.path then
-          local index = Farm.path:GetIndex(nodeId)
-          if index then
-            pathNodeId = nodeId
-            lastPathIndex = index
-          else
-            if pathNodeId then
-              Farm.path:InsertAfter(pathNodeId, nodeId)
+      playerX, playerY, playerZ = Harvest.GetPlayer3DPosition()
+      local nodeId, nodeDistance = HarvestRoute:GetClosestNode()
+      if nodeId then
+        if nodeId ~= lastNodeId  and (nodeDistance < HarvestRoute.savedVars.trackerRange) then
+          lastNodeId = nodeId
+          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
+              HarvestRoute:debug('path node '..nodeId..' ')
               pathNodeId = nodeId
-              lastPathIndex = Farm.path:GetIndex(nodeId)
-              Harvest.farm.display:Refresh(Farm.path, Harvest.farm.display.selectedPins, Harvest.farm.display.selectedMapCache)
-              --Harvest.callbackManager:FireCallbacks(Harvest.events.TOUR_CHANGED, Farm.path)
-              -- 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()
+              lastPathIndex = index
+            else
+              if pathNodeId then
+                HarvestRoute:debug('insert '..nodeId..' after path node '..pathNodeId..' ')
+                Farm.path:InsertAfter(pathNodeId, nodeId)
+                pathNodeId = 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())
+
+                --Harvest.callbackManager:FireCallbacks(Harvest.events.TOUR_CHANGED, Farm.path)
+                -- 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(Harvest.mapPins.mapCache)
+              path:GenerateFromNodeIds(pathNodes, 1, #pathNodes)
+              Farm:SetPath(path)
+              lastPathIndex = Farm.path:GetIndex(nodeId)
+              pathNodeId = nodeId
+              pathNodes = {}
+            end
           end
-        else --create a new path
-          pathNodes[#pathNodes + 1] = nodeId
-          if #pathNodes > 2 then
-            path = Harvest.path:New(Harvest.mapPins.mapCache)
-            path:GenerateFromNodeIds(pathNodes, 1, #pathNodes)
-            Farm:SetPath(path)
-            pathNodeId = nodeId
-            pathNodes = {}
-          end
-        end
-      end
-    end
+        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
+      return nil, nil
     end
-    local x, y, z = Harvest.GetPlayer3DPosition()

     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 = x - halfDivisionInMeters
-    local half_before_y = y - halfDivisionInMeters
+    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
@@ -215,13 +329,28 @@ function HarvestRoute:GetClosestNode()
                 -- 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] - x
-                    dy = MapCache.worldY[nodeId] - y
+                    dx = MapCache.worldX[nodeId] - playerX
+                    dy = MapCache.worldY[nodeId] - playerY
                     distance = dx * dx + dy * dy
                     if distance < bestDistance then
                       bestNodeId = nodeId
                       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
@@ -229,14 +358,124 @@ function HarvestRoute:GetClosestNode()
           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
-        local realDistance = bestDistance ^ 0.5
-        if realDistance < self.savedVars.trackerRange then
-          return bestNodeId
-        end
+        realDistance = bestDistance ^ 0.5
+        return bestNodeId, realDistance
       end
     end
-    return nil
+    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)
\ No newline at end of file
diff --git a/HarvestRoute.txt b/HarvestRoute.txt
index 8d0cce8..7208571 100644
--- a/HarvestRoute.txt
+++ b/HarvestRoute.txt
@@ -2,6 +2,7 @@
 ## Author: generic
 ## APIVersion: 101034 101033
 ## AddOnVersion: 2
+## SavedVariables: HarvestRouteVars
 ## DependsOn: HarvestMap LibAddonMenu-2.0>=32


@@ -10,3 +11,4 @@ HarvestRoute.lua
 Localization/$(language).lua
 Localization/default.lua

+Tracker.xml
diff --git a/Localization/de.lua b/Localization/de.lua
index 8b5ad97..034f428 100644
--- a/Localization/de.lua
+++ b/Localization/de.lua
@@ -3,6 +3,22 @@ HarvestRoute.localizedStrings = {
   ["routenodes"] = "Visited Nodes and Farming Helper",
   ["routerangemultiplier"] = "Tracking Node Reichweite",
   ["routerangemultipliertooltip"] = "Nodes innerhalb von X Metern werden vom Tracker hinzugefügt bzw. als zuletzt besuchte Tour-Node berücksichtigt.",
+  ["enabletrackerwindow"] = "Tracker-Fenster anzeigen",
+  ["enabletrackerwindowtooltip"] = "Zeigt Farmtour-Infos, die nächste Resource und die letzte Resource der aktuellen Tour",
+  ["showtrackerwindow"] = "Tracker Fenster automatisch anzeigen",
+  ["showtrackerwindowtooltip"] = "Wenn ausgeschaltet wird der Tracker nur angezeigt wenn Du das Tracking startest",
+
+  ["trackeractive"] = "|C7FCF7FTracker läuft|r",
+  ["trackerinactive"] = "|CFF7F7FTracker ist aus|r",
+  ["pathinfomissing"] = "|CFF7F7Fkeine Tour aktiv|r",
+  ["pathinfotitle"] = "aktuelle Tour:",
+  ["pathinfo"] = "<<1>> Nodes |CA0A0A0(<<2>> m lang)|r",
+  ["nearestnodetitle"] = "dichteste Resource:",
+  ["nearestnodetooltip"] = "Die am nächsten gelegene Resource, die noch nicht in der Tour enthalten ist",
+  ["lastpathnodetitle"] = "letzte Resource der Tour:",
+  ["lastpathnodetooltip"] = "Neue Nodes werden nach dieser Resource-Node in der Tour eingefügt",
+  ["nodeinfounknown"] = "|CA0A0A0keine Node innerhalb von 50 m|r",
+  ["nodeinfo"] = [[<<1>> |CA0A0A0(<<2>> m)|r]],


   ["tourtrackerdescription"] = [[Nutze den Tracker um neue Nodes automagisch  nach der zuletzt besuchten Node einzufügen.
diff --git a/Localization/default.lua b/Localization/default.lua
index af69038..d2041b9 100644
--- a/Localization/default.lua
+++ b/Localization/default.lua
@@ -8,7 +8,22 @@ HarvestRoute.defaultLocalizedStrings = {
 	routenodes = "Visited Nodes and Farming Helper",
 	routerangemultiplier = "Tour tracking node range",
 	routerangemultipliertooltip = "Nodes within X meters are considered as visited by the tour tracker.",
+	enabletrackerwindow = "enable Tracker window",
+	enabletrackerwindowtooltip = "display tour info, nearest resource, and the last node from tour",
+  showtrackerwindow = "always show Tracker window",
+  showtrackerwindowtooltip = "if disabled, the Tracker window will only be shown once you activate the Tracker",

+	trackeractive = "|C7FCF7FTracker is active|r",
+  trackerinactive = "|CFF7F7FTracker is off|r",
+	pathinfomissing = "|CFF7F7Fno tour active|r",
+  pathinfotitle = "current tour:",
+  pathinfo = "<<1>> nodes |CA0A0A0(<<2>> m length)|r",
+  nearestnodetitle = "nearest resource:",
+  nearestnodetooltip = "the nearest resource to your position, that is not included in the current tour",
+  lastpathnodetitle = "last resource from tour:",
+  lastpathnodetooltip = "new nodes will be inserted after this node",
+  nodeinfounknown = "|CA0A0A0no node within 50m|r",
+	nodeinfo = [[<<1>> |CA0A0A0(<<2>> m)|r]],

 	tourtrackerdescription = [[Use the tour tracker to automatically insert new nodes after the last visited node included in your tour.
 	If there is no tour yet, one will be created after you visited at least 3 nodes.
diff --git a/Tracker.xml b/Tracker.xml
new file mode 100644
index 0000000..0cdc165
--- /dev/null
+++ b/Tracker.xml
@@ -0,0 +1,70 @@
+<GuiXml>
+	<Controls>
+		<TopLevelControl name="HarvestRouteTracker" mouseEnabled="true" movable="true" hidden="true">
+			<Anchor point="TOPRIGHT" offsetX="0" offsetY="0" />
+			<Dimensions x="300" y="250"/>
+			<Controls>
+				<Backdrop name="$(parent)BG" inherits="ZO_DefaultBackdrop" >
+					<Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="0" />
+					<Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="0" />
+				</Backdrop>
+
+				<Button name="$(parent)Close" text="Close" inherits="ZO_CloseButton" >
+					<Anchor point="TOPRIGHT" relativeTo="$(parent)"  relativePoint="TOPRIGHT" offsetX="-4" offsetY="4" />
+					<OnClicked>
+						HarvestRoute:SetTrackerHidden(true)
+					</OnClicked>
+				</Button>
+
+				<Label name="$(parent)Active" text="Tracker active" font="ZoFontGameBold" color="FFFFFF" horizontalAlignment="LEFT" >
+					<Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="16" offsetY="10" />
+					<Anchor point="TOPRIGHT" relativeTo="$(parent)" relativePoint="TOPRIGHT" offsetX="-16" offsetY="10" />
+				</Label>
+
+                <Label name="$(parent)PathInfoTitle" font="ZoFontGame" color="FFFFFF" text="XXXX" horizontalAlignment="LEFT" >
+                    <Anchor point="TOPLEFT" relativeTo="$(parent)Active"  relativePoint="BOTTOMLEFT" offsetX="0" offsetY="0" />
+                    <Anchor point="TOPRIGHT" relativeTo="$(parent)Active"  relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="0" />
+                </Label>
+        				<Label name="$(parent)PathInfo" font="ZoFontGame" color="FFFFFF" text="<<1>> nodes |CA0A0A0(<<2>> m length)|r" horizontalAlignment="RIGHT" >
+        					<Anchor point="TOPRIGHT" relativeTo="$(parent)PathInfoTitle"  relativePoint="BOTTOMRIGHT" offsetX="-2" offsetY="2" />
+        				</Label>
+
+                <Label name="$(parent)NearestNodeTitle" font="ZoFontGame" color="FFFFFF" text="nearest node" horizontalAlignment="LEFT" mouseEnabled="true">
+                    <Anchor point="TOPLEFT" relativeTo="$(parent)PathInfoTitle"  relativePoint="BOTTOMLEFT" offsetX="0" offsetY="24" />
+                    <Anchor point="TOPRIGHT" relativeTo="$(parent)PathInfoTitle"  relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="24" />
+                </Label>
+        				<Label name="$(parent)NearestNode" font="ZoFontGame" color="FFFFFF" text="Runestone at x.xx / y.yy" horizontalAlignment="RIGHT" >
+        					<Anchor point="TOPRIGHT" relativeTo="$(parent)NearestNodeTitle"  relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="8" />
+        				</Label>
+
+                <Texture name="$(parent)NearestArrow" textureFile="HarvestMap/Textures/Arrow/arrow.dds" >
+                    <Anchor point="TOPLEFT" relativeTo="$(parent)NearestNodeTitle" relativePoint="BOTTOMLEFT" offsetX="-16" offsetY="-8" />
+                    <Dimensions x="50" y="50"/>
+                </Texture>
+
+
+                <Label name="$(parent)LastPathNodeTitle" font="ZoFontGame" color="A0A0A0" text="last resource from tour:" horizontalAlignment="LEFT" mouseEnabled="true">
+                    <Anchor point="TOPLEFT" relativeTo="$(parent)NearestNodeTitle"  relativePoint="BOTTOMLEFT" offsetX="0" offsetY="40" />
+                    <Anchor point="TOPRIGHT" relativeTo="$(parent)NearestNodeTitle"  relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="40" />
+                </Label>
+        				<Label name="$(parent)LastPathNode" font="ZoFontGame" color="7F7F7F" text="Flower at x.xx / y.yy" horizontalAlignment="RIGHT" >
+        					<Anchor point="TOPRIGHT" relativeTo="$(parent)LastPathNodeTitle"  relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="8" />
+        				</Label>
+
+                <Texture name="$(parent)PathArrow" textureFile="HarvestMap/Textures/Arrow/arrow.dds" >
+                    <Anchor point="TOPLEFT" relativeTo="$(parent)LastPathNodeTitle" relativePoint="BOTTOMLEFT" offsetX="-16" offsetY="-8" />
+                    <Dimensions x="50" y="50"/>
+                </Texture>
+
+
+				<Button name="$(parent)Button" text="activate" inherits="ZO_DefaultButton" >
+					<Anchor point="BOTTOMLEFT" relativeTo="$(parent)"  relativePoint="BOTTOMLEFT" offsetX="2" offsetY="-2" />
+					<Anchor point="BOTTOMRIGHT" relativeTo="$(parent)"  relativePoint="BOTTOMRIGHT" offsetX="-2" offsetY="-2" />
+					<OnClicked>
+						HarvestRoute:OnButton()
+					</OnClicked>
+				</Button>
+			</Controls>
+		</TopLevelControl>
+	</Controls>
+</GuiXml>