Mercurial > wow > askmrrobot
comparison Gear.lua @ 139:c229c759a125 v65
Update to support multiple setups per spec.
| author | yellowfive |
|---|---|
| date | Mon, 05 Nov 2018 16:06:00 -0800 |
| parents | 6dc0e8e9f960 |
| children | 55823e37403b |
comparison
equal
deleted
inserted
replaced
| 138:45e467335a1b | 139:c229c759a125 |
|---|---|
| 1 local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot") | 1 local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot") |
| 2 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true) | 2 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true) |
| 3 local AceGUI = LibStub("AceGUI-3.0") | 3 local AceGUI = LibStub("AceGUI-3.0") |
| 4 | 4 |
| 5 local _gearTabs | 5 local _cboSetups |
| 6 local _activeTab | 6 local _panelGear |
| 7 local _activeSetupId | |
| 8 | |
| 9 local function getSetupById(id) | |
| 10 if not id then | |
| 11 id = _activeSetupId | |
| 12 end | |
| 13 local setup | |
| 14 for i,s in ipairs(Amr.db.char.GearSetups) do | |
| 15 if s.Id == id then | |
| 16 setup = s | |
| 17 break | |
| 18 end | |
| 19 end | |
| 20 return setup | |
| 21 end | |
| 7 | 22 |
| 8 -- Returns a number indicating how different two items are (0 means the same, higher means more different) | 23 -- Returns a number indicating how different two items are (0 means the same, higher means more different) |
| 9 local function countItemDifferences(item1, item2) | 24 local function countItemDifferences(item1, item2) |
| 10 -- both nil, the same | 25 -- both nil, the same |
| 11 if not item1 and not item2 then | 26 if not item1 and not item2 then |
| 186 socketIcon:SetHeight(18) | 201 socketIcon:SetHeight(18) |
| 187 | 202 |
| 188 return socketBorder, socketIcon | 203 return socketBorder, socketIcon |
| 189 end | 204 end |
| 190 | 205 |
| 191 local function renderGear(spec, container) | 206 local function renderGear(setupId, container) |
| 207 | |
| 208 -- release all children that were previously rendered, we gonna redo it now | |
| 209 container:ReleaseChildren() | |
| 192 | 210 |
| 193 local player = Amr:ExportCharacter() | 211 local player = Amr:ExportCharacter() |
| 194 local gear = Amr.db.char.GearSets[spec] | 212 |
| 213 local gear | |
| 214 local spec | |
| 215 local setupIndex | |
| 216 for i, setup in ipairs(Amr.db.char.GearSetups) do | |
| 217 if setup.Id == setupId then | |
| 218 setupIndex = i | |
| 219 gear = setup.Gear | |
| 220 spec = setup.SpecSlot | |
| 221 break | |
| 222 end | |
| 223 end | |
| 224 | |
| 195 local equipped = player.Equipped[player.ActiveSpec] | 225 local equipped = player.Equipped[player.ActiveSpec] |
| 196 | 226 |
| 197 if not gear then | 227 if not gear then |
| 198 -- no gear has been imported for this spec so show a message | 228 -- no gear has been imported for this spec so show a message |
| 199 renderEmptyGear(container) | 229 renderEmptyGear(container) |
| 234 btnEquip:SetBackgroundColor(Amr.Colors.Green) | 264 btnEquip:SetBackgroundColor(Amr.Colors.Green) |
| 235 btnEquip:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) | 265 btnEquip:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) |
| 236 btnEquip:SetWidth(300) | 266 btnEquip:SetWidth(300) |
| 237 btnEquip:SetHeight(26) | 267 btnEquip:SetHeight(26) |
| 238 btnEquip:SetCallback("OnClick", function(widget) | 268 btnEquip:SetCallback("OnClick", function(widget) |
| 239 Amr:EquipGearSet(spec) | 269 Amr:EquipGearSet(setupIndex) |
| 240 end) | 270 end) |
| 241 panelGear:AddChild(btnEquip) | 271 panelGear:AddChild(btnEquip) |
| 242 btnEquip:SetPoint("LEFT", icon.frame, "RIGHT", 40, 0) | 272 btnEquip:SetPoint("LEFT", icon.frame, "RIGHT", 40, 0) |
| 243 btnEquip:SetPoint("RIGHT", panelGear.content, "RIGHT", -40, 0) | 273 btnEquip:SetPoint("RIGHT", panelGear.content, "RIGHT", -40, 0) |
| 244 | 274 |
| 390 prevElem = lbl | 420 prevElem = lbl |
| 391 end | 421 end |
| 392 end | 422 end |
| 393 end | 423 end |
| 394 | 424 |
| 395 local function onGearTabSelected(container, event, group) | 425 local function onSetupChange(widget, eventName, value) |
| 396 container:ReleaseChildren() | 426 _activeSetupId = value |
| 397 _activeTab = group | 427 renderGear(_activeSetupId, _panelGear) |
| 398 renderGear(tonumber(group), container) | |
| 399 end | 428 end |
| 400 | 429 |
| 401 local function onImportClick(widget) | 430 local function onImportClick(widget) |
| 402 Amr:ShowImportWindow() | 431 Amr:ShowImportWindow() |
| 432 end | |
| 433 | |
| 434 function Amr:PickFirstSetupForSpec() | |
| 435 local specSlot = GetSpecialization() | |
| 436 for i, setup in ipairs(Amr.db.char.GearSetups) do | |
| 437 if setup.SpecSlot == specSlot then | |
| 438 _activeSetupId = setup.Id | |
| 439 break | |
| 440 end | |
| 441 end | |
| 442 end | |
| 443 | |
| 444 function Amr:GetActiveSetupId() | |
| 445 return _activeSetupId | |
| 446 end | |
| 447 | |
| 448 function Amr:SetActiveSetupId(setupId) | |
| 449 _activeSetupId = setupId | |
| 450 end | |
| 451 | |
| 452 function Amr:GetActiveSetupLabel() | |
| 453 if not _activeSetupId then | |
| 454 return nil | |
| 455 end | |
| 456 local setup = getSetupById(_activeSetupId) | |
| 457 if not setup then | |
| 458 return nil | |
| 459 else | |
| 460 return setup.Label | |
| 461 end | |
| 403 end | 462 end |
| 404 | 463 |
| 405 -- renders the main UI for the Gear tab | 464 -- renders the main UI for the Gear tab |
| 406 function Amr:RenderTabGear(container) | 465 function Amr:RenderTabGear(container) |
| 407 | 466 |
| 444 lbl2:SetText(L.GearTipCommands) | 503 lbl2:SetText(L.GearTipCommands) |
| 445 lbl2:SetWidth(130) | 504 lbl2:SetWidth(130) |
| 446 lbl2:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text)) | 505 lbl2:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text)) |
| 447 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 10, -5) | 506 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 10, -5) |
| 448 | 507 |
| 449 local t = AceGUI:Create("AmrUiTabGroup") | 508 _cboSetups = AceGUI:Create("AmrUiDropDown") |
| 450 t:SetLayout("None") | 509 _cboSetups:SetWidth(300) |
| 451 | 510 container:AddChild(_cboSetups) |
| 452 local tabz = {} | 511 _cboSetups:SetPoint("TOPLEFT", container.content, "TOPLEFT", 150, -27.5) |
| 453 for pos = 1, 4 do | 512 |
| 454 local specId = GetSpecializationInfo(pos) | 513 _panelGear = AceGUI:Create("AmrUiPanel") |
| 455 if specId then | 514 _panelGear:SetLayout("None") |
| 456 table.insert(tabz, { text = L.SpecsShort[Amr.SpecIds[specId]], value = pos .. "", style = "bold" }) | 515 _panelGear:SetBackgroundColor(Amr.Colors.Bg) |
| 457 end | 516 container:AddChild(_panelGear) |
| 458 end | 517 _panelGear:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -58) |
| 459 | 518 _panelGear:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") |
| 460 t:SetTabs(tabz) | |
| 461 t:SetCallback("OnGroupSelected", onGearTabSelected) | |
| 462 container:AddChild(t) | |
| 463 t:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -30) | |
| 464 t:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") | |
| 465 _gearTabs = t; | |
| 466 | 519 |
| 467 local btnShop = AceGUI:Create("AmrUiButton") | 520 local btnShop = AceGUI:Create("AmrUiButton") |
| 468 btnShop:SetText(L.GearButtonShop) | 521 btnShop:SetText(L.GearButtonShop) |
| 469 btnShop:SetBackgroundColor(Amr.Colors.Blue) | 522 btnShop:SetBackgroundColor(Amr.Colors.Blue) |
| 470 btnShop:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) | 523 btnShop:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) |
| 471 btnShop:SetWidth(245) | 524 btnShop:SetWidth(245) |
| 472 btnShop:SetHeight(26) | 525 btnShop:SetHeight(26) |
| 473 btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end) | 526 btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end) |
| 474 container:AddChild(btnShop) | 527 container:AddChild(btnShop) |
| 475 btnShop:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", -20, -25) | 528 btnShop:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", -20, -25) |
| 476 | 529 |
| 477 if not _activeTab then | 530 -- pick a default tab based on player's current spec if none is already specified |
| 478 _activeTab = tostring(GetSpecialization()) | 531 if not _activeSetupId then |
| 479 end | 532 Amr:PickFirstSetupForSpec() |
| 480 | 533 end |
| 481 t:SelectTab(_activeTab) | 534 |
| 482 end | 535 Amr:RefreshGearDisplay() |
| 483 | 536 |
| 484 -- do cleanup when the gear tab is released | 537 -- set event on dropdown after UI has been initially rendered |
| 538 _cboSetups:SetCallback("OnChange", onSetupChange) | |
| 539 end | |
| 540 | |
| 485 function Amr:ReleaseTabGear() | 541 function Amr:ReleaseTabGear() |
| 486 _gearTabs = nil | 542 _cboSetups = nil |
| 487 end | 543 _panelGear = nil |
| 488 | |
| 489 -- show and update the gear tab for the specified spec | |
| 490 function Amr:ShowGearTab(spec) | |
| 491 if not _gearTabs then return end | |
| 492 | |
| 493 _activeTab = tostring(spec) | |
| 494 _gearTabs:SelectTab(_activeTab) | |
| 495 end | 544 end |
| 496 | 545 |
| 497 -- refresh display of the current gear tab | 546 -- refresh display of the current gear tab |
| 498 function Amr:RefreshGearTab() | 547 function Amr:RefreshGearDisplay() |
| 499 if not _gearTabs then return end | 548 |
| 500 _gearTabs:SelectTab(_activeTab) | 549 if not _panelGear then |
| 550 return | |
| 551 end | |
| 552 | |
| 553 -- fill the gear setup picker | |
| 554 local setupList = {} | |
| 555 for i, setup in ipairs(Amr.db.char.GearSetups) do | |
| 556 table.insert(setupList, { text = setup.Label, value = setup.Id }) | |
| 557 end | |
| 558 _cboSetups:SetItems(setupList) | |
| 559 | |
| 560 -- set selected value | |
| 561 local prev = _activeSetupId | |
| 562 _cboSetups:SelectItem(_activeSetupId) | |
| 563 | |
| 564 if prev == _activeSetupId then | |
| 565 -- selecting will trigger the change event if it changed; if it didn't change, do a render now | |
| 566 renderGear(_activeSetupId, _panelGear) | |
| 567 end | |
| 501 end | 568 end |
| 502 | 569 |
| 503 | 570 |
| 504 ------------------------------------------------------------------------------------------------ | 571 ------------------------------------------------------------------------------------------------ |
| 505 -- Gear Set Management | 572 -- Gear Set Management |
| 637 setIcon = itemObj:GetItemIcon() | 704 setIcon = itemObj:GetItemIcon() |
| 638 end | 705 end |
| 639 end | 706 end |
| 640 ]] | 707 ]] |
| 641 | 708 |
| 642 local setname = "AMR " .. specName | 709 local setup = getSetupById(_activeSetupId) |
| 710 local setname = setup.Label -- "AMR " .. specName | |
| 643 local setid = C_EquipmentSet.GetEquipmentSetID(setname) | 711 local setid = C_EquipmentSet.GetEquipmentSetID(setname) |
| 644 if setid then | 712 if setid then |
| 645 C_EquipmentSet.SaveEquipmentSet(setid, setIcon) | 713 C_EquipmentSet.SaveEquipmentSet(setid, setIcon) |
| 646 else | 714 else |
| 647 C_EquipmentSet.CreateEquipmentSet(setname, setIcon) | 715 C_EquipmentSet.CreateEquipmentSet(setname, setIcon) |
| 655 _itemLockAction = nil | 723 _itemLockAction = nil |
| 656 _gearOpPasses = 0 | 724 _gearOpPasses = 0 |
| 657 _gearOpWaiting = nil | 725 _gearOpWaiting = nil |
| 658 | 726 |
| 659 -- make sure the gear tab is still in sync | 727 -- make sure the gear tab is still in sync |
| 660 Amr:RefreshGearTab() | 728 Amr:RefreshGearDisplay() |
| 661 end | 729 end |
| 662 | 730 |
| 663 -- initialize a gear op to start running it | 731 -- initialize a gear op to start running it |
| 664 local function initializeGearOp(op, spec, pos) | 732 local function initializeGearOp(op, setupId, pos) |
| 665 op.pos = pos | 733 op.pos = pos |
| 666 op.spec = spec | 734 op.setupId = setupId |
| 667 | 735 |
| 668 -- fill the remaining slot list and set the starting slot | 736 -- fill the remaining slot list and set the starting slot |
| 669 op.nextSlot = nil | 737 op.nextSlot = nil |
| 670 op.slotsRemaining = {} | 738 op.slotsRemaining = {} |
| 671 op.isWaiting = false | 739 op.isWaiting = false |
| 788 | 856 |
| 789 -- when a gear op completes successfully, this will advance to the next op or finish | 857 -- when a gear op completes successfully, this will advance to the next op or finish |
| 790 function nextGearOp() | 858 function nextGearOp() |
| 791 if not _currentGearOp then return end | 859 if not _currentGearOp then return end |
| 792 | 860 |
| 793 local spec = _currentGearOp.spec | 861 local setupId = _currentGearOp.setupId |
| 794 local pos = _currentGearOp.pos | 862 local pos = _currentGearOp.pos |
| 795 local passes = _gearOpPasses | 863 local passes = _gearOpPasses |
| 796 | 864 |
| 797 -- mark the slot as done and move to the next | 865 -- mark the slot as done and move to the next |
| 798 if _currentGearOp.nextSlot then | 866 if _currentGearOp.nextSlot then |
| 815 _currentGearOp.isWaiting = true | 883 _currentGearOp.isWaiting = true |
| 816 else | 884 else |
| 817 _currentGearOp = _pendingGearOps[pos + 1] | 885 _currentGearOp = _pendingGearOps[pos + 1] |
| 818 if _currentGearOp then | 886 if _currentGearOp then |
| 819 -- we have another op, do it | 887 -- we have another op, do it |
| 820 initializeGearOp(_currentGearOp, spec, pos + 1) | 888 initializeGearOp(_currentGearOp, setupId, pos + 1) |
| 821 processCurrentGearOp() | 889 processCurrentGearOp() |
| 822 else | 890 else |
| 823 -- we are done | 891 -- we are done |
| 824 disposeGearOp() | 892 disposeGearOp() |
| 825 | 893 |
| 826 -- this will check if not all items were swapped, and either finish up, try again, or abort if have tried too many times | 894 -- this will check if not all items were swapped, and either finish up, try again, or abort if have tried too many times |
| 827 beginEquipGearSet(spec, passes + 1) | 895 beginEquipGearSet(setupId, passes + 1) |
| 828 end | 896 end |
| 829 end | 897 end |
| 830 else | 898 else |
| 831 -- do the next item | 899 -- do the next item |
| 832 processCurrentGearOp() | 900 processCurrentGearOp() |
| 905 [20] = true, -- PaladinProtection | 973 [20] = true, -- PaladinProtection |
| 906 [32] = true, -- WarlockDemonology | 974 [32] = true, -- WarlockDemonology |
| 907 [36] = true -- WarriorProtection | 975 [36] = true -- WarriorProtection |
| 908 } | 976 } |
| 909 | 977 |
| 910 function beginEquipGearSet(spec, passes) | 978 function beginEquipGearSet(setupId, passes) |
| 911 | 979 |
| 912 local gear = Amr.db.char.GearSets[spec] | 980 local setup = getSetupById(setupId) |
| 913 if not gear then | 981 |
| 982 if not setup or not setup.Gear then | |
| 914 Amr:Print(L.GearEquipErrorEmpty) | 983 Amr:Print(L.GearEquipErrorEmpty) |
| 915 return | 984 return |
| 916 end | 985 end |
| 917 | 986 |
| 918 -- ensure all our stored data is up to date | 987 local gear = setup.Gear |
| 988 local spec = setup.SpecSlot | |
| 989 | |
| 990 -- ensure all our stored data is up to date | |
| 919 local player = Amr:ExportCharacter() | 991 local player = Amr:ExportCharacter() |
| 920 local doOhFirst = _ohFirst[player.Specs[spec]] | 992 local doOhFirst = _ohFirst[player.Specs[spec]] |
| 921 | 993 |
| 922 local itemsToEquip = { | 994 local itemsToEquip = { |
| 923 legendaries = {}, | 995 legendaries = {}, |
| 1058 _gearOpWaiting = { inventory = {} } | 1130 _gearOpWaiting = { inventory = {} } |
| 1059 end | 1131 end |
| 1060 | 1132 |
| 1061 _gearOpPasses = passes | 1133 _gearOpPasses = passes |
| 1062 _currentGearOp = _pendingGearOps[1] | 1134 _currentGearOp = _pendingGearOps[1] |
| 1063 initializeGearOp(_currentGearOp, spec, 1) | 1135 initializeGearOp(_currentGearOp, setupId, 1) |
| 1064 | 1136 |
| 1065 processCurrentGearOp() | 1137 processCurrentGearOp() |
| 1066 else | 1138 else |
| 1067 -- TODO: print message that gear set couldn't be equipped | 1139 -- TODO: print message that gear set couldn't be equipped |
| 1068 end | 1140 end |
| 1077 local auto = Amr.db.profile.options.autoGear | 1149 local auto = Amr.db.profile.options.autoGear |
| 1078 local currentSpec = GetSpecialization() | 1150 local currentSpec = GetSpecialization() |
| 1079 local waitingSpec = _waitingForSpec | 1151 local waitingSpec = _waitingForSpec |
| 1080 _waitingForSpec = 0 | 1152 _waitingForSpec = 0 |
| 1081 | 1153 |
| 1154 -- when spec changes, change active setup to first one for this spec (does nothing if they have no setups for this spec) | |
| 1155 if _activeSetupId then | |
| 1156 local currentSetup = getSetupById(_activeSetupId) | |
| 1157 if currentSetup.SpecSlot ~= currentSpec then | |
| 1158 Amr:PickFirstSetupForSpec() | |
| 1159 end | |
| 1160 end | |
| 1161 | |
| 1082 if currentSpec == waitingSpec or auto then | 1162 if currentSpec == waitingSpec or auto then |
| 1083 -- spec is what we want, now equip the gear but after a short delay because the game auto-swaps artifact weapons | 1163 -- spec is what we want, now equip the gear but after a short delay because the game auto-swaps artifact weapons |
| 1084 Amr.Wait(2, function() | 1164 Amr.Wait(2, function() |
| 1085 beginEquipGearSet(GetSpecialization(), 0) | 1165 beginEquipGearSet(_activeSetupId, 0) |
| 1086 end) | 1166 end) |
| 1087 end | 1167 end |
| 1088 end | 1168 end |
| 1089 | 1169 |
| 1090 -- activate the specified spec and then equip the saved gear set | 1170 -- activate the specified spec and then equip the saved gear set |
| 1091 function Amr:EquipGearSet(spec) | 1171 function Amr:EquipGearSet(setupIndex) |
| 1092 | 1172 |
| 1093 -- if no argument, then cycle spec | 1173 -- if no argument, then cycle |
| 1094 if not spec then | 1174 if not setupIndex then |
| 1095 spec = GetSpecialization() + 1 | 1175 if not _activeSetupId then |
| 1096 end | 1176 Amr:PickFirstSetupForSpec() |
| 1097 | 1177 end |
| 1098 -- allow some flexibility in the arguments | 1178 for i,setup in ipairs(Amr.db.char.GearSetups) do |
| 1099 if spec == "1" or spec == "2" or spec == "3" or spec == "4" then spec = tonumber(spec) end | 1179 if setup.Id == _activeSetupId then |
| 1100 | 1180 setupIndex = i |
| 1101 local specId = GetSpecializationInfo(spec) | 1181 break |
| 1102 if not specId then spec = 1 end | 1182 end |
| 1103 | 1183 end |
| 1184 if not setupIndex then | |
| 1185 setupIndex = 1 | |
| 1186 else | |
| 1187 setupIndex = setupIndex + 1 | |
| 1188 end | |
| 1189 end | |
| 1190 | |
| 1191 setupIndex = tonumber(setupIndex) | |
| 1192 | |
| 1193 if setupIndex > #Amr.db.char.GearSetups then | |
| 1194 setupIndex = 1 | |
| 1195 end | |
| 1196 | |
| 1104 if UnitAffectingCombat("player") then | 1197 if UnitAffectingCombat("player") then |
| 1105 Amr:Print(L.GearEquipErrorCombat) | 1198 Amr:Print(L.GearEquipErrorCombat) |
| 1106 return | 1199 return |
| 1107 end | 1200 end |
| 1108 | 1201 |
| 1202 _activeSetupId = Amr.db.char.GearSetups[setupIndex].Id | |
| 1203 Amr:RefreshGearDisplay() | |
| 1204 | |
| 1205 local setup = Amr.db.char.GearSetups[setupIndex] | |
| 1109 local currentSpec = GetSpecialization() | 1206 local currentSpec = GetSpecialization() |
| 1110 if currentSpec ~= spec then | 1207 if currentSpec ~= setup.SpecSlot then |
| 1111 _waitingForSpec = spec | 1208 _waitingForSpec = setup.SpecSlot |
| 1112 SetSpecialization(spec) | 1209 SetSpecialization(setup.SpecSlot) |
| 1113 else | 1210 else |
| 1114 -- spec is what we want, now equip the gear | 1211 -- spec is what we want, now equip the gear |
| 1115 beginEquipGearSet(currentSpec, 0) | 1212 beginEquipGearSet(_activeSetupId, 0) |
| 1116 end | 1213 end |
| 1117 end | 1214 end |
| 1118 | 1215 |
| 1119 -- moves any gear in bags to the bank if not part of a gear set | 1216 -- moves any gear in bags to the bank if not part of a gear set |
| 1120 function Amr:CleanBags() | 1217 function Amr:CleanBags() |
| 1136 if unitID and unitID ~= "player" then return end | 1233 if unitID and unitID ~= "player" then return end |
| 1137 | 1234 |
| 1138 -- don't update during a gear operation, wait until it is totally finished | 1235 -- don't update during a gear operation, wait until it is totally finished |
| 1139 if _pendingGearOps then return end | 1236 if _pendingGearOps then return end |
| 1140 | 1237 |
| 1141 Amr:RefreshGearTab() | 1238 Amr:RefreshGearDisplay() |
| 1142 end) | 1239 end) |
| 1143 | 1240 |
| 1144 Amr:AddEventHandler("ITEM_UNLOCKED", handleItemUnlocked) | 1241 Amr:AddEventHandler("ITEM_UNLOCKED", handleItemUnlocked) |
| 1145 end | 1242 end |
