• 🏆 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!

Equipment v2.5.2

Equipment
Code

Library Equipment extends the Warcraft III inventory surface
by using a dummy unit and ability buttons as user interface.
Equipped items can have own ability- and/or abstract bonus lists
as well as special effect models and animatags.



~~~~~~~~~~~~~~~~~~~~~~~~~~
Color_balance.png
Import Instructions

  • Copy library Equipment and ErrorMessage into your map.
  • Copy the equipment dummy unit from the demo map into your map.
  • Read over the global user settings inside library Equipment.



~~~~~~~~~~~~~~~~~~~~~~~~~~

Copy.png
Library Equipment

JASS:
library Equipment /* v2.5.2
*************************************************************************************
*
*   Library Equipment extends the Warcraft III inventory surface
*   by using a dummy unit and ability buttons as user interface.
*   Equipped items can have own ability- and/or abstract bonus lists
*   as well as special effect models and animatags.
*
**************************************************************************************
*
*   */ uses /*
*
*       */ ErrorMessage                        /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
*
*       */ optional Bonus                   /* github.com/nestharus/JASS/tree/master/jass/Systems/Bonus
*       */ optional ItemSet                 /* hiveworkshop.com/forums/spells-569/itempower-v1-1-0-0-a-243917/
*       */ optional BonusMod                /* wc3c.net/showthread.php?t=107940
*   
************************************************************************************
*
*   1. Import Instruction:
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       • Copy library Equipment, ErrorMessage into your map.
*       • Copy the Equipment dummy unit into your map.
*       • For startes it's recommended to import the demo abilties aswell.
*        • Read over the user settings. ( Below the API )
*       • Within the EquipmentManual you'll find all information, about setting up Equipment.
*       
*   2. Equipment with: ItemPower, Bonus and ItemSet ( any combination of these three )
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       Everything is installed fully automatic, you don't have do anything. 
*       Just copy and paste the desired libraries into your map.
*
*   3. API
*   ¯¯¯¯¯¯
*
*       Creator/Destructor:
*       ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*           function CreateEquipment  takes unit whichUnit returns Equipment
*           function DestroyEquipment takes unit whichUnit returns nothing            
*        
*      Pre-game Functions:
*      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*           function RegisterEquipmentClass  takes integer class,  integer emptyIcon returns nothing
*           function RegisterEquipmentItemId takes integer itemId, integer icon, integer class, boolean twohanded, string animation returns EquipmentItem
*         
*           function ItemIdAddAbility takes integer itemId, integer abilityId, integer level returns nothing
*           function ItemIdAddBonus   takes integer itemId, integer bonus, integer amount returns nothing
*                • Each item id ( example: 'I00A') can have individual affixes.
*                  Affixes are added/removed when items are equipped/unequipped.
*                  Those affixes can be Warcraft III abilities or abstract bonuses from library BonusMod.        
*
*           function ItemIdAddSpecialEffect takes integer itemId, string file, string attachPointName returns nothing
*
*           function EnableItemIdForUnitId takes integer itemId, integer unitId, boolean flag returns nothing
*               • You can disable item ids for certain unit ids.
*                 Example: call EnableItemIdForUnitId('I00A', 'hfoo', false).
*
*      Equipment events:
*      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*           function RegisterItemEquipEvent    takes code func returns nothing
*           function RegisterItemUnequipEvent  takes code func returns nothing
*        
*           constant function GetTriggerItemId takes nothing returns integer
*           constant function GetEquippingUnit takes nothing returns unit
*
*      Ingame Functions:
*      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*           function UnitHasEquipment      takes unit whichUnit returns boolean
*           function UnitItemIdInSlot      takes unit whichUnit, integer class returns integer
*           function IsEquipmentClassEmpty takes unit whichUnit, integer class returns boolean
*           function UnequipItemClass      takes unit whichUnit, integer class returns nothing
*           function EquipItem             takes unit whichUnit, item whichItem returns boolean
*           function EquipItemIdById       takes unit whichUnit, integer itemId returns boolean
*           function UnitHasItemIdEquipped takes unit whichUnit, integer itemId returns boolean
*
*      Special:
*      ¯¯¯¯¯¯¯¯
*           function EquipmentEnableAnimationTags takes unit whichUnit, boolean flag returns nothing
*               • Some units shouldn't change animation tags, because it messes with their model.
*                 A good is example is the demon hunter with an active metamorphosis,
*                 because metamorphosis also uses animationtags.
*
**************************************************************************************/

// User settings:
// ==============

    globals

        //======================================================
        // Item Class Configuration
        //
        // Do not forget to initialize your classes.
        // For that use function RegisterEquipmentClass(class, icon)
        // For example: call RegisterEquipmentClass(ITEM_CLASS_MAINHAND, 'A00D')

        // Set the total amount of item classes in your map.
        //
        private constant integer ITEM_CLASSES = 9

        // Mainhand and offhand are default classes which do not require any changes.
        //
        constant integer ITEM_CLASS_MAINHAND  = 0
        constant integer ITEM_CLASS_OFFHAND   = 1
     
        // Add as many further classes as desired. These below are just examples.
        // For that start of with two and increase 1 per class.
        //
        constant integer ITEM_CLASS_AMULET    = 2
        constant integer ITEM_CLASS_ARMOR     = 3
        constant integer ITEM_CLASS_BOOTS     = 4
        constant integer ITEM_CLASS_GLOVES    = 5
        constant integer ITEM_CLASS_RING      = 6
        constant integer ITEM_CLASS_HELMET    = 7
        constant integer ITEM_CLASS_SPECIAL   = 8
     
        //======================================================
        // Object Data Configuration
        //
     
        // Set the data value of the inventory dummy unit.
        //
        private constant integer INVENTORY_DUMMY_ID   = 'h007'
     
        // Set the data value of the ability to close the inventory.
        //
        private constant integer EXIT_ABILITY         = 'A00G'
     
        // Set the data value of the ability to open the inventory
        //
        private constant integer OPEN_ABILITY         = 'A003'
     
        // Set the data value of the ability, which gives the dummy the same inventory as the hero has.
        private constant integer INVENTORY_ABILITY    = 'AInv'
     
        // Set the data value of the ability shown in the offhand slot, when a twohanded weapon is equipped.
        //
        private constant integer TWOHAND_ABILITY      = 'A00Z'
        private constant boolean SHOW_TWOHAND_ABILITY = true
  
        //======================================================
        // Misc Configuration

        // This text is shown, when a unit wants to equip an item, which it isn't allowed to.
        // For example: " can't equip " becomes ingame: Rifleman can't equip Giant Sword!
        //
        private constant string UNABLE_TO_EQUIP_STRING = " can't equip "
    endglobals
 
