-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDWMK.lua
More file actions
672 lines (522 loc) · 19.8 KB
/
DWMK.lua
File metadata and controls
672 lines (522 loc) · 19.8 KB
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
--[[
Dude Where's My K'arroc - Campaign-based mount persistence and immersion
Author: [Your Name]
Version: 0.1.0
]]
local ADDON_NAME = ...
local ADDON_VERSION = "0.1.0"
-- Create main frame
local frame = CreateFrame("Frame")
-- Constants
local ENFORCEMENT_LEVELS = {
[0] = "Off",
[1] = "Permissive",
[2] = "Balanced",
[3] = "Strict"
}
local MIN_ANCHOR_RADIUS = 10
local MAX_ANCHOR_RADIUS = 200
local DEFAULT_ANCHOR_RADIUS = 30
-- State tracking
local lastMountSpellID = nil -- Track spell from UNIT_SPELLCAST_SUCCEEDED
local enforcementDismountInProgress = false -- Flag to prevent anchor recording on enforcement
--------------------------------------------------------------------------------
-- Utility Functions
--------------------------------------------------------------------------------
local function Print(msg)
local tag =
"|cffffa500D|r" ..
"|cffff0000W|r" ..
"|cff00ff00M|r" ..
"|cff0000ffK|r"
DEFAULT_CHAT_FRAME:AddMessage("[" .. tag .. "]" .. msg)
end
local function PrintWarning(msg)
UIErrorsFrame:AddMessage(msg, 1.0, 0.8, 0.0, 1.0, 3)
end
--------------------------------------------------------------------------------
-- Database Management
--------------------------------------------------------------------------------
local function CreateDefaultCampaign(name)
return {
name = name or "Default Campaign",
created = time(),
lastUsed = time(),
settings = {
enforcementLevel = 1, -- Permissive by default
anchorRadius = DEFAULT_ANCHOR_RADIUS,
},
mounts = {
ground = nil,
flying = nil,
},
anchors = {}
}
end
local function InitializeDatabase()
-- Create account-wide database
if not DismountedDB then
DismountedDB = {
version = 1,
campaigns = {}
}
Print("Database initialized")
end
-- Create character database
if not DismountedCharDB then
DismountedCharDB = {
activeCampaign = nil
}
end
-- Ensure default campaign exists
local hasCampaigns = false
for _ in pairs(DismountedDB.campaigns) do
hasCampaigns = true
break
end
if not hasCampaigns then
DismountedDB.campaigns["default"] = CreateDefaultCampaign()
Print("Default campaign created")
end
-- Ensure character has valid active campaign
if not DismountedCharDB.activeCampaign or not DismountedDB.campaigns[DismountedCharDB.activeCampaign] then
-- find the first available campaign
local firstCampaignID = nil
for campaignID in pairs(DismountedDB.campaigns) do
firstCampaignID = campaignID
break
end
if firstCampaignID then
if DismountedCharDB.activeCampaign and DismountedCharDB.activeCampaign ~= firstCampaignID then
Print("Warning: Active campaign not found, switching to " .. firstCampaignID)
end
DismountedCharDB.activeCampaign = firstCampaignID
else
DismountedDB.campaigns["default"] = CreateDefaultCampaign()
DismountedCharDB.activeCampaign = "default"
Print("Created fallback default campaign")
end
end
end
local function GetActiveCampaign()
if not DismountedCharDB or not DismountedCharDB.activeCampaign then
return nil
end
return DismountedDB.campaigns[DismountedCharDB.activeCampaign]
end
local function GetActiveCampaignID()
return DismountedCharDB and DismountedCharDB.activeCampaign
end
--------------------------------------------------------------------------------
-- Position and Map Functions
--------------------------------------------------------------------------------
local function GetCurrentPosition()
local mapID = C_Map.GetBestMapForUnit("player")
if not mapID then
return nil, nil, nil
end
local position = C_Map.GetPlayerMapPosition(mapID, "player")
if not position then
return nil, nil, nil
end
local x, y = position:GetXY()
return mapID, x, y
end
local function GetMapInfo(mapID)
if not mapID then
return "Unknown"
end
local mapInfo = C_Map.GetMapInfo(mapID)
if mapInfo then
return mapInfo.name
end
return "Map " .. mapID
end
local function FormatCoordinates(x, y)
if not x or not y then
return "Unknown"
end
return string.format("%.1f, %.1f", x * 100, y * 100)
end
local function SetTomTomWaypoint(mapID, x, y, mountName)
-- Check if TomTom is loaded
if not TomTom then
return false
end
-- TomTom API: AddWaypoint(mapID, x, y, options)
if TomTom.AddWaypoint then
local waypointInfo = {
title = "Dismounted: " .. (mountName or "Mount Location"),
persistent = false, -- Don't save permanently
minimap = true,
world = true
}
TomTom:AddWaypoint(mapID, x, y, waypointInfo)
Print("TomTom waypoint set for your mount location")
return true
end
return false
end
--------------------------------------------------------------------------------
-- Mount Detection
--------------------------------------------------------------------------------
local function GetCurrentMountInfo()
if not IsMounted() then
return nil
end
-- Check for taxi/flight path first
if UnitOnTaxi("player") then
return nil
end
-- Use new C_UnitAuras API
if C_UnitAuras and C_UnitAuras.GetAuraDataByIndex then
for i = 1, 40 do
local auraData = C_UnitAuras.GetAuraDataByIndex("player", i, "HELPFUL")
if not auraData then
break
end
if C_MountJournal and C_MountJournal.GetMountFromSpell then
local mountID = C_MountJournal.GetMountFromSpell(auraData.spellId)
if mountID then
-- Get full mount details
local name, spellID, icon = C_MountJournal.GetMountInfoByID(mountID)
return {
mountID = mountID,
spellID = auraData.spellId,
name = name or auraData.name,
icon = icon
}
end
end
end
end
-- FALLBACK: Use the spell ID we tracked from UNIT_SPELLCAST_SUCCEEDED
if lastMountSpellID and C_MountJournal then
local mountID = C_MountJournal.GetMountFromSpell(lastMountSpellID)
if mountID then
local name, spellID, icon = C_MountJournal.GetMountInfoByID(mountID)
return {
mountID = mountID,
spellID = lastMountSpellID,
name = name,
icon = icon
}
end
end
return nil
end
--------------------------------------------------------------------------------
-- Enforcement Logic
--------------------------------------------------------------------------------
local function HandleViolation(reason, campaign, mountInfo)
local level = campaign.settings.enforcementLevel or 1
if level == 0 then
-- Off - do nothing
return
elseif level == 1 then
-- Permissive - warn only (no dismount, no flag)
elseif level == 2 then
-- Balanced - warn then dismount after 3 seconds
PrintWarning("Warning: " .. reason)
Print("Grace period: 3 seconds to dismount voluntarily...")
C_Timer.After(3, function()
if IsMounted() then
enforcementDismountInProgress = true -- SET FLAG BEFORE DISMOUNT
Dismount()
Print("Dismounted after grace period")
else
end
end)
elseif level == 3 then
-- Strict - immediate dismount
enforcementDismountInProgress = true -- SET FLAG BEFORE DISMOUNT
Dismount()
PrintWarning("Dismounted: " .. reason)
end
end
local function CheckMountRestrictions(campaign, mountInfo)
-- Check 1: Is this mount assigned to the campaign?
local isAssigned = false
local assignedSlot = nil
for slotName, slotSpellID in pairs(campaign.mounts) do
if slotSpellID == mountInfo.spellID then
isAssigned = true
assignedSlot = slotName
break
end
end
if not isAssigned then
-- Check if ANY mounts are assigned
local hasAnyMounts = false
for _, spellID in pairs(campaign.mounts) do
if spellID then
hasAnyMounts = true
break
end
end
if not hasAnyMounts then
-- No mounts assigned yet - inform but allow
Print("No mounts assigned to campaign '" .. campaign.name .. "' yet.")
Print("This mount will be allowed until you configure the campaign.")
return true
else
-- Mounts ARE assigned, but this isn't one of them
HandleViolation(
"'" .. mountInfo.name .. "' is not assigned to campaign '" .. campaign.name .. "'",
campaign,
mountInfo
)
return false
end
end
-- Check 2: Does this mount have an anchor?
local anchor = campaign.anchors[mountInfo.spellID]
if not anchor then
-- First use of this mount in this campaign
Print("First use of '" .. mountInfo.name .. "' in this campaign.")
Print("Anchor will be set when you dismount.")
return true
end
-- Check 3: Are we within range of the anchor?
local currentMapID, currentX, currentY = GetCurrentPosition()
if not currentMapID then
Print("Warning: Could not determine your current position")
return true -- Allow if we can't check
end
local anchorMapID = anchor[1]
local anchorX = anchor[2]
local anchorY = anchor[3]
-- Different maps = not within range
if currentMapID ~= anchorMapID then
local currentMapName = GetMapInfo(currentMapID)
local anchorMapName = GetMapInfo(anchorMapID)
Print("Your '" .. mountInfo.name .. "' is in a different location:")
Print(" Current: " .. currentMapName)
Print(" Mount at: " .. anchorMapName .. " (" .. FormatCoordinates(anchorX, anchorY) .. ")")
-- Set TomTom waypoint (will work even on different map)
if TomTom then
SetTomTomWaypoint(anchorMapID, anchorX, anchorY, mountInfo.name)
else
Print(" (Install TomTom addon for automatic waypoint)")
end
HandleViolation(
"Mount is in " .. anchorMapName .. ", you are in " .. currentMapName,
campaign,
mountInfo
)
return false
end
-- Same map - calculate distance using actual map dimensions
local mapWidth, mapHeight = C_Map.GetMapWorldSize(currentMapID)
if not mapWidth or not mapHeight then
Print("Warning: Could not determine map size, allowing mount")
return true
end
-- Calculate actual distance in yards
local dx = (currentX - anchorX) * mapWidth
local dy = (currentY - anchorY) * mapHeight
local distance = math.sqrt(dx * dx + dy * dy)
local radius = campaign.settings.anchorRadius or DEFAULT_ANCHOR_RADIUS
if distance > radius then
local mapName = GetMapInfo(currentMapID)
Print("Your '" .. mountInfo.name .. "' is too far away:")
Print(" Distance: " .. string.format("%.0f", distance) .. " yards (limit: " .. radius .. " yards)")
Print(" Location: " .. mapName .. " (" .. FormatCoordinates(anchorX, anchorY) .. ")")
-- Automatically set TomTom waypoint if available
if TomTom then
SetTomTomWaypoint(anchorMapID, anchorX, anchorY, mountInfo.name)
else
Print(" (Install TomTom addon for automatic waypoint)")
end
HandleViolation(
string.format("Mount is %.0f yards away (limit: %d yards)", distance, radius),
campaign,
mountInfo
)
return false
end
-- All checks passed
return true
end
--------------------------------------------------------------------------------
-- Mount Event Handlers
--------------------------------------------------------------------------------
local function OnPlayerMounted()
-- Get active campaign
local campaign = GetActiveCampaign()
if not campaign then
return
end
-- Detect which mount
local mountInfo = GetCurrentMountInfo()
if not mountInfo then
return
end
-- Check restrictions
CheckMountRestrictions(campaign, mountInfo)
-- Update campaign last used time
campaign.lastUsed = time()
end
local function OnPlayerDismounted()
-- Check if this was an enforcement dismount
if enforcementDismountInProgress then
enforcementDismountInProgress = false -- Clear flag
lastMountSpellID = nil -- Clear tracked mount
return -- Exit early, don't record anchor
end
-- Get active campaign
local campaign = GetActiveCampaign()
if not campaign then
return
end
-- Try to detect which mount we were on
-- Use lastMountSpellID from the cast event
if not lastMountSpellID then
return
end
local mountID = C_MountJournal.GetMountFromSpell(lastMountSpellID)
if not mountID then
return
end
-- Get mount name for messaging
local mountName = C_MountJournal.GetMountInfoByID(mountID)
-- Get current position
local mapID, x, y = GetCurrentPosition()
if not mapID or not x or not y then
Print("Warning: Could not record dismount location")
return
end
-- Record anchor
campaign.anchors[lastMountSpellID] = {mapID, x, y, time()}
local mapName = GetMapInfo(mapID)
local coords = FormatCoordinates(x, y)
Print("Mount anchored: '" .. (mountName or "Unknown") .. "' at " .. mapName .. " (" .. coords .. ")")
-- Clear the tracked spell
lastMountSpellID = nil
end
--------------------------------------------------------------------------------
-- Exposing campaign creation function for UI
--------------------------------------------------------------------------------
DWMK_CreateCampaign = function(name)
return CreateDefaultCampaign(name)
end
--------------------------------------------------------------------------------
-- Event Handlers
--------------------------------------------------------------------------------
frame:SetScript("OnEvent", function(self, event, ...)
if event == "ADDON_LOADED" then
local addonName = ...
if addonName == ADDON_NAME then
InitializeDatabase()
local campaign = GetActiveCampaign()
if campaign then
local levelName = ENFORCEMENT_LEVELS[campaign.settings.enforcementLevel] or "Unknown"
Print("v" .. ADDON_VERSION .. " loaded")
Print("Active campaign: " .. campaign.name .. " (Level: " .. levelName .. ")")
else
Print("v" .. ADDON_VERSION .. " loaded (no active campaign)")
end
end
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
local unit, castGUID, spellID = ...
if unit == "player" then
-- Check if this is a mount spell
if C_MountJournal and C_MountJournal.GetMountFromSpell then
local mountID = C_MountJournal.GetMountFromSpell(spellID)
if mountID then
-- Track this for dismount event
lastMountSpellID = spellID
end
end
end
elseif event == "PLAYER_MOUNT_DISPLAY_CHANGED" then
-- Ignore taxis/flight paths
if UnitOnTaxi("player") then
return
end
if IsMounted() then
OnPlayerMounted()
else
OnPlayerDismounted()
end
elseif event == "PLAYER_LOGOUT" then
-- If mounted on logout, record the position
if IsMounted() then
local campaign = GetActiveCampaign()
if campaign and lastMountSpellID then
local mapID, x, y = GetCurrentPosition()
if mapID and x and y then
campaign.anchors[lastMountSpellID] = {mapID, x, y, time()}
end
end
end
end
end)
-- Register events
frame:RegisterEvent("ADDON_LOADED")
frame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
frame:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED")
frame:RegisterEvent("PLAYER_LOGOUT")
--------------------------------------------------------------------------------
-- Slash Commands
--------------------------------------------------------------------------------
SLASH_DWMK1 = "/dwmk"
SLASH_DWMK2 = "/dude"
SlashCmdList["DWMK"] = function(msg)
msg = msg:lower():trim()
if msg == "" or msg == "help" then
Print("Commands:")
Print(" /dwmk status - Show current campaign status")
Print(" /dwmk level <0-3> - Set enforcement level")
Print(" /dwmk radius <10-200> - Set anchor radius in yards")
Print(" /dwmk config - Open settings panel")
elseif msg == "status" then
local campaign = GetActiveCampaign()
if not campaign then
Print("No active campaign")
return
end
local levelName = ENFORCEMENT_LEVELS[campaign.settings.enforcementLevel] or "Unknown"
Print("Campaign: " .. campaign.name)
Print(" Enforcement: " .. levelName .. " (Level " .. campaign.settings.enforcementLevel .. ")")
Print(" Anchor radius: " .. campaign.settings.anchorRadius .. " yards")
Print(" Ground mount: " .. (campaign.mounts.ground or "Not assigned"))
Print(" Flying mount: " .. (campaign.mounts.flying or "Not assigned"))
local anchorCount = 0
for _ in pairs(campaign.anchors) do
anchorCount = anchorCount + 1
end
Print(" Anchored mounts: " .. anchorCount)
elseif msg:match("^level%s+(%d+)$") then
local level = tonumber(msg:match("^level%s+(%d+)$"))
if level < 0 or level > 3 then
Print("Error: Level must be 0-3")
Print(" 0 = Off, 1 = Permissive, 2 = Balanced, 3 = Strict")
return
end
local campaign = GetActiveCampaign()
if not campaign then
Print("No active campaign")
return
end
campaign.settings.enforcementLevel = level
local levelName = ENFORCEMENT_LEVELS[level]
Print("Enforcement level set to: " .. levelName)
elseif msg:match("^radius%s+(%d+)$") then
local radius = tonumber(msg:match("^radius%s+(%d+)$"))
if radius < MIN_ANCHOR_RADIUS or radius > MAX_ANCHOR_RADIUS then
Print("Error: Radius must be " .. MIN_ANCHOR_RADIUS .. "-" .. MAX_ANCHOR_RADIUS .. " yards")
return
end
local campaign = GetActiveCampaign()
if not campaign then
Print("No active campaign")
return
end
campaign.settings.anchorRadius = radius
Print("Anchor radius set to: " .. radius .. " yards")
else
Print("Unknown command. Type /dwmk help for commands.")
end
end
Print("Type /dwmk help for commands")