-
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathClickMorph.lua
430 lines (398 loc) · 12.5 KB
/
ClickMorph.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
ClickMorph = {}
local CM = ClickMorph
CM.isRetail = (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE)
CM.project = CM.isRetail and "Live" or "Classic"
local FileData
-- inventory type -> equipment slot -> slot name
CM.SlotNames = {
[INVSLOT_HEAD] = "head", -- 1
[INVSLOT_SHOULDER] = "shoulder", -- 3
[INVSLOT_BODY] = "shirt", -- 4
[INVSLOT_CHEST] = "chest", -- 5
[INVSLOT_WAIST] = "belt", -- 6
[INVSLOT_LEGS] = "legs", -- 7
[INVSLOT_FEET] = "feet", -- 8
[INVSLOT_WRIST] = "wrist", -- 9
[INVSLOT_HAND] = "hands", -- 10
[INVSLOT_BACK] = "cloak", -- 15
[INVSLOT_MAINHAND] = "mainhand", -- 16
[INVSLOT_OFFHAND] = "offhand", -- 17
[INVSLOT_RANGED] = "ranged", -- 18
[INVSLOT_TABARD] = "tabard", -- 19
}
-- https://wow.gamepedia.com/Enum_Item.InventoryType
local InvTypeToSlot = {
INVTYPE_HEAD = INVSLOT_HEAD, -- 1
INVTYPE_SHOULDER = INVSLOT_SHOULDER, -- 3
INVTYPE_BODY = INVSLOT_BODY, -- 4
INVTYPE_CHEST = INVSLOT_CHEST, -- 5
INVTYPE_ROBE = INVSLOT_CHEST, -- 5 (cloth)
INVTYPE_WAIST = INVSLOT_WAIST, -- 6
INVTYPE_LEGS = INVSLOT_LEGS, -- 7
INVTYPE_FEET = INVSLOT_FEET, -- 8
INVTYPE_WRIST = INVSLOT_WRIST, -- 9
INVTYPE_HAND = INVSLOT_HAND, -- 10
INVTYPE_CLOAK = INVSLOT_BACK, -- 15
INVTYPE_2HWEAPON = INVSLOT_MAINHAND, -- 16
INVTYPE_WEAPON = INVSLOT_MAINHAND, -- 16
INVTYPE_WEAPONMAINHAND = INVSLOT_MAINHAND, -- 16
INVTYPE_WEAPONOFFHAND = INVSLOT_OFFHAND, -- 17
INVTYPE_HOLDABLE = INVSLOT_OFFHAND, -- 17
INVTYPE_RANGED = INVSLOT_RANGED, -- 18
INVTYPE_THROWN = INVSLOT_RANGED, -- 18
INVTYPE_RANGEDRIGHT = INVSLOT_RANGED, -- 18
INVTYPE_SHIELD = INVSLOT_OFFHAND, -- 17
INVTYPE_TABARD = INVSLOT_TABARD, -- 19
}
local GearSlots = {
INVSLOT_HEAD, -- 1
INVSLOT_SHOULDER, -- 3
INVSLOT_BODY, -- 4
INVSLOT_CHEST, -- 5
INVSLOT_WAIST, -- 6
INVSLOT_LEGS, -- 7
INVSLOT_FEET, -- 8
INVSLOT_WRIST, -- 9
INVSLOT_HAND, -- 10
INVSLOT_BACK, -- 15
INVSLOT_TABARD, -- 19
}
local lastWeaponSlot = INVSLOT_OFFHAND
local DualWieldSlot = {
[INVSLOT_MAINHAND] = INVSLOT_OFFHAND,
[INVSLOT_OFFHAND] = INVSLOT_MAINHAND,
}
local genders = {MALE, FEMALE}
function CM:PrintChat(msg, r, g, b)
if not ClickMorphDB.silent then
DEFAULT_CHAT_FRAME:AddMessage(format("|cff7fff00ClickMorph|r: |r%s", msg), r, g, b)
end
end
function CM:GetFileData(frame)
if not FileData then
local addon = "ClickMorphData"
local loaded, reason = LoadAddOn(addon)
if not loaded then
if reason == "DISABLED" then
EnableAddOn(addon, true)
LoadAddOn(addon)
else
frame:SetScript("OnUpdate", nil) -- cancel any wardrobe timer
self:PrintChat("The ClickMorphData folder could not be found."
.."Make sure you downloaded the release zip from https://github.com/ketho-wow/ClickMorph/releases", 1, 1, 0)
error(addon..": "..reason)
end
end
FileData = _G[addon]
end
return FileData
end
function CM:CanMorph(override)
if IsAltKeyDown() or override then
for _, morpher in pairs(self.morphers) do
if morpher.loaded() then
return morpher
end
end
local name = CM.isRetail and "jMorph" or "iMorph"
self:PrintChat("Could not find |cffFFFF00"..name.."|r. Make sure it is loaded before you use ClickMorph.", 1, 1, 0)
end
end
function CM:CanMorphMount()
local isMounted = IsMounted()
local onTaxi = UnitOnTaxi("player")
if isMounted and not onTaxi then
return true
else
if onTaxi then
CM:PrintChat("You need to be not on a flight path", 1, 1, 0)
elseif not isMounted then
CM:PrintChat("You need to be mounted", 1, 1, 0)
end
end
end
CM.morphers = {
iMorph = { -- classic
-- morphers can be unloaded and initialized at any later moment
loaded = function() return IMorphInfo end,
reset = function() -- todo: add reset to naked
iMorphFrame:Reset()
wipe(ClickMorph_iMorphV1)
end,
model = function(_, displayID)
Morph(displayID)
end,
race = function(_, raceID, genderID)
SetRace(raceID, genderID)
end,
mount = function(_, displayID)
if CM:CanMorphMount() then
SetMount(displayID)
return true
end
end,
item = function(_, slotID, itemID)
SetItem(slotID, itemID)
end,
scale = function(_, value)
SetScale(value)
ClickMorph_iMorphV1.tempscale = value -- workaround
end,
--SetEnchant(slotId, enchantId)
--SetTitle(titleId)
--SetMedal(medalId)
--SetFace(face)
--SetFeatures(feature)
--SetHairStyle(style)
--SetHairColor(color)
--SetSkinColor(color)
},
jMorph = { -- retail
loaded = function() return jMorphLoaded end,
update = function(unit)
UpdateModel(unit)
end,
model = function(unit, displayID)
SetDisplayID(unit, displayID)
UpdateModel(unit)
end,
race = function(unit, raceID)
SetDisplayID(unit, 0)
SetAlternateRace(unit, raceID)
UpdateModel(unit)
end,
gender = function(unit, genderID, raceID)
SetGender(unit, genderID)
SetAlternateRace(unit, raceID)
UpdateModel(unit)
end,
mount = function(unit, displayID)
if CM:CanMorphMount() then
SetMountDisplayID(unit, displayID)
MorphPlayerMount()
return true
end
end,
item = function(unit, slotID, itemID, itemModID)
SetVisibleItem(unit, slotID, itemID, itemModID)
-- dont automatically update for every item in an item set
end,
enchant = function(unit, slotID, visualID)
SetVisibleEnchant(unit, slotID, visualID)
UpdateModel(unit)
end,
scale = function(unit, value)
SetScale(unit, value)
end,
-- spell (nyi)
-- title
-- scale
-- skin
-- face
-- hair
-- haircolor
-- piercings
-- tattoos
-- horns
-- blindfold
-- shapeshift
-- weather
},
}
function CM:ResetMorph()
local morph = self:CanMorph(true)
if morph then
if morph.reset then
morph.reset()
end
--[[ resetting the scale slider already sets the value to 1
if morph.scale then -- imorph doesnt reset scale
morph.scale("player", 1)
end
]]
end
end
function CM:Undress(unit)
local morph = self:CanMorph(true)
if morph and morph.item then
for _, invSlot in pairs(GearSlots) do -- excludes weapons
morph.item(unit, invSlot, 0)
end
end
end
-- Mounts
function CM:MorphMount(unit, mountID)
local morph = self:CanMorph()
if morph and morph.mount then
local _, spellID = C_MountJournal.GetMountInfoByID(mountID)
local displayID = C_MountJournal.GetMountInfoExtraByID(mountID)
if not displayID then
local multipleIDs = C_MountJournal.GetMountAllCreatureDisplayInfoByID(mountID)
displayID = multipleIDs[random(#multipleIDs)].creatureDisplayID
end
if morph.mount(unit, displayID) then
CM:PrintChat(format("mount -> |cffFFFF00%d|r %s", displayID, GetSpellLink(spellID)))
end
end
end
function CM:MorphMountClassic(unit, displayID, spellID, override)
local morph = self:CanMorph(override)
if morph and morph.mount then
if morph.mount(unit, displayID) then
CM:PrintChat(format("mount -> |cffFFFF00%d|r %s", displayID, GetSpellLink(spellID)))
end
end
end
function CM.MorphMountModelScene()
local mountID = MountJournal.selectedMountID
CM:MorphMount("player", mountID)
end
function CM.MorphMountScrollFrame(frame)
local mountID = select(12, C_MountJournal.GetDisplayedMountInfo(frame.index))
CM:MorphMount("player", mountID)
end
-- Items
function CM:GetItemInfo(item)
-- try to preserve item link if we receive one
if type(item) == "string" then
local itemID = tonumber(item:match("item:(%d+)"))
local equipLoc = select(9, GetItemInfo(itemID))
return itemID, item, equipLoc
else
local itemLink, _, _, _, _, _, _, equipLoc = select(2, GetItemInfo(item))
return item, itemLink, equipLoc
end
end
-- only alternate clickmorphing both weapon slots when the player is actually dual wielding since the animation will reflect that
-- IsDualWielding seems to return true when both MH/OH or only OH are equipped with a (one or two-hand) weapon
-- in jMorph it will conflict since they also clickmorph from the appearances tab, e.g. when we morph offhand they will morph mainhand simultaenously
-- bug: when trying to morph shields it will attempt to morph MH to shield and fail but cba to check that with MorphItemBySource not providing equiploc
function CM:GetDualWieldSlot(slot)
if DualWieldSlot[slot] and IsDualWielding() then
lastWeaponSlot = DualWieldSlot[lastWeaponSlot]
return lastWeaponSlot
else
return slot
end
end
local function IsLooting()
if ElvLootSlot1 then -- elvui
return ElvLootSlot1:IsShown()
else
return LootFrame:IsShown()
end
end
function CM:MorphItem(unit, item, silent)
local morph = CM:CanMorph()
-- nobody wants to morph while looting and it would interfere with dkp addons
if item and morph and morph.item and not IsLooting() then
local itemID, itemLink, equipLoc = CM:GetItemInfo(item)
local slotID = InvTypeToSlot[equipLoc]
if slotID then
slotID = CM:GetDualWieldSlot(slotID)
morph.item(unit, slotID, itemID)
if not silent then
CM:PrintChat(format("|cffFFFF00%s|r -> item |cff71D5FF%d|r %s", CM.SlotNames[slotID], itemID, itemLink))
end
end
end
end
hooksecurefunc("HandleModifiedItemClick", function(item)
CM:MorphItem("player", item)
end)
function CM:MorphItemBySource(unit, source, silent)
local morph = self:CanMorph()
if morph and morph.item then
local slotID = C_Transmog.GetSlotForInventoryType(source.invType)
slotID = self:GetDualWieldSlot(slotID)
local itemLink = select(6, C_TransmogCollection.GetAppearanceSourceInfo(source.sourceID))
local itemText = itemLink:find("%[%]") and CM.ItemAppearance and CM.ItemAppearance[source.visualID] or itemLink
morph.item(unit, slotID, source.itemID, source.itemModID)
morph.update(unit)
local fullItemId = source.itemID..(source.itemModID > 0 and ":"..source.itemModID or "")
if not silent then
self:PrintChat(format("|cffFFFF00%s|r -> item |cff71D5FF%s|r %s", CM.SlotNames[slotID], fullItemId, itemText))
end
end
end
function CM:MorphEnchant(unit, slotID, visualID, enchantName)
local morph = self:CanMorph()
if morph and morph.enchant then
morph.enchant(unit, slotID, visualID)
self:PrintChat(format("|cffFFFF00%s|r -> enchant |cff71D5FF%d|r %s", CM.SlotNames[slotID], visualID, enchantName))
end
end
function CM:MorphModel(unit, displayID, npcID, npcName, override)
local morph = self:CanMorph(override)
if morph and morph.model then
morph.model(unit, displayID)
if npcID and npcName then
self:PrintChat(format("model -> |cff71D5FF%d|r (NPC |cffFFFF00%d|r %s)", displayID, npcID, npcName))
else
self:PrintChat(format("model -> |cff71D5FF%d|r", displayID))
end
end
end
-- Appearances
function CM.MorphTransmogSet() -- retail
local morph = CM:CanMorph()
if morph and morph.item then
local setID = WardrobeCollectionFrame.SetsCollectionFrame.selectedSetID
local setInfo = C_TransmogSets.GetSetInfo(setID)
for _, v in pairs(WardrobeSetsDataProviderMixin:GetSortedSetSources(setID)) do
local source = C_TransmogCollection.GetSourceInfo(v.sourceID)
local slotID = C_Transmog.GetSlotForInventoryType(v.invType)
morph.item("player", CM.SlotNames[slotID], source.itemID, source.itemModID)
end
morph.update("player")
CM:PrintChat(format("itemset -> |cff71D5FF%d|r |cffFFFF00%s|r (%s)", setID, setInfo.name, setInfo.description or ""))
end
end
function CM.MorphTransmogItem(frame) -- retail
local loc = WardrobeCollectionFrame.ItemsCollectionFrame.transmogLocation
local visualID = frame.visualInfo.visualID
if loc.transmogType == Enum.TransmogType.Illusion then
local slotID = GetInventorySlotInfo(loc.slotID)
local name
if frame.visualInfo.sourceID then
local link = select(3, C_TransmogCollection.GetIllusionSourceInfo(frame.visualInfo.sourceID))
name = #link > 0 and link
end
CM:MorphEnchant("player", slotID, visualID, name or CM.ItemVisuals[visualID])
elseif loc.transmogType == Enum.TransmogType.Appearance then
local sources = WardrobeCollectionFrame_GetSortedAppearanceSources(visualID)
for idx, source in pairs(sources) do
-- get the index the arrow is pointing at
if idx == WardrobeCollectionFrame.tooltipSourceIndex then
CM:MorphItemBySource("player", source)
end
end
end
end
function CM:MorphItemSet(itemSetID, override) -- classic
local morph = CM:CanMorph(override)
if morph then
local itemset = self:GetFileData().Classic.ItemSet[itemSetID]
if morph.item and itemset then
self:Undress() -- todo: only undress gear for gear item sets instead of weapon sets
for slot, item in pairs(itemset) do
if type(slot) == "number" then -- ignore "name" key
SetItem(slot, item)
end
end
CM:PrintChat(format("itemset -> |cff71D5FF%d|r |cffFFFF00%s|r", itemSetID, itemset.name))
end
end
end
function CM:MorphRace(unit, race, sex)
local morph = self:CanMorph(true)
if morph and morph.race then
morph.race(unit, race, sex)
end
end
function CM:MorphScale(unit, value)
local morph = self:CanMorph(true)
if morph and morph.scale then
morph.scale(unit, value)
end
end