//==============================================================================
// Functions, constants and variables used by Equipment. Make changes carefully.
//==============================================================================
 
    globals
        //======================================================
        // Constants
        //
     
        // Constants concerning the interface error msg.
        private constant string    ERROR_MSG_PREFIX   = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"
        private constant real      ERROR_MSG_POS_X    = 0.52
        private constant real      ERROR_MSG_POS_Y    = 0.96
        private constant real      ERROR_MSG_DURATION = 5.00

        // Misc constants.
        private constant integer   ORDER_ID_STOP      = 0xD0004
     
        // Data storage constants.
        private constant hashtable TABLE                  = InitHashtable()
     
        // Event related constans.
        private constant trigger   EQUIP              = CreateTrigger()
        private constant trigger   UNEQUIP            = CreateTrigger()
     
        //======================================================
        // Variables
        //
     
        // Event related variables.
        private unit    eventUnit = null
        private integer eventItem = 0
     
        // Misc variables.
        private sound   error     = null 
    endglobals

//***************************************************************************
//*
//*  Unit Indexer Utility
//*
//***************************************************************************
 
    // Allows you do decide whether or not the dummy unit should be indexed.
    private function ToogleUnitIndexer takes boolean enable returns boolean
        local boolean prev = true
 
        static if LIBRARY_UnitIndexer then
            set prev = UnitIndexer.enabled
            set UnitIndexer.enabled = enable
        endif
 
        return prev
    endfunction

//***************************************************************************
//*
//*  Equipment Event
//*
//***************************************************************************

    constant function GetEquippingUnit takes nothing returns unit
        return eventUnit
    endfunction
 
    constant function GetTriggerItemId takes nothing returns integer
        return eventItem
    endfunction
 
    function RegisterItemEquipEvent takes code func returns nothing
        call TriggerAddCondition(EQUIP, Condition(func))
    endfunction
 
    function RegisterItemUnequipEvent takes code func returns nothing
        call TriggerAddCondition(UNEQUIP, Condition(func))
    endfunction
 
    private function Fire takes trigger eventTrigger, unit source, integer itemId returns nothing
        local integer prevItem = eventItem
        local unit    prevUnit = eventUnit
     
        set eventItem = itemId
        set eventUnit = source
        call TriggerEvaluate(eventTrigger)
        set eventItem = prevItem
        set eventUnit = prevUnit
        set prevUnit  = null
    endfunction
 
//***************************************************************************
//*
//*  Custom Interface Error
//*
//***************************************************************************
 
    private function InterfaceError takes unit source, item eventItem returns nothing
        local string msg = ERROR_MSG_PREFIX + GetUnitName(source) + UNABLE_TO_EQUIP_STRING + GetItemName(eventItem) + "|r"
     
        call DisplayTimedTextToPlayer(GetOwningPlayer(source), ERROR_MSG_POS_X, ERROR_MSG_POS_Y, ERROR_MSG_DURATION, msg)
        if GetLocalPlayer() == GetOwningPlayer(source) then
            call StartSound(error)
        endif
    endfunction
 
    function UnitHasEquipment takes unit whichUnit returns boolean
        return HaveSavedInteger(TABLE, GetHandleId(whichUnit), 0)
    endfunction

//***************************************************************************
//*
//*  Initialization Utility
//*
//***************************************************************************

    private module Init
        private static method onInit takes nothing returns nothing
            call thistype.init()
        endmethod
    endmodule

