• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Inventory v1.0.8

This bundle is marked as pending. It has not been reviewed by a staff member yet.



Inventory

by SmitingDevil

Contents
* Introduction
* Code
* Screenshots
* How to Import
* Changelog
* Credits

Introduction

This is a simple system with the following features:
  • Compatibility with the default inventory
  • Drag and drop items
    - Move items from custom inventory to default one and vice versa​
    - Drop items to the ground from custom inventory​
    - Sell items from the custom inventory to shop​
    - Give items from custom inventory to another unit​

Code

Wurstscript

vJASS


Wurst:
package Inventory

import FramehandleNames
import ClosureFrames
import ClosureTimers
import ClosureEvents
import LinkedList
import HashMap
import SyncSimple
import MapBounds
import UI

import InventoryEvent
import HoverOriginButton

@configurable constant SLOTS_PER_PAGE = 64
@configurable constant SLOTS_PER_ROW = 8
@configurable constant SLOT_SIZE = 0.02
@configurable constant SPACE_BETWEEN_SLOTS = 0.008
@configurable constant EMPTY_SLOT_TEXTURE = "ui\\widgets\\escmenu\\human\\quest-completed-background.blp"
@configurable constant SELECT_SLOT_MODEL = UI.uI_ModalButtonOn

@configurable constant BACKDROP_BORDER_SIZE = 0.029
@configurable constant BACKDROP_POSITION = vec2(0.35, 0.35)

var itemSlot = -1
var itemSlotEx = -1
//let mapItem = new HashMap<framehandle, item> // Needs to be player based
let mapInventory = new HashMap<player, Inventory>
let mapUnitItem = new HashMap<unit, item>
let mapSelectedFrame = new HashMap<player, framehandle>
let mapSlot = new HashMap<framehandle, Slot>
let mapFlag = new HashMap<item, bool>
let mapUnit = new HashMap<player, unit>
framehandle dummyFrame
framehandle dummyFrameEx
framehandle dummyFrameIcon
framehandle dummyTooltip
framehandle dummyTooltipBox
framehandle dummyTooltipText
trigger dropTrigger

// Determines whether the player is currently an item or not
function isInTargetingMode() returns boolean
    var index = 0
    for i = 0 to 11 // when 12 is reached no button is visible
        if BlzFrameIsVisible(BlzGetOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, i))
            break
        index++
    return index == 11 //when the loop broke in index == 11 its targeting mode

function filter() returns bool
    return GetFilterUnit().isType(UNIT_TYPE_HERO)

