-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.lua
426 lines (385 loc) · 12.9 KB
/
Main.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
local addon, Dta = ...
--Addon information
Dta.Version = addon.toc.Version --Inspect.Addon.Detail(Inspect.Addon.Current()).toc.Version
Dta.AddonID = addon.toc.Identifier
Dta.SettingsRevision = 3
Dta.Language = Inspect.System.Language()
Dta.Lang = {}
Dta.Tools = {}
--Items
Dta.selectedItems = {}
Dta.selectionCount = 0
Dta.clipboard = {}
Dta.notifyThreshold = 10
Dta.selectionChanged = false
--Load & Save
Dta.constructionsdefaults = {}
Dta.constructionstbx = {}
Dta.SelectionQueue = {}
--Import & Export
Dta.ExportTbx = {}
Dta.ExportSaved = {}
Dta.ExportImport_Sets = {}
--Move, Rotate and Scale
Dta.pendingActions = {}
--Copy & Paste
Dta.FlickerOffset = true
--Others
Dta.PlayerID = Inspect.Unit.Lookup("player")
Dta.InDimension = false
Dta.debugVerbosity = 0
--Flying
Dta.desiredPitch = 0
Dta.waitingForCarpet = false
Dta.FlyingType = "IFEC11D174272F87C,3E1F104FE8C67224,,,,,,"
Dta.magicYOffset = 0.4755 -- height of Tribal Table
Dta.carpetId = false
Dta.lastPlayerPos = {coordX = 0, coordY = 0, coordZ = 0}
Dta.savePosNew = {coordX = 0, coordY = 0, coordZ = 0}
Dta.savePosOld = {coordX = -0.2, coordY = 0, coordZ = 0}
Dta.nextCarpetMove = 0
---------------------------------
--CATCH COROUTINES
---------------------------------
Dta.AddItem_Co = nil
Dta.pending_add = false
--------------------------------------
--MAIN FUNCTIONS
--------------------------------------
function Dta.main()
Command.Event.Attach(Command.Slash.Register("tt"), Dta.commandHandler, "Tinker Tools Command")
Command.Event.Attach(Event.Unit.Availability.Full, Dta.Event_Unit_Availability_Full, "Event_Unit_Availability_Full")
Command.Event.Attach(Event.Unit.Detail.Zone, Dta.Event_Unit_Detail_Zone, "Event_Unit_Detail_Zone")
Command.Event.Attach(Event.Dimension.Layout.Add, Dta.addEventHandler, "Update selection")
Command.Event.Attach(Event.Dimension.Layout.Remove, Dta.removeEventHandler, "Update selection")
Command.Event.Attach(Event.Dimension.Layout.Update, Dta.updateEventHandler, "Update selection")
Dta.triggerSelectionChange, Dta.selectionChangeHandle = Utility.Event.Create(Dta.AddonID, "Selection")
end
-- print text to all consoles (chat windows/tabs) enabled in settings
function Dta.CPrint(text, html)
local consoles = Dta.settings.get("ConsoleOutput")
local open_consoles = Inspect.Console.List()
-- apparently not available during loading screens
if not open_consoles then return end
for k, _ in pairs(consoles) do
local console_id = string.format("v00000000%08x", k-1)
if open_consoles[console_id] then
Command.Console.Display(console_id, false, text, html == true)
end
end
end
function Dta.DebugPrint(level, text, html)
if Dta.debugVerbosity >= level then
Dta.CPrint(text, html)
end
end
function Dta.DebugDump(level, ...)
if Dta.debugVerbosity >= level then
local arg_table = { ... }
for i, val in pairs(arg_table) do
if type(val) ~= "string" then
arg_table[i] = Utility.Serialize.Inline(val)
end
end
Dta.CPrint(table.concat(arg_table, " | "), false)
end
end
local function toolIsActive(self)
return self.window and self.window:GetVisible()
end
local function toolToggle(self)
if self.window then
if self.window:GetVisible() then
self.Hide(self.window)
else
self.Show(self.window)
end
else
self.window = self.CreateUI()
-- give window a chance to update with current selection after being installed
self.Show(self.window)
end
end
-- register a tool with some commonly required callbacks
function Dta.RegisterTool(name, createFunc, showFunc, hideFunc)
assert(type(createFunc) == "function" and type(showFunc) == "function" and type(hideFunc) == "function")
local tool = { CreateUI = createFunc, Show = showFunc, Hide = hideFunc, IsActive = toolIsActive }
tool.Toggle = function() toolToggle(tool) end
Dta.Tools[name] = tool
end
function Dta.checkIdle()
if Dta.AddItem_Co then
-- ask user whether to abort
Dta.ui.showNotification(Dta.Locale.Text.NotIdleNotification,
Dta.killProcess, nil)
return false
end
return true
end
function Dta.killProcess()
Dta.AddItem_Co = nil
Dta.pending_add = false
if #Dta.pendingActions > 0 then
Dta.pendingActions = {}
end
if #Dta.SelectionQueue > 0 then
Dta.SelectionQueue = {}
end
end
function Dta.StartRecordingAdds()
Dta.recordedAdds = {}
end
function Dta.FinishRecordingAdds()
local itemList = Dta.recordedAdds
Dta.recordedAdds = nil
return itemList
end
-------------------------
-- Event Handlers
-------------------------
function Dta.addEventHandler(hEvent, dimensionItem) --executed all the time in a dimension
Dta.DebugDump(2, "Add", dimensionItem)
if Dta.pending_add then
local id, _ = next(dimensionItem)
Dta.items.QueueSelection(id)
Dta.pending_add = false
if Dta.recordedAdds then
Dta.recordedAdds[id] = true
end
coroutine.resume(Dta.AddItem_Co)
end
if Dta.waitingForCarpet == true then
for id, value in pairs(dimensionItem) do
local data = Inspect.Dimension.Layout.Detail(id)
if data ~= nil then
if data.type == Dta.FlyingType then
Dta.carpetId = id
Dta.nextCarpetMove = Inspect.Time.Frame()
Dta.waitingForCarpet = false
end
end
end
end
end
function Dta.removeEventHandler(hEvent, dimensionItem) --Executed when item is removed
Dta.DebugDump(2, "Remove", dimensionItem)
if Dta.carpetId and dimensionItem[Dta.carpetId] then
Dta.flying.FlyingRemoved()
end
if Dta.selectionCount > 0 then
Dta.items.updateSelection(dimensionItem, true)
end
end
function Dta.updateEventHandler(hEvent, dimensionItem) --Executed on every select/ deselect or change of an dimension item
Dta.DebugDump(2, "Update", dimensionItem)
Dta.items.updateSelection(dimensionItem, false)
end
local function EnterDimension()
-- reset copy&paste pivot, because it may be off-limit
Dta.copa.pivot = nil
Dta.losa.updateRefPoint(nil)
if Dta.InDimension then return end
Dta.ui.showMainButton()
Command.Event.Attach(Event.System.Update.Begin, Dta.tick, "TT_Update")
Dta.InDimension = true
end
local function LeaveDimension()
if not Dta.InDimension then return end
Dta.ui.hideMainButton()
Command.Event.Detach(Event.System.Update.Begin, Dta.tick, "TT_Update")
if Dta.ui.active then Dta.ui.hideMainWindow() end
Dta.InDimension = false
end
local function IsDimension(zoneID)
local ZoneDetails = Inspect.Zone.Detail(zoneID)
if not ZoneDetails or not ZoneDetails.name then
return false
end
local ZoneName = ZoneDetails.name
if string.sub(ZoneName, 1, 9) == "Dimension" or string.sub(ZoneName, 1, 9) == "Измерение" then
return true
end
-- Octus Monastery fix:
if ZoneDetails.id == "z7E4460FA8D2B98EF" then
return true
end
return false
end
------------------------
-- entering a dimension (or changing zones in general) can happen in two ways:
-- player data stays available and we get an Event.Unit.Detail.Zone, or
-- player enters a Event.Unit.Availability.Partial in loading screen (zone
-- becomes undefined) and we need to check zone again at Event.Unit.Availability.Full
------------------------
function Dta.Event_Unit_Detail_Zone(hEvent, u)
if u[Dta.PlayerID] ~= nil then -- value may be "false", so explicit nil check
local newZone = u[Dta.PlayerID]
-- Dta._CurrentZoneID = newZone
if newZone and IsDimension(newZone) then
EnterDimension()
else
LeaveDimension()
end
if Dta.carpetId then
Dta.flying.FlyingRemoved()
--Dta.CPrint("You left Tribal Magic behind...")
end
end
end
function Dta.Event_Unit_Availability_Full(hEvent, t)
for k,v in pairs(t) do
if v == "player" then
local PlayerDetails = Inspect.Unit.Detail("player")
-- Dta._CurrentZoneID = PlayerDetails.zone
if IsDimension(PlayerDetails.zone) then
EnterDimension()
else
LeaveDimension()
end
break
end
end
end
function Dta.commandHandler(hEvent, command_string)
local args = command_string:split(" +", true)
local command = args[1]
if command == "reset" then
for name, val in pairs(Dta.settings.defaults) do
if string.sub(name, -4, -2) == "Pos" then
Dta.settings.set(name, val)
end
end
local elements = { Dta.ui.windowtest, Dta.ui.buttonMainToggle, Dta.ui.windowHelp }
for _, tool in pairs(Dta.Tools) do
table.insert(elements, tool.window)
end
for _, window in pairs(elements) do
window:SetPoint("TOPLEFT", UIParent, "TOPLEFT", Dta.settings.get(window.settings.POS_X), Dta.settings.get(window.settings.POS_Y))
end
Dta.CPrint("Positions reset")
elseif command == "help" then
Dta.help_ui.toggleHelpWindow()
elseif command == "import_dt" then
Dta.settings.import_dimtools()
elseif command == "config" or
command == "settings" then
Dta.config_ui.showConfigWindow()
elseif command == "debug" then
Dta.debugVerbosity = tonumber(args[2]) or 0
Dta.CPrint("Debug verbosity set to " .. Dta.debugVerbosity)
elseif command == "force" then
EnterDimension()
elseif command == "check_lang" then
local function checkLang(lang1, lang2, stack)
for key, val in pairs(lang1) do
if type(val) == "table" then
if not rawget(lang2, key) then
Dta.CPrint("Missing table: " .. table.concat(stack, ".") .. "." .. key)
else
table.insert(stack, key)
checkLang(val, lang2[key], stack)
table.remove(stack)
end
elseif not rawget(lang2, key) then
Dta.CPrint("Missing string: " .. table.concat(stack, ".") .. "." .. key)
end
end
end
local lang = args[2] or Dta.Language
if not Dta.Lang[lang] then
Dta.CPrint("Language '" .. lang .. "' not available")
return
elseif lang == "English" then -- nothing to do for "master" language
return
end
checkLang(Dta.Lang.English, Dta.Lang[lang], {lang})
for name, skin_def in pairs(Dta.Defaults.Skins) do
if not skin_def[lang] then
Dta.CPrint("Missing skin name: " .. name)
end
end
else
if Dta.InDimension == true then
Dta.ui.toggleMainWindow()
else
Dta.CPrint(Dta.Locale.Prints.DimensionOnly)
end
end
end
--------------------------------------
--QUEUE HANDLER
--------------------------------------
function Dta.tick(handle)
if #Dta.pendingActions > 0 and not Dta.pending_add then
-- Rift (3.6) has a global command queue with a depth of 100 and throttling (25/sec ?).
-- We don't want to completely cram the queue, but keep 20 command slots for other addons;
-- 10 commands per tick should also be plenty to reach queue limit eventually.
local count = 0
while #Dta.pendingActions > 0 and count < 10 and Inspect.Queue.Status("global", 20) do
local action = table.remove(Dta.pendingActions, 1)
if action.op == "scale" then
Command.Dimension.Layout.Place(action.id, {scale=action.amount})
elseif action.op == "move" then
Command.Dimension.Layout.Place(action.id, {coordX=action.x, coordY=action.y, coordZ=action.z})
elseif action.op == "pickup" then
Command.Dimension.Layout.Pickup(action.id)
elseif action.op == "rotate" then
Command.Dimension.Layout.Place(action.id, {pitch=action.pitch, yaw=action.yaw, roll=action.roll})
elseif action.op == "select" then
Command.Dimension.Layout.Select(action.id, true)
elseif action.op == "add" then
Dta.pending_add = true
-- Command.Dimension.Layout.Place(action.id, action.details)
-- *NOTE*: below code is to get more useful information about a seemingly random error
-- that claims incorrect function usage despite the given arguments should always have
-- the same structure, and at least their data type clearly matches the docs.
local success, rval = pcall(Command.Dimension.Layout.Place, action.id, action.details)
if not success then
local details = Inspect.Item.Detail(action.id)
local item_info = details and Utility.Serialize.Inline(details) or "N/A"
local placement_info = Utility.Serialize.Inline(action.details)
local error_details = "Item Details:\n" .. item_info .. "\nPlacement Info:\n" .. placement_info .. "\n Rift error:\n" .. rval
error(error_details)
end
-- can't queue multiple new items placements
break
elseif action.op == "xform" then
Command.Dimension.Layout.Place(action.id, action.details)
elseif action.op == "notify" then
Dta.CPrint(action.text)
end
count = count + 1
end
end
-- remove Coroutine when it has finished; TODO: move to Add handler?
if Dta.AddItem_Co and not Dta.pending_add then
if coroutine.status(Dta.AddItem_Co) == "dead" then
Dta.AddItem_Co = nil
end
end
if #Dta.SelectionQueue > 0 and not Dta.AddItem_Co then
local count = 0
while #Dta.SelectionQueue > 0 and count < 10 and Inspect.Queue.Status("global", 20) do
local action = table.remove(Dta.SelectionQueue, 1)
if action.op == "deselect" then
Command.Dimension.Layout.Select(action.id, false)
elseif action.op == "select" then
Command.Dimension.Layout.Select(action.id, true)
end
if not next(Dta.SelectionQueue) and Dta.Co_DoneMessage then
Dta.CPrint(Dta.Co_DoneMessage)
Dta.Co_DoneMessage = nil
end
count = count + 1
end
end
-- Trigger selection change to update Connector UI etc.
if Dta.selectionChanged then
Dta.triggerSelectionChange()
Dta.selectionChanged = false
end
if Dta.carpetId then
Dta.flying.UpdateCarpet()
end
end
Dta.main()