//***************************************************************************
//*
//*  Equipment Source Code
//*
//***************************************************************************

    //===========================================================================
    // Item id based bonuses:
    // Data structure is a stack without destructors.
    // The maximum allocation limit is 8190 instances.
    // An ItemBonus can be of type ability or an abstract bonus from BonusMod.
 
    private struct ItemBonus extends array
        private static thistype collectionCount = 0
        private static thistype nodeCount       = 0
     
        //===========================================================================
        // Struct members
     
        readonly thistype first
        readonly thistype next
     
        readonly integer  bonus
        readonly integer  quantity
        readonly boolean  isAbility
    
        method add takes integer bonus, integer quantity, boolean flag returns nothing
            local thistype node = thistype(0).next
         
            if node == 0 then
        
                debug call ThrowError(nodeCount == 8191, "Equipment", "ItemBonus(this).add", "thistype", this, "Overflow!")
        
                set node         = nodeCount + 1
                set nodeCount    = node
            else
                set thistype(0).next = next
            endif
            set node.next      = first
            set first          = node
            set node.bonus     = bonus
            set node.quantity  = quantity
            set node.isAbility = flag
        endmethod
    
        static method create takes nothing returns thistype
            local thistype this = thistype(0).first
         
            if this == 0 then
        
                debug call ThrowError(collectionCount == 8191, "Equipment", "ItemBonus.create", "thistype", this, "Overflow!")
        
                set this = collectionCount + 1
                set collectionCount = this
            else
                set thistype(0).first = first
            endif
            set first = 0
            return this
        endmethod
    
    endstruct

    //===========================================================================
    // EquipmentItem struct to extends item data fields

    private struct EquipmentItem extends array
        private static integer alloc = ITEM_CLASSES + 1
    
        //===========================================================================
        // Struct members
     
        readonly integer   icon
        readonly integer   class
        readonly integer   itemId
        readonly string    tag
        readonly string    file
        readonly string    position
        readonly boolean   twohanded
        readonly ItemBonus bonuses
    
        static method operator [] takes integer itemId returns EquipmentItem
            return LoadInteger(TABLE, itemId, 0)
        endmethod
    
        method destroyFx takes unit source returns nothing
            local integer id = GetHandleId(source)
         
            if HaveSavedHandle(TABLE, id, itemId) then
                call DestroyEffect(LoadEffectHandle(TABLE, id, itemId))
                call RemoveSavedHandle(TABLE, id, itemId)
            endif
        endmethod
    
        method addFx takes unit source returns nothing
            local integer id = GetHandleId(source)
         
            if file != null and not HaveSavedHandle(TABLE, id, itemId) then
                call SaveEffectHandle(TABLE, id, itemId, AddSpecialEffectTarget(file, source, position))
            endif
        endmethod
    
        static method createClass takes integer class, integer icon returns thistype
            debug call ThrowError(class < 0,                           "EquipmentItem", "createClass", "class", class, "The class argument must be greater than zero!")
            debug call ThrowError(class > ITEM_CLASSES,                 "EquipmentItem", "createClass", "class", class, "Class out of bound! Increase the amount of ITEM_CLASSES in the Equipment setup!")
            debug call ThrowError(HaveSavedInteger(TABLE, class, 0), "EquipmentItem", "createClass", "class", class, "Attempt to override class!")         
        
            call SaveInteger(TABLE, class, 0, 0)
            set thistype(class).class = class
            set thistype(class).icon  = icon
            return class
        endmethod
    
        method addBonus takes integer bonus, integer quantity, boolean isAbility returns nothing
            if 0 == bonuses then
                set bonuses = ItemBonus.create()
            endif
            call bonuses.add(bonus, quantity, isAbility)
        endmethod
    
        method removeBonuses takes unit source returns nothing
            local ItemBonus node
         
            if bonuses == 0 then
                return
            endif
            set node = bonuses.first
            loop
                exitwhen node == 0
                if node.isAbility then
                    call UnitRemoveAbility(source, node.bonus)
                else
                    static if LIBRARY_Bonus then
                        call AddUnitBonus(source, node.bonus, -node.quantity)
                    elseif LIBRARY_BonusMod then
                        call AddUnitBonus(source, node.bonus, -node.quantity)
                    endif
                endif
                set node = node.next
            endloop
        endmethod
    
        method addBonuses takes unit source returns nothing
            local ItemBonus node
         
            if bonuses == 0 then
                return
            endif
            set node = bonuses.first
            loop
                exitwhen node == 0
                if node.isAbility then
                    if UnitAddAbility(source, node.bonus) then
                        call UnitMakeAbilityPermanent(source, true, node.bonus)
                        call SetUnitAbilityLevel(source, node.bonus, node.quantity)
                    endif
                else
                    static if LIBRARY_Bonus then
                        call AddUnitBonus(source, node.bonus, node.quantity)
                    elseif LIBRARY_BonusMod then
                        call AddUnitBonus(source, node.bonus, node.quantity)
                    endif
                endif
                set node = node.next
            endloop
        endmethod
    
        method addSpecialEffect takes string str, string attachPointName returns nothing
            debug call ThrowWarning(StringLength(file) > 0, "Equipment", "addSpecialEffect", "file", this, "Attempt to override" + file + " for [" + GetObjectName(itemId) + "] to " + str + "!")

            set file     = str
            set position = attachPointName
        endmethod
     
        static method create takes integer itemTypeId, integer abilityIcon, integer itemClass, boolean isTwohanded, string animation returns thistype
            local thistype this = thistype.alloc + 1
            set thistype.alloc  = integer(this)
        
            debug call ThrowError(itemTypeId == 0,                        "Equipment", "EquipmentItem.create", "itemTypeId", 0,          "Invalid item id ( 0 )")
            debug call ThrowError(HaveSavedInteger(TABLE, itemTypeId, 0), "Equipment", "EquipmentItem.create", "itemTypeId", itemTypeId, "Attempt to double register [" + GetObjectName(itemTypeId) + "]")
            debug call ThrowError(not HaveSavedInteger(TABLE, class, 0),  "Equipment", "EquipmentItem.create", "class",      class,      "Unknown class for object [" + GetObjectName(itemTypeId) + "]")
        
            call SaveInteger(TABLE, itemTypeId, 0, integer(this))
            set itemId    = itemTypeId
            set class     = itemClass
            set icon      = abilityIcon
            set bonuses   = 0
            set twohanded = isTwohanded and itemClass == ITEM_CLASS_MAINHAND

            if itemClass == ITEM_CLASS_MAINHAND or itemClass == ITEM_CLASS_OFFHAND then
                set tag = animation
            else
                set tag = null
            endif
        
            return this
        endmethod
     
    endstruct

    //===========================================================================
    // Equipment struct

    struct Equipment

        private static EquipmentItem twohandIcon = 0

        //===========================================================================
        // Struct members

        readonly unit    source
        readonly string  animtag
     
        readonly EquipmentItem array itemSlot [ITEM_CLASSES]
    
        private  unit    dummy
        private  trigger trig

        //===========================================================================
        // Typecast unit to Equipment instance

        static method operator [] takes unit whichUnit returns thistype
            return LoadInteger(TABLE, GetHandleId(whichUnit), 0)
        endmethod

        method operator exists takes nothing returns boolean
            return trig != null
        endmethod

        method hasItemId takes integer itemId returns boolean
            local EquipmentItem node = EquipmentItem[itemId]
            return itemSlot[node.class] == node
        endmethod

        //===========================================================================
        // Syncing source and dummy inventories
     
        private method sync takes nothing returns nothing
            local integer index = UnitInventorySize(source)
            loop
                exitwhen 0 >= index
                set index = index - 1
                call RemoveItem(UnitItemInSlot(dummy, index))
                if UnitAddItemToSlotById(dummy, GetItemTypeId(UnitItemInSlot(source, index)), index) then
                    call SetItemDroppable(UnitItemInSlot(dummy, index), false)
                endif
            endloop
        endmethod

        private method refreshAnimation takes nothing returns nothing
            local string s
        
            if itemSlot[ITEM_CLASS_OFFHAND] != 0 and itemSlot[ITEM_CLASS_OFFHAND] != twohandIcon then
                set s = itemSlot[ITEM_CLASS_OFFHAND].tag
            else
                set s = itemSlot[ITEM_CLASS_MAINHAND].tag
            endif
            call AddUnitAnimationProperties(source, animtag, false)
            call AddUnitAnimationProperties(source, s, true)
            set animtag = s
        endmethod

        private method equipEmptyClass takes integer class returns nothing
            call UnitAddAbility(dummy, EquipmentItem(class).icon)
            call UnitMakeAbilityPermanent(dummy, true, EquipmentItem(class).icon)
            set itemSlot[class] = 0
        endmethod
    
        private method equipTwohandIcon takes nothing returns nothing
            call UnitAddAbility(dummy, twohandIcon.icon)
            call UnitMakeAbilityPermanent(dummy, true, twohandIcon.icon)
            set itemSlot[ITEM_CLASS_OFFHAND] = twohandIcon
        endmethod

        //===========================================================================
        // Equip and unequip

        method unequip takes integer class returns boolean
            local EquipmentItem eventItem = itemSlot[class]
        
            debug call ThrowWarning(not exists, "Equipment", "unequip", "thistype", this, "Running unequip for a null instance!")
     
            if exists then
                if eventItem != 0 then
            
                    if eventItem.twohanded then
                        call UnitRemoveAbility(dummy, TWOHAND_ABILITY)
                        call equipEmptyClass(ITEM_CLASS_OFFHAND)
                    endif
        
                    call eventItem.removeBonuses(source)             
                    call eventItem.destroyFx(source)
                    call UnitRemoveAbility(dummy, eventItem.icon)
                    call UnitAddItemById(source, eventItem.itemId)
                    static if LIBRARY_ItemPower then
                        call ItemPower_UnitRemoveItemId(source, eventItem.itemId)
                    endif
                    static if LIBRARY_ItemSet then
                        call ItemSet.onItemUnequip(source, eventItem.itemId)
                    endif
            
                    call Fire(UNEQUIP, source, eventItem.itemId)
                    set itemSlot[class] = 0
                    if LoadBoolean(TABLE, GetHandleId(source), 0) then
                        call refreshAnimation()
                    endif
                    return true
                else
                    return UnitRemoveAbility(dummy, EquipmentItem(class).icon)
                endif
            endif
     
            return false
        endmethod

        method equip takes integer itemId returns boolean
            local EquipmentItem toEquip
            local EquipmentItem mainEquip
        
            debug call ThrowWarning(not exists, "Equipment", "equip", "thistype", this, "Running equip for a null instance!")
     
            // Evaluate if the desired item id is a valid equipment item.
            if not HaveSavedInteger(TABLE, itemId, 0) or not exists then
                return false
            endif
            set toEquip   = EquipmentItem[itemId]
            set mainEquip = itemSlot[ITEM_CLASS_MAINHAND]
        
            if mainEquip != 0 then
                if toEquip.twohanded or mainEquip.twohanded and (toEquip.class == ITEM_CLASS_OFFHAND or toEquip.class == ITEM_CLASS_MAINHAND) then
                    call unequip(ITEM_CLASS_OFFHAND)
                    call unequip(ITEM_CLASS_MAINHAND)
                    if not toEquip.twohanded and toEquip.class == ITEM_CLASS_MAINHAND then
                        call equipEmptyClass(ITEM_CLASS_OFFHAND)
                    elseif toEquip.class == ITEM_CLASS_OFFHAND then
                        call equipEmptyClass(ITEM_CLASS_MAINHAND)
                    endif
                endif
            endif
            call unequip(toEquip.class)
        
            if toEquip.twohanded then
                call unequip(ITEM_CLASS_OFFHAND)
                static if SHOW_TWOHAND_ABILITY then
                    call equipTwohandIcon()
                else
                    set itemSlot[ITEM_CLASS_OFFHAND] = twohandIcon
                endif
            endif
        
            call toEquip.addFx(source)
            call toEquip.addBonuses(source)
            call UnitAddAbility(dummy, toEquip.icon)
            call UnitMakeAbilityPermanent(dummy, true, toEquip.icon)
            static if LIBRARY_ItemPower then
                call ItemPower_UnitAddItemId(source, toEquip.itemId)
            endif
            static if LIBRARY_ItemSet then
                call ItemSet.onItemEquip(source, toEquip.itemId)
            endif
        
            set itemSlot[toEquip.class] = toEquip
        
            // Check if the unit is allowed to use custom animation tags.
            if LoadBoolean(TABLE, GetHandleId(source), 0) then
                call refreshAnimation()
            endif
        
            // Fire the event trigger.
            call Fire(EQUIP, source, itemId)
            return true
        endmethod
     
        //===========================================================================
        // Native trigger event response
     
                              // EVENT_UNIT_SPELL_CAST
        private static method unitSpellCast takes unit eventUnit, integer abilityId returns nothing
            local thistype this = LoadInteger(TABLE, GetHandleId(eventUnit), 0) 
            local integer  class
        
            if abilityId == EXIT_ABILITY then
                if GetLocalPlayer() == GetOwningPlayer(eventUnit) then
                    call ClearSelection()
                    call SelectUnit(source, true)
                endif
            
            elseif abilityId == OPEN_ABILITY then
                call SetUnitX(dummy, GetUnitX(eventUnit))
                call SetUnitY(dummy, GetUnitY(eventUnit))
                if GetLocalPlayer() == GetOwningPlayer(eventUnit) then
                    call ClearSelection()
                    call SelectUnit(dummy, true)
                endif
                call sync()
        
            // Check if the event ability has been casted by the dummy.
            elseif GetUnitTypeId(eventUnit) == INVENTORY_DUMMY_ID then
                set class = 0
                loop
                    exitwhen itemSlot[class].icon == abilityId or class >= ITEM_CLASSES
                    set class = class + 1
                endloop
            
                if class < ITEM_CLASSES then
                    call unequip(class)
                    call equipEmptyClass(class)
                    call sync()
                endif
            endif
        endmethod
                              // EVENT_UNIT_USE_ITEM
        private static method unitUseItem takes unit eventUnit, item eventItem returns nothing
            local integer  itemId = GetItemTypeId(eventItem)
            local integer  slot
            local thistype this
        
            // Evaluate if the item is registered to Equipment.
            if HaveSavedInteger(TABLE, itemId, 0) then
                set this = LoadInteger(TABLE, GetHandleId(eventUnit), 0)
            
                // Check if the item id is banned for the source unit type id.
                if LoadBoolean(TABLE, itemId, GetUnitTypeId(source)) then
                    call InterfaceError(source, eventItem)
            
                elseif eventUnit == source then
                    if not hasItemId(itemId) and equip(itemId) then
                        call RemoveItem(eventItem)
                        call sync()
                    endif
                    
                elseif eventUnit == dummy then
                    set eventUnit = source
                    set slot      = UnitInventorySize(eventUnit)
                    loop
                        exitwhen slot <= 0
                        set slot = slot - 1
                        if itemId == GetItemTypeId(UnitItemInSlot(eventUnit, slot)) then
                      
                            call UnitUseItem(eventUnit, UnitItemInSlot(eventUnit, slot))
                            call RemoveItem(eventItem)
                            exitwhen true
                            
                        endif
                    endloop
                    set eventUnit = null
                endif
            endif
        endmethod
    
        //===========================================================================
        // Bug fix for EVENT_UNIT_ISSUED_TARGET_ORDER:
        // The dummy must not pick up items.
        // Otherwise those items would be purged from the map.
     
        private static method rescueItem takes unit eventUnit returns nothing     
            call PauseUnit(eventUnit, true)
            call IssueImmediateOrderById(eventUnit, ORDER_ID_STOP)
            call PauseUnit(eventUnit, false)
        endmethod
     
        private static method onEvent takes nothing returns boolean
            if GetTriggerEventId() == EVENT_UNIT_USE_ITEM then
                call unitUseItem(GetTriggerUnit(), GetManipulatedItem())
            elseif GetTriggerEventId() == EVENT_UNIT_SPELL_CAST then
                call unitSpellCast(GetTriggerUnit(), GetSpellAbilityId())
            elseif GetOrderTargetItem() != null then
                call rescueItem(GetTriggerUnit())
            endif

            return false
        endmethod
     
        //===========================================================================
        // Creator & Destructor
     
        method destroy takes nothing returns nothing
            local integer index = 0

            debug call ThrowError(not exists, "Equipment", "destroy", "not exists", this, "Attempt to destroy null instance!")

            loop
                exitwhen index >= ITEM_CLASSES
                if itemSlot[index] != 0 then
                    call unequip(itemSlot[index].class)
                endif
                set index = index + 1
            endloop

            call AddUnitAnimationProperties(source, animtag, false)
            call UnitRemoveAbility(source, OPEN_ABILITY)
            call RemoveSavedInteger(TABLE, GetHandleId(source), 0)
            call RemoveSavedInteger(TABLE, GetHandleId(dummy),  0)
            call RemoveSavedBoolean(TABLE, GetHandleId(source), 0)
            call RemoveUnit(dummy)
            call TriggerClearConditions(trig)
            call DestroyTrigger(trig)
         
            set trig   = null
            set source = null
            set dummy  = null
            call deallocate()
        endmethod
     
     
        static method create takes unit whichUnit returns Equipment
            local thistype this  = allocate()
            local integer  index = 0
            local boolean  reset = ToogleUnitIndexer(false)
        
            debug call ThrowError(UnitHasEquipment(whichUnit),   "Equipment", "create", "exists",    0, "An equipment already exists for " + GetUnitName(whichUnit))
            debug call ThrowError(GetUnitTypeId(whichUnit) == 0, "Equipment", "create", "whichUnit", 0, "Invalid unit argument ( null )!")
        
            set trig   = CreateTrigger()
            set dummy  = CreateUnit(GetOwningPlayer(whichUnit), INVENTORY_DUMMY_ID, GetUnitX(whichUnit), GetUnitY(whichUnit), 0.00)
            set source = whichUnit
            call ToogleUnitIndexer(reset)
        
            call UnitAddAbility(dummy, INVENTORY_ABILITY)
            call UnitAddAbility(dummy, EXIT_ABILITY)
            call UnitMakeAbilityPermanent(dummy, true, INVENTORY_ABILITY)
            call UnitMakeAbilityPermanent(dummy, true, EXIT_ABILITY)
            call TriggerRegisterUnitEvent(trig, whichUnit, EVENT_UNIT_USE_ITEM)
            call TriggerRegisterUnitEvent(trig, whichUnit, EVENT_UNIT_SPELL_CAST)
            call TriggerRegisterUnitEvent(trig, dummy, EVENT_UNIT_USE_ITEM)
            call TriggerRegisterUnitEvent(trig, dummy, EVENT_UNIT_SPELL_CAST)
            call TriggerRegisterUnitEvent(trig, dummy, EVENT_UNIT_ISSUED_TARGET_ORDER)
            call TriggerAddCondition(trig, Condition(function thistype.onEvent))
        
            call UnitAddAbility(source, OPEN_ABILITY)
            call UnitMakeAbilityPermanent(source, true, OPEN_ABILITY)
        
            // Create references:
            call SaveInteger(TABLE, GetHandleId(whichUnit), 0, integer(this))
            call SaveInteger(TABLE, GetHandleId(dummy),     0, integer(this))
            call SaveBoolean(TABLE, GetHandleId(whichUnit), 0, true)
        
            loop
                exitwhen index >= ITEM_CLASSES
                call equipEmptyClass(index)
                set index = index + 1
            endloop
        
            return this
        endmethod
    
        //===========================================================================
        // Initialization
     
        private static method init takes nothing returns nothing
            set twohandIcon = EquipmentItem.createClass(ITEM_CLASSES, TWOHAND_ABILITY)
            set error       = CreateSoundFromLabel("InterfaceError", false, false, false, 10, 10)
        endmethod 
    
        implement Init
    
    endstruct

    //===========================================================================
    // Procedural Equipment API

    function RegisterEquipmentClass takes integer class, integer emptyIcon returns nothing
        call EquipmentItem.createClass(class, emptyIcon)
    endfunction

    function RegisterEquipmentItemId takes integer itemId, integer icon, integer class, boolean twohanded, string animation returns nothing
        call EquipmentItem.create(itemId, icon, class, twohanded, animation)
    endfunction

    function CreateEquipment takes unit whichUnit returns Equipment
        return Equipment.create(whichUnit)
    endfunction

    function GetUnitEquipment takes unit source returns Equipment
        return Equipment[source]
    endfunction

    function DestroyEquipment takes unit source returns nothing
        if UnitHasEquipment(source) then
            call Equipment[source].destroy()
        endif
    endfunction

    function UnitHasItemIdEquipped takes unit source, integer itemId returns boolean
        return Equipment[source].hasItemId(itemId)
    endfunction

    function UnitItemIdInSlot takes unit source, integer class returns integer
        return Equipment[source].itemSlot[class].itemId
    endfunction

    function ItemIdAddAbility takes integer itemId, integer abilityId, integer level returns nothing
        call EquipmentItem[itemId].addBonus(abilityId, level, true)
    endfunction

    function ItemIdAddBonus takes integer itemId, integer bonus, integer amount returns nothing
        call EquipmentItem[itemId].addBonus(bonus, amount, false)
    endfunction

    function ItemIdAddSpecialEffect takes integer itemId, string file, string attachPointName returns nothing
        call EquipmentItem[itemId].addSpecialEffect(file, attachPointName)
    endfunction

    function EnableItemIdForUnitId takes integer itemId, integer unitId, boolean flag returns nothing
        call SaveBoolean(TABLE, itemId, unitId, not flag)
    endfunction

    function IsEquipmentClassEmpty takes unit source, integer class returns boolean
        return Equipment[source].itemSlot[class] == 0
    endfunction

    function UnitEquipItem takes unit source, item whichItem returns boolean
        if Equipment[source].equip(GetItemTypeId(whichItem)) then
            call RemoveItem(whichItem)
            return true
        endif
        return false
    endfunction

    function UnitEquipItemById takes unit source, integer itemId returns boolean
        return Equipment[source].equip(itemId)
    endfunction

    function UnitUnequipItemClass takes unit source, integer class returns boolean
        return Equipment[source].unequip(class)
    endfunction

    function EquipmentEnableAnimationTags takes unit source, boolean flag returns nothing
        call SaveBoolean(TABLE, GetHandleId(source), 0, flag)
    endfunction