class Slot
    framehandle buttonFrame
    framehandle iconFrame
    framehandle tooltipFrame
    framehandle tooltipText
    framehandle tooltipBox
    framehandle indicator
    framehandle chargeBackdrop
    framehandle chargeText

    construct(framehandle parent, int id, int index, real x, real y, Inventory inv)
        buttonFrame = createFrame(FramehandleNames.scriptDialogButton, parent, 0, id*SLOTS_PER_PAGE + index)
         ..setSize(SLOT_SIZE, SLOT_SIZE)
         ..setPoint(FRAMEPOINT_TOPLEFT, parent, FRAMEPOINT_TOPLEFT, x, y)

        // Link the button frame to this slot instance
        mapSlot.put(buttonFrame, this)

        iconFrame = createFrame(FramehandleTypeNames.backdrop, "InventorySlotIcon", buttonFrame, "", id*SLOTS_PER_PAGE + index)
         ..setAllPoints(buttonFrame)
         ..setTexture(EMPTY_SLOT_TEXTURE, 0, true)

        tooltipFrame = createFrame("FRAME", "InventorySlotTooltipParent", buttonFrame, "", id*SLOTS_PER_PAGE + index)
        ..disable()

        tooltipBox = createFrame("TasToolTipBox", tooltipFrame, 0, id*SLOTS_PER_PAGE + index)
        ..setVisible(false)

        tooltipText = createFrame("TasTooltipText", tooltipBox, 0, id*SLOTS_PER_PAGE + index)
        ..setPoint(FRAMEPOINT_TOPLEFT, buttonFrame, FRAMEPOINT_TOPRIGHT, 0.01, 0.01)
        ..setSize(0.2, 0)

        tooltipBox..setPoint(FRAMEPOINT_TOPLEFT, tooltipText, FRAMEPOINT_TOPLEFT, -0.007, 0.007)
        ..setPoint(FRAMEPOINT_BOTTOMRIGHT, tooltipText, FRAMEPOINT_BOTTOMRIGHT, 0.007, -0.007)

        indicator = createFrame("SPRITE", "MySlotIndicator", buttonFrame, "", id*SLOTS_PER_PAGE + index)
        ..setAllPoints(buttonFrame)
        ..setScale(SLOT_SIZE/0.036)
        ..setVisible(false)
        ..setModel(SELECT_SLOT_MODEL, 0)

        chargeBackdrop = createFrame(FramehandleTypeNames.backdrop, "", buttonFrame, "", id*SLOTS_PER_PAGE + index)
        ..setSize(0.015, 0.015)
        ..setPoint(FRAMEPOINT_BOTTOMRIGHT, buttonFrame, FRAMEPOINT_BOTTOMRIGHT)
        ..setTexture("ui\\widgets\\console\\human\\commandbutton\\human-button-lvls-overlay", 0, true)
        ..setVisible(false)

        chargeText = createFrame(FramehandleTypeNames.text, "", chargeBackdrop, "", id*SLOTS_PER_PAGE + index)
        ..setAllPoints(chargeBackdrop)
        ..setTextAlignment(TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
        ..setFont("MasterFont", 0.01, 0)
        ..setText("1")

        buttonFrame..setTooltip(tooltipFrame)
        ..onClick() ->
            let frame = EventData.getTriggerFrame()
            let p = EventData.getTriggerPlayer()
            let u = ENUM_GROUP..enumUnitsSelected(p, Filter(function filter)).getRandom()
            let mapItemFrame = inv.mapItem.get(p)
            if u != null
                frame.unfocus(p)
                // The custom inventory slot clicked does not contain an item
                if not mapItemFrame.has(frame)
                    itemSlot.sync(p) (int data) ->
                        // The player currently is dragging an item
                        if data != -1/*  and isInTargetingMode()  */
                            isInTargetingMode().sync(p) (boolean dataBool) ->
                                if dataBool
                                    // The player selected a custom inventory slot with an item beforehand
                                    if mapSelectedFrame.has(p)

                                        // Empty out the custom inventory slot
                                        let selectedFrame = mapSelectedFrame.get(p)
                                        let itemInSlot = mapItemFrame.get(selectedFrame)

                                        mapSelectedFrame.remove(p)

                                        mapItemFrame.remove(selectedFrame)
                                        mapItemFrame.put(frame, itemInSlot)
                                     
                                        // Update the visuals
                                        if localPlayer == p
                                            let slot = mapSlot.get(selectedFrame)
                                            slot.indicator.setVisible(p, false)
                                            slot.iconFrame.setTexture(EMPTY_SLOT_TEXTURE, 0, true)
                                            slot.tooltipBox.setVisible(p, false)
                                            slot.chargeBackdrop.setVisible(false)

                                    // Move manipulated item to clicked custom inventory slot
                                    let itm = u.itemInSlot(data)
                                    if itm != null
                                        let charges = itm.getCharges()
                                        if localPlayer == p
                                            iconFrame.setTexture(itm.getIconPath(), 0, true)
                                            tooltipBox..setVisible(true)
                                            tooltipText..setText(GetLocalizedString("|cffffcc00" + itm.getName() + "|r|n" + itm.getExtendedTooltip()))
                                            if charges > 0
                                                chargeBackdrop.setVisible(true)
                                                chargeText.setText(charges.toString())

                                        mapItemFrame.put(frame, itm)
                                        itm..setPos(boundRect.getLeftTop())..setVisible(false)

                                    // Move back the origin item button frame back to its original location
                                    if localPlayer == p
                                        getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                                        ..setAllPoints(dummyFrame)

                                    // Return the original item in first slot to default unit inventory
                                    if mapUnitItem.has(u)
                                        mapUnitItem.get(u).setVisible(true)
                                        u.addItemHandle(mapUnitItem.get(u))
                                        mapUnitItem.remove(u)
                                        if localPlayer == p
                                            dummyFrameIcon.setVisible(false)
                                            dummyTooltipBox.setVisible(false)

                else // The slot clicked contains an item
                    dummyFrameEx.enable()

                    // The player did not select a slot with an item beforehand
                    if not mapSelectedFrame.has(p)
                        isInTargetingMode().sync(p) (boolean dataBool) ->

                            // The player is not currently dragging an item
                            if not dataBool
                                let itm = u.itemInSlot(0)
                                if itm != null
                                    itm
                                    ..setPos(boundRect.getLeftTop())..setVisible(false)
                                    mapUnitItem.put(u, itm)
                                u.addItemHandle(mapItemFrame.get(frame))
                                mapSelectedFrame.put(p, frame)

                                // Update the visuals
                                if localPlayer == p
                                    indicator
                                    ..setVisible(true)
                                    tooltipBox..setVisible(false)
                                    getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                                    ..setAllPoints(frame)
                                    if mapUnitItem.has(u)
                                        dummyFrameIcon..setVisible(true)
                                        ..setTexture(mapUnitItem.get(u).getIconPath(), 0, true)
                                        dummyTooltipBox.setVisible(true)
                                        dummyTooltipText.setText(GetLocalizedString("|cffffcc00" + itm.getName() + "|r|n" + itm.getExtendedTooltip()))

                            else // The player currently is dragging an item
                                itemSlot.sync(p) (int data) ->
                                    if data != -1
                                        let itm = mapItemFrame.get(frame)
                                        let itemInSlot = u.itemInSlot(data)

                                        if itemInSlot != null
                                            // Flag the items to be ignored by item events
                                            mapFlag.put(itm, true)
                                            mapFlag.put(itemInSlot, true)

                                            itemInSlot..setPos(boundRect.getLeftTop())..setVisible(false)
                                            u.addItemHandle(itm)
                                            mapFlag.put(itm, true)
                                            u.dropItemSlot(itm, data)

                                            // Swap the item that the player is dragging with the item in slot
                                            mapItemFrame.remove(frame)
                                            mapItemFrame.put(frame, itemInSlot)

                                            // Update the visuals
                                            let charges = itemInSlot.getCharges()
                                            if localPlayer == p
                                                iconFrame
                                                ..setTexture(itemInSlot.getIconPath(), 0, true)
                                                tooltipBox..setVisible(true)
                                                tooltipText..setText(GetLocalizedString("|cffffcc00" + itemInSlot.getName() + "|r|n" + itemInSlot.getExtendedTooltip()))
                                                if charges > 0
                                                    chargeBackdrop.setVisible(true)
                                                    chargeText.setText(charges.toString())
                    else   // The player did select a slot with an item beforehand
                        isInTargetingMode().sync(p) (boolean dataBool) ->
                            if dataBool
                                itemSlot.sync(p) (int data) ->
                                    if data == 0
                                        let selectedFrame = mapSelectedFrame.get(p)
                                        let slot = mapSlot.get(selectedFrame)
                                        let clickedSlot = mapSlot.get(frame)
                                        let clickedItem = mapItemFrame.get(frame)
                                        let selectedItem = mapItemFrame.get(selectedFrame)
                                        let clickedItemCharges = clickedItem.getCharges()
                                        let selectedItemCharges = selectedItem.getCharges()

                                        mapSelectedFrame.remove(p)
                                        mapItemFrame.put(selectedFrame, clickedItem)
                                        mapItemFrame.put(frame, selectedItem)
                                        dropTrigger.disable()
                                        selectedItem..setPos(boundRect.getLeftTop())..setVisible(false)
                                        dropTrigger.enable()

                                        // Update the visuals
                                        if localPlayer == p
                                            // Swap the two items' position
                                            slot.indicator.setVisible(false)
                                            slot.iconFrame.setTexture(clickedItem.getIconPath(), 0, true)
                                            slot.tooltipBox.setVisible(true)
                                            slot.tooltipText.setText(GetLocalizedString("|cffffcc00" + clickedItem.getName() + "|r|n" + clickedItem.getExtendedTooltip()))
                                            if clickedItemCharges > 0
                                                slot.chargeBackdrop.setVisible(true)
                                                slot.chargeText.setText(clickedItemCharges.toString())

                                            clickedSlot.iconFrame.setTexture(selectedItem.getIconPath(), 0, true)
                                            clickedSlot.tooltipBox.setVisible(true)
                                            clickedSlot.tooltipText.setText(GetLocalizedString("|cffffcc00" + selectedItem.getName() + "|r|n" + selectedItem.getExtendedTooltip()))
                                            if selectedItemCharges > 0
                                                clickedSlot.chargeBackdrop.setVisible(true)
                                                clickedSlot.chargeText.setText(selectedItemCharges.toString())
                                         
                                            getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                                            ..setAllPoints(dummyFrame)

                                        // Return the original item in first slot to default unit inventory
                                        if mapUnitItem.has(u)
                                            mapUnitItem.get(u).setVisible(true)
                                            u.addItemHandle(mapUnitItem.get(u))
                                            mapUnitItem.remove(u)
                                            if localPlayer == p
                                                dummyFrameIcon.setVisible(false)
                                                dummyTooltipBox.setVisible(false)    
                            else // The player is not currently dragging an item
                                let selectedFrame = mapSelectedFrame.get(p)
                                let slot = mapSlot.get(selectedFrame)
                                let clickedSlot = mapSlot.get(frame)
                                let clickedItem = mapItemFrame.get(frame)
                                mapSelectedFrame.remove(p)
                                dropTrigger.disable()
                                u.itemInSlot(0)..setPos(boundRect.getLeftTop())..setVisible(false)
                                dropTrigger.enable()
                                u.addItemHandle(clickedItem)
                                if localPlayer == p
                                    slot.indicator.setVisible(false)
                                    clickedSlot.indicator.setVisible(true)
                                    slot.tooltipBox.setVisible(true)
                                    clickedSlot.tooltipBox.setVisible(true)
                                    getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                                    ..setAllPoints(frame)
                                mapSelectedFrame.put(p, frame)

public class Inventory
    private let slots = new LinkedList<Slot>
    protected let mapItem = new HashMap<player, HashMap<framehandle, item>>
    private framehandle parent
    private framehandle next
    private framehandle prev
    framehandle open

    construct()
        let id = this castTo int
        let titleSize = 0.015

        for i = 0 to bj_MAX_PLAYER_SLOTS - 1
            if players[i].isIngame()
                let map = new HashMap<framehandle, item>
                mapItem.put(players[i], map)

        // Create the inventory backdrop
        let rowCount = SLOTS_PER_PAGE/SLOTS_PER_ROW
        parent = createFrame(FramehandleNames.questButtonBaseTemplate, GAME_UI, 0, 0)
        ..setAbsPoint(FRAMEPOINT_CENTER, BACKDROP_POSITION)
        ..setSize(BACKDROP_BORDER_SIZE*2 + SLOT_SIZE*SLOTS_PER_ROW + SPACE_BETWEEN_SLOTS*(SLOTS_PER_ROW - 1), BACKDROP_BORDER_SIZE*2 + SLOT_SIZE*rowCount + SPACE_BETWEEN_SLOTS*(rowCount - 1) + titleSize*2 + 0.0145)

        // Create the inventory title
        let title = createFrame("HeroSelectorTitle", parent, 0, 0)
        ..setTextAlignment(TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
        ..setPoint(FRAMEPOINT_TOP, parent, FRAMEPOINT_TOP, 0, -BACKDROP_BORDER_SIZE*0.6)
        ..setText(GetLocalizedString("Inventory"))
        ..setSize(parent.getWidth() - BACKDROP_BORDER_SIZE*2, 0.03)

/*         // Create page number
        createFrame("HeroSelectorTitle", parent, 0, 0)
        ..setTextAlignment(TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
        ..setPoint(FRAMEPOINT_BOTTOM, parent, FRAMEPOINT_BOTTOM, 0, 0)
        ..setSize(parent.getWidth() - BACKDROP_BORDER_SIZE*2, 0.03)
        ..setText("1") */

        // Create close button
        createFrame(FramehandleNames.scriptDialogButton, parent, 0, 0)
        ..setSize(0.03, 0.03)
        ..setPoint(FRAMEPOINT_TOPRIGHT, parent, FRAMEPOINT_TOPRIGHT, 0, 0)
        ..setText("X")
        ..setTextAlignment(TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
        ..onClick() ->
            EventData.getTriggerFrame().unfocus(EventData.getTriggerPlayer())
            parent.setVisible(EventData.getTriggerPlayer(), false)
            let u = ENUM_GROUP..enumUnitsSelected(EventData.getTriggerPlayer(), Filter(function filter)).getRandom()
            let selectedFrame = mapSelectedFrame.get(EventData.getTriggerPlayer())
            let mapItemFrame = mapItem.get(EventData.getTriggerPlayer())
            let itm = mapItemFrame.get(selectedFrame)
            mapSelectedFrame.remove(EventData.getTriggerPlayer())
            itm..setPos(boundRect.getLeftTop())..setVisible(false)
            let slot = mapSlot.get(selectedFrame)
            if slot != null
                if localPlayer == EventData.getTriggerPlayer()
                    getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                    ..setAllPoints(dummyFrame)
                    dummyFrameIcon.setVisible(false)
                    dummyTooltipBox.setVisible(false)
                    slot.indicator.setVisible(false)
            // Return the original item in first slot to default unit inventory
            if mapUnitItem.has(u)
                let item0 = mapUnitItem.get(u)
                mapFlag.put(item0, true)
                item0.setVisible(true)
                u.addItemHandle(item0)
                mapUnitItem.remove(u)
            mapInventory.remove(EventData.getTriggerPlayer())

        // Create the open button
        let inv = this
        open = createFrame(FramehandleNames.scriptDialogButton, GAME_UI, 0, 0)
        ..setSize(0.2, 0.05)
        ..setTextAlignment(TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
        ..setAbsPoint(FRAMEPOINT_CENTER, 0.007, 0.175)
        ..setText(GetLocalizedString("Open Inventory"))
        ..setScale(0.5)
        ..onClick() ->
            EventData.getTriggerFrame().unfocus(EventData.getTriggerPlayer())
            parent.setVisible(EventData.getTriggerPlayer(), true)
            mapInventory.put(EventData.getTriggerPlayer(), inv)
            if mapSelectedFrame.has(EventData.getTriggerPlayer())
                let selectedFrame = mapSelectedFrame.get(EventData.getTriggerPlayer())
                let slot = mapSlot.get(selectedFrame)
                if slot != null
                    if localPlayer == EventData.getTriggerPlayer()
                        slot.indicator.setVisible(false)

        // Create the next and prev buttons
/*         next = createFrame(FramehandleNames.scriptDialogButton, parent, 0, 0)
        ..setSize(0.03, 0.03)
        ..setPoint(FRAMEPOINT_BOTTOMRIGHT, parent, FRAMEPOINT_BOTTOMRIGHT, 0, 0)
        ..setText(">")

        prev = createFrame(FramehandleNames.scriptDialogButton, parent, 0, 0)
        ..setSize(0.03, 0.03)
        ..setPoint(FRAMEPOINT_BOTTOMLEFT, parent, FRAMEPOINT_BOTTOMLEFT, 0, 0)
        ..setVisible(false)
        ..setText("<")
*/
        // Create the inventory slots
        var x = BACKDROP_BORDER_SIZE
        var y = -BACKDROP_BORDER_SIZE - title.getHeight()/*  - 0.0125 */
        var j = 0
        for i = 0 to (SLOTS_PER_PAGE - 1)
            slots.add(new Slot(parent, id, i, x, y, this))
            x += SLOT_SIZE + SPACE_BETWEEN_SLOTS
            j++
            if j == SLOTS_PER_ROW
                j = 0
                y -= SLOT_SIZE + SPACE_BETWEEN_SLOTS
                x = BACKDROP_BORDER_SIZE

        // Hide inventory by default
        parent.setVisible(false)

    function show(bool flag)
        parent.setVisible(flag)

init
    // Detect player entering the origin item button frames
    hoverOriginButtonAdd(false) ->
        if not isInTargetingMode()
            itemSlot = currentSelectedButtonIndex - itemButtonOffset
        if (currentSelectedButtonIndex - itemButtonOffset) != 0
            itemSlotEx = currentSelectedButtonIndex - itemButtonOffset
            dummyFrameEx.enable()

    // Detect player leaving the origin item button frames
    hoverOriginButtonAddClose() ->
        if not isInTargetingMode()
            itemSlot = -1
        itemSlotEx = -1

    // Detect unit selection
    EventListener.add(EVENT_PLAYER_UNIT_SELECTED) ->
        let p = EventData.getTriggerPlayer()
        let u = EventData.getTriggerUnit()

        if mapInventory.has(p)
            let inv = mapInventory.get(p)
            let mapItemFrame = inv.mapItem.get(p)
            // Check if player has an item on custom inventory selected
            if mapSelectedFrame.has(p)

                // Did the player select an unit before?
                if mapUnit.has(p)
                    let prev = mapUnit.get(p)
                    let itm = prev.itemInSlot(0)

                    // Check if this unit had an item in the first slot
                    if mapUnitItem.has(prev)
                        let item0 = mapUnitItem.get(prev)
                        mapFlag.put(item0, true)
                        prev.addItemHandle(item0)
                        mapFlag.put(itm, true)
                        if itm != null
                            itm..setPos(boundRect.getLeftTop())..setVisible(false)
                        mapUnitItem.remove(prev)
                        nullTimer() ->
                            mapFlag.put(item0, true)
                            prev.dropItemSlot(item0, 0)

                if u.inventorySize() > 0
                    let itemInFirstSlot = u.itemInSlot(0)
                    if itemInFirstSlot != null
                        mapUnitItem.put(u, itemInFirstSlot)
                        mapFlag.put(itemInFirstSlot, true)
                        itemInFirstSlot..setPos(boundRect.getLeftTop())..setVisible(false)
                        if localPlayer == p
                            dummyFrameIcon.setVisible(true)
                            dummyFrameIcon.setTexture(itemInFirstSlot.getIconPath(), 0, true)
                    else
                        if localPlayer == p
                            dummyFrameIcon.setVisible(false)
                    let selectedItem = mapItemFrame.get(mapSelectedFrame.get(p))
                    mapFlag.put(selectedItem, true)
                    u.addItemHandle(selectedItem)
                    nullTimer() ->
                        mapFlag.put(selectedItem, true)
                        u.dropItemSlot(selectedItem, 0)
                else
                    let slot = mapSlot.get(mapSelectedFrame.get(p))
                    if localPlayer == p
                        dummyFrameIcon.setVisible(false)
                        getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                        ..setAllPoints(dummyFrame)
                        dummyFrameEx.disable()
                        slot.indicator.setVisible(false)
                        slot.tooltipBox.setVisible(true)
                    mapSelectedFrame.remove(p)

            mapUnit.put(p, u)

    // Detect Mouse Click
    EventListener.add(EVENT_PLAYER_MOUSE_UP) ->
        let p = EventData.getTriggerPlayer()
        if mapInventory.has(p)
            let inv = mapInventory.get(p)
            let mapItemFrame = inv.mapItem.get(p)
            itemSlotEx.sync(p) (int data) ->
                if data == 0
                    if mapSelectedFrame.has(p)
                        let selectedFrame = mapSelectedFrame.get(p)
                        let u = ENUM_GROUP..enumUnitsSelected(p, Filter(function filter)).getRandom()

                        let slot = mapSlot.get(selectedFrame)
                        mapItemFrame.remove(selectedFrame)
                        let itm = mapUnitItem.get(u)

                        mapSelectedFrame.remove(p)

                        if localPlayer == p
                            getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                            ..setAllPoints(dummyFrame)
                            dummyFrameIcon.setVisible(false)
                            dummyTooltipBox.setVisible(false)
                            itemSlot = 0
                            slot.indicator.setVisible(false)
                            if itm == null
                                slot.iconFrame.setTexture(EMPTY_SLOT_TEXTURE, 0, true)
                                slot.chargeBackdrop.setVisible(false)
                                slot.tooltipBox.setVisible(false)
                            else
                                slot.iconFrame.setTexture(itm.getIconPath(), 0, true)
                                slot.tooltipBox.setVisible(true)
                                slot.tooltipText.setText(GetLocalizedString("|cffffcc00" + itm.getName() + "|r|n" + itm.getExtendedTooltip()))
                            itemSlotEx = -1
                            dummyFrameEx.disable()
                            ForceUICancel()
                        if itm != null
                            mapItemFrame.put(selectedFrame, itm)
                            mapUnitItem.remove(u)

    // Detect player dropping an item from custom inventory
    dropTrigger = CreateTrigger()..addAction() ->
        let p = EventData.getTriggerPlayer()

        if mapInventory.has(p)
            let inv = mapInventory.get(p)
            let mapItemFrame = inv.mapItem.get(p)
            let itm = EventData.getManipulatedItem()
            let u = EventData.getTriggerUnit()
         
            if not mapFlag.has(itm)
                if mapSelectedFrame.has(p) and u.getItemSlot(itm) == 0

                    // Update the visuals
                    let slot = mapSlot.get(mapSelectedFrame.get(p))
                    if localPlayer == p
                        dummyFrameIcon.setVisible(false)
                        dummyTooltipBox.setVisible(false)
                        getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                        ..setAllPoints(dummyFrame)
                        slot.iconFrame.setTexture(EMPTY_SLOT_TEXTURE, 0, true)
                        slot.tooltipBox.setVisible(false)
                        slot.indicator.setVisible(p, false)
                        slot.chargeBackdrop.setVisible(false)

                    // Return the original item in first slot to default unit inventory
                    if mapUnitItem.has(u)
                        let item0 = mapUnitItem.get(u)
                        item0.setVisible(true)
                        mapFlag.put(item0, true)
                        u.addItemHandle(item0)
                        nullTimer() ->
                            mapFlag.put(item0, true)
                            u.dropItemSlot(item0, 0)
                        mapUnitItem.remove(u)

                    // Remove the link between item and custom inventory slot selected
                    mapItemFrame.remove(mapSelectedFrame.get(p))
                    mapSelectedFrame.remove(p)
            else
                mapFlag.remove(itm)

    TriggerRegisterAnyUnitEventBJ(dropTrigger, EVENT_PLAYER_UNIT_DROP_ITEM)

    // Detect player moving items in default unit inventory
    registerInventoryEvent(EVENT_ITEM_INVENTORY.MOVE) ->
        let u = getInventoryManipulatingUnit()
        let p = u.getOwner()
        if mapInventory.has(p)
            let inv = mapInventory.get(p)
            let mapItemFrame = inv.mapItem.get(p)
            let itm = getInventoryManipulatedItem()
            let slotFrom = getInventorySlotFrom()
            let slotTo = getInventorySlotTo()

            if localPlayer == p
                itemSlot = slotTo

            if not mapFlag.has(itm)
                let swappedItem = getInventorySwappedItem()
                if slotFrom == 0
                    if swappedItem == null
                        // The player selected a custom inventory slot with an item beforehand
                        if mapSelectedFrame.has(p)
                            let selectedFrame = mapSelectedFrame.get(p)
                            let slot = mapSlot.get(selectedFrame)
                         
                            if slotFrom != slotTo
                                // Empty out the selected custom inventory slot
                                mapItemFrame.remove(selectedFrame)
                                if localPlayer == p
                                    slot.iconFrame.setTexture(EMPTY_SLOT_TEXTURE, 0, true)
                                    slot.tooltipBox.setVisible(false)
                                    slot.chargeBackdrop.setVisible(false)
                            else
                                itm..setPos(boundRect.getLeftTop())..setVisible(false)

                            // Update the visuals
                            if localPlayer == p
                                dummyFrameIcon.setVisible(false)
                                dummyTooltipBox.setVisible(false)
                                getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                                ..setAllPoints(dummyFrame)
                                slot.indicator.setVisible(p, false)
                         
                            // Return the original item in first slot to default unit inventory
                            if mapUnitItem.has(u)
                                let item0 = mapUnitItem.get(u)
                                item0.setVisible(true)
                                mapFlag.put(item0, true)
                                u.addItemHandle(item0)
                                mapUnitItem.remove(u)
                                nullTimer() ->
                                    mapFlag.put(item0, true)
                                    u.dropItemSlot(item0, 0)
                            mapSelectedFrame.remove(p)

                    else if swappedItem != itm
                        // The player selected a custom inventory slot with an item beforehand
                        if mapSelectedFrame.has(p)
                            let selectedFrame = mapSelectedFrame.get(p)
                            let slot = mapSlot.get(selectedFrame)

                            mapItemFrame.remove(selectedFrame)
                            mapItemFrame.put(selectedFrame, swappedItem)
                            swappedItem..setPos(boundRect.getLeftTop())..setVisible(false)

                            // Update the visuals
                            let charges = swappedItem.getCharges()
                            if localPlayer == p
                                dummyFrameIcon.setVisible(false)
                                dummyTooltipBox.setVisible(false)
                                getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                                ..setAllPoints(dummyFrame)
                                slot.iconFrame.setTexture(swappedItem.getIconPath(), 0, true)
                                slot.tooltipBox.setVisible(true)
                                slot.tooltipText
                                ..setText(GetLocalizedString("|cffffcc00" + swappedItem.getName() + "|r|n" + swappedItem.getExtendedTooltip()))
                                slot.indicator.setVisible(p, false)
                                if charges > 0
                                    slot.chargeBackdrop.setVisible(true)
                                    slot.chargeText.setText(charges.toString())

                            // Return the original item in first slot to default unit inventory
                            if mapUnitItem.has(u)
                                mapUnitItem.get(u).setVisible(true)
                                u.addItemHandle(mapUnitItem.get(u))
                                mapUnitItem.remove(u)
                            mapSelectedFrame.remove(p)

                else // Item being manipulated was not in first slot of default unit inventory
                    if slotTo == 0
                        // The player selected a slot in custom inventory with an item beforehand
                        if mapSelectedFrame.has(p)
                            let selectedFrame = mapSelectedFrame.get(p)
                            let slot = mapSlot.get(selectedFrame)
                         
                            // Place the item being manipulated in the custom inventory slot
                            mapItemFrame.remove(selectedFrame)
                            mapItemFrame.put(selectedFrame, itm)

                            mapFlag.put(itm, true)
                            mapFlag.put(swappedItem, true)
                            itm..setPos(boundRect.getLeftTop())..setVisible(false)
                            u.dropItemSlot(swappedItem, slotFrom)

                            // Update the visuals
                            let charges = itm.getCharges()
                            if localPlayer == p
                                dummyFrameIcon.setVisible(false)
                                dummyTooltipBox.setVisible(false)
                                getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
                                ..setAllPoints(dummyFrame)
                                slot.iconFrame.setTexture(itm.getIconPath(), 0, true)
                                slot.tooltipBox.setVisible(true)
                                slot.tooltipText
                                ..setText(GetLocalizedString("|cffffcc00" + itm.getName() + "|r|n" + itm.getExtendedTooltip()))
                                slot.indicator.setVisible(p, false)
                                if charges > 0
                                    slot.chargeBackdrop.setVisible(true)
                                    slot.chargeText.setText(charges.toString())

                            // Return the original item in first slot to default unit inventory
                            if mapUnitItem.has(u)
                                mapUnitItem.get(u).setVisible(true)
                                u.addItemHandle(mapUnitItem.get(u))
                                mapUnitItem.remove(u)
                            mapSelectedFrame.remove(p)
            else
                mapFlag.remove(itm)

    // Create a dummy frame for the first slot of default unit inventory
    dummyFrame = createFrame(FramehandleTypeNames.gluetextbutton, "DummyFrame", GAME_UI, "", -1)
    ..setSize(0.030, 0.030)
    ..setAbsPoint(FRAMEPOINT_CENTER, 0.531 + 0.0008, 0.101 - 0.004)

    // Create the icon texture frame for the dummy frame
    dummyFrameIcon = createFrame(FramehandleTypeNames.backdrop, "DummyFrameIcon", dummyFrame, "", -1)
    ..setAllPoints(dummyFrame)
    ..setVisible(false)

    // Create a dummy button to detect if player entered the first item slot when the ORIGIN_ITEM_BUTTON is moved
    dummyFrameEx = createFrame(FramehandleTypeNames.gluetextbutton, "DummyFrameEx", dummyFrame, "", -1)
    ..setSize(0.04, 0.04)
    ..setAbsPoint(FRAMEPOINT_CENTER, 0.531 + 0.0008, 0.101 - 0.004)
    ..onMouseEnter() ->
        EventData.getTriggerFrame()..unfocus(EventData.getTriggerPlayer())
        ..disable()
        if isInTargetingMode()
            itemSlotEx = 0

    // dummyTooltip = createFrame("FRAME", "DummyTooltipParent", dummyFrameEx, "", 0)
    // ..disable()

    // dummyTooltipBox = createFrame("TasToolTipBox", dummyTooltip, 0, 0)

    // dummyTooltipText = createFrame("TasTooltipText", dummyTooltipBox, 0, 0)
    // ..setPoint(FRAMEPOINT_TOPLEFT, dummyFrame, FRAMEPOINT_TOPRIGHT, 0.01, 0.01)
    // ..setSize(0.2, 0)

    // dummyTooltipBox..setPoint(FRAMEPOINT_TOPLEFT, dummyTooltipText, FRAMEPOINT_TOPLEFT, -0.007, 0.007)
    // ..setPoint(FRAMEPOINT_BOTTOMRIGHT, dummyTooltipText, FRAMEPOINT_BOTTOMRIGHT, 0.007, -0.007)

    // dummyFrameEx.setTooltip(dummyTooltip)

init
    loadTOCFile("war3mapImported\\TasInventoryEx.toc")

Other than importing the main package and the standard library, the following two packages are also required to use Inventory:

Wurst:
package HoverOriginButton

// Original by Tasyen

import FramehandleNames
import ClosureTimers

@configurable constant INTERVAL = 1/60

public let itemButtonOffset = 30
framehandle array frames
public var currentSelectedButtonIndex = -1
trigger actionsCommand = CreateTrigger()
trigger actionsClose = CreateTrigger()
trigger actionsItem = CreateTrigger()

public function hoverOriginButtonAdd(bool forCommandButton, code action) returns triggercondition
    if forCommandButton
        return TriggerAddCondition(actionsCommand, Filter(action))
    else
        return TriggerAddCondition(actionsItem, Filter(action))

public function hoverOriginButtonAddClose(code action) returns triggercondition
    return TriggerAddCondition(actionsClose, Filter(action))

public function hoverOriginButtonRemove(triggercondition action)
    actionsClose.removeCondition(action)
    actionsCommand.removeCondition(action)
    actionsItem.removeCondition(action)

init
    nullTimer() ->
        // Create one tooltip frame for each command button
        for i = 0 to 11
            let buttonFrame = getOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, i)
            let frame = createFrame(FramehandleTypeNames.simpleframe, "", buttonFrame, "", 0)
            ..setVisible(false)
            buttonFrame..setTooltip(frame)
            frames[i] = frame

        // Create one tooltip frame for each item button
        for i = 0 to 5
            let buttonFrame = getOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, i)
            let frame = createFrame(FramehandleTypeNames.simpleframe, "", buttonFrame, "", 0)
            ..setVisible(false)
            buttonFrame..setTooltip(frame)
            frames[i + itemButtonOffset] = frame
   
        doPeriodically(INTERVAL) (CallbackPeriodic cb) ->
            var selectedAnything = false
            for i = 0 to 11
                if frames[i].isVisible()
                    selectedAnything = true
                    if currentSelectedButtonIndex != i
                        currentSelectedButtonIndex = i
                        actionsCommand.evaluate()
                if i < 6
                    let tableIndex = i + itemButtonOffset
                    if frames[tableIndex].isVisible()
                        selectedAnything = true
                        if currentSelectedButtonIndex != tableIndex
                            currentSelectedButtonIndex = tableIndex
                            actionsItem.evaluate()
                       
            if not selectedAnything and currentSelectedButtonIndex != -1
                actionsClose.evaluate()
                currentSelectedButtonIndex = -1
Wurst:
/*
*  InventoryEvent v1.0.1.5
*     by Bannar
*
*  For intuitive inventory event handling.
*/

package InventoryEvent

import RegisterEvents
import Orders

tuple eventInfo(unit u, item itm, int slotFrom, int slotTo)
var eventState = eventInfo(null, null, -1, -1)
constant eventMovedTrigger = CreateTrigger()
constant eventUsedTrigger = CreateTrigger()

public enum EVENT_ITEM_INVENTORY
    MOVE
    USE

/** Returns unit which manipulated event item. */
public function getInventoryManipulatingUnit() returns unit
    return eventState.u

/** Returns manupilated event item. */
public function getInventoryManipulatedItem() returns item
    return eventState.itm

/** Returns slot index of manipulated item from which it was moved or used. */
public function getInventorySlotFrom() returns int
    return eventState.slotFrom

/** Returns slot index of manipulated item to which it was moved. */
public function getInventorySlotTo() returns int
    return eventState.slotTo

/** Returns item which manipulated item switched position with if any. */
public function getInventorySwappedItem() returns item
    return eventState.u.itemInSlot(eventState.slotTo)

public function getInventoryEventTrigger(EVENT_ITEM_INVENTORY whichEvent) returns trigger
    trigger result
    switch whichEvent
        case EVENT_ITEM_INVENTORY.MOVE
            result = eventMovedTrigger
        case EVENT_ITEM_INVENTORY.USE
            result = eventUsedTrigger
    return result

public function registerInventoryEvent(EVENT_ITEM_INVENTORY whichEvent, code func)
    switch whichEvent
        case EVENT_ITEM_INVENTORY.MOVE
            eventMovedTrigger.addCondition(Condition(func))
        case EVENT_ITEM_INVENTORY.USE
            eventUsedTrigger.addCondition(Condition(func))

function fireEvent(trigger evt, eventInfo currState)
    let prevState = eventState
    eventState = currState
    evt.evaluate()
    eventState = prevState

function onItemOrder()
    let order = GetIssuedOrderId()
    let u = GetTriggerUnit()
    if order >= SpecialOrders.itemdrag00 and order <= SpecialOrders.itemdrag05
        let itm = GetOrderTargetItem()
        let slotFrom = u.getItemSlot(itm)
        let slotTo = order - SpecialOrders.itemdrag00
        fireEvent(eventMovedTrigger, eventInfo(u, itm, slotFrom, slotTo))
    else
        let slotFrom = order - SpecialOrders.itemuse00
        let itm = u.itemInSlot(slotFrom)
        fireEvent(eventUsedTrigger, eventInfo(u, itm, slotFrom, -1))

function onAnyOrder()
    let order = GetIssuedOrderId()
    if order >= SpecialOrders.itemdrag00 and order <= SpecialOrders.itemuse05
        onItemOrder()

init
    // MOVED is order of type TARGET_ORDER yet USED can be anyone of them
    registerPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, () -> onAnyOrder())
    registerPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, () -> onAnyOrder())
    registerPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, () -> onAnyOrder())
