Name | Type | is_array | initial_value |
i | integer | No | |
u | unit | No |
//TESH.scrollpos=48
//TESH.alwaysfold=0
library Bag initializer Init
//newest version: http://www.hiveworkshop.com/forums/spells-569/bag-v1-2-a-252002/
/*************************** Bag v1.6.1 *************************************
**
** Info
**
** Bag is an improvement of the normal hero inventory.
** It extends the treatment with items in inventory, (stack/split)
** and also removes the item limit of six. (bag use)
**
** Features
**
** - Usage of bag ability to switch between inventories
** - Automatically stacking when picking up items
** - Manually item stacking in inventory possible
** - Item splitting possible (double right click on item)
** - Unit also can pick up items when current inventory is full.
** (stacked, or placed in an emtpy slot anywhere in bags)
**
** API
**
** AddBag (unit, integer) returns nothing
** - Which unit
** - Amount of bags should be added (also negative value possible)
**
**
** SetBag (unit, integer) returns nothing
** - Which unit
** - Amount of bags unit should have
**
**
** FlushBagUnit (unit) returns nothing
** - Use this for example if you want to remove a unit from the game,
** or want completly remove it as "Bag-Unit"
** - It will remove all bags from the unit, and flushes the hashtable
**
**
** GetBags (unit) returns integer
** Returns the amount of bags the unit currenctly has
**
**
** MaxBags() returns integer
** Returns the max amount of bags that are available (set in config by user)
**
**
** NOTE: You don't have to worry about adding/removing
** the bag ability in your actions when using Add/Set-Bag.
** Doing so is implemented in the API-functions.
**
***************************************************************************/
globals
/************** Config ******************/
private constant integer BAG_SPELL_ID = 'A000' // Bag ability Id.
private constant integer MAX_BAG_AMOUNT = 10 // Maximum amount of bags a unit can have.
private constant boolean TEXT_ONE = true // Prints info about picked up items.
private constant boolean TEXT_TWO = true // Prints when unit switches bags.
private constant boolean TEXT_THREE = true // Prints warning if unit tries to pick up items with no empty slot in bags.
private constant real DISPLAY_DURATION = 6.0 // Duration for texts printed by the system. (named above)
private constant real PICKUP_RANGE = 120.0 // This is the range unit will pick up items with full inventory.
// It should be more or less the same range as in your map settings
// for normal item picking up.
/************ End-Config ****************/
private constant integer SMART = 851971 //(right click)
private constant integer STOP = 851972
private constant integer MOVE = 851986
private integer itemChecks = 0
private group checkGroup = CreateGroup()
private group backupGroup = CreateGroup()
private group tmpGroup
private timer checkTimer = CreateTimer()
private constant real CHECK_TIMEOUT = .0312500
private trigger pickUp = CreateTrigger()
private trigger drop = CreateTrigger()
private hashtable hash = InitHashtable()
private constant integer MAX_BAG_KEY = 0
private constant integer X_KEY = 0
private constant integer Y_KEY = 1
private constant integer CURRENT_BAG_KEY = 1
private constant integer SLOT_KEY = 2
private constant integer CHARGES_KEY = 3
private constant integer ITEM_KEY = -1
endglobals
/****************** API *********************/
function AddBag takes unit u, integer bags returns nothing
local integer handleId
local integer maxBag
local integer oldMax
local integer i
local integer j
local item it
local integer bagUsed
local boolean b = false
local real x
local real y
if bags == 0 then
return
endif
set handleId = GetHandleId(u)
set oldMax = LoadInteger(hash, handleId, MAX_BAG_KEY)
set maxBag = oldMax + bags
if bags > 0 then
/*
* User wants to add bags.
*/
call UnitAddAbility(u, BAG_SPELL_ID)
if maxBag <= MAX_BAG_AMOUNT then
call SaveInteger(hash, handleId, MAX_BAG_KEY, maxBag)
else
call SaveInteger(hash, handleId, MAX_BAG_KEY, MAX_BAG_AMOUNT)
endif
else
/*
* User wants to substract bags.
* We remove bag ability if needed.
*/
if maxBag < 1 then
call UnitRemoveAbility(u, BAG_SPELL_ID)
set maxBag = 0
endif
call SaveInteger(hash, handleId, MAX_BAG_KEY, maxBag)
/*
* Now we check if current bag that is used
* also would be removed or not. If yes we have to fix
* unit's inventory after removing, else it would be empty.
*/
set bagUsed = LoadInteger(hash, handleId, CURRENT_BAG_KEY)
if bagUsed > maxBag then
call SaveInteger(hash, handleId, CURRENT_BAG_KEY, maxBag)
set b = true
endif
/*
* We loop through removed bags
* and drop their items from hero. (or make them visible)
*/
call DisableTrigger(drop)
set x = GetUnitX(u)
set y = GetUnitY(u)
set i = (maxBag + 1)*6
set j = (oldMax + 1)*6
loop
exitwhen i == j
set it = LoadItemHandle(hash, handleId, i)
if UnitHasItem(u, it) then
call UnitRemoveItem(u, it)
else
call SetItemVisible(it, true)
endif
call SetItemPosition(it, x, y)
call RemoveSavedHandle(hash, handleId, i)
call RemoveSavedInteger(hash, GetHandleId(it), 0)
set i = i + 1
endloop
set it = null
call EnableTrigger(drop)
if b then
/*
* We also have removed unit's current inventory in loop above.
* Now we load items from unit's last bag, to fix inventory. (else it would be empty)
*/
call DisableTrigger(pickUp)
set i = maxBag*6
set j = (maxBag+1)*6
loop
exitwhen i == j
call UnitAddItem(u, LoadItemHandle(hash, handleId, i))
set i = i + 1
endloop
call EnableTrigger(pickUp)
endif
endif
endfunction
/*
* SetBag only can make it easier, but also will call the AddBag function.
*/
function SetBag takes unit u, integer bags returns nothing
call AddBag(u, bags - LoadInteger(hash, GetHandleId(u), MAX_BAG_KEY))
endfunction
function GetBags takes unit u returns integer
return LoadInteger(hash, GetHandleId(u), MAX_BAG_KEY)
endfunction
constant function MaxBags takes nothing returns integer
return MAX_BAG_AMOUNT
endfunction
function FlushBagUnit takes unit u returns nothing
call FlushChildHashtable(hash, GetHandleId(u))
call AddBag(u, -MAX_BAG_AMOUNT)
endfunction
/****************** End of API *******************/
private function GetUnitItemSlot takes unit u, item it returns integer
local integer i = 0
local integer maxSlot = UnitInventorySize(u) - 1
loop
if UnitItemInSlot(u, i) == it then
return i
endif
exitwhen i == maxSlot
set i = i + 1
endloop
return -1
endfunction
private function BagUse takes nothing returns boolean
local integer spellId = GetSpellAbilityId()
local unit u
local integer i = 0
local integer k = 0
local integer j = 0
local item it
local integer handleId
local integer bagUsed
local integer maxBag
local string s
if spellId != BAG_SPELL_ID then
return false
endif
set u = GetTriggerUnit()
set handleId = GetHandleId(u)
set bagUsed = LoadInteger(hash, handleId, CURRENT_BAG_KEY)
set maxBag = LoadInteger(hash, handleId, MAX_BAG_KEY)
if bagUsed == maxBag then
set j = 0
call SaveInteger(hash, handleId, CURRENT_BAG_KEY, 0)
static if TEXT_TWO then
set s = "Inventory"
endif
else
set j = (bagUsed + 1)*6
call SaveInteger(hash, handleId, CURRENT_BAG_KEY, bagUsed + 1)
static if TEXT_TWO then
set s = "Bag " + I2S(bagUsed + 1)
endif
endif
call DisableTrigger(pickUp)
call DisableTrigger(drop)
loop
set it = UnitItemInSlot(u, i)
call UnitRemoveItem(u, it)
call SetItemVisible(it, false)
set it = LoadItemHandle(hash, handleId, j+i)
/*
* We immediatly remove the handle, because it
* might get a new postion in hashtable.
*/
call RemoveSavedHandle(hash, handleId, j+i)
/*
* Only if loaded item is not null (empty slot)
* we save it into hashtable again.
* Else the item's position in hashtable might get
* mixed up with emtpy slots and causes bugs.
*/
if it != null then
call UnitAddItem(u, it)
call SaveItemHandle(hash, handleId, j+k, it)
call SaveInteger(hash, GetHandleId(it), 0, j+k)
set k = k + 1 // k is counter for not-null items.
endif
set i = i + 1
exitwhen i > 5
endloop
static if TEXT_TWO then
call DisplayTimedTextToPlayer(GetTriggerPlayer(), 0, 0, DISPLAY_DURATION, "--------|CFF7EBFF1 " + s + " opened|r --------|r")
endif
call EnableTrigger(pickUp)
call EnableTrigger(drop)
set u = null
set it = null
return false
endfunction
private function PickUp takes nothing returns boolean
local item it = GetManipulatedItem()
local integer iType = GetItemTypeId(it)
local item it2
local integer i
local integer j = GetItemCharges(it)
local unit u = GetTriggerUnit()
local integer handleId = GetHandleId(u)
local integer maxBag
static if TEXT_ONE then
local player p = GetTriggerPlayer()
endif
/*
* If item has charges we check if there already exists
* same iType anywhere in a bag. (for possible stacking)
*/
if j > 0 then
set maxBag = (LoadInteger(hash, handleId, MAX_BAG_KEY)+1)*6
set i = 0
loop
set it2 = LoadItemHandle(hash, handleId, i)
if iType == GetItemTypeId(it2) then
call SetItemCharges(it2, j + GetItemCharges(it2))
static if TEXT_ONE then
call DisplayTimedTextToPlayer(p, 0, 0, DISPLAY_DURATION, "|c00ffcc00" + GetItemName(it) + " [+" + I2S(j) + "]" + "|r received")
endif
call DisableTrigger(drop)
call RemoveItem(it)
call EnableTrigger(drop)
set it = null
set it2 = null
set u = null
return false
endif
set i = i + 1
exitwhen i == maxBag
endloop
set it2 = null
endif
static if TEXT_ONE then
if j > 0 then
call DisplayTimedTextToPlayer(p, 0, 0, DISPLAY_DURATION, "|c00ffcc00" + GetItemName(it) + " [" + I2S(j) + "]" + "|r received")
else
call DisplayTimedTextToPlayer(p, 0, 0, DISPLAY_DURATION, "|c00ffcc00" + GetItemName(it) + "|r received")
endif
endif
/*
* Item was normaly picked up.
* Now we only have to save the handle correctly into hashtable.
*/
set i = 0
loop
exitwhen UnitItemInSlot(u, i) == it // Unit has picked up item,
set i = i + 1 // so there can't occur an infinite loop.
endloop
set j = LoadInteger(hash, handleId, CURRENT_BAG_KEY)*6 + i
call SaveItemHandle(hash, handleId, j, it)
call SaveInteger(hash, GetHandleId(it), 0, j)
set u = null
set it = null
return false
endfunction
private function Drop takes nothing returns boolean
call RemoveSavedHandle(hash, GetHandleId(GetTriggerUnit()), LoadInteger(hash, GetHandleId(GetManipulatedItem()), 0))
return false
endfunction
/*
* CheckDistance runs periodicly. It is the function to pick up
* items even if hero's inventory is full. If the unit is close enough to the
* wanted item, it will be picked up automatically and stored somewhere in bag.
*/
private function CheckDistance takes nothing returns nothing
local unit u
local integer handleId
local integer charges
local item checkItem
local item it
local integer slot
static if TEXT_ONE then
local player p
endif
loop
set u = FirstOfGroup(checkGroup)
exitwhen u == null
set handleId = GetHandleId(u)
static if TEXT_ONE then
set p = GetOwningPlayer(u)
endif
call GroupAddUnit(backupGroup, u) // We do this because we don't want to empty
call GroupRemoveUnit(checkGroup, u) // the actual checkGroup after each function run.
if IsUnitInRangeXY(u, LoadReal(hash, handleId, X_KEY), LoadReal(hash, handleId, Y_KEY), PICKUP_RANGE) then
set charges = LoadInteger(hash, handleId, CHARGES_KEY)
set slot = LoadInteger(hash, handleId, SLOT_KEY)
set checkItem = LoadItemHandle(hash, handleId, ITEM_KEY)
call RemoveSavedHandle(hash, handleId, ITEM_KEY)
if charges > 0 then
set it = LoadItemHandle(hash, handleId, slot)
if it == null then
static if TEXT_ONE then
call DisplayTimedTextToPlayer(p, 0, 0, DISPLAY_DURATION, "|c00ffcc00" + GetItemName(checkItem) + " [" + I2S(charges) + "]" + "|r received")
endif
call SaveItemHandle(hash, handleId, slot, checkItem)
call SaveInteger(hash, GetHandleId(checkItem), 0, slot)
call SetItemVisible(checkItem, false)
else
call SetItemCharges(it, GetItemCharges(it) + charges)
static if TEXT_ONE then
call DisplayTimedTextToPlayer(p, 0, 0, DISPLAY_DURATION, "|c00ffcc00" + GetItemName(checkItem) + " [+" + I2S(charges) + "]" + "|r received")
endif
call RemoveItem(checkItem)
endif
set it = null
else
static if TEXT_ONE then
call DisplayTimedTextToPlayer(p, 0, 0, DISPLAY_DURATION, "|c00ffcc00" + GetItemName(checkItem) + "|r received")
endif
call SaveItemHandle(hash, handleId, slot, checkItem)
call SaveInteger(hash, GetHandleId(checkItem), 0, slot)
call SetItemVisible(checkItem, false)
endif
call IssueImmediateOrderById(u, STOP) // Stop unit after item is picked up.
call GroupRemoveUnit(backupGroup, u) // We don't want it to come back anymore in our checkGroup!
set itemChecks = itemChecks - 1
if itemChecks == 0 then
call PauseTimer(checkTimer)
endif
set checkItem = null
endif
endloop
set tmpGroup = checkGroup
set checkGroup = backupGroup
set backupGroup = tmpGroup
endfunction
/*
* We need next function, because there must be a small timeout
* after we order the unit to move to the wanted item. (Order function below)
*/
private function AddUnit takes nothing returns nothing
local timer tim = GetExpiredTimer()
local integer handleId = GetHandleId(tim)
call GroupAddUnit(checkGroup, LoadUnitHandle(hash, handleId, 0))
call RemoveSavedHandle(hash, handleId, 0)
set itemChecks = itemChecks + 1
if itemChecks == 1 then
call TimerStart(checkTimer, CHECK_TIMEOUT, true, function CheckDistance)
endif
call DestroyTimer(tim)
set tim = null
endfunction
private function Order takes nothing returns boolean
local item it = GetOrderTargetItem()
local item it2
local integer order = GetIssuedOrderId()
local integer i = order - 852002 // To easily check slot order.
local integer j
local integer k
local integer l = 0
local integer iType
local integer iType2
local unit u
local boolean b = false
local integer handleId
local integer maxBag
local integer bagUsed
local real x
local real y
local timer tim
/*
* Check if it's an inventory order.
*/
if i > -1 and i < 6 then
set u = GetTriggerUnit()
set it2 = UnitItemInSlot(u, i)
set j = GetItemCharges(it)
set iType = GetItemTypeId(it)
set handleId = GetHandleId(u)
set maxBag = (LoadInteger(hash, handleId, MAX_BAG_KEY)+1)*6
set bagUsed = LoadInteger(hash, handleId, CURRENT_BAG_KEY)*6
// We first get slot of item that is moved.
loop
exitwhen UnitItemInSlot(u, l) == it
set l = l + 1
endloop
// Check if order is on same slot.
if i == l then
// Check for possible split.
if j > 1 then
call SetItemCharges(it, j - 1)
// Check if same iType exists in any bag. (possible stack of splitted item)
set i = 0
set k = -1
loop
set it2 = LoadItemHandle(hash, handleId, i)
if iType == GetItemTypeId(it2) and it != it2 then
call SetItemCharges( it2, GetItemCharges(it2) + 1)
set u = null
set it = null
set it2 = null
return false
elseif it2 == null and not b then // We do this for backup. In case we don't find same
set b = true // iType for stack, we would want to put it in an empty slot.
set k = i
endif
set i = i + 1
exitwhen i == maxBag
endloop
// No same iType exists.
// We create new one.
call DisableTrigger(pickUp)
set it = CreateItem(iType, GetUnitX(u), GetUnitY(u))
call UnitAddItem(u, it)
call SetItemCharges(it, 1)
call EnableTrigger(pickUp)
set i = GetUnitItemSlot(u, it)
if i != -1 then
// Unit has picked up the splitted item.
call SaveItemHandle(hash, handleId, bagUsed + i, it)
call SaveInteger(hash, GetHandleId(it), 0, bagUsed + i)
set u = null
set it = null
set it2 = null
return false
elseif k != -1 then
// Unit has not picked up the splitted item. (full inventory)
// Now we may use our empty backup slot, if exists.
call SaveItemHandle(hash, handleId, k, it)
call SaveInteger(hash, GetHandleId(it), 0, k)
call SetItemVisible(it, false)
endif
endif
set u = null
set it2 = null
else
// Order was on another slot.
set iType2 = GetItemTypeId(it2)
set k = GetItemCharges(it2)
if iType == iType2 and j > 0 then
//Items are stackable.
call SetItemCharges(it2, k + j)
call RemoveSavedHandle(hash, handleId, bagUsed + l)
call RemoveSavedInteger(hash, GetHandleId(it), 0)
call DisableTrigger(drop)
call RemoveItem( it )
call EnableTrigger(drop)
else
/*
* Unstackable items were swapped.
* We only have to refresh handles and indices in hashtable.
*/
call SaveInteger(hash, GetHandleId(it), 0, bagUsed + i)
call SaveInteger(hash, GetHandleId(it2), 0, bagUsed + l)
call RemoveSavedHandle(hash, handleId, bagUsed + i)
call RemoveSavedHandle(hash, handleId, bagUsed + l)
call SaveItemHandle(hash, handleId, bagUsed + i, it)
call SaveItemHandle(hash, handleId, bagUsed + l, it2)
endif
set u = null
set it2 = null
endif
/*
* We check if order was "right click" on an item.
* If unit has full inventory, we will order it to move to
* item's position and periodicly check the distance for possible pickUp.
*/
elseif order == SMART and it != null then
set u = GetTriggerUnit()
// If unit has an emtpy slot, we return immediatly.
if GetUnitItemSlot(u, null) != -1 then
set u = null
set it = null
return false
endif
// Unit has no empty slot.
set iType = GetItemTypeId(it)
set j = GetItemCharges(it)
set handleId = GetHandleId(u)
set maxBag = (LoadInteger(hash, handleId, MAX_BAG_KEY)+1)*6
/*
* If item has charges we check,
* if there already exists such an
* iType anywhere in bags. (possible stack)
*/
set k = -1
if j > 0 then
set i = 0
loop
set l = GetItemTypeId(LoadItemHandle(hash, handleId, i))
if iType == l then
set b = true
exitwhen true
elseif k == -1 and l == 0 then // If we find first empty slot we remember the index. (might need it later)
set k = i
endif
set i = i + 1
exitwhen i == maxBag
endloop
endif
if not b then
/*
* Item has no charges, or no matching iType was found in bag.
* Now we only need an empty slot.
*/
if k == -1 then // Loop through bags and look for an empty slot.
set i = 0
loop
exitwhen i == maxBag
if GetItemTypeId(LoadItemHandle(hash, handleId, i)) == 0 then
set b = true
exitwhen true
endif
set i = i + 1
endloop
else
set i = k // That means we already found an empty slot before.
set b = true
endif
endif
if b then
call SaveInteger(hash, handleId, SLOT_KEY, i)
call SaveInteger(hash, handleId, CHARGES_KEY, j)
call SaveItemHandle(hash, handleId, ITEM_KEY, it)
set x = GetItemX(it)
set y = GetItemY(it)
call SaveReal(hash, handleId, X_KEY, x)
call SaveReal(hash, handleId, Y_KEY, y)
call IssuePointOrderById(u, MOVE, x, y)
set tim = CreateTimer()
call SaveUnitHandle(hash, GetHandleId(tim), 0, u)
call TimerStart(tim, 0, false, function AddUnit)
set tim = null
else
static if TEXT_THREE then
call DisplayTimedTextToPlayer(GetTriggerPlayer(), 0, 0, DISPLAY_DURATION, "-------- |CFFFE8A0EFull Inventory|r --------")
endif
endif
set u = null
endif
set it = null
return false
endfunction
/*
* This will fire when a unit gets a new order, after we
* ordered it to move to wanted item. So the unit will be
* immediatly removed from checkGroup.
* (This also fires on death event)
*/
private function Cancel takes nothing returns boolean
local unit u = GetTriggerUnit()
local integer handleId
if IsUnitInGroup(u, checkGroup) then
set handleId = GetHandleId(u)
call GroupRemoveUnit(checkGroup, u)
call RemoveSavedHandle(hash, handleId, ITEM_KEY)
call RemoveSavedReal(hash, handleId, X_KEY)
call RemoveSavedReal(hash, handleId, Y_KEY)
call RemoveSavedInteger(hash, handleId, SLOT_KEY)
call RemoveSavedInteger(hash, handleId, CHARGES_KEY)
set itemChecks = itemChecks - 1
if itemChecks == 0 then
call PauseTimer(checkTimer)
endif
endif
set u = null
return false
endfunction
private function Init takes nothing returns nothing
local trigger bagUse = CreateTrigger()
local trigger order = CreateTrigger()
local trigger cancel = CreateTrigger()
local integer i = 0
local player p
loop
set p = Player(i)
call TriggerRegisterPlayerUnitEvent(bagUse, p, EVENT_PLAYER_UNIT_SPELL_CAST, null)
call TriggerRegisterPlayerUnitEvent(pickUp, p, EVENT_PLAYER_UNIT_PICKUP_ITEM, null)
call TriggerRegisterPlayerUnitEvent(drop, p, EVENT_PLAYER_UNIT_DROP_ITEM , null)
call TriggerRegisterPlayerUnitEvent(order, p, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER , null)
call TriggerRegisterPlayerUnitEvent(cancel, p, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER , null)
call TriggerRegisterPlayerUnitEvent(cancel, p, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER , null)
call TriggerRegisterPlayerUnitEvent(cancel, p, EVENT_PLAYER_UNIT_ISSUED_ORDER , null)
call TriggerRegisterPlayerUnitEvent(cancel, p, EVENT_PLAYER_UNIT_DEATH , null)
set i = i + 1
exitwhen i > bj_MAX_PLAYERS
endloop
call TriggerAddCondition(bagUse, Filter(function BagUse))
call TriggerAddCondition(pickUp, Filter(function PickUp))
call TriggerAddCondition(drop, Filter(function Drop))
call TriggerAddCondition(order, Filter(function Order))
call TriggerAddCondition(cancel, Filter(function Cancel))
endfunction
endlibrary