endlibrary



~~~~~~~~~~~~~~~~~~~~~~~~~~
Flow_block.png
Requirements

  • Equipment can draw back on the following optional requirements:
    ____
    • Earth-Fury's BonusMod to register abstract bonuses to item ids.
    • Nestharus's Bonus to register abstract bonuses to item ids.
    • BPower's ItemSet to create item collections.



~~~~~~~~~~~~~~~~~~~~~~~~~~
Scenario.png
API
JASS:
*       Creator/Destructor:
*       ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*           function CreateEquipment  takes unit whichUnit returns Equipment
*           function DestroyEquipment takes unit whichUnit returns nothing            
*        
*      Pre-game Functions:
*      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*           function RegisterEquipmentClass  takes integer class,  integer emptyIcon returns nothing
*           function RegisterEquipmentItemId takes integer itemId, integer icon, integer class, boolean twohanded, string animation returns EquipmentItem
*         
*           function ItemIdAddAbility takes integer itemId, integer abilityId, integer level returns nothing
*           function ItemIdAddBonus   takes integer itemId, integer bonus, integer amount returns nothing
*                • Each item id ( example: 'I00A') can have individual affixes.
*                  Affixes are added/removed when items are equipped/unequipped.
*                  Those affixes can be Warcraft III abilities or abstract bonuses from library BonusMod.        
*
*           function ItemIdAddSpecialEffect takes integer itemId, string file, string attachPointName returns nothing
*
*           function EnableItemIdForUnitId takes integer itemId, integer unitId, boolean flag returns nothing
*               • You can disable item ids for certain unit ids.
*                 Example: call EnableItemIdForUnitId('I00A', 'hfoo', false).
*
*      Equipment events:
*      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*           function RegisterItemEquipEvent    takes code func returns nothing
*           function RegisterItemUnequipEvent  takes code func returns nothing
*        
*           constant function GetTriggerItemId takes nothing returns integer
*           constant function GetEquippingUnit takes nothing returns unit
*
*      Ingame Functions:
*      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*           function UnitHasEquipment      takes unit whichUnit returns boolean
*           function UnitItemIdInSlot      takes unit whichUnit, integer class returns integer
*           function IsEquipmentClassEmpty takes unit whichUnit, integer class returns boolean
*           function UnequipItemClass      takes unit whichUnit, integer class returns nothing
*           function EquipItem             takes unit whichUnit, item whichItem returns boolean
*           function EquipItemIdById       takes unit whichUnit, integer itemId returns boolean
*           function UnitHasItemIdEquipped takes unit whichUnit, integer itemId returns boolean
*
*      Special:
*      ¯¯¯¯¯¯¯¯
*           function EquipmentEnableAnimationTags takes unit whichUnit, boolean flag returns nothing
*               • Some units should not change animation tags, because it messes with their model.
*                 A good is example is the demon hunter with an active metamorphosis,
*                 because metamorphosis also uses animationtags.