JASS:
/*****************************************************************************
*
*    Inventory v1.0.8
*       by SmitingDevil
*
*    Custom inventory that is compatible with the vanilla one
*
******************************************************************************
*
*    Requirements:
*
*       Table by Bribe
*          https://www.hiveworkshop.com/threads/lua-vjass-new-table.188084/
*
*       Alloc - choose whatever you like
*          e.g.: by AGD https://www.hiveworkshop.com/threads/global-alloc.324937/
*
*       HoverOriginButton by Tasyen
*           https://www.hiveworkshop.com/threads/hoveroriginbutton.337965/
*
*       GetMainSelectedUnit (vJASS) by Tasyen
*           https://www.hiveworkshop.com/threads/getmainselectedunit.325337/
*
*       Sync by TriggerHappy
*           https://www.hiveworkshop.com/threads/sync-game-cache.279148/
*
*       List<T> by Bannar
*           https://www.hiveworkshop.com/threads/containers-list-t.249011/
*
*       InventoryEvent by Bannar
*           https://www.hiveworkshop.com/threads/snippet-inventoryevent.287084/
*
*       StringIterator by edo494
*           https://www.hiveworkshop.com/threads/snippet-stringiterator.246143/
*
*       WorldBounds by Nestharus
*           https://raw.githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j
*
*       TimerUtils by Magtheridon96/Vexorian/Bribe
*           https://www.hiveworkshop.com/threads/system-timerutilsex.204500/#post-2019478
*
******************************************************************************
*
*    Inventory Struct API:
*
*       General:
*
*        | static method create takes nothing returns thistype
*        |    Default constructor.
*        |
*        | static method createEx takes integer slotCount, integer cols, real buttonSize, real spaceGap, real borderSize, real titleSize, real posX, real posY returns thistype
*        |    Constructor that builds the inventory in one go.
*        |
*        | method destroy takes nothing returns nothing
*        |    Default destructor.
*
*
*       Access:
*
*        | readonly emptySlotTexture
*        | readonly slotIndicatorModel
*        | readonly backdrop
*        | readonly buttonSize
*
*
*       Modifiers:
*
*        [Design]
*
*        | method setSlotCount takes integer count returns thistype
*        |    Sets the number of inventory slots.
*        |
*        | method setSlotEmptyTexture takes string texture returns thistype
*        |    Sets the default texture for slots.
*        |
*        | method setSlotIndicatorModel takes string str returns thistype
*        |    Sets the model that shows a slot is selected.
*        |
*        | method setButtonSize takes real size returns thistype
*        |    Sets the size of the inventory slots and other buttons.
*        |
*        | method setButtonSpaceGap takes real size returns thistype
*        |    Sets the size of the space between inventory slots.
*        |
*        | method setColumnCount takes integer cols returns thistype
*        |    Sets the number of slots in a row.
*        |
*        | method setTitle takes string str returns thistype
*        |    Sets the name of the inventory.
*        |
*        | method setTitleSize takes real size returns thistype
*        |    Sets the size of the title's margins.
*        |
*        | method setBorderSize takes real size returns thistype
*        |    Sets the size of the inventory's margins.
         |
*        | method setOpenButtonPosition takes real x, real y returns thistype
*        |    Sets the position of the open button icon.
*        |
*        | method setOpenButtonTexture takes string str returns thistype
*        |    Sets the texture for open button icon.
*        |
*        | method build takes nothing returns nothing
*        |    Creates the inventory and all its elements.
*        |    All methods for design must be called before running this method.
*        |        They cannot be modified after build.
*        |        Only title name and methods for the open button are the only exceptions.
*        |            They can be modified before and after build and are optional.
*
*        [Technical]
*
*        | method setPosition takes real x, real y returns thistype
*        |    Moves the inventory to given point on the screen
*        |
*        | method show takes boolean flag returns nothing
*        |    Hides or displays the inventory
*        |
*        | method showEx takes player p, boolean flag returns nothing
*        |    Hides or displays the inventory for specific player only
*        |    Links the inventory to player as the one being displayed
*
*
*****************************************************************************/
library Inventory initializer Init requires TimerUtilsEx, WorldBounds, ListT, StringIterator, Sync, InventoryEvent, HoverOriginButton, GetMainSelectedUnit
    globals
        private framehandle dummyFrame
        private framehandle dummyFrameEx
        private framehandle dummyIcon
        private framehandle array playerFrame
        private integer originItemSlot = -1
        private integer originItemSlotEx = -1
        private Table player2Inventory
        //private Table item2Unit
        player LOCAL_PLAYER
        framehandle GAME_UI
        framehandle ORIGIN_ITEM_BUTTON_ONE
       
        //private integer count = 0
    endglobals
   
    function IsFrameEnabled takes framehandle frame, player p returns boolean
        if GetLocalPlayer() == p or p == null then
            return BlzFrameGetEnable(frame)
        else
            return false
        endif
    endfunction
   
    function UnfocusFrame takes framehandle frame, player p returns nothing
        if IsFrameEnabled(frame, p) then
            call BlzFrameSetEnable(frame, false)
            call BlzFrameSetEnable(frame, true)
        else
            call BlzFrameSetEnable(frame, true)
            call BlzFrameSetEnable(frame, false)
        endif
    endfunction
   
    function print takes string msg returns nothing
        call DisplayTimedTextToPlayer(LOCAL_PLAYER, 0, 0, 3, msg)
    endfunction
   
    private function IsMouseDraggingItem takes nothing returns boolean
        local integer i = 0
        loop
            if BlzFrameIsVisible(BlzGetOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, i)) then
                exitwhen true
            endif
            set i = i + 1
            exitwhen i == 12
        endloop
        return i == 11
    endfunction

    private module MyModule
        private static method onInit takes nothing returns nothing
            //this is run on map init
            call init()
        endmethod
    endmodule
   
    private struct Frame extends array
        implement Alloc
       
        framehandle handle
       
        static method create takes framehandle frame returns thistype
            local thistype this = allocate()
           
            set handle = frame
            return this
        endmethod
       
        method destroy takes nothing returns nothing
            set handle = null
            call deallocate()
        endmethod
    endstruct
   
    private struct UnitItem extends array
        implement Alloc
       
        item itemHandle
        unit unitHandle
       
        static method create takes unit u, item it returns thistype
            local thistype this = allocate()
           
            set unitHandle = u
            set itemHandle = it
           
            return this
        endmethod
       
        method destroy takes nothing returns nothing
            set unitHandle = null
            set itemHandle = null
           
            call deallocate()
        endmethod
    endstruct

    private struct Slot extends array
        implement Alloc
       
        private framehandle actionButton
        private framehandle icon
        private framehandle tooltipParent
        private framehandle tooltipText
        private framehandle tooltipBackdrop
        readonly framehandle indicator
        readonly Inventory parent
        readonly static Table frame2Slot
        readonly static Table frame2Item
        readonly static Table unit2Item
        readonly static Table player2Unit
        readonly static Table data2Frame
        private static trigger actionTrigger
        private static trigger enterTrigger
        //private static framehandle clickedFrame
       
        static method create takes real size, real x, real y, Inventory parent returns thistype
            local thistype this = allocate()

            set actionButton = BlzCreateFrame("ScriptDialogButton", parent.backdrop, 0, 0)
            call BlzFrameSetSize(actionButton, size, size)
            call BlzFrameSetPoint(actionButton, FRAMEPOINT_TOPLEFT, parent.backdrop, FRAMEPOINT_TOPLEFT, x, y)
           
            set frame2Slot[GetHandleId(actionButton)] = this
            set frame2Item[GetHandleId(actionButton)] = Table.create()

            set icon = BlzCreateFrameByType("BACKDROP", "SlotIcon", actionButton, "", 0)
            call BlzFrameSetAllPoints(icon, actionButton)
            call BlzFrameSetTexture(icon, parent.emptySlotTexture, 0, true)

            set tooltipParent = BlzCreateFrameByType("FRAME", "SlotTooltipParent", actionButton, "", 0)
            call BlzFrameSetTooltip(actionButton, tooltipParent)

            set tooltipBackdrop = BlzCreateFrameByType("BACKDROP", "SlotTooltipBackdrop", tooltipParent, "", 0)
            call BlzFrameSetVisible(tooltipBackdrop, false)

            set tooltipText = BlzCreateFrameByType("TEXT", "SlotTooltipText", tooltipBackdrop, "", 0)
            call BlzFrameSetSize(tooltipText, 0.2, 0)
            call BlzFrameSetPoint(tooltipText, FRAMEPOINT_TOPLEFT, actionButton, FRAMEPOINT_TOPRIGHT, 0, 0)

            call BlzFrameSetPoint(tooltipBackdrop, FRAMEPOINT_TOPLEFT, tooltipText, FRAMEPOINT_TOPLEFT, -0.007, 0.007)
            call BlzFrameSetPoint(tooltipBackdrop, FRAMEPOINT_BOTTOMRIGHT, tooltipText, FRAMEPOINT_BOTTOMRIGHT, 0.007, -0.007)
            call BlzFrameSetTexture(tooltipBackdrop, "UI\\Widgets\\ToolTips\\Human\\human-tooltip-background.blp", 0, true)
           
            set indicator = BlzCreateFrameByType("SPRITE", "SlotIndicator", actionButton, "", 0)
            call BlzFrameSetAllPoints(indicator, actionButton)
            call BlzFrameSetScale(indicator, parent.buttonSize/0.036)
            call BlzFrameSetVisible(indicator, false)
            call BlzFrameSetModel(indicator, parent.slotIndicatorModel, 0)
           
            call BlzTriggerRegisterFrameEvent(actionTrigger, actionButton, FRAMEEVENT_CONTROL_CLICK)

            set .parent = parent

            return this
        endmethod
       
        private method update takes item itm returns nothing
            if itm != null then
                call BlzFrameSetTexture(icon, BlzGetItemIconPath(itm), 0, true)
                call BlzFrameSetVisible(tooltipBackdrop, true)
                call BlzFrameSetText(tooltipText, GetLocalizedString("|cffffcc00" + GetItemName(itm) + "|r\n\n" + BlzGetAbilityExtendedTooltip(GetItemTypeId(itm), 0)))
            else
                call BlzFrameSetTexture(icon, parent.emptySlotTexture, 0, true)
                call BlzFrameSetVisible(tooltipBackdrop, false)
            endif
        endmethod
       
        private static method onExpired takes nothing returns nothing
            local UnitItem data = ReleaseTimer(GetExpiredTimer())
           
            call SetItemVisible(data.itemHandle, true)
            call UnitAddItem(data.unitHandle, data.itemHandle)
            call UnitDropItemSlot(data.unitHandle, data.itemHandle, 0)
           
            call data.destroy()
        endmethod
       
        private static method clickAction takes nothing returns nothing
            local SyncData d = GetSyncedData()
            local StringIterator iter = StringIterator.create(d.readString(0))
            local Frame clickedFrame = data2Frame[d]
            local integer originSlot
            local integer pid
            local integer selectedIndex = S2I(iter.read())
            local unit mainSelectedUnit = GetMainSelectedUnit(selectedIndex)
            local framehandle frame
            local framehandle pFrame
            local boolean itemOnMouse
            local player syncer
            local item itemInOriginSlot
            local item slotItem
            local item item0
            local thistype slot
            local thistype pSlot
            local Table table
            local Table tb
           
            call data2Frame.remove(d)
           
            //call print("Click Action: " + GetUnitName(mainSelectedUnit))
            //call ClearTextMessages()
            //call print("------------------------------------------------------")
            if mainSelectedUnit != null then
                set frame = clickedFrame.handle
                set syncer = d.from
                set pid = GetPlayerId(syncer)
                set table = frame2Item[GetHandleId(frame)]
                set slot = frame2Slot[GetHandleId(frame)]
                set originSlot = S2I(iter.read())
                if iter.read() == "true" then
                    set itemOnMouse = true
                else
                    set itemOnMouse = false
                endif
               
                if itemOnMouse then
                    set itemInOriginSlot = UnitItemInSlot(mainSelectedUnit, originSlot)
                    if itemInOriginSlot != null then
                        set pFrame = playerFrame[pid]
                        set playerFrame[pid] = null
                        call SetItemPosition(itemInOriginSlot, WorldBounds.minX, WorldBounds.minY)
                        call SetItemVisible(itemInOriginSlot, false)
                       
                        if LOCAL_PLAYER == syncer then
                            call slot.update(itemInOriginSlot)
                            set originItemSlot = -1
                            set originItemSlotEx = -1
                        endif
                       
                        set slotItem = table.item[GetHandleId(mainSelectedUnit)]
                        set table.item[GetHandleId(mainSelectedUnit)] = itemInOriginSlot
                        set playerFrame[pid] = pFrame
                        if pFrame == null then
                            if slotItem != null then
                                //call print("Click Slot - itemOnMouse - no pFrame: " + GetItemName(slotItem))
                                call UnitAddItem(mainSelectedUnit, slotItem)
                                call UnitDropItemSlot(mainSelectedUnit, slotItem, originSlot)
                            endif
                        else
                            if originSlot == 0 then
                                set playerFrame[pid] = null
                                set slot = frame2Slot[GetHandleId(pFrame)]
                                set tb = frame2Item[GetHandleId(pFrame)]
                                if slotItem == null then
                                    call tb.item.remove(GetHandleId(mainSelectedUnit))
                                else
                                    set tb.item[GetHandleId(mainSelectedUnit)] = slotItem
                                endif
                                //call print("Click Slot - itemOnMouse - pFrame - originSlot 0: " + GetItemName(slotItem))
                                if LOCAL_PLAYER == syncer then
                                    call slot.update(slotItem)
                                    call BlzFrameSetVisible(dummyIcon, false)
                                    call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, dummyFrame)
                                    call BlzFrameSetVisible(slot.indicator, false)
                                endif
                                if unit2Item.item.has(GetHandleId(mainSelectedUnit)) then
                                    set item0 = unit2Item.item[GetHandleId(mainSelectedUnit)]
                                    call TimerStart(NewTimerEx(UnitItem.create(mainSelectedUnit, item0)), 0, false, function thistype.onExpired)
                                    //call UnitAddItem(mainSelectedUnit, item0)
                                    //call UnitDropItemSlot(mainSelectedUnit, item0, 0)
                                    call unit2Item.item.remove(GetHandleId(mainSelectedUnit))
                                endif
                            else
                                set playerFrame[pid] = null
                                if slotItem != null then
                                    call UnitAddItem(mainSelectedUnit, slotItem)
                                    call UnitDropItemSlot(mainSelectedUnit, slotItem, originSlot)
                                endif
                                set playerFrame[pid] = pFrame
                            endif
                            set pFrame = null
                        endif
                    endif
                    set itemInOriginSlot = null
                else
                    if table.item.has(GetHandleId(mainSelectedUnit)) then

                        if playerFrame[GetPlayerId(syncer)] != null then
                            set pFrame = playerFrame[GetPlayerId(syncer)]
                            set playerFrame[GetPlayerId(syncer)] = null
                            set pSlot = frame2Slot[GetHandleId(pFrame)]
                            set tb = frame2Item[GetHandleId(pFrame)]
                            set slotItem = tb.item[GetHandleId(mainSelectedUnit)]
                            call SetItemPosition(slotItem, WorldBounds.minX, WorldBounds.minY)
                            call SetItemVisible(slotItem, false)
                            if LOCAL_PLAYER == syncer then
                                call BlzFrameSetVisible(pSlot.indicator, false)
                            endif
                           
                            if unit2Item.item.has(GetHandleId(mainSelectedUnit)) then
                                set item0 = unit2Item.item[GetHandleId(mainSelectedUnit)]
                                call UnitAddItem(mainSelectedUnit, item0)
                                call UnitDropItemSlot(mainSelectedUnit, item0, 0)
                                call unit2Item.item.remove(GetHandleId(mainSelectedUnit))
                            endif
                        endif
                       
                        if LOCAL_PLAYER == syncer then
                            call BlzFrameSetVisible(slot.indicator, true)
                            call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, frame)
                            set originItemSlot = -1
                            set originItemSlotEx = -1
                            call BlzFrameSetEnable(dummyFrameEx, true)
                        endif

                        set slotItem = UnitItemInSlot(mainSelectedUnit, 0)
                        if slotItem != null then
                            set unit2Item.item[GetHandleId(mainSelectedUnit)] = slotItem
                            //set item2Unit.unit[GetHandleId(slotItem)] = mainSelectedUnit
                            //call print("Clicked slot no item on mouse: " + GetItemName(slotItem))
                            if LOCAL_PLAYER == syncer then
                                call BlzFrameSetTexture(dummyIcon, BlzGetAbilityIcon(GetItemTypeId(slotItem)), 0, true)
                                call BlzFrameSetVisible(dummyIcon, true)
                            endif
                            call SetItemPosition(slotItem, WorldBounds.minX, WorldBounds.minY)
                            call SetItemVisible(slotItem, false)
                            set slotItem = null
                        endif
                           
                        set slotItem = table.item[GetHandleId(mainSelectedUnit)]
                        call UnitAddItem(mainSelectedUnit, slotItem)
                        call UnitDropItemSlot(mainSelectedUnit, slotItem, 0)
                        set slotItem = null

                        set playerFrame[GetPlayerId(syncer)] = frame
                    endif
                endif
               
                set frame = null
                set mainSelectedUnit = null
                set syncer = null
            endif
           
            call d.destroy()
            call clickedFrame.destroy()
        endmethod
       
        private static method onClick takes nothing returns nothing
            local SyncData req = SyncData.create(GetTriggerPlayer())
            local string str
           
            set data2Frame[req] = Frame.create(BlzGetTriggerFrame())
           
            call UnfocusFrame(BlzGetTriggerFrame(), GetTriggerPlayer())
           
            if frame2Slot.has(GetHandleId(BlzGetTriggerFrame())) then
                set str = ""
                if LOCAL_PLAYER == GetTriggerPlayer() then
                    set str = I2S(GetSelectedUnitIndex()) + " "
                    set str = str + I2S(originItemSlot) + " "
                    if IsMouseDraggingItem() then
                        set str = str + "true"
                    else
                        set str = str + "false"
                    endif
                endif
                set req.onComplete = Filter(function thistype.clickAction)
                call req.syncString(str, StringLength(str))
                //call SyncStr(str, GetTriggerPlayer(), function thistype.clickAction)
            endif
        endmethod
       
        private static method onEnter takes nothing returns nothing
            call UnfocusFrame(BlzGetTriggerFrame(), GetTriggerPlayer())
            call BlzFrameSetEnable(BlzGetTriggerFrame(), false)
            if BlzGetTriggerFrame() == dummyFrameEx then
                if IsMouseDraggingItem() then
                    set originItemSlotEx = 0
                endif
            endif
        endmethod
       
        private static method onItemMove takes nothing returns nothing
            local item item0
            local unit u = GetInventoryManipulatingUnit()
            local item itm
            local item swapped
            local integer slotFrom
            local integer slotTo = GetInventorySlotTo()
            local player p = GetOwningPlayer(u)
            local integer pid = GetPlayerId(p)
            local thistype slot
            local framehandle pFrame
            local Table tb
           
            if LOCAL_PLAYER == p then
                set originItemSlot = slotTo
            endif
           
            set pFrame = playerFrame[pid]
            set playerFrame[pid] = null
            if pFrame != null then
                //call print("Item Move: " + GetItemName(GetInventoryManipulatedItem()))
                set itm = GetInventoryManipulatedItem()
                set swapped = GetInventorySwappedItem()
                set slot = frame2Slot[GetHandleId(pFrame)]
                set slotFrom = GetInventorySlotFrom()
               
                if slotTo == slotFrom then
                   
                else
                    call BlzFrameSetEnable(dummyFrame, true)
                   
                    set tb = frame2Item[GetHandleId(pFrame)]
                   
                    if slotFrom == 0 then
                        if swapped != null then
                            set tb.item[GetHandleId(u)] = swapped
                            call SetItemPosition(swapped, WorldBounds.minX, WorldBounds.minY)
                            call SetItemVisible(swapped, false)
                           
                            if LOCAL_PLAYER == p then
                                call slot.update(swapped)
                                call BlzFrameSetVisible(slot.indicator, false)
                                call BlzFrameSetVisible(dummyIcon, false)
                                call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, dummyFrame)
                                //set originItemSlot = -1
                                set originItemSlotEx = -1
                            endif
                        else
                            call tb.item.remove(GetHandleId(u))
                            if LOCAL_PLAYER == p then
                                call slot.update(null)
                                call BlzFrameSetVisible(slot.indicator, false)
                                call BlzFrameSetVisible(dummyIcon, false)
                                call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, dummyFrame)
                                //set originItemSlot = -1
                                set originItemSlotEx = -1
                            endif
                        endif
                    elseif slotTo == 0 then
                        set tb.item[GetHandleId(u)] = itm
                        call print("sloTo 0 - swapped item: " + GetItemName(itm))
                        call SetItemPosition(itm, WorldBounds.minX, WorldBounds.minY)
                        call SetItemVisible(itm, false)
                       
                        if LOCAL_PLAYER == p then
                            call slot.update(itm)
                            call BlzFrameSetVisible(slot.indicator, false)
                            call BlzFrameSetVisible(dummyIcon, false)
                            call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, dummyFrame)
                            //set originItemSlot = -1
                            set originItemSlotEx = -1
                        endif
                       
                        call UnitDropItemSlot(u, swapped, slotFrom)
                    endif
                    if slotFrom == 0 or slotTo == 0 then
                        set pFrame = null
                        set item0 = unit2Item.item[GetHandleId(u)]
                        //call print("sloTo 0 - item0: " + GetItemName(item0))
                        if item0 != null then
                            call unit2Item.item.remove(GetHandleId(u))
                            call SetItemVisible(item0, true)
                            call UnitAddItem(u, item0)
                            call UnitDropItemSlot(u, item0, 0)
                            set item0 = null
                        endif
                    endif
                endif
            endif
           
            set u = null
            set itm = null
            set swapped = null
            set playerFrame[pid] = pFrame
            set pFrame = null
        endmethod
       
        private static method onItemDrop takes nothing returns nothing
            local item itm = GetManipulatedItem()
            local item item0
            local unit u = GetTriggerUnit()
            local integer pid = GetPlayerId(GetTriggerPlayer())
            local thistype slot
            local framehandle pFrame = playerFrame[pid]
            local Table tb
           
            if pFrame != null and UnitItemInSlot(u, 0) == itm then
                set playerFrame[pid] = null
                //call print("Item Drop: " + GetItemName(itm))
                call BlzFrameSetEnable(dummyFrame, true)
                set slot = frame2Slot[GetHandleId(pFrame)]
                set tb = frame2Item[GetHandleId(pFrame)]
                call tb.item.remove(GetHandleId(u))
                if LOCAL_PLAYER == GetTriggerPlayer() then
                    call slot.update(null)
                    call BlzFrameSetVisible(slot.indicator, false)
                    call BlzFrameSetVisible(dummyIcon, false)
                    call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, dummyFrame)
                    set originItemSlot = -1
                    set originItemSlotEx = -1
                endif
                set item0 = unit2Item.item[GetHandleId(u)]
                if item0 != null then
                    //call print("Item Drop - item0: " + GetItemName(item0))
                    call unit2Item.item.remove(GetHandleId(u))
                    call TimerStart(NewTimerEx(UnitItem.create(u, item0)), 0, false, function thistype.onExpired)
                    set item0 = null
                endif
            endif
        endmethod
       
        private static method mouseAction takes nothing returns nothing
            local SyncData d = GetSyncedData()
            local StringIterator iter = StringIterator.create(d.readString(0))
            local IntegerListItem node
            local Inventory inv
            local Table tb
            local player p = d.from
            local integer pid = GetPlayerId(p)
            local integer selectedIndex
            local integer originSlot
            local thistype slot
            local boolean itemOnMouse
            local item item0
            local item itm
            local unit u
            local unit mainUnit
            local string str = iter.read()
            local framehandle pFrame
           
            call d.destroy()
           
            if not player2Inventory.has(pid) then
                return
            endif
           
            if str == "true" then
                set itemOnMouse = true
            else
                set itemOnMouse = false
            endif
            set originSlot = S2I(iter.read())
            set selectedIndex = S2I(iter.read())
            //set count = count + 1
           
            if itemOnMouse then
                //call print("Main Selected Unit Index: " + I2S(selectedIndex))
                if playerFrame[pid] != null and originSlot == 0 then
                    set u = GetMainSelectedUnit(selectedIndex)
                    set slot = frame2Slot[GetHandleId(playerFrame[pid])]
                    set item0 = unit2Item.item[GetHandleId(u)]
                    set tb = frame2Item[GetHandleId(playerFrame[pid])]
                   
                    if item0 == null then
                        call tb.item.remove(GetHandleId(u))
                    else
                        call unit2Item.item.remove(GetHandleId(u))
                        set tb.item[GetHandleId(u)] = item0
                    endif
                   
                    if LOCAL_PLAYER == p then
                        call slot.update(item0)
                        call BlzFrameSetVisible(slot.indicator, false)
                        call BlzFrameSetVisible(dummyIcon, false)
                        call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, dummyFrame)
                        set originItemSlot = -1
                        set originItemSlotEx = -1
                        call ForceUICancel()
                        call BlzFrameSetEnable(dummyFrameEx, true)
                    endif
                   
                    set item0 = null
                    set u = null
                    set playerFrame[pid] = null
                endif
            else
                set pFrame = playerFrame[pid]
                set playerFrame[pid] = null
                set mainUnit = GetMainSelectedUnit(selectedIndex)
                set u = player2Unit.unit[pid]
                set player2Unit.unit[pid] = mainUnit
                if mainUnit != u and u != null then
                    if unit2Item.item.has(GetHandleId(u)) then
                        set item0 = unit2Item.item[GetHandleId(u)]
                        set itm = UnitItemInSlot(u, 0)
                        call SetItemPosition(itm, WorldBounds.minX, WorldBounds.minY)
                        call SetItemVisible(itm, false)
                        call UnitAddItem(u, item0)
                        call UnitDropItemSlot(u, item0, 0)
                        call unit2Item.item.remove(GetHandleId(u))
                        set itm = null
                        set item0 = null
                    endif
                   
                    set inv = player2Inventory[pid]
                    set node = inv.slots.first
                    loop
                        exitwhen node == 0
                        set slot = node.data
                        set tb = frame2Item[GetHandleId(slot.actionButton)]
                        set itm = tb.item[GetHandleId(mainUnit)]
                        if itm != null then
                             call SetItemPosition(itm, WorldBounds.minX, WorldBounds.minY)
                             call SetItemVisible(itm, false)
                        endif
                        if LOCAL_PLAYER == p then
                            call slot.update(itm)
                        endif
                        set itm = null
                        set node = node.next
                    endloop
                   
                    if unit2Item.item.has(GetHandleId(mainUnit)) then
                        set item0 = unit2Item.item[GetHandleId(mainUnit)]
                        //call print("mainUnit - item0: " + GetItemName(item0))
                        call UnitAddItem(mainUnit, item0)
                        call UnitDropItemSlot(mainUnit, item0, 0)
                        call unit2Item.item.remove(GetHandleId(mainUnit))
                        set item0 = null
                    endif
                   
                    if pFrame != null then
                        set slot = frame2Slot[GetHandleId(pFrame)]
                        set pFrame = null
                        if LOCAL_PLAYER == p then
                            call BlzFrameSetVisible(slot.indicator, false)
                            call BlzFrameSetVisible(dummyIcon, false)
                            call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, dummyFrame)
                            set originItemSlot = -1
                            set originItemSlotEx = -1
                            call BlzFrameSetEnable(dummyFrameEx, true)
                        endif
                    endif
                endif
                set playerFrame[pid] = pFrame
            endif
        endmethod
       
        private static method onMouseClick takes nothing returns nothing
            local SyncData req = SyncData.create(GetTriggerPlayer())
            local string str = ""
            if LOCAL_PLAYER == GetTriggerPlayer() then
                if IsMouseDraggingItem() then
                    set str = "true "
                else
                    set str = "false "
                endif
                set str = str + I2S(originItemSlotEx) + " "
                set str = str + I2S(GetSelectedUnitIndex())
            endif
            set req.onComplete = Filter(function thistype.mouseAction)
            call req.syncString(str, StringLength(str))
            //call print(str + ": " + I2S(StringLength(str)) + " characters")
            //call SyncStr(str, GetTriggerPlayer(), function thistype.mouseAction)
        endmethod
       
        private static method onInit takes nothing returns nothing
            set frame2Slot = Table.create()
            set frame2Item = Table.create()
            set unit2Item = Table.create()
            set player2Unit = Table.create()
            set data2Frame = Table.create()
           
            set actionTrigger = CreateTrigger()
            set enterTrigger = CreateTrigger()
            call TriggerAddCondition(actionTrigger, function thistype.onClick)
            call TriggerAddCondition(enterTrigger, function thistype.onEnter)
           
            set ORIGIN_ITEM_BUTTON_ONE = BlzGetOriginFrame(ORIGIN_FRAME_ITEM_BUTTON, 0)
           
            set dummyFrame = BlzCreateFrameByType("GLUETEXTBUTTON", "DummyFrame", GAME_UI, "", 0)
            call BlzFrameSetSize(dummyFrame, 0.030, 0.031)
            call BlzFrameSetAbsPoint(dummyFrame, FRAMEPOINT_CENTER, 0.5318, 0.097)
           
            set dummyIcon = BlzCreateFrameByType("BACKDROP", "DummyIcon", dummyFrame, "", 0)
            call BlzFrameSetAllPoints(dummyIcon, dummyFrame)
            call BlzFrameSetVisible(dummyIcon, false)
           
            set dummyFrameEx = BlzCreateFrameByType("GLUETEXTBUTTON", "DummyFrame", dummyFrame, "", 0)
            call BlzFrameSetSize(dummyFrameEx, 0.04, 0.04)
            call BlzFrameSetAbsPoint(dummyFrameEx, FRAMEPOINT_CENTER, 0.5318, 0.097)
            call BlzTriggerRegisterFrameEvent(enterTrigger, dummyFrameEx, FRAMEEVENT_MOUSE_ENTER)
           
            call RegisterNativeEvent(EVENT_ITEM_INVENTORY_MOVE, function thistype.onItemMove)
           
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function thistype.onItemDrop)
           
            call RegisterAnyPlayerEvent(EVENT_PLAYER_MOUSE_UP, function thistype.onMouseClick)
        endmethod
    endstruct

    struct Inventory extends array
        implement Alloc
           
        private static trigger closeTrigger
        private static trigger openTrigger
        private static Table frame2Inventory
       
        private framehandle title
        private framehandle closeButton
        private framehandle openIcon
        private framehandle nextButton
        private framehandle prevButton
        private framehandle pageNumber
        private integer slotCount
        private integer columns
        private real borderSize
        private real titleSize
        private real spaceGap

        readonly string emptySlotTexture
        readonly string slotIndicatorModel
        readonly framehandle backdrop
        readonly real buttonSize
       
        framehandle openButton
        IntegerList slots

        method setSlotCount takes integer count returns thistype
            set slotCount = count
            return this
        endmethod

        method setColumnCount takes integer cols returns thistype
            set columns = cols
            return this
        endmethod

        method setTitle takes string str returns thistype
            call BlzFrameSetText(title, GetLocalizedString(str))
            return this
        endmethod

        method setTitleSize takes real size returns thistype
            set titleSize = size
            return this
        endmethod

        method setBorderSize takes real size returns thistype
            set borderSize = size
            return this
        endmethod

        method setButtonSize takes real size returns thistype
            set buttonSize = size
            return this
        endmethod

        method setButtonSpaceGap takes real size returns thistype
            set spaceGap = size
            return this
        endmethod

        method setSlotEmptyTexture takes string texture returns thistype
            set emptySlotTexture = texture
            return this
        endmethod

        method setPosition takes real x, real y returns thistype
            call BlzFrameSetAbsPoint(backdrop, FRAMEPOINT_CENTER, x, y)
            return this
        endmethod
       
        method setOpenButtonPosition takes real x, real y returns thistype
            call BlzFrameSetAbsPoint(openButton, FRAMEPOINT_CENTER, x, y)
            return this
        endmethod
       
        method setOpenButtonTexture takes string str returns thistype
            call BlzFrameSetTexture(openIcon, str, 0, true)
            return this
        endmethod
       
        method setSlotIndicatorModel takes string str returns thistype
            set slotIndicatorModel = str
            return this
        endmethod

        method show takes boolean flag returns nothing
            call BlzFrameSetVisible(backdrop, flag)
        endmethod
       
        method showEx takes player p, boolean flag returns nothing
            if LOCAL_PLAYER == p then
                call BlzFrameSetVisible(backdrop, flag)
            endif
            if flag then
                set player2Inventory[GetPlayerId(p)] = this
            else
                call player2Inventory.remove(GetPlayerId(p))
            endif
        endmethod

        method build takes nothing returns nothing
            local integer rows = slotCount/columns
            local integer i = 0
            local integer j = 0
            local real x = borderSize
            local real y = -borderSize - titleSize
           
            call BlzFrameSetSize(backdrop, columns*buttonSize + (columns - 1)*spaceGap + 2*borderSize, rows*buttonSize + (rows - 1)*spaceGap + 2*borderSize + titleSize)
           
            loop
                exitwhen i == slotCount
                call slots.push(Slot.create(buttonSize, x, y, this))
                set x = x + spaceGap + buttonSize
                set j = j + 1
                if j == columns then
                    set j = 0
                    set x = borderSize
                    set y = y - spaceGap - buttonSize
                endif
                set i = i + 1
            endloop
           
            call BlzFrameSetSize(openButton, buttonSize, buttonSize)

            call BlzFrameSetSize(title, 0, titleSize)
            call BlzFrameSetPoint(title, FRAMEPOINT_CENTER, backdrop, FRAMEPOINT_TOP, 0, -borderSize*.75)
            //call BlzFrameSetSize(pageNumber, 0, titleSize)
            //call BlzFrameSetPoint(pageNumber, FRAMEPOINT_CENTER, backdrop, FRAMEPOINT_BOTTOM, 0, borderSize)
           
            call BlzFrameSetSize(openButton, buttonSize, buttonSize)
            call BlzTriggerRegisterFrameEvent(openTrigger, openButton, FRAMEEVENT_CONTROL_CLICK)

            call BlzFrameSetPoint(closeButton, FRAMEPOINT_TOPRIGHT, backdrop, FRAMEPOINT_TOPRIGHT, 0, 0)
            call BlzFrameSetSize(closeButton, buttonSize, buttonSize)
            call BlzFrameSetText(closeButton, "X")
            call BlzTriggerRegisterFrameEvent(closeTrigger, closeButton, FRAMEEVENT_CONTROL_CLICK)

