Zerotorescue@80: local addon = select(2, ...); Zerotorescue@80: local mod = addon:NewModule("Mover", "AceEvent-3.0", "AceTimer-3.0"); Zerotorescue@80: Zerotorescue@80: local Scanner; Zerotorescue@80: local queuedMoves = {}; -- table storing all queued moves before BeginMove is called Zerotorescue@80: local combinedMoves = {}; -- table storing all combined moves (with source and target) that is to be processed by the actual mover in the order of the index (1 to #) Zerotorescue@81: local movesSource; Zerotorescue@80: Zerotorescue@110: addon.Locations = { Zerotorescue@110: Bag = 0, Zerotorescue@110: Bank = 1, Zerotorescue@110: Guild = 2, Zerotorescue@110: Mailbox = 3, Zerotorescue@110: Merchant = 4, Zerotorescue@110: }; Zerotorescue@110: Zerotorescue@110: local ContainerFunctions = { Zerotorescue@110: [addon.Locations.Bag] = { Zerotorescue@110: GetItemId = GetContainerItemID, Zerotorescue@110: PickupItem = SplitContainerItem, Zerotorescue@110: IsLocked = function(sourceContainer, sourceSlot) Zerotorescue@110: return select(3, GetContainerItemInfo(sourceContainer, sourceSlot)); Zerotorescue@110: end, Zerotorescue@110: Event = "ITEM_LOCK_CHANGED", Zerotorescue@110: }, Zerotorescue@110: [addon.Locations.Bank] = { Zerotorescue@110: GetItemId = GetContainerItemID, Zerotorescue@110: PickupItem = SplitContainerItem, Zerotorescue@110: IsLocked = function(sourceContainer, sourceSlot) Zerotorescue@110: return select(3, GetContainerItemInfo(sourceContainer, sourceSlot)); Zerotorescue@110: end, Zerotorescue@110: Event = "ITEM_LOCK_CHANGED", Zerotorescue@110: }, Zerotorescue@110: [addon.Locations.Guild] = { Zerotorescue@110: GetItemId = function(tabId, slotId) Zerotorescue@110: return addon:GetItemId(GetGuildBankItemLink(tabId, slotId)); Zerotorescue@110: end, Zerotorescue@110: PickupItem = SplitGuildBankItem, Zerotorescue@110: IsLocked = function(sourceContainer, sourceSlot) Zerotorescue@110: return select(3, GetGuildBankItemInfo(sourceContainer, sourceSlot)); Zerotorescue@110: end, Zerotorescue@110: Event = "ITEM_LOCK_CHANGED", Zerotorescue@110: }, Zerotorescue@110: [addon.Locations.Mailbox] = { Zerotorescue@110: GetItemId = function(mailIndex, attachmentId) Zerotorescue@110: return addon:GetItemId(GetInboxItemLink(mailIndex, attachmentId)); Zerotorescue@110: end, Zerotorescue@110: PickupItem = TakeInboxItem, Zerotorescue@110: IsLocked = function() return false; end, Zerotorescue@110: DoNotDrop = true, -- TakeInboxItem does not support picking up Zerotorescue@110: Synchronous = true, -- wait after every single move Zerotorescue@110: Event = "BAG_UPDATE", Zerotorescue@110: }, Zerotorescue@110: [addon.Locations.Merchant] = { Zerotorescue@110: GetItemId = function(_, merchantIndex) Zerotorescue@110: return addon:GetItemId(GetMerchantItemLink(merchantIndex)); Zerotorescue@110: end, Zerotorescue@110: PickupItem = function(_, merchantIndex, num) Zerotorescue@110: -- The below behavior was changed in patch 4.0.1, it now acts as expected; quantity requested equals exact quantity bought, even with increased batchsize Zerotorescue@110: Zerotorescue@110: -- Some merchant items are sold in batches (e.g. of 5) Zerotorescue@110: -- In that case BuyMerchantItem wants the num stacks, rather than the quantity to be bought Zerotorescue@110: --local batchSize = select(4, GetMerchantItemInfo(merchantIndex)); Zerotorescue@110: Zerotorescue@110: --local batches = math.ceil(num / batchSize); Zerotorescue@110: Zerotorescue@110: --BuyMerchantItem(merchantIndex, batches); Zerotorescue@110: Zerotorescue@110: return BuyMerchantItem(merchantIndex, num); Zerotorescue@110: end, Zerotorescue@110: IsLocked = function() return false; end, Zerotorescue@110: DoNotDrop = true, -- BuyMerchantItem does not support picking up Zerotorescue@110: Burst = true, -- spam buy items, the source can take it Zerotorescue@110: Event = "BAG_UPDATE", Zerotorescue@110: }, Zerotorescue@110: }; Zerotorescue@110: Zerotorescue@110: function mod:AddMove(itemId, amount, numMissing, numAvailable, price) Zerotorescue@80: table.insert(queuedMoves, { Zerotorescue@110: ["itemId"] = itemId, Zerotorescue@110: ["num"] = amount, -- can not be unlimited Zerotorescue@110: ["missing"] = numMissing, Zerotorescue@110: ["available"] = numAvailable, Zerotorescue@110: ["price"] = price, Zerotorescue@80: }); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@81: function mod:HasMoves() Zerotorescue@81: return (#queuedMoves ~= 0); Zerotorescue@81: end Zerotorescue@81: Zerotorescue@101: function mod:GetMoves() Zerotorescue@101: return queuedMoves; Zerotorescue@101: end Zerotorescue@101: Zerotorescue@101: function mod:ResetQueue() Zerotorescue@101: table.wipe(queuedMoves); Zerotorescue@101: end Zerotorescue@101: Zerotorescue@84: if not table.reverse then Zerotorescue@84: table.reverse = function(orig) Zerotorescue@84: local temp = {}; Zerotorescue@84: local origLength = #orig; Zerotorescue@84: for i = 1, origLength do Zerotorescue@84: temp[(origLength - i + 1)] = orig[i]; Zerotorescue@84: end Zerotorescue@84: Zerotorescue@84: -- -- Update the original table (can't do orig = temp as that would change the reference-link instead of the original table) Zerotorescue@84: -- for i, v in pairs(temp) do Zerotorescue@84: -- orig[i] = v; Zerotorescue@84: -- end Zerotorescue@84: return temp; -- for speed we choose to do a return instead Zerotorescue@84: end Zerotorescue@84: end Zerotorescue@84: Zerotorescue@110: local function GetEmptySlots() Zerotorescue@110: local emptySlots = {}; Zerotorescue@110: Zerotorescue@110: -- Go through all our bags, including the backpack Zerotorescue@110: for bagId = 0, NUM_BAG_SLOTS do Zerotorescue@110: -- Go through all our slots (0 = backpack) Zerotorescue@110: for slotId = 1, GetContainerNumSlots(bagId) do Zerotorescue@110: local itemId = GetContainerItemID(bagId, slotId); -- we're scanning our local bags here, so no need to get messy with guild bank support Zerotorescue@110: local bagFamily = select(2, GetContainerNumFreeSlots(bagId)); Zerotorescue@110: Zerotorescue@110: if not itemId then Zerotorescue@110: table.insert(emptySlots, { Zerotorescue@110: ["container"] = bagId, Zerotorescue@110: ["slot"] = slotId, Zerotorescue@110: ["family"] = bagFamily, Zerotorescue@110: }); Zerotorescue@110: end Zerotorescue@110: end Zerotorescue@110: end Zerotorescue@110: Zerotorescue@110: return emptySlots; Zerotorescue@110: end Zerotorescue@110: Zerotorescue@110: local function GetFirstEmptySlot(emptySlots, prefFamily) Zerotorescue@110: while prefFamily do Zerotorescue@110: for _, slot in pairs(emptySlots) do Zerotorescue@110: if slot.family == prefFamily then Zerotorescue@110: return slot; Zerotorescue@110: end Zerotorescue@110: end Zerotorescue@110: Zerotorescue@110: -- If this was a special family, no special bag available, now check normal bags Zerotorescue@110: if prefFamily > 0 then Zerotorescue@110: prefFamily = 0; Zerotorescue@110: else Zerotorescue@110: prefFamily = nil; Zerotorescue@110: end Zerotorescue@110: end Zerotorescue@110: end Zerotorescue@110: Zerotorescue@80: function mod:BeginMove(location, onFinish) Zerotorescue@81: addon:Debug("BeginMove"); Zerotorescue@80: Zerotorescue@80: -- Find the outgoing moves Zerotorescue@80: -- We need the source container and slot, find all the requires sources and put them in a list which we go through later to find matching targets Zerotorescue@80: Zerotorescue@80: -- Get a list of items in the source container Zerotorescue@80: local sourceContents = Scanner:CacheLocation(location, false); Zerotorescue@80: Zerotorescue@80: local outgoingMoves = {}; Zerotorescue@80: Zerotorescue@89: addon:Debug("%d moves were queued.", #queuedMoves); Zerotorescue@82: Zerotorescue@81: for _, singleMove in pairs(queuedMoves) do Zerotorescue@110: local sourceItem = sourceContents[singleMove.itemId]; Zerotorescue@80: if not sourceItem then Zerotorescue@110: addon:Print(("Can't move %s, this doesn't exist in the source."):format(IdToItemLink(singleMove.itemId)), addon.Colors.Red); Zerotorescue@80: else Zerotorescue@82: -- We want to move the smallest stacks first to keep stuff pretty (and minimize space usage, splitting a stack takes 2 slots, moving something only 1) Zerotorescue@80: table.sort(sourceItem.locations, function(a, b) Zerotorescue@110: -- -1 indicates unlimited, this is always more than an actual amount Zerotorescue@110: if a.count == -1 then Zerotorescue@110: return false; Zerotorescue@110: elseif b.count == -1 then Zerotorescue@110: return true; Zerotorescue@110: end Zerotorescue@110: Zerotorescue@81: return a.count < b.count; Zerotorescue@80: end); Zerotorescue@80: Zerotorescue@81: for _, itemLocation in pairs(sourceItem.locations) do Zerotorescue@80: -- if this location has more items than we need, only move what we need, otherwise move everything in this stack Zerotorescue@110: local movingNum = (((itemLocation.count == -1 or itemLocation.count > singleMove.num) and singleMove.num) or itemLocation.count); Zerotorescue@110: Zerotorescue@110: if itemLocation.count == -1 then Zerotorescue@110: -- If the source has an unlimited quantity, makes moves based on the max stacksize Zerotorescue@110: Zerotorescue@110: local stackSize = select(8, GetItemInfo(singleMove.itemId)); -- 8 = stacksize Zerotorescue@110: Zerotorescue@110: if stackSize then Zerotorescue@110: while movingNum > stackSize do Zerotorescue@110: -- Move a single stack size while the amount remaining to be moved is above the stack size num Zerotorescue@110: Zerotorescue@110: table.insert(outgoingMoves, { Zerotorescue@110: ["itemId"] = singleMove.itemId, Zerotorescue@110: ["num"] = stackSize, Zerotorescue@110: ["container"] = itemLocation.container, Zerotorescue@110: ["slot"] = itemLocation.slot, Zerotorescue@110: }); Zerotorescue@110: Zerotorescue@110: movingNum = (movingNum - stackSize); Zerotorescue@110: end Zerotorescue@110: end Zerotorescue@110: end Zerotorescue@80: Zerotorescue@80: table.insert(outgoingMoves, { Zerotorescue@110: ["itemId"] = singleMove.itemId, Zerotorescue@110: ["num"] = movingNum, Zerotorescue@110: ["container"] = itemLocation.container, Zerotorescue@110: ["slot"] = itemLocation.slot, Zerotorescue@80: }); Zerotorescue@80: Zerotorescue@80: singleMove.num = (singleMove.num - movingNum); Zerotorescue@80: Zerotorescue@80: if singleMove.num == 0 then Zerotorescue@80: -- If we have prepared everything we wanted, go to the next queued move Zerotorescue@81: break; -- stop the locations-loop Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@82: Zerotorescue@89: addon:Debug("%d outgoing moves are possible.", #outgoingMoves); Zerotorescue@80: Zerotorescue@80: -- No longer needed Zerotorescue@80: table.wipe(queuedMoves); Zerotorescue@80: Zerotorescue@110: Zerotorescue@110: Zerotorescue@80: -- Process every single outgoing move and find fitting targets Zerotorescue@80: Zerotorescue@80: -- Get a list of items already in the target container Zerotorescue@80: local targetContents = Scanner:CacheLocation(addon.Locations.Bag, false); Zerotorescue@80: Zerotorescue@80: -- Find all empty slots Zerotorescue@110: local emptySlots = GetEmptySlots(); Zerotorescue@82: Zerotorescue@89: addon:Debug("%d empty slots are available.", #emptySlots); Zerotorescue@80: Zerotorescue@81: -- Remember where we're moving from Zerotorescue@81: movesSource = location; Zerotorescue@81: Zerotorescue@110: local backup = 0; -- the below loop should never break, but if it does for any reason, this is here to stop it from freezing the game Zerotorescue@84: Zerotorescue@80: while #outgoingMoves ~= 0 do Zerotorescue@110: -- Repeat the below loop until nothing is remaining Zerotorescue@80: Zerotorescue@81: for _, outgoingMove in pairs(outgoingMoves) do Zerotorescue@110: if outgoingMove.itemId then -- itemId will be set to nil when this outgoing move was processed - sanity check Zerotorescue@80: local targetItem = targetContents[outgoingMove.itemId]; Zerotorescue@80: Zerotorescue@110: if not targetItem or ContainerFunctions[location].DoNotDrop then Zerotorescue@110: -- There is no partial stack which can be filled or this source container doesn't allow manual allocation of items (in which case we always assume it takes a full empty slot) Zerotorescue@80: Zerotorescue@110: local family = GetItemFamily(outgoingMove.itemId); Zerotorescue@110: if family and family ~= 0 and select(9, GetItemInfo(outgoingMove.itemId)) == "INVTYPE_BAG" then Zerotorescue@110: -- Containers can only fit in general slots but GetItemFamily will return what they can contain themselves Zerotorescue@110: family = 0; Zerotorescue@110: end Zerotorescue@110: local firstAvailableSlot = GetFirstEmptySlot(emptySlots, family); Zerotorescue@80: Zerotorescue@80: if not firstAvailableSlot then Zerotorescue@110: -- No empty slot available - bags are full Zerotorescue@110: Zerotorescue@98: addon:Print(("Bags are full. Skipping %s."):format(IdToItemLink(outgoingMove.itemId)), addon.Colors.Orange); Zerotorescue@80: Zerotorescue@82: outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table Zerotorescue@109: Zerotorescue@109: -- Not a single item with this item id can be moved, since we only want the bags are full announcement once, we remove all other moves with this item id Zerotorescue@109: for _, otherMove in pairs(outgoingMoves) do Zerotorescue@109: if otherMove.itemId and otherMove.itemId == outgoingMove.itemId then Zerotorescue@109: otherMove.itemId = nil; Zerotorescue@109: end Zerotorescue@109: end Zerotorescue@80: else Zerotorescue@110: -- Consume empty slot Zerotorescue@110: Zerotorescue@80: table.insert(combinedMoves, { Zerotorescue@110: ["itemId"] = outgoingMove.itemId, Zerotorescue@110: ["num"] = outgoingMove.num, Zerotorescue@110: ["sourceContainer"] = outgoingMove.container, Zerotorescue@110: ["sourceSlot"] = outgoingMove.slot, Zerotorescue@110: ["targetContainer"] = firstAvailableSlot.container, Zerotorescue@110: ["targetSlot"] = firstAvailableSlot.slot, Zerotorescue@80: }); Zerotorescue@80: Zerotorescue@80: -- We filled an empty slot so the target contents now has one more item, Zerotorescue@80: -- make a new instance of the ItemMove class so any additional items with this id can be stacked on top of it Zerotorescue@81: local itemMove = addon.ContainerItem:New(); Zerotorescue@82: itemMove:AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.num); Zerotorescue@80: targetContents[outgoingMove.itemId] = itemMove; Zerotorescue@80: Zerotorescue@81: table.remove(emptySlots, 1); -- no longer empty Zerotorescue@80: Zerotorescue@82: outgoingMove.num = 0; -- nothing remaining - sanity check Zerotorescue@80: outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table Zerotorescue@80: end Zerotorescue@80: else Zerotorescue@80: -- Find the maximum stack size for this item Zerotorescue@80: local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId)); Zerotorescue@80: Zerotorescue@80: -- We want to move to the largest stacks first to keep stuff pretty Zerotorescue@80: table.sort(targetItem.locations, function(a, b) Zerotorescue@81: return a.count > b.count; Zerotorescue@80: end); Zerotorescue@80: Zerotorescue@81: for _, itemLocation in pairs(targetItem.locations) do Zerotorescue@82: if itemLocation.count < itemStackCount and outgoingMove.num > 0 then Zerotorescue@80: -- Check if this stack isn't already full (and we still need to move this item) Zerotorescue@80: Zerotorescue@80: local remainingSpace = (itemStackCount - itemLocation.count); Zerotorescue@84: if remainingSpace >= outgoingMove.num then Zerotorescue@80: -- Enough room to move this entire stack Zerotorescue@80: -- Deposit this item and then forget this outgoing move as everything in it was processed Zerotorescue@80: Zerotorescue@80: table.insert(combinedMoves, { Zerotorescue@110: ["itemId"] = outgoingMove.itemId, Zerotorescue@110: ["num"] = outgoingMove.num, Zerotorescue@110: ["sourceContainer"] = outgoingMove.container, Zerotorescue@110: ["sourceSlot"] = outgoingMove.slot, Zerotorescue@110: ["targetContainer"] = itemLocation.container, Zerotorescue@110: ["targetSlot"] = itemLocation.slot, Zerotorescue@80: }); Zerotorescue@80: Zerotorescue@82: itemLocation.count = (itemLocation.count + outgoingMove.num); Zerotorescue@84: outgoingMove.num = 0; -- nothing remaining Zerotorescue@80: outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table Zerotorescue@80: break; -- stop the locations-loop Zerotorescue@80: else Zerotorescue@80: -- Deposit this item but don't remove the outgoing move as there are some items left to move Zerotorescue@80: Zerotorescue@80: table.insert(combinedMoves, { Zerotorescue@110: ["itemId"] = outgoingMove.itemId, Zerotorescue@110: ["num"] = outgoingMove.num, Zerotorescue@110: ["sourceContainer"] = outgoingMove.container, Zerotorescue@110: ["sourceSlot"] = outgoingMove.slot, Zerotorescue@110: ["targetContainer"] = itemLocation.container, Zerotorescue@110: ["targetSlot"] = itemLocation.slot, Zerotorescue@80: }); Zerotorescue@80: Zerotorescue@80: -- The target will be full when we complete, but the source will still have remaining items left to be moved Zerotorescue@80: itemLocation.count = itemStackCount; Zerotorescue@82: outgoingMove.num = (outgoingMove.num - remainingSpace); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@82: if outgoingMove.num > 0 then Zerotorescue@80: -- We went through all matching items and checked their stack sizes if we could move this there, no room available Zerotorescue@80: -- So forget about the target item (even though it may just have full locations, these are useless anyway) and the next loop move it onto an empty slot Zerotorescue@84: targetContents[outgoingMove.itemId] = nil; Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- Loop through the array to find items that should be removed, start with the last element or the loop would break Zerotorescue@80: local numOutgoingMoves = #outgoingMoves; -- since LUA-tables start at an index of 1, this is actually an existing index (outgoingMoves[#outgoingMoves] would return a value) Zerotorescue@80: while numOutgoingMoves ~= 0 do Zerotorescue@80: -- A not equal-comparison should be quicker than a larger/smaller than-comparison Zerotorescue@80: Zerotorescue@80: -- Check if the item id is nil, this is set to nil when this outgoing move has been processed Zerotorescue@84: if not outgoingMoves[numOutgoingMoves].itemId or outgoingMoves[numOutgoingMoves].num == 0 then Zerotorescue@80: -- Remove this element from the array Zerotorescue@80: table.remove(outgoingMoves, numOutgoingMoves); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- Proceed with the next element (or previous considering we're going from last to first) Zerotorescue@80: numOutgoingMoves = (numOutgoingMoves - 1); Zerotorescue@80: end Zerotorescue@84: Zerotorescue@89: addon:Debug("%d moves remaining.", #outgoingMoves); Zerotorescue@84: Zerotorescue@84: backup = (backup + 1); Zerotorescue@84: if backup > 1000 then Zerotorescue@84: table.wipe(outgoingMoves); Zerotorescue@84: self:Abort("mover crashed", "Error preparing moves, hit an endless loop"); Zerotorescue@84: onFinish(); Zerotorescue@84: return; Zerotorescue@84: end Zerotorescue@80: end Zerotorescue@84: Zerotorescue@84: -- Reverse table, we need to go through it from last to first because we'll be removing elements, but we don't want the actions to be executed in a different order Zerotorescue@84: combinedMoves = table.reverse(combinedMoves); Zerotorescue@82: Zerotorescue@89: addon:Debug("%d moves should be possible.", #combinedMoves); Zerotorescue@80: Zerotorescue@80: -- No longer needed Zerotorescue@80: table.wipe(emptySlots); Zerotorescue@80: Zerotorescue@81: self:ProcessMove(); Zerotorescue@80: Zerotorescue@82: -- Even though we aren't completely done yet, allow requeueing Zerotorescue@81: onFinish(); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@110: local tmrRetry; Zerotorescue@81: function mod:ProcessMove() Zerotorescue@81: addon:Debug("ProcessMove"); Zerotorescue@81: Zerotorescue@81: if #combinedMoves == 0 then Zerotorescue@98: addon:Print("Nothing to move."); Zerotorescue@81: Zerotorescue@81: self:Abort(); Zerotorescue@81: Zerotorescue@81: return; Zerotorescue@110: elseif movesSource == addon.Locations.Mailbox and MailAddonBusy and MailAddonBusy ~= addon:GetName() then Zerotorescue@110: addon:Debug("Anoter addon (%s) is busy with the mailbox.", MailAddonBusy); Zerotorescue@110: Zerotorescue@110: self:CancelTimer(tmrRetry, true); -- silent Zerotorescue@110: tmrRetry = self:ScheduleTimer("ProcessMove", .5); Zerotorescue@110: Zerotorescue@110: return; Zerotorescue@81: end Zerotorescue@81: Zerotorescue@98: -- Make sure nothing is at the mouse Zerotorescue@98: ClearCursor(); Zerotorescue@98: Zerotorescue@110: if movesSource == addon.Locations.Mailbox then Zerotorescue@110: MailAddonBusy = addon:GetName(); Zerotorescue@110: end Zerotorescue@110: Zerotorescue@110: self:RegisterEvent(ContainerFunctions[movesSource].Event, "SourceUpdated"); Zerotorescue@81: self:RegisterEvent("UI_ERROR_MESSAGE"); Zerotorescue@81: Zerotorescue@80: -- combinedMoves now has all moves in it (source -> target) Zerotorescue@80: -- go through list, move everything inside it Zerotorescue@81: -- add source and target to lists, if either is already in this list, skip the move Zerotorescue@81: -- repeat every few seconds until we're completely done Zerotorescue@80: Zerotorescue@80: local sourceLocationsLocked = {}; Zerotorescue@80: local targetLocationsLocked = {}; Zerotorescue@80: Zerotorescue@110: local hasMoved; Zerotorescue@110: Zerotorescue@82: local combinedMovesOriginalLength = #combinedMoves; Zerotorescue@82: local numCurrentMove = combinedMovesOriginalLength; Zerotorescue@80: while numCurrentMove ~= 0 do Zerotorescue@80: local move = combinedMoves[numCurrentMove]; Zerotorescue@80: Zerotorescue@110: -- Only check if the source is locked when we're not bursting (some functions allow mass calling simultaneously and don't trigger item locks) Zerotorescue@110: local isSourceLocked = ((not ContainerFunctions[movesSource].Burst and sourceLocationsLocked[move.sourceContainer] and sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) or ContainerFunctions[movesSource].IsLocked(move.sourceContainer, move.sourceSlot)); Zerotorescue@109: -- Target are always the local bags Zerotorescue@109: local isTargetLocked = ((targetLocationsLocked[move.targetContainer] and targetLocationsLocked[move.targetContainer][move.targetSlot]) or ContainerFunctions[addon.Locations.Bag].IsLocked(move.targetContainer, move.targetSlot)); Zerotorescue@88: Zerotorescue@110: if move and not isSourceLocked and not isTargetLocked and (not ContainerFunctions[movesSource].Synchronous or not hasMoved) then Zerotorescue@98: addon:Print(("Moving %dx%s."):format(move.num, IdToItemLink(move.itemId))); Zerotorescue@80: Zerotorescue@89: addon:Debug("Moving %dx%s from (%d,%d) to (%d,%d)", move.num, IdToItemLink(move.itemId), move.sourceContainer, move.sourceSlot, move.targetContainer, move.targetSlot); Zerotorescue@81: Zerotorescue@110: -- Check if the source has been changed since out scan Zerotorescue@109: if ContainerFunctions[movesSource].GetItemId(move.sourceContainer, move.sourceSlot) ~= move.itemId then Zerotorescue@81: self:Abort("source changed", "Source (" .. move.sourceContainer .. "," .. move.sourceSlot .. ") is not " .. IdToItemLink(move.itemId)); Zerotorescue@81: return; Zerotorescue@81: end Zerotorescue@81: Zerotorescue@80: -- Pickup stack Zerotorescue@109: ContainerFunctions[movesSource].PickupItem(move.sourceContainer, move.sourceSlot, move.num); Zerotorescue@80: Zerotorescue@110: hasMoved = true; Zerotorescue@110: Zerotorescue@80: -- Remember we picked this item up and thus it is now locked Zerotorescue@80: if not sourceLocationsLocked[move.sourceContainer] then Zerotorescue@80: sourceLocationsLocked[move.sourceContainer] = {}; Zerotorescue@80: end Zerotorescue@80: sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true; Zerotorescue@80: Zerotorescue@110: if not ContainerFunctions[movesSource].DoNotDrop then Zerotorescue@110: -- Some sources don't actually pick items up but just move them, this makes the code below unnessary Zerotorescue@110: Zerotorescue@110: if movesSource ~= addon.Locations.Bank or CursorHasItem() then -- CursorHasItem only works when moving from the bank Zerotorescue@110: -- We are moving into our local bags, so the below must check normal bags Zerotorescue@110: Zerotorescue@110: -- Check if the target has been changed since out scan Zerotorescue@110: local targetItemId = ContainerFunctions[addon.Locations.Bag].GetItemId(move.targetContainer, move.targetSlot); Zerotorescue@110: if targetItemId and targetItemId ~= move.itemId then Zerotorescue@110: self:Abort("target changed", "Target (" .. move.targetContainer .. "," .. move.targetSlot .. ") is not " .. IdToItemLink(move.itemId) .. " nor empty"); Zerotorescue@110: return; Zerotorescue@110: end Zerotorescue@110: Zerotorescue@110: -- And drop it (this is always a local bag so no need to do any guild-checks) Zerotorescue@110: PickupContainerItem(move.targetContainer, move.targetSlot); Zerotorescue@110: Zerotorescue@110: -- Remember we dropped an item here and thus this is now locked Zerotorescue@110: if not targetLocationsLocked[move.targetContainer] then Zerotorescue@110: targetLocationsLocked[move.targetContainer] = {}; Zerotorescue@110: end Zerotorescue@110: targetLocationsLocked[move.targetContainer][move.targetSlot] = true; Zerotorescue@110: Zerotorescue@110: -- This move was processed Zerotorescue@110: table.remove(combinedMoves, numCurrentMove); Zerotorescue@110: else Zerotorescue@110: self:Abort("item disappeared from mouse", "Couldn't move " .. IdToItemLink(move.itemId) .. ", CursorHasItem() is false"); Zerotorescue@81: return; Zerotorescue@81: end Zerotorescue@110: else Zerotorescue@110: -- When items are deposit automatically we still need to remember when a move has been processed Zerotorescue@80: Zerotorescue@80: -- This move was processed Zerotorescue@80: table.remove(combinedMoves, numCurrentMove); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- Proceed with the next element (or previous considering we're going from last to first) Zerotorescue@80: numCurrentMove = (numCurrentMove - 1); Zerotorescue@80: end Zerotorescue@81: Zerotorescue@89: addon:Debug("%d moves processed. %d moves remaining.", (combinedMovesOriginalLength - #combinedMoves), #combinedMoves); Zerotorescue@82: Zerotorescue@81: if #combinedMoves == 0 then Zerotorescue@98: addon:Print("Finished.", addon.Colors.Green); Zerotorescue@81: Zerotorescue@81: self:Abort(); Zerotorescue@81: Zerotorescue@81: return; Zerotorescue@81: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: local tmrProcessNext; Zerotorescue@110: function mod:SourceUpdated() Zerotorescue@81: self:CancelTimer(tmrProcessNext, true); -- silent Zerotorescue@89: tmrProcessNext = self:ScheduleTimer("ProcessMove", .5); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@81: function IdToItemLink(itemId) Zerotorescue@81: local itemLink = select(2, GetItemInfo(itemId)); Zerotorescue@81: itemLink = itemLink or "Unknown (" .. itemId .. ")"; Zerotorescue@81: return itemLink; Zerotorescue@81: end Zerotorescue@81: Zerotorescue@81: function mod:UI_ERROR_MESSAGE(e, errorMessage) Zerotorescue@81: if errorMessage == ERR_SPLIT_FAILED then Zerotorescue@81: self:Abort("splitting failed", "Splitting failed."); Zerotorescue@110: elseif errorMessage == ERR_GUILD_WITHDRAW_LIMIT then Zerotorescue@110: self:Abort("at guild withdrawal limit", "At guild withdrawal limit"); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@81: function mod:Abort(simple, debugMsg) Zerotorescue@110: -- Announce Zerotorescue@81: if debugMsg then Zerotorescue@89: addon:Debug("Aborting:%s", debugMsg); Zerotorescue@81: end Zerotorescue@81: if simple then Zerotorescue@98: addon:Print(("Aborting: %s."):format(simple), addon.Colors.Red); Zerotorescue@81: end Zerotorescue@80: Zerotorescue@98: -- Make sure nothing is at the mouse Zerotorescue@98: ClearCursor(); Zerotorescue@98: Zerotorescue@81: -- Stop timer Zerotorescue@110: self:UnregisterEvent(ContainerFunctions[movesSource].Event); Zerotorescue@81: self:CancelTimer(tmrProcessNext, true); -- silent Zerotorescue@80: Zerotorescue@81: self:UnregisterEvent("UI_ERROR_MESSAGE"); Zerotorescue@110: Zerotorescue@110: -- Reset vars Zerotorescue@110: table.wipe(combinedMoves); Zerotorescue@110: movesSource = nil; Zerotorescue@110: if MailAddonBusy == addon:GetName() then Zerotorescue@110: MailAddonBusy = nil; Zerotorescue@110: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function mod:OnEnable() Zerotorescue@80: Scanner = addon:GetModule("Scanner"); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function mod:OnDisable() Zerotorescue@80: Scanner = nil; Zerotorescue@81: Zerotorescue@81: self:Abort(); Zerotorescue@80: end