~~~~~~~~~~~~~~~~~~~~~~~~~~
Forward.png
Demo Code

  • See first comment.


Keywords:
equipment, item, system, Equipment System, modification, hero, unit, bonus, stat, equip, Set, ItemSet, SetItems, Inventory, inventory
Contents

Equipment (Map)

Reviews
14 Dec 2015: Bribe This is a great system which has purposes the demo map probably can't fully show off. When I think of the amount of work that went into coding this customized machine from the ground up (as WC3 provides none of this...
Level 19
Joined
Mar 18, 2012
Messages
1,716
  • Next to abilities you can register Bonuses from BonusMod, ItemPowers and SpecialEffects to item ids.
  • You will not run into bugs for units using metamorphosis or bear form.
  • You will not lose item handles ingame, because the dummy unit can't pick up items.
    ( If the dummy picks up an item, it will be removed from the game )
  • You get debug message, if you do something wrong.
  • The code is much much more optimized, hence also faster.
  • I did not base the allocation on a base of 'I000', which is a bit hacky.
  • You do not need linked list as requirement.

It looks similar, but it this one is bug free the other one is not.
You definitely want to use this version.​
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
I removed RegisterPlayerUnitEvent as requirement and instead merged all events into
the original dummy order target trigger. A small performance boost bonus from my side :)