//            call BlzFrameSetPoint(nextButton, FRAMEPOINT_BOTTOMRIGHT, backdrop, FRAMEPOINT_BOTTOMRIGHT, 0, 0)
//            call BlzFrameSetSize(nextButton, buttonSize, buttonSize)
//            call BlzFrameSetText(nextButton, ">")
//
//            call BlzFrameSetPoint(prevButton, FRAMEPOINT_BOTTOMLEFT, backdrop, FRAMEPOINT_BOTTOMLEFT, 0, 0)
//            call BlzFrameSetSize(prevButton, buttonSize, buttonSize)
//            call BlzFrameSetText(prevButton, "<")
        endmethod
       
        private static method closeAction takes nothing returns nothing
            local SyncData d = GetSyncedData()
            local integer pid = GetPlayerId(d.from)
            local thistype this = player2Inventory[pid]
            local unit selectedUnit = GetMainSelectedUnit(d.readInt(0))
            local item item0
            local item itemSlot
            local framehandle pFrame
            local Slot slot
            local Table tb
           
            if LOCAL_PLAYER == GetTriggerPlayer() then
                call BlzFrameSetVisible(this.backdrop, false)
            endif
           
            call player2Inventory.remove(pid)
           
            set pFrame = playerFrame[pid]
            set playerFrame[pid] = null
            if pFrame != null then
                set slot = Slot.frame2Slot[GetHandleId(pFrame)]
                set tb = Slot.frame2Item[GetHandleId(pFrame)]
                set itemSlot = tb.item[GetHandleId(selectedUnit)]
                call SetItemPosition(itemSlot, WorldBounds.minX, WorldBounds.minY)
                call SetItemVisible(itemSlot, false)
                set itemSlot = null
                set pFrame = null
               
                if LOCAL_PLAYER == GetTriggerPlayer() then
                    call BlzFrameSetVisible(slot.indicator, false)
                    call BlzFrameSetVisible(dummyIcon, false)
                    call BlzFrameSetAllPoints(ORIGIN_ITEM_BUTTON_ONE, dummyFrame)
                    call BlzFrameSetEnable(dummyFrameEx, true)
                endif
               
                set item0 = Slot.unit2Item.item[GetHandleId(selectedUnit)]
                if item0 != null then
                    call UnitAddItem(selectedUnit, item0)
                    call UnitDropItemSlot(selectedUnit, item0, 0)
                    call Slot.unit2Item.item.remove(GetHandleId(selectedUnit))
                    set item0 = null
                endif
            endif
           
            set selectedUnit = null
            call d.destroy()
        endmethod
       
        private static method close takes nothing returns nothing
            local SyncData req = SyncData.create(GetTriggerPlayer())
            call UnfocusFrame(BlzGetTriggerFrame(), GetTriggerPlayer())
            set req.onComplete = Filter(function thistype.closeAction)
            call req.syncInt(GetSelectedUnitIndex())
        endmethod
       
        private static method open takes nothing returns nothing
            local thistype this = frame2Inventory[GetHandleId(BlzGetTriggerFrame())]
            call UnfocusFrame(BlzGetTriggerFrame(), GetTriggerPlayer())
            if LOCAL_PLAYER == GetTriggerPlayer() then
                call BlzFrameSetVisible(.backdrop, true)
            endif
            set player2Inventory[GetPlayerId(GetTriggerPlayer())] = this
        endmethod

        static method createEx takes integer slotCount, integer cols, real buttonSize, real spaceGap, real borderSize, real titleSize, real posX, real posY returns thistype
            local thistype this = allocate()
            local integer rows = slotCount/columns
            local integer i = 0
            local integer j = 0
            local real x = borderSize
            local real y = -borderSize - titleSize

            set .slotCount = slotCount
            set .columns = cols
            set .buttonSize = buttonSize
            set .spaceGap = spaceGap
            set .buttonSize = buttonSize
            set .borderSize = borderSize
            set .titleSize = titleSize

            set backdrop = BlzCreateFrame("QuestButtonBackdropTemplate", GAME_UI, 0, this)
            call BlzFrameSetSize(backdrop, columns*buttonSize + (columns - 1)*spaceGap + 2*borderSize, rows*buttonSize + (rows - 1)*spaceGap + 2*borderSize + titleSize)
            call BlzFrameSetAbsPoint(backdrop, FRAMEPOINT_CENTER, posX, posY)

            set title = BlzCreateFrameByType("TEXT", "InventoryTitle", backdrop, "", this)
            call BlzFrameSetSize(title, 0, titleSize)
            call BlzFrameSetPoint(title, FRAMEPOINT_CENTER, backdrop, FRAMEPOINT_TOP, 0, -borderSize*.75)
            call BlzFrameSetTextAlignment(title, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_TOP)
            call BlzFrameSetScale(title, 1.2)

