forked from Nevcairiel/HereBeDragons
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHereBeDragons-1.0.lua
727 lines (620 loc) · 26.7 KB
/
HereBeDragons-1.0.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
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
-- HereBeDragons is a data API for the World of Warcraft mapping system
local MAJOR, MINOR = "HereBeDragons-1.0", 21
assert(LibStub, MAJOR .. " requires LibStub")
local HereBeDragons, oldversion = LibStub:NewLibrary(MAJOR, MINOR)
if not HereBeDragons then return end
local CBH = LibStub("CallbackHandler-1.0")
HereBeDragons.eventFrame = HereBeDragons.eventFrame or CreateFrame("Frame")
HereBeDragons.mapData = HereBeDragons.mapData or {}
HereBeDragons.continentZoneMap = HereBeDragons.continentZoneMap or { [-1] = { [0] = WORLDMAP_COSMIC_ID }, [0] = { [0] = WORLDMAP_AZEROTH_ID }}
HereBeDragons.mapToID = HereBeDragons.mapToID or { Cosmic = WORLDMAP_COSMIC_ID, World = WORLDMAP_AZEROTH_ID }
HereBeDragons.microDungeons = HereBeDragons.microDungeons or {}
HereBeDragons.transforms = HereBeDragons.transforms or {}
HereBeDragons.callbacks = CBH:New(HereBeDragons, nil, nil, false)
local IsLegion = select(4, GetBuildInfo()) >= 70000
-- constants
local TERRAIN_MATCH = "_terrain%d+$"
-- Lua upvalues
local PI2 = math.pi * 2
local atan2 = math.atan2
local pairs, ipairs = pairs, ipairs
local type = type
-- WoW API upvalues
local UnitPosition = UnitPosition
-- data table upvalues
local mapData = HereBeDragons.mapData -- table { width, height, left, top }
local continentZoneMap = HereBeDragons.continentZoneMap
local mapToID = HereBeDragons.mapToID
local microDungeons = HereBeDragons.microDungeons
local transforms = HereBeDragons.transforms
local currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon
-- Override instance ids for phased content
local instanceIDOverrides = {
-- Draenor
[1152] = 1116, -- Horde Garrison 1
[1330] = 1116, -- Horde Garrison 2
[1153] = 1116, -- Horde Garrison 3
[1154] = 1116, -- Horde Garrison 4 (unused)
[1158] = 1116, -- Alliance Garrison 1
[1331] = 1116, -- Alliance Garrison 2
[1159] = 1116, -- Alliance Garrison 3
[1160] = 1116, -- Alliance Garrison 4 (unused)
[1191] = 1116, -- Ashran PvP Zone
[1203] = 1116, -- Frostfire Finale Scenario
[1207] = 1116, -- Talador Finale Scenario
[1277] = 1116, -- Defense of Karabor Scenario (SMV)
[1402] = 1116, -- Gorgrond Finale Scenario
[1464] = 1116, -- Tanaan
[1465] = 1116, -- Tanaan
-- Legion
[1478] = 1220, -- Temple of Elune Scenario (Val'Sharah)
[1502] = 1220, -- Dalaran Underbelly
[1533] = 0, -- Karazhan Artifact Scenario
[1612] = 1220, -- Feral Druid Artifact Scenario (Suramar)
}
-- unregister and store all WORLD_MAP_UPDATE registrants, to avoid excess processing when
-- retrieving info from stateful map APIs
local wmuRegistry
local function UnregisterWMU()
wmuRegistry = {GetFramesRegisteredForEvent("WORLD_MAP_UPDATE")}
for _, frame in ipairs(wmuRegistry) do
frame:UnregisterEvent("WORLD_MAP_UPDATE")
end
end
-- restore WORLD_MAP_UPDATE to all frames in the registry
local function RestoreWMU()
assert(wmuRegistry)
for _, frame in ipairs(wmuRegistry) do
frame:RegisterEvent("WORLD_MAP_UPDATE")
end
wmuRegistry = nil
end
-- gather map info, but only if this isn't an upgrade (or the upgrade version forces a re-map)
if not oldversion or oldversion < 21 then
-- wipe old data, if required, otherwise the upgrade path isn't triggered
if oldversion then
wipe(mapData)
wipe(microDungeons)
end
local MAPS_TO_REMAP = {
-- alliance garrison
[973] = 971,
[974] = 971,
[975] = 971,
[991] = 971,
-- horde garrison
[980] = 976,
[981] = 976,
[982] = 976,
[990] = 976,
}
-- some zones will remap initially, but have a fixup later
local REMAP_FIXUP_EXEMPT = {
-- main draenor garrison maps
[971] = true,
[976] = true,
}
local function processTransforms()
wipe(transforms)
for _, tID in ipairs(GetWorldMapTransforms()) do
local terrainMapID, newTerrainMapID, _, _, transformMinY, transformMaxY, transformMinX, transformMaxX, offsetY, offsetX = GetWorldMapTransformInfo(tID)
if offsetY ~= 0 or offsetX ~= 0 then
local transform = {
instanceID = terrainMapID,
newInstanceID = newTerrainMapID,
minY = transformMinY,
maxY = transformMaxY,
minX = transformMinX,
maxX = transformMaxX,
offsetY = offsetY,
offsetX = offsetX
}
table.insert(transforms, transform)
end
end
end
local function applyMapTransforms(instanceID, left, right, top, bottom)
for _, transformData in ipairs(transforms) do
if transformData.instanceID == instanceID then
if left < transformData.maxX and right > transformData.minX and top < transformData.maxY and bottom > transformData.minY then
instanceID = transformData.newInstanceID
left = left + transformData.offsetX
right = right + transformData.offsetX
top = top + transformData.offsetY
bottom = bottom + transformData.offsetY
break
end
end
end
return instanceID, left, right, top, bottom
end
-- gather the data of one zone (by mapID)
local function processZone(id)
if not id or mapData[id] then return end
-- set the map and verify it could be set
local success = SetMapByID(id)
if not success then
return
elseif id ~= GetCurrentMapAreaID() and not REMAP_FIXUP_EXEMPT[id] then
-- this is an alias zone (phasing terrain changes), just skip it and remap it later
if not MAPS_TO_REMAP[id] then
MAPS_TO_REMAP[id] = GetCurrentMapAreaID()
end
return
end
-- dimensions of the map
local originalInstanceID, _, _, left, right, top, bottom = GetAreaMapInfo(id)
local instanceID = originalInstanceID
if (left and top and right and bottom and (left ~= 0 or top ~= 0 or right ~= 0 or bottom ~= 0)) then
instanceID, left, right, top, bottom = applyMapTransforms(originalInstanceID, left, right, top, bottom)
mapData[id] = { left - right, top - bottom, left, top }
else
mapData[id] = { 0, 0, 0, 0 }
end
mapData[id].instance = instanceID
mapData[id].name = GetMapNameByID(id)
-- store the original instance id (ie. not remapped for map transforms) for micro dungeons
mapData[id].originalInstance = originalInstanceID
local mapFile = GetMapInfo()
if mapFile then
-- remove phased terrain from the map names
mapFile = mapFile:gsub(TERRAIN_MATCH, "")
if not mapToID[mapFile] then mapToID[mapFile] = id end
mapData[id].mapFile = mapFile
end
local C, Z = GetCurrentMapContinent(), GetCurrentMapZone()
mapData[id].C = C or -100
mapData[id].Z = Z or -100
if mapData[id].C > 0 and mapData[id].Z >= 0 then
-- store C/Z lookup table
if not continentZoneMap[C] then
continentZoneMap[C] = {}
end
if not continentZoneMap[C][Z] then
continentZoneMap[C][Z] = id
end
end
local floors
if IsLegion then
floors = { GetNumDungeonMapLevels() }
-- offset floors for terrain map
if DungeonUsesTerrainMap() then
for i = 1, #floors do
floors[i] = floors[i] + 1
end
end
else
floors = {}
for f = 1, GetNumDungeonMapLevels() do
floors[f] = f
end
end
if #floors == 0 and GetCurrentMapDungeonLevel() > 0 then
floors[1] = GetCurrentMapDungeonLevel()
mapData[id].fakefloor = GetCurrentMapDungeonLevel()
end
mapData[id].floors = {}
mapData[id].numFloors = #floors
for i = 1, mapData[id].numFloors do
local f = floors[i]
SetDungeonMapLevel(f)
local _, right, bottom, left, top = GetCurrentMapDungeonLevel()
if left and top and right and bottom then
instanceID, left, right, top, bottom = applyMapTransforms(originalInstanceID, left, right, top, bottom)
mapData[id].floors[f] = { left - right, top - bottom, left, top }
mapData[id].floors[f].instance = mapData[id].instance
elseif f == 1 and DungeonUsesTerrainMap() then
mapData[id].floors[f] = { mapData[id][1], mapData[id][2], mapData[id][3], mapData[id][4] }
mapData[id].floors[f].instance = mapData[id].instance
end
end
-- setup microdungeon storage if the its a zone map or has no floors of its own
if (mapData[id].C > 0 and mapData[id].Z > 0) or mapData[id].numFloors == 0 then
if not microDungeons[originalInstanceID] then
microDungeons[originalInstanceID] = {}
end
end
end
local function processMicroDungeons()
for _, dID in ipairs(GetDungeonMaps()) do
local floorIndex, minX, maxX, minY, maxY, terrainMapID, parentWorldMapID, flags = GetDungeonMapInfo(dID)
-- apply transform
local originalTerrainMapID = terrainMapID
terrainMapID, maxX, minX, maxY, minY = applyMapTransforms(terrainMapID, maxX, minX, maxY, minY)
-- check if this zone can have microdungeons
if microDungeons[originalTerrainMapID] then
microDungeons[originalTerrainMapID][floorIndex] = { maxX - minX, maxY - minY, maxX, maxY }
microDungeons[originalTerrainMapID][floorIndex].instance = terrainMapID
end
end
end
local function fixupZones()
-- fake cosmic map
mapData[WORLDMAP_COSMIC_ID] = {0, 0, 0, 0}
mapData[WORLDMAP_COSMIC_ID].instance = -1
mapData[WORLDMAP_COSMIC_ID].mapFile = "Cosmic"
mapData[WORLDMAP_COSMIC_ID].floors = {}
mapData[WORLDMAP_COSMIC_ID].C = -1
mapData[WORLDMAP_COSMIC_ID].Z = 0
mapData[WORLDMAP_COSMIC_ID].name = WORLD_MAP
-- fake azeroth world map
-- the world map has one "floor" per continent it contains, which allows
-- using these floors to translate coordinates from and to the world map.
-- note: due to artistic differences in the drawn azeroth maps, the values
-- used for the continents are estimates and not perfectly accurate
mapData[WORLDMAP_AZEROTH_ID] = { 63570, 42382, 53730, 19600 } -- Eastern Kingdoms, or floor 0
mapData[WORLDMAP_AZEROTH_ID].floors = {
-- Kalimdor
[1] = { 65700, 43795, 11900, 23760, instance = 1 },
-- Northrend
[571] = { 65700, 43795, 33440, 11960, instance = 571 },
-- Pandaria
[870] = { 58520, 39015, 29070, 34410, instance = 870 },
-- Broken Isles
[1220] = { 96710, 64476, 63100, 29960, instance = 1220 },
}
mapData[WORLDMAP_AZEROTH_ID].instance = 0
mapData[WORLDMAP_AZEROTH_ID].mapFile = "World"
mapData[WORLDMAP_AZEROTH_ID].C = 0
mapData[WORLDMAP_AZEROTH_ID].Z = 0
mapData[WORLDMAP_AZEROTH_ID].name = WORLD_MAP
-- we only have data for legion clients, zeroing the coordinates
-- and niling out the floors temporarily disables the logic on live
if not IsLegion then
mapData[WORLDMAP_AZEROTH_ID][1] = 0
mapData[WORLDMAP_AZEROTH_ID][2] = 0
mapData[WORLDMAP_AZEROTH_ID][3] = 0
mapData[WORLDMAP_AZEROTH_ID][4] = 0
mapData[WORLDMAP_AZEROTH_ID].floors = {}
end
-- alliance draenor garrison
if mapData[971] then
mapData[971].Z = 5
mapToID["garrisonsmvalliance_tier1"] = 971
mapToID["garrisonsmvalliance_tier2"] = 971
mapToID["garrisonsmvalliance_tier3"] = 971
end
-- horde draenor garrison
if mapData[976] then
mapData[976].Z = 3
mapToID["garrisonffhorde_tier1"] = 976
mapToID["garrisonffhorde_tier2"] = 976
mapToID["garrisonffhorde_tier3"] = 976
end
-- remap zones with alias IDs
for remapID, validMapID in pairs(MAPS_TO_REMAP) do
if mapData[validMapID] then
mapData[remapID] = mapData[validMapID]
end
end
end
local function gatherMapData()
-- unregister WMU to reduce the processing burden
UnregisterWMU()
-- load transforms
processTransforms()
-- load the main zones
-- these should be processed first so they take precedence in the mapFile lookup table
local continents = {GetMapContinents()}
for i = 1, #continents, 2 do
processZone(continents[i])
local zones = {GetMapZones((i + 1) / 2)}
for z = 1, #zones, 2 do
processZone(zones[z])
end
end
-- process all other zones, this includes dungeons and more
local areas = GetAreaMaps()
for idx, zoneID in pairs(areas) do
processZone(zoneID)
end
-- fix a few zones with data lookup problems
fixupZones()
-- and finally, the microdungeons
processMicroDungeons()
-- restore WMU
RestoreWMU()
end
gatherMapData()
end
-- Transform a set of coordinates based on the defined map transformations
local function applyCoordinateTransforms(x, y, instanceID)
for _, transformData in ipairs(transforms) do
if transformData.instanceID == instanceID then
if transformData.minX <= x and transformData.maxX >= x and transformData.minY <= y and transformData.maxY >= y then
instanceID = transformData.newInstanceID
x = x + transformData.offsetX
y = y + transformData.offsetY
break
end
end
end
if instanceIDOverrides[instanceID] then
instanceID = instanceIDOverrides[instanceID]
end
return x, y, instanceID
end
-- get the data table for a map and its level (floor)
local function getMapDataTable(mapID, level)
if not mapID then return nil end
if type(mapID) == "string" then
mapID = mapID:gsub(TERRAIN_MATCH, "")
mapID = mapToID[mapID]
end
local data = mapData[mapID]
if not data then return nil end
if (type(level) ~= "number" or level == 0) and data.fakefloor then
level = data.fakefloor
end
if type(level) == "number" and level > 0 then
if data.floors[level] then
return data.floors[level]
elseif data.originalInstance and microDungeons[data.originalInstance] and microDungeons[data.originalInstance][level] then
return microDungeons[data.originalInstance][level]
end
else
return data
end
end
local function UpdateCurrentPosition()
UnregisterWMU()
-- save active map and level
local prevContinent
local prevMapID, prevLevel = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel()
-- handle continent maps (751 is the maelstrom continent, which fails with SetMapByID)
if not prevMapID or prevMapID < 0 or prevMapID == 751 then
prevContinent = GetCurrentMapContinent()
end
-- set current map
SetMapToCurrentZone()
-- retrieve active values
local newMapID, newLevel = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel()
local mapFile, _, _, isMicroDungeon, microFile = GetMapInfo()
-- we want to ignore any terrain phasings
if mapFile then
mapFile = mapFile:gsub(TERRAIN_MATCH, "")
end
-- hack to update the mapfile for the garrison map (as it changes when the player updates his garrison)
-- its not ideal to only update it when the player is in the garrison, but updates should only really happen then
if (newMapID == 971 or newMapID == 976) and mapData[newMapID] and mapFile ~= mapData[newMapID].mapFile then
mapData[newMapID].mapFile = mapFile
end
-- restore previous map
if prevContinent then
SetMapZoom(prevContinent)
else
if prevMapID and prevMapID ~= newMapID then
SetMapByID(prevMapID)
end
-- and level
if prevLevel and prevLevel > 0 then
SetDungeonMapLevel(prevLevel)
end
end
RestoreWMU()
if newMapID ~= currentPlayerZoneMapID or newLevel ~= currentPlayerLevel then
-- store micro dungeon map lookup, if available
if microFile and not mapToID[microFile] then mapToID[microFile] = newMapID end
-- update upvalues and signal callback
currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon = newMapID, newLevel, microFile or mapFile, isMicroDungeon
HereBeDragons.callbacks:Fire("PlayerZoneChanged", currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon)
end
end
local function OnEvent(frame, event, ...)
UpdateCurrentPosition()
end
HereBeDragons.eventFrame:SetScript("OnEvent", OnEvent)
HereBeDragons.eventFrame:UnregisterAllEvents()
HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED")
HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED_INDOORS")
HereBeDragons.eventFrame:RegisterEvent("NEW_WMO_CHUNK")
HereBeDragons.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
-- if we're loading after entering the world (ie. on demand), update position now
if IsLoggedIn() then
UpdateCurrentPosition()
end
--- Return the localized zone name for a given mapID or mapFile
-- @param mapID numeric mapID or mapFile
function HereBeDragons:GetLocalizedMap(mapID)
if type(mapID) == "string" then
mapID = mapID:gsub(TERRAIN_MATCH, "")
mapID = mapToID[mapID]
end
return mapData[mapID] and mapData[mapID].name or nil
end
--- Return the map id to a mapFile
-- @param mapFile Map File
function HereBeDragons:GetMapIDFromFile(mapFile)
if mapFile then
mapFile = mapFile:gsub(TERRAIN_MATCH, "")
return mapToID[mapFile]
end
return nil
end
--- Return the mapFile to a map ID
-- @param mapID Map ID
function HereBeDragons:GetMapFileFromID(mapID)
return mapData[mapID] and mapData[mapID].mapFile or nil
end
--- Lookup the map ID for a Continent / Zone index combination
-- @param C continent index from GetCurrentMapContinent
-- @param Z zone index from GetCurrentMapZone
function HereBeDragons:GetMapIDFromCZ(C, Z)
if C and continentZoneMap[C] then
return Z and continentZoneMap[C][Z]
end
return nil
end
--- Lookup the C/Z values for map
-- @param mapID the MapID
function HereBeDragons:GetCZFromMapID(mapID)
if mapData[mapID] then
return mapData[mapID].C, mapData[mapID].Z
end
return nil, nil
end
--- Get the size of the zone
-- @param mapID Map ID or MapFile of the zone
-- @param level Optional map level
-- @return width, height of the zone, in yards
function HereBeDragons:GetZoneSize(mapID, level)
local data = getMapDataTable(mapID, level)
if not data then return 0, 0 end
return data[1], data[2]
end
--- Get the number of floors for a map
-- @param mapID map ID or mapFile of the zone
function HereBeDragons:GetNumFloors(mapID)
if not mapID then return 0 end
if type(mapID) == "string" then
mapID = mapID:gsub(TERRAIN_MATCH, "")
mapID = mapToID[mapID]
end
if not mapData[mapID] or not mapData[mapID].numFloors then return 0 end
return mapData[mapID].numFloors
end
--- Get a list of all map IDs
-- @return array-style table with all known/valid map IDs
function HereBeDragons:GetAllMapIDs()
local t = {}
for id in pairs(mapData) do
table.insert(t, id)
end
return t
end
--- Convert local/point coordinates to world coordinates in yards
-- @param x X position in 0-1 point coordinates
-- @param y Y position in 0-1 point coordinates
-- @param zone MapID or MapFile of the zone
-- @param level Optional level of the zone
function HereBeDragons:GetWorldCoordinatesFromZone(x, y, zone, level)
local data = getMapDataTable(zone, level)
if not data or data[0] == 0 or data[1] == 0 then return nil, nil, nil end
local width, height, left, top = data[1], data[2], data[3], data[4]
x, y = left - width * x, top - height * y
return x, y, data.instance
end
--- Convert world coordinates to local/point zone coordinates
-- @param x Global X position
-- @param y Global Y position
-- @param zone MapID or MapFile of the zone
-- @param level Optional level of the zone
-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned
function HereBeDragons:GetZoneCoordinatesFromWorld(x, y, zone, level, allowOutOfBounds)
local data = getMapDataTable(zone, level)
if not data or data[0] == 0 or data[1] == 0 then return nil, nil end
local width, height, left, top = data[1], data[2], data[3], data[4]
x, y = (left - x) / width, (top - y) / height
-- verify the coordinates fall into the zone
if not allowOutOfBounds and (x < 0 or x > 1 or y < 0 or y > 1) then return nil, nil end
return x, y
end
--- Translate zone coordinates from one zone to another
-- @param x X position in 0-1 point coordinates, relative to the origin zone
-- @param y Y position in 0-1 point coordinates, relative to the origin zone
-- @param oZone Origin Zone, mapID or mapFile
-- @param oLevel Origin Zone Level
-- @param dZone Destination Zone, mapID or mapFile
-- @param dLevel Destination Zone Level
-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned
function HereBeDragons:TranslateZoneCoordinates(x, y, oZone, oLevel, dZone, dLevel, allowOutOfBounds)
local xCoord, yCoord, instance = self:GetWorldCoordinatesFromZone(x, y, oZone, oLevel)
if not xCoord then return nil, nil end
local data = getMapDataTable(dZone, dLevel)
if not data or data.instance ~= instance then return nil, nil end
return self:GetZoneCoordinatesFromWorld(xCoord, yCoord, dZone, dLevel, allowOutOfBounds)
end
--- Return the distance from an origin position to a destination position in the same instance/continent.
-- @param instanceID instance ID
-- @param oX origin X
-- @param oY origin Y
-- @param dX destination X
-- @param dY destination Y
-- @return distance, deltaX, deltaY
function HereBeDragons:GetWorldDistance(instanceID, oX, oY, dX, dY)
local deltaX, deltaY = dX - oX, dY - oY
return (deltaX * deltaX + deltaY * deltaY)^0.5, deltaX, deltaY
end
--- Return the distance between two points on the same continent
-- @param oZone origin zone map id or mapfile
-- @param oLevel optional origin zone level (floor)
-- @param oX origin X, in local zone/point coordinates
-- @param oY origin Y, in local zone/point coordinates
-- @param dZone destination zone map id or mapfile
-- @param dLevel optional destination zone level (floor)
-- @param dX destination X, in local zone/point coordinates
-- @param dY destination Y, in local zone/point coordinates
-- @return distance, deltaX, deltaY in yards
function HereBeDragons:GetZoneDistance(oZone, oLevel, oX, oY, dZone, dLevel, dX, dY)
local oX, oY, oInstance = self:GetWorldCoordinatesFromZone(oX, oY, oZone, oLevel)
if not oX then return nil, nil, nil end
-- translate dX, dY to the origin zone
local dX, dY, dInstance = self:GetWorldCoordinatesFromZone(dX, dY, dZone, dLevel)
if not dX then return nil, nil, nil end
if oInstance ~= dInstance then return nil, nil, nil end
return self:GetWorldDistance(oInstance, oX, oY, dX, dY)
end
--- Return the angle and distance from an origin position to a destination position in the same instance/continent.
-- @param instanceID instance ID
-- @param oX origin X
-- @param oY origin Y
-- @param dX destination X
-- @param dY destination Y
-- @return angle, distance where angle is in radians and distance in yards
function HereBeDragons:GetWorldVector(instanceID, oX, oY, dX, dY)
local distance, deltaX, deltaY = self:GetWorldDistance(instanceID, oX, oY, dX, dY)
if not distance then return nil, nil end
-- calculate the angle from deltaY and deltaX
local angle = atan2(-deltaX, deltaY)
-- normalize the angle
if angle > 0 then
angle = PI2 - angle
else
angle = -angle
end
return angle, distance
end
--- Get the current world position of the specified unit
-- The position is transformed to the current continent, if applicable
-- NOTE: The same restrictions as for the UnitPosition() API apply,
-- which means a very limited set of unit ids will actually work.
-- @param unitId Unit Id
-- @return x, y, instanceID
function HereBeDragons:GetUnitWorldPosition(unitId)
-- get the current position
local y, x, z, instanceID = UnitPosition(unitId)
if not x or not y then return nil, nil, nil end
-- return transformed coordinates
return applyCoordinateTransforms(x, y, instanceID)
end
--- Get the current world position of the player
-- The position is transformed to the current continent, if applicable
-- @return x, y, instanceID
function HereBeDragons:GetPlayerWorldPosition()
-- get the current position
local y, x, z, instanceID = UnitPosition("player")
if not x or not y then return nil, nil, nil end
-- return transformed coordinates
return applyCoordinateTransforms(x, y, instanceID)
end
--- Get the current zone and level of the player
-- The returned mapFile can represent a micro dungeon, if the player currently is inside one.
-- @return mapID, level, mapFile, isMicroDungeon
function HereBeDragons:GetPlayerZone()
return currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon
end
--- Get the current position of the player on a zone level
-- The returned values are local point coordinates, 0-1. The mapFile can represent a micro dungeon.
-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned
-- @return x, y, mapID, level, mapFile, isMicroDungeon
function HereBeDragons:GetPlayerZonePosition(allowOutOfBounds)
if not currentPlayerZoneMapID then return nil, nil, nil, nil end
local x, y, instanceID = self:GetPlayerWorldPosition()
if not x or not y then return nil, nil, nil, nil end
x, y = self:GetZoneCoordinatesFromWorld(x, y, currentPlayerZoneMapID, currentPlayerLevel, allowOutOfBounds)
if x and y then
return x, y, currentPlayerZoneMapID, currentPlayerLevel, currentMapFile, currentMapIsMicroDungeon
end
return nil, nil, nil, nil
end