in GUI ? PLEASE !!!
It's very easy to setup and use in JASS. Even for GUI-only users.

Also there is no point in writting this system in GUI. It's internally too complex.
 
Level 1
Joined
Jan 23, 2015
Messages
160
Screenshot_2.jpg

Can't seem to get a screen shot that doesnt turn the text sight-burning.

I've been wrestling to make this system work for a long time now, tried to use a few others in its place but they simply wouldn't do the job. I am going insane with this one.

Upon download, if I make any edit at all open the map and try to save, I get 2 simple errors having to do with the "Item Set" and "Item Set Setup Example." As these are optional, I disable them, and then I get a 4 errors, shown from screen shot.



1. Line 600:Undeclared function ItemPower_UnitRemoveItemId

JASS:
                        call ItemPower_UnitRemoveItemId(s__Equipment_source[this] , s__Equipment___EquipmentItem_itemId[l__Equipment___eventItem])

2. Line 660: Undeclared function ItemPower_UnitAddItemId

JASS:
                call ItemPower_UnitAddItemId(s__Equipment_source[this] , s__Equipment___EquipmentItem_itemId[toEquip])





1. Line 1349: Functions passed to Filter or Condition must return a boolean

JASS:
        call TriggerAddCondition(Equipment___EQUIP, Condition((function Board__OnEvent))) // INLINED!!