//            set pageNumber = BlzCreateFrameByType("TEXT", "InventoryPage", backdrop, "", this)
//            call BlzFrameSetSize(pageNumber, 0, titleSize)
//            call BlzFrameSetPoint(pageNumber, FRAMEPOINT_CENTER, backdrop, FRAMEPOINT_BOTTOM, 0, borderSize)
//            call BlzFrameSetTextAlignment(pageNumber, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
//            call BlzFrameSetText(pageNumber, "1")

            set openButton = BlzCreateFrameByType("GLUETEXTBUTTON", "InventoryOpen", GAME_UI, "", this)
            set openIcon = BlzCreateFrameByType("BACKDROP", "InventoryOpenIcon", openButton, "", this)
            call BlzFrameSetAllPoints(openIcon, openButton)
           
            set closeButton = BlzCreateFrame("ScriptDialogButton", backdrop, 0, this)
            call BlzFrameSetPoint(closeButton, FRAMEPOINT_TOPRIGHT, backdrop, FRAMEPOINT_TOPRIGHT, 0, 0)
            call BlzFrameSetSize(closeButton, buttonSize, buttonSize)
            call BlzFrameSetText(closeButton, "X")
            call BlzTriggerRegisterFrameEvent(closeTrigger, closeButton, FRAMEEVENT_CONTROL_CLICK)

