// Baradé's Item Unstack System 1.3
//
// Supports the missing Warcraft III feature of unstacking stacked items in your inventory.
//
// Usage:
// - Enable Warcraft's native stack system in the "Advanced - Gameplay Constants - Inventory - Enable Item Stacking".
// - Give certain item types a maximum stack value: "Stats - Max Stacks". Note that some of them like Wards do already have specified some values greater than 0 here.
// - Copy this code into your map script.
// - Call the function ItemUnstackSystemInit during the map initialization.
//
// Recommended (optional):
// - Change the "Advanced - Game Interface - Text - General" from "|cff808080Drop item on shop to sell|R" into "|cff808080Drop item on shop to sell|NDouble right click item to unstack|R" to guide the player.
//
// Download:
// https://www.hiveworkshop.com/threads/barad%C3%A9s-item-unstack-system-1-1.339109/
//
// Change Log:
//
// 1.3 2022-04-16:
// - Refactor function names.
//
// 1.2 2022-04-13:
// - Use UnitInventorySize instead of bj_MAX_INVENTORY to support different inventory sizes.
// - Place Footmen with unit inventories to check different inventory sizes.
//
// 1.1 2022-04-11:
// - Split into multiple functions.
// - Do not apply item name, tooltip and icon path on unstacking since it is broken.
// - Prefer the nearest empty slot on unstacking.
// - Add a line break to the unstacking hint.
// - Add some items with custom names and descriptions to test stacking/unstacking them.
// - Move system code into converted trigger.
// - Update preview image.
function ItemUnstackCopyItemProps takes item sourceItem, item targetItem returns nothing
// some seem broken
//call BlzSetItemName(targetItem, GetItemName(sourceItem))
call BlzSetItemDescription(targetItem, BlzGetItemDescription(sourceItem))
//call BlzSetItemTooltip(targetItem, BlzGetItemTooltip(sourceItem))
call BlzSetItemExtendedTooltip(targetItem, BlzGetItemExtendedTooltip(sourceItem))
//call BlzSetItemIconPath(targetItem, BlzGetItemIconPath(sourceItem))
call SetItemPawnable(targetItem, IsItemPawnable(sourceItem))
call SetItemInvulnerable(targetItem, IsItemInvulnerable(sourceItem))
if (GetItemPlayer(sourceItem) != null) then
call SetItemPlayer(targetItem, GetItemPlayer(sourceItem), false)
endif
endfunction
function ItemUnstackGetItemSlot takes unit hero, item whichItem returns integer
local integer sourceSlot = -1
local integer i = 0
loop
if (UnitItemInSlot(hero, i) == whichItem) then
set sourceSlot = i
endif
set i = i + 1
exitwhen (sourceSlot != -1 or i >= UnitInventorySize(hero))
endloop
return sourceSlot
endfunction
function ItemUnstackAddItemToNearestFreeSlot takes unit hero, integer itemTypeId, integer sourceSlot returns item
// we need to specify the target slot explicitly to prevent stacking the items again
// we prefer empty slots next to the unstacked item
local item unstackedItem = null
local integer i = sourceSlot + 1
local integer j = sourceSlot - 1
loop
if (i < UnitInventorySize(hero) and UnitItemInSlot(hero, i) == null) then
call UnitAddItemToSlotById(hero, itemTypeId, i)
set unstackedItem = UnitItemInSlot(hero, i)
elseif (j >= 0 and UnitItemInSlot(hero, j) == null) then
call UnitAddItemToSlotById(hero, itemTypeId, j)
set unstackedItem = UnitItemInSlot(hero, j)
endif
set i = i + 1
set j = j - 1
exitwhen (unstackedItem != null or (i >= UnitInventorySize(hero) and j < 0))
endloop
return unstackedItem
endfunction
function ItemUnstackAddItemToNearestFreeSlotOrGround takes unit hero, integer itemTypeId, integer sourceSlot, item sourceItem returns nothing
// search for a free slot to unstack the item
local item unstackedItem = ItemUnstackAddItemToNearestFreeSlot(hero, itemTypeId, sourceSlot)
// create the item for the hero with one slot if all slots are used
if (unstackedItem == null) then
set unstackedItem = CreateItem(itemTypeId, GetUnitX(hero), GetUnitY(hero))
endif
call SetItemCharges(unstackedItem, 1)
call ItemUnstackCopyItemProps(sourceItem, unstackedItem)
set unstackedItem = null
endfunction
function ItemUnstackTriggerCondition takes nothing returns boolean
return GetIssuedOrderId() >= 852002 and GetIssuedOrderId() <= 852007 and GetOrderTargetItem() != null and GetItemCharges(GetOrderTargetItem()) > 1 and ItemUnstackGetItemSlot(GetTriggerUnit(), GetOrderTargetItem()) == GetIssuedOrderId() - 852002
endfunction
function ItemUnstackTriggerAction takes nothing returns nothing
local unit hero = GetTriggerUnit()
local item sourceItem = GetOrderTargetItem()
local integer sourceItemTypeId = GetItemTypeId(sourceItem)
local integer sourceSlot = ItemUnstackGetItemSlot(hero, sourceItem)
// wait for completing the order or the item is not at the target slot
call TriggerSleepAction(0.0)
// item does still exist and was dropped on its previous slot
// we are not sure if this works when the item is removed via triggers since the value of the variable becomes an invalid reference
if (sourceItem != null and GetWidgetLife(sourceItem) > 0.0 and GetItemCharges(sourceItem) > 0 and UnitItemInSlot(hero, sourceSlot) == sourceItem) then
call SetItemCharges(sourceItem, GetItemCharges(sourceItem) - 1)
call ItemUnstackAddItemToNearestFreeSlotOrGround(hero, sourceItemTypeId, sourceSlot, sourceItem)
endif
set hero = null
set sourceItem = null
endfunction
function ItemUnstackSystemInit takes nothing returns nothing
local trigger whichTrigger = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(whichTrigger, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerAddCondition(whichTrigger, Condition(function ItemUnstackTriggerCondition))
call TriggerAddAction(whichTrigger, function ItemUnstackTriggerAction)
endfunction