2. Line 1350:

JASS:
call TriggerAddCondition(Equipment___UNEQUIP, Condition((function Board__OnEvent))) // INLINED!!


Thanks for any assistance!

-Edit

Looks like I was able to save with disabling all the optional features, that is something I tried months ago and iirc it didn't work then either, but I just got it to save without any of the optional features. Would prefer to use them but looks like the main aspects of the system are usable without the optional parts.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
The first is because you didn't import the ItemPower library into your map.

The second is a pJass error, which your compiler detects and mine not. I can look into it.
Anyway the board is just a demo an shouldn't be used directly in other maps.

Edit: I purged the pJass error out of the demo trigger for you.
 
Last edited:
Level 5
Joined
Jul 17, 2010
Messages
140
In addition to the The_Witchers version.
Can you add the same Ability ID to more of the set you have on you?

Because i noticed that. Lets say your equipment Harold´s Cleaver and Harolds Ring both will add 7 Agility.
Do you need to make 2, 7 ability or can you use the same id?

In the_Witchers version if you used the same Id on 2 items that is equipped it did NOT stack and i had to make a new id for every 7 Abi i use.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
You may share abilties over items of the same item slot. For example + 7 agility can
be given to weapon1 and weapon2. Because unequipping weapon1 will remove the ability
and equipping weapon2 adds it and backwards.
You can't share same abilities over items of different slots,
for that you would need to make a new ability id.
 
Level 5
Joined
Jul 17, 2010
Messages
140
You may share abilties over items of the same item slot. For example + 7 agility can
be given to weapon1 and weapon2. Because unequipping weapon1 will remove the ability
and equipping weapon2 adds it and backwards.
You can't share same abilities over items of different slots,
for that you would need to make a new ability id.


Yea okay, good.

How about the saving system?
Have any plans on improving that to the system? :)
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
It isn't working, it keeps saying "Unable to save due to etc, etc"
I just downloaded the attached map file, opened it then saved it on WEX. Everything worked fine for me.
Are you using BonusMod? Because I once experienced problems with WEX beeing unable to save LUA code. Could you explain your issue more detailed?

is it mandatory for items to have a BTN ability counterpart

Yes, because all items are displayed as ability buttons inside the inventory dummy unit.
 
Level 1
Joined
Jan 23, 2015
Messages
160
Hey @BPower I'm using this system and everything has been going pretty good. I got BonusMod working too and am trying to use it with your system but the example you gave to add the Bonus to an item doesn't seem to be working. Any ideas? Thanks bud
upload_2018-3-3_17-27-42.png

-Edit