//            set nextButton = BlzCreateFrame("ScriptDialogButton", backdrop, 0, this)
//            call BlzFrameSetPoint(nextButton, FRAMEPOINT_BOTTOMRIGHT, backdrop, FRAMEPOINT_BOTTOMRIGHT, 0, 0)
//            call BlzFrameSetSize(nextButton, buttonSize, buttonSize)
//            call BlzFrameSetText(nextButton, "→")
//
//            set prevButton = BlzCreateFrame("ScriptDialogButton", backdrop, 0, this)
//            call BlzFrameSetPoint(prevButton, FRAMEPOINT_BOTTOMLEFT, backdrop, FRAMEPOINT_BOTTOMLEFT, 0, 0)
//            call BlzFrameSetSize(prevButton, buttonSize, buttonSize)
//            call BlzFrameSetText(prevButton, "←")
//            call BlzFrameSetVisible(prevButton, false)

            set slots = IntegerList.create()
            loop
                exitwhen i == slotCount
                call slots.push(Slot.create(buttonSize, x, y, this))
                set x = x + spaceGap + buttonSize
                set j = j + 1
                if j == columns then
                    set j = 0
                    set x = borderSize
                    set y = y - spaceGap - buttonSize
                endif
                set i = i + 1
            endloop

            call BlzFrameSetVisible(backdrop, false)

            return this
        endmethod

        static method create takes nothing returns thistype
            local thistype this = allocate()

            set backdrop = BlzCreateFrame("QuestButtonBackdropTemplate", GAME_UI, 0, this)

            set title = BlzCreateFrameByType("TEXT", "InventoryTitle", backdrop, "", this)
            call BlzFrameSetTextAlignment(title, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_TOP)
            call BlzFrameSetScale(title, 1.2)

