HarvestRoute = {} HarvestRoute.name = "HarvestRoute" HarvestRoute.displayName = 'HarvestRoute for Harvestmap' HarvestRoute.version = "1.0.8" HarvestRoute.author = "generic" HarvestRoute.settingsVersion = 3 HarvestRoute.savedVars = {} HarvestRoute.defaultSettings = { trackerRange = 5, enableTrackerWindow = true, showTrackerWindow = false, usePathHeuristic = true, wm = { x = 1500, y = 0, }, } 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 ourPathChange = false 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 ) HarvestRoute.ApplyStyle() 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, }, { type = "checkbox", name = HarvestRoute.GetLocalization("usepathheuristics"), tooltip = HarvestRoute.GetLocalization("usepathheuristicstooltip"), getFunc = function () return HarvestRoute.savedVars.usePathHeuristic end, setFunc = function (value) HarvestRoute.savedVars.usePathHeuristic = value end, default = HarvestRoute.defaultSettings.usePathHeuristic, }, } 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") Farm = Harvest.farm Editor = Harvest.editor Helper = Farm.helper 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 = nil 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) if ourPathChange then return end -- if we are responsible we do not care. 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..' ') if HarvestRoute.savedVars.usePathHeuristic then pathNodeId = HarvestRoute.getSmartInsertNode(pathNodeId, lastNodeAddedToPath, nodeId) end ourPathChange = true Farm.path:InsertAfter(pathNodeId, nodeId) pathNodeId = nodeId lastNodeAddedToPath = nodeId lastPathIndex = Farm.path:GetIndex(nodeId) ourPathChange = false 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 ourPathChange = true path = Harvest.path:New(MapCache) path:GenerateFromNodeIds(pathNodes, 1, #pathNodes) Farm:SetPath(path) ourPathChange = false lastPathIndex = Farm.path:GetIndex(nodeId) lastNodeAddedToPath = nodeId pathNodeId = nodeId pathNodes = {} end end lastNodeId = nodeId end --nodeDistance HarvestRoute.UpdateTracker() end --nodeId end --isActive end function HarvestRoute.getNodeDistance(n1, n2) local distance = 10000 if MapCache.worldX and MapCache.worldX[n1] and MapCache.worldX[n2] then local dx = MapCache.worldX[n1] - MapCache.worldX[n2] local dy = MapCache.worldY[n1] - MapCache.worldY[n2] distance = ( dx * dx + dy * dy ) ^ 0.5 end return distance end function HarvestRoute.GetPathNodeWithOffset(nodeId, offset) local index = Farm.path:GetIndex(nodeId) local oldindex = index if not index then return nil end if not offset then return nodeId end local numNodes = Farm.path.numNodes --offset = offset % numNodes index = index + offset + numNodes index = ((index - 1) % numNodes) +1 if index < 1 then --index = index + numNodes elseif index > numNodes then --index = index - numNodes end HarvestRoute.debug("Index ".. oldindex .. " + " .. offset .. " = " .. index .. " (" .. numNodes .. ")" ) return Farm.path.nodeIndices[index] end function HarvestRoute.GetNextPathNode(nodeId) return HarvestRoute.GetPathNodeWithOffset(nodeId, 1) end function HarvestRoute.GetPreviousPathNode(nodeId) return HarvestRoute.GetPathNodeWithOffset(nodeId, -1) end -- be "smart" and detect if adding before or after either n1 or n2 wourld result in a better path -- should remove most zigzags when backtracking or enhancing existing paths function HarvestRoute.getSmartInsertNode(n1, n2, addNode) --if not n2 then return n1 end --if n1 == n2 then return n1 end HarvestRoute.debug("getSmartInsertNode") -- HarvestRoute.debug("checkPathHeuristic " .. n1 .." / ".. n2 .. " (" .. addNode ..")") local result = n1 if Farm.path then local n1_result, n1_distance = HarvestRoute.getBetterInsertNode(n1, addNode) result = n1_result if n2 and n1 ~= n2 then local n2_result, n2_distance = HarvestRoute.getBetterInsertNode(n2, addNode) if n2_distance < n1_distance then result = n2_result end end end return result end -- return previous node, if the resulting path would be better (basically insert before node, instead of insert after) function HarvestRoute.getBetterInsertNode(node, addNode) local nextnode = HarvestRoute.GetNextPathNode(node) local prevnode = HarvestRoute.GetPreviousPathNode(node) if prevnode and nextnode then -- using stackoverflow https://stackoverflow.com/questions/33155240/how-to-check-if-a-point-is-between-two-other-points-but-not-limited-to-be-align -- add = A, node = B, next / prev = C local a_prev_to_node = HarvestRoute.getNodeDistance(prevnode, node) local a_node_to_next = HarvestRoute.getNodeDistance(node, nextnode) local b_prev_to_add = HarvestRoute.getNodeDistance(prevnode, addNode) local b_add_to_next = HarvestRoute.getNodeDistance(addNode, nextnode) local c_node_to_add = HarvestRoute.getNodeDistance(node, addNode) -- path length changes for both node and prevnode local d_next = a_prev_to_node + c_node_to_add + b_add_to_next - a_node_to_next local d_prev = b_prev_to_add + c_node_to_add + a_node_to_next - a_prev_to_node -- some mathemagics which are not needed --[[ local node_between_next = HarvestRoute.NodeAisBetweenBandCusingDistances(a_node_to_next, b_add_to_next, c_node_to_add) local node_between_prev = HarvestRoute.NodeAisBetweenBandCusingDistances(a_prev_to_node, b_prev_to_add, c_node_to_add) local prev_crossed = HarvestRoute.linesAreCrossed(prevnode, addNode, node, nextnode) local next_crossed = HarvestRoute.linesAreCrossed(prevnode, node, addNode, nextnode) --]] if d_prev < d_next then HarvestRoute.debug("PrevNode " .. prevnode .. " (prevnode shorter " .. d_prev .. " vs. " .. d_next .. ")") return prevnode, d_prev else HarvestRoute.debug("Node " .. node .. " (node shorter " .. d_prev .. " vs. " .. d_next.. ")") return node, d_next end end return node, 0 end -- checks if the two line-segments defined by points [a/b] to [c/d] and [p/q] to [r/s] do intersect or not function HarvestRoute.intersects(a,b,c,d, p,q,r,s) local det, gamma, lambda; det = (c - a) * (s - q) - (r - p) * (d - b); if det == 0 then return false end lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det; gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det; return (0 < lambda and lambda < 1) and (0 < gamma and gamma < 1); end function HarvestRoute.linesAreCrossed(node_a, node_b, node_c, node_d) local wx = MapCache.worldX local wy = MapCache.worldY return HarvestRoute.intersects( wx[node_a],wy[node_a], wx[node_b],wy[node_b], wx[node_c],wy[node_c], wx[node_d],wy[node_d]) end function HarvestRoute.NodeAisBetweenBandC(nodeA, nodeB, nodeC) local a = HarvestRoute.getNodeDistance(nodeB, nodeC) local b = HarvestRoute.getNodeDistance(nodeC, nodeA) local c = HarvestRoute.getNodeDistance(nodeA, nodeB) return HarvestRoute.NodeAisBetweenBandCusingDistances(a, b, c) end function HarvestRoute.NodeAisBetweenBandCusingDistances(a, b, c) return (a*a + b*b >= c*c and a*a + c*c >= b*b) 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:OnMoveStop( ) HarvestRoute.savedVars.wm.x = self:GetLeft() HarvestRoute.savedVars.wm.y = self:GetTop() HarvestRoute.savedVars.wm.width = self:GetWidth() HarvestRoute.savedVars.wm.height = self:GetHeight() end function HarvestRoute.ApplyStyle() HarvestRouteTracker:SetAnchor( TOPLEFT, GuiRoot, TOPLEFT, HarvestRoute.savedVars.wm.x, HarvestRoute.savedVars.wm.y ) end 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)