Nos partenaires et nous-mêmes utilisons différentes technologies, telles que les cookies, pour personnaliser les contenus et les publicités, proposer des fonctionnalités sur les réseaux sociaux et analyser le trafic. Merci de cliquer sur le bouton ci-dessous pour donner votre accord. Vous pouvez changer d’avis et modifier vos choix à tout moment. Informations RGPD
local Lib3D = LibStub("Lib3D2") if not Harvest.Data then Harvest.Data = {} end local Harvest = _G["Harvest"] local Data = Harvest.Data local CallbackManager = Harvest.callbackManager local Events = Harvest.events Data.dataDefault = { data = {}, dataVersion = Harvest.dataVersion, } Data.subModules = {} -- when retrieved data from merge, remove very old data function Data:GetMinGameVersionFromMerge() local lastMerge = self.savedVars.lastMerge if lastMerge then -- "2017-09-24 14:58:11 [342938]" local year, month, day = lastMerge:match("(%d+)-(%d+)-(%d+)") if tonumber(year) >= 2017 and tonumber(month) >= 8 and tonumber(day) >= 1 then local version, update, patch = 2, 7, 0 return version * 10000 + update * 100 + patch end end return 0 end local addOnNameToGlobal = { HarvestMapAD = "HarvestAD", HarvestMapEP = "HarvestEP", HarvestMapDC = "HarvestDC", HarvestMapDLC = "HarvestDLC", } local addOnNameToZones = { HarvestMapAD = { ["auridon"] = true, ["grahtwood"] = true, ["greenshade"] = true, ["malabaltor"] = true, ["reapersmarch"] = true }, HarvestMapEP = { ["stonefalls"] = true, ["deshaan"] = true, ["shadowfen"] = true, ["eastmarch"] = true, ["therift"] = true }, HarvestMapDC = { ["glenumbra"] = true, ["stormhaven"] = true, ["rivenspire"] = true, ["alikr"] = true, ["bangkorai"] = true }, HarvestMapDLC = { --imperialcity, part of cyrodiil ["wrothgar"] = true, ["thievesguild"] = true, ["darkbrotherhood"] = true, --dungeonthingy, does that one even have a prefix? ["vvardenfell"] = true, -- 2nd dungeon thingy ["clockwork"] = true, }, } function Data:CheckSubModule(addOnName) local globalVarName = addOnNameToGlobal[addOnName] if not globalVarName then return end --[[ local addOnManager = GetAddOnManager() local numAddOns = addOnManager:GetNumAddOns() for i = 1, numAddOns do local name, _, _, _, _, _, isOutOfDate = GetAddOnInfo(i) if name == addOnName then if isOutOfDate then ZO_ERROR_FRAME:OnUIError("Outdated HarvestMap modules detected. Go to your addons folder and delete the following folders: HarvestMapAD, HarvestMapED, HarvestMapDC, HarvestMap") end end end --]] subModule = {} _G[globalVarName] = subModule subModule.zones = addOnNameToZones[addOnName] self.subModules[globalVarName] = subModule -- load data stored in submodules subModule.savedVars = ZO_SavedVars:NewAccountWide(globalVarName .. "_SavedVars", 1, "nodes", self.dataDefault, nil, Harvest.GetAccountName()) if not subModule.savedVars.firstLoaded then subModule.savedVars.firstLoaded = Harvest.GetCurrentTimestamp() end subModule.savedVars.lastLoaded = Harvest.GetCurrentTimestamp() end function Data:LoadSavedVars() self.savedVars = {} self.savedVars = ZO_SavedVars:NewAccountWide("Harvest_SavedVars", 2, "nodes", self.dataDefault, nil, Harvest.GetAccountName()) if not self.savedVars.firstLoaded then self.savedVars.firstLoaded = Harvest.GetCurrentTimestamp() end self.savedVars.lastLoaded = Harvest.GetCurrentTimestamp() -- load data stored in submodules for subModuleName, module in pairs(self.subModules) do module.savedVars = ZO_SavedVars:NewAccountWide(subModuleName .. "_SavedVars", 1, "nodes", self.dataDefault, nil, Harvest.GetAccountName()) if not module.savedVars.firstLoaded then module.savedVars.firstLoaded = Harvest.GetCurrentTimestamp() end module.savedVars.lastLoaded = Harvest.GetCurrentTimestamp() end end function Data:ClearCaches() self.currentZoneCache = nil self.mapCaches = {} self.numCaches = 0 CallbackManager:FireCallbacks(Events.SETTING_CHANGED, "cacheCleared") end function Data:Initialize() -- cache the ACE deserialized nodes -- this way changing maps multiple times will create less lag self.mapCaches = {} self.numCaches = 0 -- load nodes self.savedVars = {} self.savedVars = ZO_SavedVars:NewAccountWide("Harvest_SavedVars", 2, "nodes", self.dataDefault, nil, Harvest.GetAccountName()) if not self.savedVars.firstLoaded then self.savedVars.firstLoaded = Harvest.GetCurrentTimestamp() end self.savedVars.lastLoaded = Harvest.GetCurrentTimestamp() -- check if saved data is from an older version, -- update the data if needed Harvest.AddToUpdateQueue(function() Data:UpdateDataVersion() end) -- move data to correct save files -- if AD was disabled while harvesting in AD, everything was saved in self.savedvars -- when ad is enabled, everything needs to be moved to that save file -- HOWEVER, only execute this after the save files were updated! Harvest.AddToUpdateQueue(function() self:MoveData() end) -- when the time setting is changed, all caches need to be reloaded local clearCache = function(event, setting, value) if setting == "applyTimeDifference" then self:ClearCaches() elseif setting == "computerWideNodes" then if not Harvest.IsUpdateQueueEmpty() then return end if value then -- remember current savedVars local savedVars = { ["Main AddOn"] = self.savedVars } for subModuleName, module in pairs(self.subModules) do savedVars[subModuleName] = module.savedVars end -- load new computerwide saved vars self:LoadSavedVars() -- move old data to computerwide data for name, savedVar in pairs(savedVars) do local moduleSavedVar = savedVar local moduleName = name Harvest.AddToUpdateQueue(function() for map, data in pairs( moduleSavedVar.data ) do local zone = string.gsub( map, "/.*$", "" ) local file = self:GetSaveFile( map ) if file ~= nil then --d(map) file.data[ map ] = data moduleSavedVar.data[ map ] = nil end end local file = _G[moduleName.."_SavedVars"] if file then for userName, data in pairs(file["Default"]) do if userName ~= Harvest.globalUser then file["Default"][userName] = nil end end end d("Finished " .. moduleName .. ". " .. tostring(Harvest.GetQueuePercent()) .. "%") end) --Harvest.AddToUpdateQueue(function() --d("Finished " .. moduleName .. ". " .. tostring(Harvest.GetQueuePercent()) .. "%") --end) end else -- load new accountwide saved vars self:LoadSavedVars() end -- clear cache as it doesn't represent the current data anymore self:ClearCaches() end end CallbackManager:RegisterForEvent(Events.SETTING_CHANGED, clearCache) EVENT_MANAGER:RegisterForEvent("HarvestMapNewZone", EVENT_PLAYER_ACTIVATED, function() self:OnPlayerActivated() end) end -- imports all the nodes on 'map' from the table 'data' into the save file table 'saveFile' -- if checkPinType is true, data will be skipped if Harvest.IsPinTypeSavedOnImport(pinTypeId) returns false function Data:ImportFromMap( map, data, saveFile, checkPinType ) local insert = table.insert local pairs = _G["pairs"] local zo_max = _G["zo_max"] local type = _G["type"] local next = _G["next"] -- nothing to merge, data can simply be copied if saveFile.data[ map ] == nil then saveFile.data[ map ] = data return end -- the target table contains already a bunch of nodes, so the data has to be merged local targetData = nil local newNode = nil local index = 0 local oldNode = nil local distance = Harvest.GetMinDistanceBetweenPins() local timestamp = Harvest.GetCurrentTimestamp() local maxTimeDifference = Harvest.GetMaxTimeDifference() * 3600 local success, x, y, x2, y2, z, items, stamp, version, dx, dy, globalX, globalY local isValid local cache = Data.MapCache:New(map) for _, pinTypeId in ipairs(Harvest.PINTYPES) do if (not checkPinType) or Harvest.IsPinTypeSavedOnImport( pinTypeId ) then cache:InitializePinType(pinTypeId) if saveFile.data[ map ][ pinTypeId ] == nil then -- nothing to merge for this pin type, just copy the data saveFile.data[ map ][ pinTypeId ] = data[ pinTypeId ] else -- deserialize target data and clear the serialized target data table (we'll fill it again at the end) local startIndex = cache.lastNodeId+1 for nodeIndex, node in pairs( saveFile.data[ map ][ pinTypeId ] ) do success, x, y, z, items, stamp, version, globalX, globalY = self:Deserialize(node, pinTypeId) if success then -- check if something went wrong while deserializing the node cache:Add(pinTypeId, nil, x, y, z, items, stamp, version, globalX, globalY ) end end local endIndex = cache.lastNodeId saveFile.data[ map ][ pinTypeId ] = {} -- deserialize every new node and merge them with the old nodes data[ pinTypeId ] = data[ pinTypeId ] or {} for nodeIndex, node in pairs( data[ pinTypeId ] ) do success, x, y, z, items, stamp, version, globalX, globalY = self:Deserialize(node, pinTypeId) if success then -- check if something went wrong while deserializing the node -- If the node is new enough to be saved isValid = true if maxTimeDifference > 0 and (timestamp - stamp) > maxTimeDifference then isValid = false end if isValid then -- If we have found this node already then we don't need to save it again for nodeId = startIndex, endIndex do x2 = cache.localX[nodeId] y2 = cache.localY[nodeId] dx = x - x2 dy = y - y2 -- the nodes are too close if dx * dx + dy * dy < distance then cache:Merge(nodeId, x, y, z, items, stamp, version, globalX, globalY) isValid = false break end end end if isValid then cache:Add(pinTypeId, nil, x, y, z, items, stamp, version, globalX, globalY ) end end end -- serialize the new data for nodeIndex, nodeId in pairs( cache.nodesOfPinType[pinTypeId] ) do x = cache.localX[nodeId] y = cache.localY[nodeId] z = cache.worldZ[nodeId] globalX = cache.globalX[nodeId] globalY = cache.globalY[nodeId] items = cache.items[nodeId] stamp = cache.timestamp[nodeId] version = cache.version[nodeId] saveFile.data[ map ][ pinTypeId ][ nodeIndex ] = self:Serialize(x, y, z, items, stamp, version, globalX, globalY ) end end end end -- as new data was added to the map, the appropiate cache has to be cleared local existingCache = self.mapCaches[map] if existingCache then self.mapCaches[map] = nil self.numCaches = self.numCaches - 1 CallbackManager:FireCallbacks(Events.SETTING_CHANGED, "cacheCleared", map) end end -- returns the correct table for the map (HarvestMap, HarvestMapAD/DC/EP save file tables) -- will return HarvestMap's table if the correct table doesn't currently exist. -- ie the HarvestMapAD addon isn't currently active function Data:GetSaveFile( map ) return self:GetSpecialSaveFile( map ) or self.savedVars end -- returns the correct (external) table for the map or nil if no such table exists function Data:GetSpecialSaveFile( map ) local zone = string.gsub( map, "/.*$", "" ) for subModuleName, subModule in pairs(self.subModules) do if subModule.zones[ zone ] then return subModule.savedVars end end return nil end -- this function moves data from the HarvestMap addon to HarvestMapAD/DC/EP function Data:MoveData() for map, data in pairs( self.savedVars.data ) do local zone = string.gsub( map, "/.*$", "" ) local file = self:GetSpecialSaveFile( map ) if file ~= nil then Harvest.AddToUpdateQueue(function() self:ImportFromMap( map, data, file ) self.savedVars.data[ map ] = nil Harvest.Debug("Moving old data to the correct save files. " .. tostring(Harvest.GetQueuePercent()) .. "%") end) end end end -- data is stored as ACE strings -- this functions deserializes the strings and saves the results in the cache function Data:LoadToCache(pinTypeId, cache) cache:InitializePinType(pinTypeId) --local lastMerge = self.savedVars.lastMerge --if lastMerge then -- local y, m, d = string.match(lastMerge,"(%d*)-(%d*)-(%d*)") -- lastMerge = ((tonumber(y) or 0) * 100 + (tonumber(m) or 0)) * 100 + (tonumber(d) or 0) --end local map = cache.map local measurement = cache.measurement if not measurement then Harvest.Debug("no measurement when loading cache!") return end local saveFile = self:GetSaveFile(map) saveFile.data[ map ] = (saveFile.data[ map ]) or {} saveFile.data[ map ][ pinTypeId ] = (saveFile.data[ map ][ pinTypeId ]) or {} local serializedNodes = saveFile.data[ map ][ pinTypeId ] local currentTimestamp = Harvest.GetCurrentTimestamp() local maxTimeDifference = Harvest.GetMaxTimeDifference() * 3600 local distance = Harvest.GetGlobalMinDistanceBetweenPins() / (measurement.distanceCorrection * measurement.distanceCorrection) local startIndex = cache.lastNodeId + 1 local minGameVersion = zo_max(Harvest.GetMinGameVersion(), self:GetMinGameVersionFromMerge()) local valid, updated, globalX, globalY, addonVersion, gameVersion local success, x, y, z, names, items, timestamp, version local globalX2, globalY2, dx, dy, stamp, stamp2, items, items2 -- deserialize the nodes for nodeIndex, node in pairs( serializedNodes ) do success, x, y, z, items, timestamp, version, globalX, globalY = self:Deserialize( node, pinTypeId ) if not success then--or not x or not y then Harvest.AddToErrorLog(x) else valid = true updated = false -- some timestamps are too large because of repeated multiplying with 3600 of the bugged update routine if valid then if currentTimestamp < timestamp then timestamp = 0 updated = true end end -- remove nodes that are too old (either number of days or patch) if maxTimeDifference > 0 and currentTimestamp - timestamp > maxTimeDifference then valid = false end -- remove outdated item ids if isValid and items and maxTimeDifference > 0 then for itemId, itemStamp in pairs(items) do if (timestamp - itemStamp) > maxTimeDifference then items[itemId] = nil updated = true end end end if minGameVersion > 0 and zo_floor((version % Harvest.trustedFlag) / 1000) < minGameVersion then valid = false end -- nodes must be inside the current map if valid then valid = (0 <= x and x < 1 and 0 <= y and x < 1) if not valid then Harvest.AddToErrorLog("invalid coord:" .. map .. node ) end end if valid then if (not globalX) or (not globalY) then globalX = x * measurement.scaleX + measurement.offsetX globalY = y * measurement.scaleY + measurement.offsetY updated = true end -- remove nodes which were wrongfully saved on their global position addonVersion = version % 1000 gameVersion = zo_floor((version % Harvest.trustedFlag) / 1000) if addonVersion < 1 then if (globalX - x)^2 + (globalY - y)^2 < 0.009 then Harvest.AddToErrorLog("old addon version, global-local-bug:" .. map .. node ) valid = false end end --if valid and lastMerge then -- if map == "auridon/auridon_base" then -- if lastMerge > 20170601 then -- if gameVersion < 30000 then -- morrowind -- valid = false -- end -- end -- end --end -- hew's bane was rescaled some time between 5th and 11th december (no patch maintenance, so it's really weird...) -- the same rescale also happened during the DB update (though the scaling was done in the other direction) -- let's try to repair it by merging nodes: -- the newer node is kept when two nodes can reconstruct each other by rescaling the map if valid then if map == "thievesguild/hewsbane_base" then for nodeId = startIndex, cache.lastNodeId do dx = cache.localX[nodeId] - x * 0.9871 dy = cache.localY[nodeId] - y * 0.9871 -- the nodes are too close if dx * dx + dy * dy < 0.00003 then x, y, z, items, timestamp, version = cache:Merge(nodeId, x, y, z, items, timestamp, version, globalX, globalY) -- update the old node serializedNodes[cache.nodeIndex[nodeId] ] = self:Serialize(x, y, z, items, timestamp, version, globalX, globalY) Harvest.AddToErrorLog("node was merged:" .. map .. node ) valid = false -- set this to false, so the node isn't added to the cache break end dx = cache.localX[nodeId] - x / 0.9871 dy = cache.localY[nodeId] - y / 0.9871 -- the nodes are too close if dx * dx + dy * dy < 0.00003 then x, y, z, items, timestamp, version, globalX, globalY = cache:Merge(nodeId, x, y, z, items, timestamp, version, globalX, globalY) -- update the old node serializedNodes[cache.nodeIndex[nodeId] ] = self:Serialize(x, y, z, items, timestamp, version, globalX, globalY) Harvest.AddToErrorLog("node was merged:" .. map .. node ) valid = false -- set this to false, so the node isn't added to the cache break end end end end end -- remove close nodes (ie duplicates on cities) if valid then -- compare distance to previous nodes for nodeId = startIndex, cache.lastNodeId do globalX2 = cache.globalX[nodeId] globalY2 = cache.globalY[nodeId] dx = globalX - globalX2 dy = globalY - globalY2 -- the nodes are too close if dx * dx + dy * dy < distance then x, y, z, items, timestamp, version, globalX, globalY = cache:Merge(nodeId, x, y, z, items, timestamp, version, globalX, globalY) -- update the old node serializedNodes[cache.nodeIndex[nodeId]] = self:Serialize(x, y, z, items, timestamp, version, globalX, globalY) Harvest.AddToErrorLog("node was merged:" .. map .. node ) valid = false -- set this to false, so the node isn't added to the cache break end end end -- there are items saved even though, there shouldn't be items for this pin type if valid and not Harvest.ShouldSaveItemId(pinTypeId) and items then if next(items) then updated = true end items = nil end if valid then cache:Add(pinTypeId, nodeIndex, x, y, z, items, timestamp, version, globalX, globalY) if updated then serializedNodes[nodeIndex] = self:Serialize(x, y, z, items, timestamp, version, globalX, globalY) end else serializedNodes[nodeIndex] = nil end end end end -- loads the nodes to cache and returns them function Data:GetMapCache(pinTypeId, map, measurement, zoneIndex, forceZoneIndex) if (not map) or (not measurement) or (not zoneIndex) then Harvest.Debug("map, measurement or zoneId missing") return end if not Harvest.IsUpdateQueueEmpty() then return end -- if the current map isn't in the cache, create the cache if not self.mapCaches[map] then if not measurement or not zoneIndex then return end local cache = Data.MapCache:New(map, measurement, zoneIndex) self.mapCaches[map] = cache self.numCaches = self.numCaches + 1 local oldest = cache local oldestMap for i = 1, self.numCaches - Harvest.GetMaxCachedMaps() do for map, cache in pairs(self.mapCaches) do if cache.time < oldest.time and cache.accessed == 0 then oldest = cache oldestMap = map end end if not oldestMap then break end Harvest.Debug("Clear cache for map " .. oldestMap) self.mapCaches[oldestMap] = nil oldest = cache self.numCaches = self.numCaches - 1 end -- fill the newly created cache with data for _, pinTypeId in ipairs(Harvest.PINTYPES) do if Harvest.IsPinTypeVisible(pinTypeId) then self:LoadToCache(pinTypeId, cache) end end if self.currentZoneCache and zoneIndex == self.currentZoneCache.zoneIndex then self.currentZoneCache:AddCache(cache) CallbackManager:FireCallbacks(Events.MAP_ADDED_TO_ZONE, cache, self.currentZoneCache) end return cache end local cache = self.mapCaches[map] -- if there was a pin type given, make sure the given pin type is in the cache if pinTypeId then self:CheckPinTypeInCache(pinTypeId, cache) else for _, pinTypeId in ipairs(Harvest.PINTYPES) do if Harvest.IsPinTypeVisible(pinTypeId) then self:CheckPinTypeInCache(pinTypeId, cache) end end end if forceZoneIndex then cache.zoneIndex = zoneIndex if self.currentZoneCache and zoneIndex == self.currentZoneCache.zoneIndex then self.currentZoneCache:AddCache(cache) CallbackManager:FireCallbacks(Events.MAP_ADDED_TO_ZONE, cache, self.currentZoneCache) end end return cache end function Data:GetCurrentZoneCache() return self.currentZoneCache end function Data:OnPlayerActivated() local zoneIndex = GetUnitZoneIndex("player") if not self.currentZoneCache or self.currentZoneCache.zoneIndex ~= zoneIndex then if self.currentZoneCache then self.currentZoneCache:Dispose() end self.currentZoneCache = self.ZoneCache:New(zoneIndex) for map, mapCache in pairs(self.mapCaches) do if mapCache.zoneIndex == zoneIndex then self.currentZoneCache:AddCache(mapCache) end end local distanceCorrection = Lib3D:GetGlobalToWorldFactor(GetZoneId(zoneIndex)) if distanceCorrection then -- usually 1 in global coords coresponds to 25 km, but some maps scale differently distanceCorrection = distanceCorrection / 2500000 else -- delves tend to be scaled down on the zone map, so we need to return a smaller value if mapType == MAP_CONTENT_DUNGEON and measurement.scaleX < 0.003 then distanceCorrection = math.sqrt(165) else distanceCorrection = 1 end end if not self.currentZoneCache:HasMapCache() then local map, x, y, measurement = Harvest.GetLocation() Data:GetMapCache(nil, map, measurement, zoneIndex, true) end CallbackManager:FireCallbacks(Events.NEW_ZONE_ENTERED, self.currentZoneCache, distanceCorrection) end end function Data:CheckPinTypeInCache(pinTypeId, mapCache) if not mapCache.nodesOfPinType[pinTypeId] then self:LoadToCache(pinTypeId, mapCache) end end