//            set pageNumber = BlzCreateFrameByType("TEXT", "InventoryPage", backdrop, "", this)
//            call BlzFrameSetTextAlignment(pageNumber, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
//            call BlzFrameSetText(pageNumber, "1")

            set openButton = BlzCreateFrameByType("GLUETEXTBUTTON", "InventoryOpen", GAME_UI, "", this)
            set openIcon = BlzCreateFrameByType("BACKDROP", "InventoryOpenIcon", openButton, "", this)
            call BlzFrameSetAllPoints(openIcon, openButton)
            set frame2Inventory[GetHandleId(openButton)] = this
           
            set closeButton = BlzCreateFrame("ScriptDialogButton", backdrop, 0, this)

            //set nextButton = BlzCreateFrame("ScriptDialogButton", backdrop, 0, this)

            //set prevButton = BlzCreateFrame("ScriptDialogButton", backdrop, 0, this)

            set slots = IntegerList.create()

            call BlzFrameSetVisible(backdrop, false)

            return this
        endmethod
       
        private static method init takes nothing returns nothing
            set GAME_UI = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
            set LOCAL_PLAYER = GetLocalPlayer()
            set player2Inventory = Table.create()
            set closeTrigger = CreateTrigger()
            call TriggerAddCondition(closeTrigger, Filter(function thistype.close))
            set openTrigger = CreateTrigger()
            call TriggerAddCondition(openTrigger, Filter(function thistype.open))
            set frame2Inventory = Table.create()
        endmethod
       
        implement MyModule
    endstruct
   
    private function MouseOnItem takes nothing returns nothing
        local integer index = HoverOriginButton_CurrentSelectedButtonIndex - HoverOriginButton_ItemButtonOffset
        if not IsMouseDraggingItem() then
            set originItemSlot = index
        endif
        if index > 0 then
            call BlzFrameSetEnable(dummyFrameEx, true)
            set originItemSlotEx = index
        endif
    endfunction
   
    private function MouseLeftItem takes nothing returns nothing
        if not IsMouseDraggingItem() then
            set originItemSlot = -1
        endif
        set originItemSlotEx = -1
    endfunction
   
    private function Init takes nothing returns nothing
        call HoverOriginButtonAdd(false, function MouseOnItem)
        call HoverOriginButtonAddClose(function MouseLeftItem)
        //set item2Unit = Table.create()
    endfunction
