-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLibDataShare.lua
401 lines (340 loc) · 12.6 KB
/
LibDataShare.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
local NAME = "LibDataShare"
local lib = {version = 6}
_G[NAME] = lib
local LMP = LibMapPing2
if LMP and not LMP.internal then LMP = nil end -- a naive check for LMP version just to avoid calling really old ones
local mapHandlers = {} -- currently registered maps
local ENABLED = true -- this lib won't send or process map pings if this setting is set to false
local MAIN_MAP_INDEX = 30 -- Vvardenfell
local PING_RATE = 2020 -- minimum time between pings
local lastPingTime = 0 -- time of the latest map ping
local lastOnPingTime = 0 -- time of the latest received and processed map ping
local EM = EVENT_MANAGER
local time = GetGameTimeMilliseconds
local function IsCallable(f)
return type(f) == "function"
end
-- Utility object that works with a single map used by a specific addon to send map pings.
-- Each addon must acquire its own object by calling LibDataShare:RegisterMap()
local MapHandler = {}
function MapHandler:New(owner, mapId, dataHandler)
handler = {mapId = mapId, owner = owner}
setmetatable(handler, self)
self.__index = self
handler:SetDataHandler(dataHandler)
return handler
end
-- Returns the number of unique points on a single axis of the current map.
-- This number squared is the largest number you can send via QueueData() and SendData().
function MapHandler:GetMapSize()
return lib.maps[self.mapId].size
end
-- Queue data for sending. It'll be sent as soon as it's safe to send the next ping.
-- If callback is a function, then it'll be called right after the data has been sent, but not received yet.
-- Returns the number of milliseconds this data needs to "wait" to be sent.
function MapHandler:QueueData(data, callback)
local t = time()
local x, y = lib:EncodeData(self.mapId, data)
local nextPingDelay = zo_max(0, lastPingTime - t + PING_RATE) -- make sure to always have at least PING_RATE delay between any pings
zo_callLater(function()
local result = false
if lib:PrepareMap(self.mapId) then
lib:SetMapPing(x, y)
lib:RestoreMap()
result = true
end
if IsCallable(callback) then
callback(result)
end
end, nextPingDelay)
lastPingTime = t + nextPingDelay
return nextPingDelay > 0 and nextPingDelay or 1
end
-- Send data instantly without checking if it's safe to do so (too many map pings in a row will kick the player from server).
-- You might want to call LibDataShare:IsSendWindow() before sending data, but if you need to send something urgently and rarely, then it's usually safe to do without any checks.
-- Returns true if data has been successfully sent, false otherwise.
function MapHandler:SendData(data)
if lib:PrepareMap(self.mapId) then
local x, y = lib:EncodeData(self.mapId, data)
lib:SetMapPing(x, y)
lib:RestoreMap()
lastPingTime = zo_max(lastPingTime, time())
return true
else
return false
end
end
-- A shortcut for LibDataShare:IsSendWindow().
function MapHandler:IsSendWindow()
return LibDataShare:IsSendWindow()
end
-- Set a function that will handle incoming data.
-- The function will receive 3 values: unitTag (sender's group tag), data (received data), time (time in milliseconds when the data was received)
function MapHandler:SetDataHandler(func)
self.dataHandler = func
end
-- When the lib is disabled, it won't send or process incoming pings.
function lib:IsEnabled()
return ENABLED
end
-- Enable/disable data sharing.
function lib:SetEnabled(enabled)
if enabled then
ENABLED = true
else
ENABLED = false
end
end
-- Encode data (a number between 0 and the corresponding map's amount of steps^2) into map coordinates.
-- mapId is ESO internal mapId found here: https://wiki.esoui.com/Maps
function lib:EncodeData(mapId, data)
local map = lib.maps[mapId]
if map and data >= 0 and data <= map.size^2 then
return zo_floor(data / map.size) * map.step, (data % map.size) * map.step
else
return 0, 0
end
end
--- Decode data from map coordinates.
function lib:DecodeData(mapId, x, y)
local map = lib.maps[mapId]
if map then
return zo_floor(x / map.step + 0.5) * map.size + zo_floor(y / map.step + 0.5)
else
return 0
end
end
-- Register a map for data sharing.
-- owner: name of the addon that wants to register the map.
-- mapId: check Map.lua for available maps.
-- dataHandler: function that will handle incoming data. It will receive 3 values: unitTag (sender's group tag), data (received data), time (time in milliseconds when the data was received).
-- Returns MapHandler object.
function lib:RegisterMap(owner, mapId, dataHandler)
if lib.maps[mapId] then
if mapHandlers[mapId] then
error(string.format("Map %d is already registered by %s.", mapId, mapHandlers[mapId].owner))
else
local handler = MapHandler:New(owner, mapId, dataHandler)
mapHandlers[mapId] = handler
return handler
end
end
end
-- Stop using a map for data sharing.
function lib:UnregisterMap(mapId)
mapHandlers[mapId] = nil
end
-- Get map ping for unitTag.
function lib:GetMapPing(unitTag)
-- Use the original function.
if LMP then
return LMP.internal.handler.original.GetMapPing(unitTag)
else
return GetMapPing(unitTag)
end
end
-- Set map ping at (x, y).
function lib:SetMapPing(x, y)
-- Use the original function.
if LMP then
LMP.internal.handler.original.PingMap(MAP_PIN_TYPE_PING, MAP_TYPE_LOCATION_CENTERED, x, y)
else
PingMap(MAP_PIN_TYPE_PING, MAP_TYPE_LOCATION_CENTERED, x, y)
end
end
-- Returns true, if it's safe to send a map ping (enough time has passed since the last ping).
-- Returns false otherwise.
function lib:IsSendWindow()
local t = time()
return t - lastPingTime > PING_RATE and t - lastOnPingTime > 100
end
-- Mute all ping sounds.
function lib:MutePings()
SOUNDS.MAP_PING = nil
SOUNDS.MAP_PING_REMOVE = nil
end
-- Unmute all ping sounds.
function lib:UnmutePings()
SOUNDS.MAP_PING = 'Map_Ping'
SOUNDS.MAP_PING_REMOVE = 'Map_Ping_Remove'
end
-- Minimum time between pings sent by the lib.
function lib:GetPingRate()
return PING_RATE
end
-- Disable data sharing in other addons.
-- This function is called by HodorReflexes when it's enabled, otherwise it will conflict with all of the following addons.
-- You need to disable Hodor Reflexes to use data sharing features of these addons.
function lib:ResolveConflicts()
-- Disable RaidNotifier ult exchange
if RaidNotifier then
RaidNotifier:UnregisterForUltimateChanges()
end
-- Disable Bandits sharing
if BUI and BUI.Vars then
if BUI.Vars.StatShare and BUI.StatShare then
BUI.Vars.StatShare = false
BUI.StatShare:Initialize(true)
end
if BUI.Vars.StatsShareDPS then
BUI.Vars.StatsShareDPS = false
end
if BUI.Vars.StatsUpdateDPS then
BUI.Vars.StatsUpdateDPS = false
EM:UnregisterForUpdate("BUI_ShareDPS")
end
BUI.StatShare.OnPing = function(eventCode, pingEventType, pingType, unitTag, offsetX, offsetY, isOwner) return end
end
-- Disable FTC dps sharing
if FTC and FTC.Stats then
FTC.Vars.StatsShareDPS = false
end
-- Disable Taos Group Tools
if TGT_SettingsHandler then
TGT_SettingsHandler.SavedVariables.IsSendingDataActive = false
EM:UnregisterForUpdate('TGT-PlayerHandler')
end
-- Disable Piece of Candy
if POC and POC.Comm and POC.Settings then
POC.Comm.Unload()
POC.Settings.SavedVariables.CommOff = true
end
-- Unregister all registered callbacks in LibMapPing.
-- It might be a bit too brutal, but helps to avoid potential conflicts and performance issues, e.g.
-- with LibGroupSocket, which processes every incoming ping without a way to disable it.
if LMP then
LMP.internal.callbackObject:UnregisterAllCallbacks("BeforePingAdded")
LMP.internal.callbackObject:UnregisterAllCallbacks("AfterPingRemoved")
end
end
--[[
-- Internal stuff.
--]]
-- Player opens/hides the world map.
local worldMapState = false -- world map is showing
local worldMapUpdate = false -- world map needs to be updated
local function MapStateChange(oldState, newState)
if newState == SCENE_SHOWING then
if worldMapUpdate then
ZO_WorldMap_UpdateMap()
worldMapUpdate = false
end
worldMapState = true
elseif newState == SCENE_HIDDEN then
worldMapState = false
end
end
-- Change map to Vvardenfell if possible.
local prepareMapResult = SET_MAP_RESULT_FAILED -- using SetMapResultCode constants, because why not
function lib:PrepareMap(mapId)
-- Don't prepare map if data sharing is disabled.
if not ENABLED then return false end
local sameMap = DoesCurrentMapMatchMapForPlayerLocation()
-- The following check fails if the player is viewing the map of another zone,
-- because we can't set Vvardenfell map and return back without complex operations (like in LibGPS).
if worldMapState and not sameMap then
prepareMapResult = SET_MAP_RESULT_FAILED
else
prepareMapResult = sameMap and SET_MAP_RESULT_CURRENT_MAP_UNCHANGED or SET_MAP_RESULT_MAP_CHANGED
SetMapToMapListIndex(mapId)
end
return prepareMapResult ~= SET_MAP_RESULT_FAILED
end
-- Return to the current map (should be called after each PrepareMap()).
function lib:RestoreMap()
if prepareMapResult ~= SET_MAP_RESULT_FAILED then
SetMapToPlayerLocation()
if prepareMapResult == SET_MAP_RESULT_MAP_CHANGED then
-- If player changed a subzone, then we need to update the world map, but only when he opens it.
worldMapUpdate = true
end
end
end
-- EVENT_MAP_PING handler.
local function OnMapPing(eventCode, pingEventType, pingType, pingTag, offsetX, offsetY, isLocalPlayerOwner)
if pingEventType == PING_EVENT_ADDED and pingType == MAP_PIN_TYPE_PING then
local t = time()
if isLocalPlayerOwner then
-- Ignore own ping.
lastPingTime = t
elseif lib:PrepareMap(MAIN_MAP_INDEX) then
lastOnPingTime = t
local x, y = lib:GetMapPing(pingTag)
-- If it's a ping on the main map, then use its handler directly, otherwise search for a suitable handler in mapHandlers.
if x >= 0 and x <= 1 and y >= 0 and y <= 1 then
local handler = mapHandlers[MAIN_MAP_INDEX]
if handler and IsCallable(handler.dataHandler) then
handler.dataHandler(pingTag, lib:DecodeData(MAIN_MAP_INDEX, x, y), t)
end
else
for mapId, handler in pairs(mapHandlers) do
if mapId ~= MAIN_MAP_INDEX then
local map = lib.maps[mapId]
if map and x >= map.x0 - map.step and x <= map.x1 + map.step and y >= map.y0 - map.step and y <= map.y1 + map.step then
if lib:PrepareMap(mapId) then
x, y = lib:GetMapPing(pingTag)
if IsCallable(handler.dataHandler) then
handler.dataHandler(pingTag, lib:DecodeData(mapId, x, y), t)
end
end
break
end
end
end
end
lib:RestoreMap()
end
end
end
local function Initialize()
local function OnPlayerActivated()
-- EOL detection
local ApiVersion = GetAPIVersion()
local WorldName = GetWorldName()
if ApiVersion >= 101046 then
if WorldName ~= "PTS" then
zo_callLater(function()
d("|cFF00FFWarning: 'LibDataShare' is now disabled due to API restrictions in Update 46. The MapPing API only allows one ping every 10 seconds, and this library is no longer functional. Please uninstall it to avoid seeing this message.|r")
end, 5000)
-- remove functionality of the addon completely by overwriting all functions of the Library
MapHandler.QueueData = function() end
MapHandler.SendData = function() end
lib.PrepareMap = function() return false end
lib.IsEnabled = function() return false end
lib.GetMapPing = function() return 0, 0 end
lib.SetMapPing = function() end
lib.ResolveConflicts = function() end
OnMapPing = function() end
MapStateChange = function() end
return -- exit Initialize function here to not load the OnPlayerActivated function
end
-- set the ping rate to every 10seconds because of API changes in U46
PING_RATE = 10200
zo_callLater(function()
d("|cFF00FFImportant: Please remove 'LibDataShare' from your add-ons! The MapPing API has been rate-limited in Update 46, allowing only one ping every 10 seconds. Continuing to use this library may result in being kicked from the server.|r")
end, 5000)
end
-- Unregister map ping handler.
EM:UnregisterForEvent(NAME, EVENT_MAP_PING)
-- Unregister map state changes.
WORLD_MAP_SCENE:UnregisterCallback("StateChange", MapStateChange)
GAMEPAD_WORLD_MAP_SCENE:UnregisterCallback("StateChange", MapStateChange)
if lib:IsEnabled() then
-- Register map ping handler.
EM:RegisterForEvent(NAME, EVENT_MAP_PING, OnMapPing)
-- Register map state changes.
WORLD_MAP_SCENE:RegisterCallback("StateChange", MapStateChange)
GAMEPAD_WORLD_MAP_SCENE:RegisterCallback("StateChange", MapStateChange)
-- Mute all pings once and for all.
lib:MutePings()
else
lib:UnmutePings()
end
end
EM:RegisterForEvent(NAME, EVENT_PLAYER_ACTIVATED, OnPlayerActivated)
end
EM:RegisterForEvent(NAME, EVENT_ADD_ON_LOADED, function(_, name)
if name ~= NAME then return end
EM:UnregisterForEvent(NAME, EVENT_ADD_ON_LOADED)
Initialize()
end)