// Baradé's Item Unstack System 1.6
//
// 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 or a trigger converted into code.
// - Optional: Adapt the values of the constants for your map.
// - Optional: Redefine ItemUnstackItemGetMaxStacks if necessary.
// - Optional (recommended): 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/
// Define this function, to return custom values. It is only used if CHECK_MAX_STACKS is false.
constant function ItemUnstackItemGetMaxStacks takes integer itemTypeId returns integer
return 1000
endfunction
library ItemUnstackSystem initializer Init requires optional MaxItemStacks
globals
// The number of charges which are unstacked at maximum if available.
private constant integer MAX_UNSTACKED_CHARGES = 1
// Overwrites the previous value if set to true. It always unstacks the half of charges. For uneven numbers it will unstack the lower value. For example, for 3 it will unstack 1 charge.
private constant boolean UNSTACK_HALF_CHARGES = false
// Unstacking an item can be moved to the next slot if it has the same item type and stack with it.
private constant boolean ALLOW_STACKING_NEXT_ITEM = true
// Checks for the maximum possible stacks for every item type. Otherwise, ItemUnstackItemGetMaxStacks is used.
private constant boolean CHECK_MAX_STACKS = true
private trigger orderTrigger = CreateTrigger()
endglobals
private function GetMaxStacksByItemTypeIdIntern takes integer itemTypeId returns integer
static if (CHECK_MAX_STACKS) then
return GetMaxStacksByItemTypeId(itemTypeId)
else
return ItemUnstackItemGetMaxStacks(itemTypeId)
endif
endfunction
private function CopyItemProps 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
private function GetItemSlot 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
private function AddUnstackedItem takes unit hero, integer itemTypeId, integer charges, integer sourceSlot, item sourceItem returns nothing
local item itemInNextSlot = null
local item itemInPreviousSlot = null
local boolean addedToFreeSlot = false
// 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
// check for a slot with an item with the same type and free stacks
if (ALLOW_STACKING_NEXT_ITEM) then
loop
set itemInNextSlot = UnitItemInSlot(hero, i)
if (i < UnitInventorySize(hero)) then
if (itemInNextSlot == null) then
set addedToFreeSlot = true
call UnitAddItemToSlotById(hero, itemTypeId, i)
set unstackedItem = UnitItemInSlot(hero, i)
elseif (GetItemTypeId(itemInNextSlot) == itemTypeId and GetItemCharges(itemInNextSlot) < GetMaxStacksByItemTypeIdIntern(itemTypeId)) then
set unstackedItem = itemInNextSlot
endif
endif
set i = i + 1
exitwhen (unstackedItem != null or i >= UnitInventorySize(hero))
endloop
endif
// check for a free slot
if (unstackedItem == null) then
set i = sourceSlot + 1
set j = sourceSlot - 1
loop
set itemInNextSlot = UnitItemInSlot(hero, i)
set itemInPreviousSlot = UnitItemInSlot(hero, j)
if (i < UnitInventorySize(hero) and itemInNextSlot == null) then
set addedToFreeSlot = true
call UnitAddItemToSlotById(hero, itemTypeId, i)
set unstackedItem = UnitItemInSlot(hero, i)
elseif (j >= 0 and itemInPreviousSlot == null) then
set addedToFreeSlot = true
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
endif
if (addedToFreeSlot) then
call SetItemCharges(unstackedItem, charges)
call CopyItemProps(sourceItem, unstackedItem)
else
call SetItemCharges(unstackedItem, GetItemCharges(unstackedItem) + charges)
endif
// 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))
call SetItemCharges(unstackedItem, charges)
call CopyItemProps(sourceItem, unstackedItem)
endif
set unstackedItem = null
endfunction
private function TriggerConditionOrderUnstack takes nothing returns boolean
static if (CHECK_MAX_STACKS) then
local boolean isNotStackDummy = GetTriggerUnit() != MaxItemStacks_GetStackItemDummy()
else
local boolean isNotStackDummy = true
endif
return isNotStackDummy and GetIssuedOrderId() >= 852002 and GetIssuedOrderId() <= 852007 and GetOrderTargetItem() != null and GetMaxStacksByItemTypeIdIntern(GetItemTypeId(GetOrderTargetItem())) > 0 and GetItemCharges(GetOrderTargetItem()) > 1 and GetItemSlot(GetTriggerUnit(), GetOrderTargetItem()) == GetIssuedOrderId() - 852002
endfunction
private function TriggerActionOrderUnstack takes nothing returns nothing
local unit hero = GetTriggerUnit()
local item sourceItem = GetOrderTargetItem()
local integer sourceItemTypeId = GetItemTypeId(sourceItem)
local integer sourceSlot = GetItemSlot(hero, sourceItem)
local integer charges = 1
// 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
if (UNSTACK_HALF_CHARGES) then
set charges = IMaxBJ(GetItemCharges(sourceItem) / 2, 1)
else
set charges = IMinBJ(GetItemCharges(sourceItem), MAX_UNSTACKED_CHARGES)
endif
call SetItemCharges(sourceItem, GetItemCharges(sourceItem) - charges)
call AddUnstackedItem(hero, sourceItemTypeId, charges, sourceSlot, sourceItem)
endif
set hero = null
set sourceItem = null
endfunction
private function Init takes nothing returns nothing
call TriggerRegisterAnyUnitEventBJ(orderTrigger, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerAddCondition(orderTrigger, Condition(function TriggerConditionOrderUnstack))
call TriggerAddAction(orderTrigger, function TriggerActionOrderUnstack)
endfunction
// Change Log:
//
// 1.6 2022-07-15:
// - Add separate library MaxItemStacks.
//
// 1.5 2022-07-10:
// - Use vJass since we have two triggers now, can use the initializer and use static ifs.
// - Use constants instead of constant functions for the options of the system.
// - Calculate the max stacks per item type with the help of a dummy if enabled.
// - Increase the max stacks possible to 1000 since this is the maximum possible value in the object editor.
// - Add option UNSTACK_HALF_CHARGES.
//
// 1.4 2022-07-09:
// - ItemUnstackMaximumCharges allows changing the number of unstacked charges.
// - ItemUnstackAllowStackingNextItem allows stacking the unstacked item to the next item with the same type instead of only using a free slot.
// - ItemUnstackItemGetMaxStacks to check if the item is even stackable and to make sure item charges will not be over the maximum.
//
// 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.
endlibrary
// Baradé's Max Item Stacks System 1.0
//
// Supports the missing Warcraft III feature of calculating the value of 'ista' per item type ID.
//
// Usage:
// - Copy this code into your map script or a trigger converted into code.
// - Use the function GetMaxStacksByItemTypeId to get the value of 'ista' per item type ID.
//
// Download:
// https://www.hiveworkshop.com/threads/barad%C3%A9s-item-unstack-system-1-1.339109/
library MaxItemStacks initializer Init
globals
// This dummy is created and hidden once only if CHECK_MAX_STACKS is set to true. It requires an inventory with at least 2 slots.
private constant integer DUMMY_UNIT_TYPE_MAX_CHECKS = 'Hpal'
// Warcraft III has a limit of number of stacks for the field "Stats - Max Stacks" ('ista').
private constant integer MAX_STACKS_ALLOWED = 1000
private constant real DUMMY_X = 0.0
private constant real DUMMY_Y = 0.0
private integer stackCounter = 0
private hashtable stackHashTable = InitHashtable()
private unit stackItemDummy = null
private trigger stackItemTrigger = CreateTrigger()
endglobals
public function GetStackItemDummy takes nothing returns unit
return stackItemDummy
endfunction
function GetMaxStacksByItemTypeId takes integer itemTypeId returns integer
local integer i = 0
local item tmpItem = null
if (HaveSavedInteger(stackHashTable, itemTypeId, 0)) then
return LoadInteger(stackHashTable, itemTypeId, 0)
endif
set stackCounter = 1
set tmpItem = CreateItem(itemTypeId, 0.0, 0.0)
call SetItemCharges(tmpItem, 1)
call UnitAddItem(stackItemDummy, tmpItem)
set i = 1
loop
set tmpItem = CreateItem(itemTypeId, 0.0, 0.0)
call SetItemCharges(tmpItem, 1)
call UnitAddItem(stackItemDummy, tmpItem)
exitwhen (stackCounter <= i)
set i = i + 1
exitwhen (i >= MAX_STACKS_ALLOWED)
endloop
if (UnitItemInSlot(stackItemDummy, 0) != null) then
call RemoveItem(UnitItemInSlot(stackItemDummy, 0))
endif
if (UnitItemInSlot(stackItemDummy, 1) != null) then
call RemoveItem(UnitItemInSlot(stackItemDummy, 1))
endif
call SaveInteger(stackHashTable, itemTypeId, 0, stackCounter)
return stackCounter
endfunction
private function TriggerConditionStack takes nothing returns boolean
set stackCounter = stackCounter + 1
return false
endfunction
private function Init takes nothing returns nothing
set stackItemDummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_UNIT_TYPE_MAX_CHECKS, DUMMY_X, DUMMY_Y, 0.0)
call SetUnitInvulnerable(stackItemDummy, true)
if (IsUnitType(stackItemDummy, UNIT_TYPE_HERO)) then
call SuspendHeroXP(stackItemDummy, true)
endif
call SetUnitUseFood(stackItemDummy, false)
call ShowUnit(stackItemDummy, false)
call BlzSetUnitWeaponBooleanField(stackItemDummy, UNIT_WEAPON_BF_ATTACKS_ENABLED, 0, false)
call BlzSetUnitWeaponBooleanField(stackItemDummy, UNIT_WEAPON_BF_ATTACKS_ENABLED, 1, false)
call SetUnitPathing(stackItemDummy, false)
call SetUnitMoveSpeed(stackItemDummy, 0.0)
call TriggerRegisterUnitEvent(stackItemTrigger, stackItemDummy, EVENT_UNIT_STACK_ITEM)
call TriggerAddCondition(stackItemTrigger, Condition(function TriggerConditionStack))
endfunction
endlibrary