endlibrary

Screenshots


inventorygif-gif.424255

How to Import

Wurstscript

vJASS


  • Import the .toc and .fdf files from test map to your own
  • Import the code to your project either directly or as a dependency via the Wurstsetup app or git

  • Import the entire Inventory folder to your map's trigger editor.
  • In the Trigger Editor, under the JassHelper submenu, make sure Enable JassHelper and Enable vJass are both checked.

Changelog

Wurstscript

vJASS


1.0.0 7 February 2023:
  • First Release
1.0.1 7 February 2023:
  • Fixes for potential desyncs.
1.0.2 7 February 2023:
  • Fixed the MPI aspect of the system - visuals and item handling
  • Items are now correctly stored in custom inventory per player

1.0.7 25 February 2023:
  • First Release
1.0.8 25 February 2023:
  • Fixed bugs with item selection with closing the inventory and reselection (Thanks to Izzetin)

Credits

Wurstscript

vJASS

General


Tasyen - FDF files, HoverOriginButton
Bannar - InventoryEvent

Tasyen - HoverOriginButton, GetMainUnitSelected, Idea for open button icon
Bannar - InventoryEvent and List<T>
Nestharus - WorldBounds
edo494 - StringIterator
TriggerHappy - Sync
AGD - Alloc
Bribe - Table
Vexorian, Bribe & Magtheridon96 - TimerUtils

Cokemonkey11 - Description Template
Contents

Inventory 1.0.2 (Map)

Inventory v1.0.8 (Map)

Level 18
Joined
Oct 17, 2012
Messages
820
Did you have to convert Tasyen's Lua script to Wurst manually? Or is there an existing transcompiler for that?
I had to convert the script manually. However, the Wurst compiler does run JASSHelper beforehand, so you could actually use functions from compiled vJASS code in the same map. The compiler doesn't work with maps that have Lua as the main scripting language.
Fixed
 
Last edited:
Level 8
Joined
May 19, 2016
Messages
146
Thats a really nice Inventory i was looking for something like that.
I hope you upload a vJASS version at anytime that would be awesome ! :)
 
Level 18
Joined
Oct 17, 2012
Messages
820
vJASS version has been released and is currently ahead of the Wurst version.

Some differences between versions:
  • (MUI) Each unit has their own custom inventory in vJASS
    • Select another unit to see its unique inventory
    • Switch to another unit in a group selection to see its unique inventory
  • All units of a player share a custom inventory in Wurst (This might change later or be kept as an addition)
  • The vJASS version does not require any imports other than code for now.
  • API exists for designing the inventory in vJASS
To do list:
  • Consider item charges (Display charges on slot button and adapt to splits and stacks)
  • (Option) Display another unit's inventory at the same time as your own unit
    • Drag and drop items from one custom inventory to another
  • Give the ability to add and remove items to inventory via code
  • Experimental Move the inventory on the screen via a button in game.
 
Last edited:
Level 18
Joined
Oct 17, 2012
Messages
820
I think theres a bug in the 1.0.7 JASS version. if you try to place an Item from 1 free Bag slot to another (With Button Effect) the Item-Icon isnt moveable anymore and is stuck on the screen (I can provide screenshots if needed)
This has been fixed as well as another bug with closing the inventory. Thank you for testing the system and spotting this bug!
 
Level 18
Joined
Oct 17, 2012
Messages
820
At the moment, the inventory is just a storage device. However, such is not off the table. I might abuse the inventory ability to do so or figure out something else. Perhaps, I would provide custom events for when an item is stored in or removed from the inventory. From there, you can add any abilities you like given the item concerned.
 
At the moment, the inventory is just a storage device. However, such is not off the table. I might abuse the inventory ability to do so or figure out something else. Perhaps, I would provide custom events for when an item is stored in or removed from the inventory. From there, you can add any abilities you like given the item concerned.
That's OK. This inventory is player-owned and not unit-owned, right?
 
Level 18
Joined
Oct 17, 2012
Messages
820
The Wurst version is player-owned, but the vJASS version is unit-owned. I am going to introduce a lock mechanism, so you can lock the inventory to a unit. From there, you can lock the inventory to a dummy unit. Then all units of a player can share an inventory. Or I might just make another version that is player based. It will definitely be easier to code than the unit-based one.
 
Last edited:
Level 8
Joined
May 19, 2016
Messages
146
You can have 6 Active Items. With this Inventory you can ''Click'' 1 Item in the Custom Inventory and the Unit gets the Ability/Stats of the clicked item. so you can have 7 Active Items at once if you leave the Inventory open. is this intended ?
 
Level 18
Joined
Oct 17, 2012
Messages
820
If the system is capable of holding tens of slots, it would make sense for the inventory to be player-owned, as a common stash for units owned by a player.
With the way I coded the system, the number of slots you can have all together in one map is the array limit in theory. You are also limited by the operation limit. So, if you have one inventory instance, that one inventory could have 32768 slots. Each inventory instance will divide up the number of slots. I will soon change this.

Some users may want the system to be player-based, while others may want it to be unit-based. I am going to cater to both sides since that would give the system greater potential for branching. I just haven't decided yet how to approach it. A two in one system or two separate systems?

You can have 6 Active Items. With this Inventory you can ''Click'' 1 Item in the Custom Inventory and the Unit gets the Ability/Stats of the clicked item. so you can have 7 Active Items at once if you leave the Inventory open. is this intended ?
It wasn't intended but it is a consequence of the method that I used to enable drag and drop. To remedy this, I could use dummy items or disable abilities. I will look into this. Besides, this is only when an item is selected. Other times, all is well.
 
Last edited:
Level 4
Joined
Jan 22, 2016
Messages
23
That system is exactly what i was looking for !!! Thnx !!!
Allthought i have no knowledge of vJASS or Wrust and in the how to import tab about vJASS you say just copy the folder ...

I did but i get like 2228 compile errors , can you please help me to fix this .
Untitled.png
 
Level 4
Joined
Oct 31, 2011
Messages
69
The system is very good, but I tested it in multiplayer and it doesn't work correctly, when two players try to open the bag at the same time it crashes. I did the test in the jass version.
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,870
While I'm not qualified to review the Wurst version, since I don't really know it, I still can say this is a pretty good system. Perhaps you could add the clicking sound when one clicks on the button to open the inventory, and highlighting, to show it's interactable and not simply a frame with no events for decoration. I only took a glimpse on the VJASS version. I'm not sure if it's actually possible for one reviewer to review part of a resource, and another one review the other part. The only current Wurst reviewer I know is Cokemonkey11.
 
Level 8
Joined
May 19, 2016
Messages
146
Is there a way to put the item directly into the Bag from the ground ? that would be awesome.

-Edit: Items stacks and Item Charges are not showing.
Would be good to know how many Potions Items are left.
 
Last edited:
Level 17
Joined
Apr 13, 2008
Messages
1,597
The vJass system crashed when I was putting an item to the inventory AND I had tens of thousands of destructibles / doodads. I spent a day to troubleshoot it :')
I selected a bunch of doodads and clicked Edit / Select All Special then delete.
Turns out that the root cause of the issue was Blizzard's Volcano doodad.
I don't know why, but it crashed the game.
 
Last edited:
Level 17
Joined
Apr 13, 2008
Messages
1,597
Another issue to report:
System crashes map when loading saves.
The root cause is Blizzard's bug with the frames being tripped by the vJass HoverOriginButton library
To reproduce:

(I'm using the latest version of Warcraft III.)

Take a completely empty map.
Inject vJass HoverOriginButton system into the triggers of the map.
Start the map.
Do a save.
Load the save.
After it loads and you press the ANY key, Warcraft III will crash.

Here is the relevant info.

To fix:

Copy/paste this library by Taysen:

JASS:
library FrameLoader initializer init_function
// in 1.31 and upto 1.32.9 PTR (when I wrote this). Frames are not correctly saved and loaded, breaking the game.
// This library runs all functions added to it with a 0s delay after the game was loaded.
// function FrameLoaderAdd takes code func returns nothing
// func runs when the game is loaded.
globals
private trigger eventTrigger = CreateTrigger()
private trigger actionTrigger = CreateTrigger()
private timer t = CreateTimer()
endglobals
function FrameLoaderAdd takes code func returns nothing
call TriggerAddAction(actionTrigger, func)
endfunction
private function timerAction takes nothing returns nothing
call TriggerExecute(actionTrigger)
endfunction
private function eventAction takes nothing returns nothing
call TimerStart(t, 0, false, function timerAction)
endfunction
private function init_function takes nothing returns nothing
call TriggerRegisterGameEvent(eventTrigger, EVENT_GAME_LOADED)
call TriggerAddAction(eventTrigger, function eventAction)
endfunction
endlibrary

And then find all the code in the Inventory library and some rewrite it to work around the bug :')
 
Last edited:
Level 4
Joined
Oct 31, 2011
Messages
69
Is this system still being updated? There is a way to combine this system with Codeless Save and Load?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,511
Is this system still being updated? There is a way to combine this system with Codeless Save and Load?
Codeless Save and Load allows you to save Integers. Integers can represent anything and everything. So you can save anything you want.

However, glancing at the Jass API, this system doesn't seem to have a simple AddItemToInventorySlot() function. Instead, everything seems to be hardcoded based on Mouse events (dragging and dropping). You'll have to request that the Author adds some kind of functionality to build an Inventory with an array of predetermined items. Or add it yourself if you understand the code.
 
Level 4
Joined
Oct 31, 2011
Messages
69
Codeless Save and Load allows you to save Integers. Integers can represent anything and everything. So you can save anything you want.

However, glancing at the Jass API, this system doesn't seem to have a simple AddItemToInventorySlot() function. Instead, everything seems to be hardcoded based on Mouse events (dragging and dropping). You'll have to request that the Author adds some kind of functionality to build an Inventory with an array of predetermined items. Or add it yourself if you understand the code.
Thanks for the explanation, it would be amazing if you could combine these systems.
 
Top