It was because I left a '.' at the end of the number. Fixed it and got it to compile but :( the bonus doesn't get added to the equipping unit.
 
Last edited:
Level 1
Joined
Jan 23, 2015
Messages
160
Hey IcemanBo, thanks for the response!

Map is below. Sorry for the large size, I have CSS implemented but it is not compatible with Bpower equipment so I'm trying to get bonus mod in. Bonus mod and abilities are all in.
I was warned that I have conflicting libraries, so autoindex has useuserdata set to false, so that my UnitEvent by Bribe can operate.

In the bottom right is a little villager there is an item of a sword next to him. This map is a bit of a mess, I haven't gotten to cut any fat yet, sorry for the size.

The coding is under Equipment Setup Example

JASS:
            // Silversword
            call RegisterEquipmentItemId('I0A1', 'A008', ITEM_CLASS_MAINHAND, false, "Alternate")
            call ItemIdAddAbility('I0A1', 'A002', 1)        // This is how you add an ability to an itemid.
            call ItemIdAddAbility('I0A1', 'Ws01', 1)        // This is how you add an ability to an itemid.
            call ItemIdAddAbility('I0A1', 'Ws04', 1)        // This is how you add an ability to an itemid.
            call ItemIdAddAbility('I0A1', 'Ws07', 1)        // This is how you add an ability to an itemid.
            call ItemIdAddBonus('I0A1', BONUS_LIFE, 1000)
            call ItemIdAddBonus('I0A1', BONUS_DAMAGE, 200)

The item is called Long Sword (Poor) but under the script it is Silversword. Sorry for confusion there. Anyway, the system accepts ability bonuses but will not show me my BonusMod add ons. Hope you can help, thanks for your time!
 

Attachments

  • BonusMod and Bpower.w3x
    2.6 MB · Views: 75
Hi. Please use more time to prepare demo next time, because others most likely then need the extra effort to remove not needed things before properly testing.

Anyways, not sure it's 100% correct, but at least something is working now, and bonuses are added. Hope you can figure out if it's what you needed.

There were two major problems:
  1. Equipment config has run before bonus mod config, bonuses were not declared, yet
  2. struct Equipment has a method method equip takes integer itemId returns boolean which seems to handle adding all bonus.
    Said method is only called by two public functions
    - function UnitEquipItem takes unit source, item whichItem returns boolean
    - function UnitEquipItemById takes unit source, integer itemId returns boolean

    I'm not sure you want to call them straight, or to build wrappers build in, in other systems. But I would have a look at them, because for example the first one removes the equiped item from game.
I modified the map a bit, "ItemEquip" is the new trigger which simply calls an equip function for an item and the config init was delayed by a timer, so solve problem #1. For fixing in your map I would re-do said things, it should be doable. Because in other codes I also might have modifed things for test, or added debug messages.

Good luck, tell me if there's an issue!
 

Attachments

  • BonusMod and Bpower.w3x
    2.5 MB · Views: 104
Level 1
Joined
Jan 23, 2015
Messages
160
Ouch, sorry to be selfish there and just upload the map I'm using instead of a real demo map. My bad buddy.

What you've figured out is very helpful. I recently got Triggerhappy's Inventory system to work but it has very little guidance, so if it doesn't work I will fall back on the map you just uploaded in response to my problem. The test map works great with bonus mod, with only a minor learning curve.

Thanks so much Bo, this really means a lot - you really are the best!
 
Level 1
Joined
Jan 23, 2015
Messages
160
Man you're welcome! Thanks!

Ok Triggerhappy's system is too much of a beast for me to handle so I'm coming back to this one.

With the fixed demo map you gave me it does appear that upon picking up an item the unit will instantly equip it. Unequipping makes the item drop to the floor instead of go back to the unit's inventory. Originally the player had to click the item in their inventory to equip it.

Just looking through this and wondering how I can put that mechanic back in. Thanks again!
 
Hiho.

(sorry, didn't check now/can't map)

I think in so in our demo we instantly call equip() when item gets picked up. BPower has some custom events for it, like you say when you click something.
On unequip he maybe has also some default code already, that takes the unequipped item from system and directly loads it in his inventory again, I might think so.

Probably isn't too much detailed help, hehe. Can you maybe try finding out how BPower implemented logics with equip() unequip(), und then take usage of it? And/or maybe posting demo with concrete attempt. ;D maybe we can solve together
 
Level 1
Joined
Jan 23, 2015
Messages
160
Oh, I missed a very obvious trigger.

  • Untitled Trigger 001
    • Events
      • Unit - A unit Acquires an item
    • Conditions
    • Actions
      • Set it = (Item being manipulated)
      • Custom script: call UnitEquipItem(GetTriggerUnit(), udg_it)
Embarrassing haha - don't worry I'll put this to good use. Thanks again Bo.
 
Level 2
Joined
Apr 17, 2017
Messages
22
Hello Sir BPower

I just like to ask if you can add a function that allows the transfer of equipment from one unit to another (I notice that the code you used is almost similar to The_Witcher's equipment system but highly improved)

I was initially using his system but i noticed the significant improvement that your system has.

The_Witcher's equipment system uses this function:

function ChangeEquipmentOwner takes unit u, unit newowner returns nothing
set Equipment.owner = newowner
endfunction

Can you help me out? You can PM TIA
 
Last edited:
Level 5
Joined
Feb 13, 2019
Messages
128
Hi all I'm reallllly new to this and am trying to learn the ropes and I honestly can't even get this to run for me at the moment, if someone would be so kind as to help spell it out for me it would be greatly appreciated!

I'm stuck on actually "initializing" everything I think? :confused:

I have JassNewgen and everything it's just setting up the code that's got me.
 
Level 6
Joined
Feb 18, 2010
Messages
153
Quick question, to who uses this system and understands more than me.

Great System I use and works very well,

Question: the only thing I can’t figure out is Dupe Ability

Is you got a Damage +300 ability on 2 different items but using same ability ID it only activates 1 of them items ability

ATM I have multiple of the same ability but different ID to work around it and works perfectly

But is there a way to have dupe ability without having to have 2 different Ability ID giving same stat?
 
Level 7
Joined
Nov 6, 2019
Messages
185
Is there any Save/Load system tha works with this equipment system? to keep equipped items and inventory?
 
Top