• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

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...

Moderator

M

Moderator

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 functionality natively), I feel that deserves a bump. Includes Lots of features and is as optimized as it gets.

Normally, I would give 4/5 due to the huge amount of Object Editor configuration required on the user's side, but due to the amount of work saved if the user had been attempting something like this, the resource should be a full 5/5.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Demo Code:

JASS:
scope SetupEquipment initializer Initialize


    private struct InitFirst extends array
        
        private static method init takes nothing returns nothing
        
            /**
            *   Init all classes and items in a module initializer to avoid ingame malfunction.
            *
            *   A class has an unique integer key and an empty slot icon ability.
            *
            *   For easy access, define each class in the globals of the Equipment library as:
            *
            *       --> constant integer ITEM_CLASS_EXAMPLE = x
            *   
            */
            call RegisterEquipmentClass(ITEM_CLASS_MAINHAND, 'A00D')
            call RegisterEquipmentClass(ITEM_CLASS_OFFHAND,  'A00E')
            call RegisterEquipmentClass(ITEM_CLASS_AMULET,   'A00A')
            call RegisterEquipmentClass(ITEM_CLASS_ARMOR,    'A007')
            call RegisterEquipmentClass(ITEM_CLASS_BOOTS,    'A00F')
            call RegisterEquipmentClass(ITEM_CLASS_GLOVES,   'A004')
            call RegisterEquipmentClass(ITEM_CLASS_RING,     'A00C')
            call RegisterEquipmentClass(ITEM_CLASS_HELMET,   'A00L')
            call RegisterEquipmentClass(ITEM_CLASS_SPECIAL,  'A00M') 
            
            /**
            *   Item ids can be registered with RegisterEquipmentItemId  
            *
            *   itemid |  icon  |  class |  twohanded?  | animationtag
            */
            // Silversword
            call RegisterEquipmentItemId('I004', 'A008', ITEM_CLASS_MAINHAND, false, "Alternate")
            call ItemIdAddAbility('I004', 'A002', 1)        // This is how you add an ability to an itemid.
            call EnableItemIdForUnitId('I004','H001', false) // Forbid this item for 'H001'
            
            /**
            *   In case you use library BonusMod, you can also do:
            *   call ItemIdAddBonus('I004', BONUS_LIFE,   1000.)
            *   call ItemIdAddBonus('I004', BONUS_DAMAGE, 200.)
            *
            *   etc...
            */
            
            //*  Sacred stone        
            call RegisterEquipmentItemId('I000', 'A00K', ITEM_CLASS_SPECIAL, false, "")
            call ItemIdAddAbility('I000', 'A000', 1)// + 10 Agility.
            call ItemIdAddAbility('I000', 'A00Y', 1)
            //*  Slizer
            call RegisterEquipmentItemId('I005' , 'A00H', ITEM_CLASS_MAINHAND, false, "Alternate")
            call ItemIdAddAbility('I005', 'A005', 1)
            //*  Razor
            call RegisterEquipmentItemId('I006', 'A00J', ITEM_CLASS_MAINHAND, true, "Channel")
            call ItemIdAddAbility('I006', 'A005', 1)// 'A005' adds the SilverRazor modelfile to 'I006'!
            //*  As an alternative you could also use:
            //-->> call AddItemIdSpecialEffect('I006', "war3mapImported\\SilverRazor.mdx", "hand left")
            
                //*  Pros and Cons of using the function call can be found in the Equipment Manual.
            
            //*  Shield 
            call RegisterEquipmentItemId('I001', 'A009', ITEM_CLASS_OFFHAND, false, "defend")
            call ItemIdAddAbility('I001', 'A006', 1)
            //*  Amulet of darkness 
            call RegisterEquipmentItemId('I00A', 'A00B', ITEM_CLASS_AMULET, false, "")
            //*  Golems Skin
            call RegisterEquipmentItemId('I003', 'A00N', ITEM_CLASS_ARMOR, false, "")
            call ItemIdAddAbility('I003', 'A00U', 1)
            //*  Windwalkers
            call RegisterEquipmentItemId('I002', 'A00O', ITEM_CLASS_BOOTS, false, "")
            call ItemIdAddAbility('I002', 'A00X', 1)
            //*  Stonefists
            call RegisterEquipmentItemId('I007', 'A00P', ITEM_CLASS_GLOVES, false, "")
            call ItemIdAddAbility('I007', 'A00W', 1)
            //*  Arthuriel  
            call RegisterEquipmentItemId('I008', 'A00Q', ITEM_CLASS_RING, false, "")
            call ItemIdAddAbility('I008', 'A00T', 1)
            //*  Mask of Horror      
            call RegisterEquipmentItemId('I009', 'A00R', ITEM_CLASS_HELMET, false, "")
            call ItemIdAddAbility('I009', 'A00V', 1)        
            //*  Harold's Cleaver Twohand Weapon
            call RegisterEquipmentItemId('I00B' , 'A013', ITEM_CLASS_MAINHAND, true, "Alternate")
            
            /**
            *   This way you attach special effects to weapons without using an ability.
            */
            call ItemIdAddSpecialEffect('I00B', "war3mapImported\\SilverRazor.mdx", "hand left")
            //** Harold's Ring
            call RegisterEquipmentItemId('I00D', 'A014', ITEM_CLASS_RING, false, "")
            call ItemIdAddAbility('I00D', 'A016', 1)//+ 10 Strenght
            //*  Harold's Horn       
            call RegisterEquipmentItemId('I00C', 'A015', ITEM_CLASS_SPECIAL, false, "")
            call ItemIdAddAbility('I00C', 'A00Y', 1)//+ 10% AttackSpeed
        endmethod
        
		private static method onInit takes nothing returns nothing
			call init()
		endmethod
    
    endstruct

    private function Initialize takes nothing returns nothing
        local unit demon = CreateUnit(Player(0), 'Edem', -14450, 13612, 0)
        local unit test  = CreateUnit(Player(0), 'H001', -14450, 13612, 0)
        call CreateEquipment(test)
        call CreateEquipment(demon)
        
        //*  Except for the village 255 animations model,
        //* Animationtags can mess with your models.
        //* You can disable this feature for a unit via:
        call EquipmentEnableAnimationTags(demon, false)
        
        //*  The demo map includes ItemPower and ItemSet,
        //* to show what can Equipment do.
        call CreateUnitItemPower(demon)
        call CreateUnitItemPower(test)
        call SetHeroLevel(demon, 10, false)
    endfunction
endscope
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
So this calculates the damage, stats of the item and the unit?

No it just calculates numbers linked to the items.
For example:
A unit has item1, item2 and now we equip item3.
On equip event ( or unequip) the system gets all values of the equipping item from an hashtable: item3.
Reals and Integers of item1 and item2 are already calculated and saved to that unit in a TableArray.
Now the real and Integer linked to item3 will be will be summed up (on unequip subtracted) to the correspsondig prior group for that unit .

This happens on equip ( Its actually a loop for each prior group i)
this refers to the unit in the TableArray, i is the prior group, id is the ItemId
set tb[this].real[i] = tb[this].real[i] + LoadReal(hash, id, i) set tb[this][i] = tb[this][i] + LoadInteger(hash, id, i)



Unit stats stay untouched, because only Hero's have stats and you cannot call UnitArmor or Damage. Such functions just don't exist, you calculate them approximately with DDS.

The demo map shows pretty much, what this system does.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Fixed a small issue in the demo map.

Edit: Added function SetItemPower to manipulate values at any time. The idea was to increase/ decrease values over a period of time, for instance increase CritChance by 10% for the next 15 seconds.

Note: It's a tricky function and can screw up the whole math if used wrong.
 
Last edited:
Level 19
Joined
Aug 8, 2007
Messages
2,765
Whats the advantage of using this over a hashtable/table array?

set unitPowers[GetUnitUserData(u)][indexOfPower] = unitPowers[GetUnitUserData(u)][indexOfPower] + 1

also, how do you accomodate for units dying? you use unitIndex[unit], but if a unit dies and a new one takes its place, that one will have all of its "powers" as the old one

the values don't stack properly together IMO. For one, 10% crit rate + 15% crit rate shouldn't be 25% as this will make stacking incredibilly imba. (10% + 15% should be 22.75%, which is n + (n#2 * (1 - n)) + (n#3 * (1-n-(n#2*(1-n)))) + . . . (for the infinite series form, theres a formula to do it without an infinite series) (though thats my personal opinion. If you ever played the game rappelz, recent updates totally destroyed the game because they could get 100% 6x crit rate and just do infinite amounts of damage)

I also feel like this system is too raw in its current form (not counting if you add the stuff in this post). If your targeting an audience that has no idea how to make a flexible system like this for their own maps, than you should do stuff like increasing health if the type of power is health, (etc for damage and whatever) applying the resistance / crit bonuses on damage event, yada.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Whats the advantage of using this over a hashtable/table array?
UnitIndexer will assign user data in the range of 0-8190, using a static thistype array seems handy to me.

the values don't stack properly together IMO
Sorry, but total nonesense.
For one, 10% crit rate + 15% crit rate shouldn't be 25% as this will make stacking incredibilly imba.
You miss the point of this system. It was never designed to balance attack features like crit. Critical Hit it is just an example for a possible cenario.
What you mention here is just a personal opinion based on a random map. I can't take that serious.

also, how do you accomodate for units dying? you use unitIndex[unit], but if a unit dies and a new one takes its place, that one will have all of its "powers" as the old one

I'll will change that variable's name. It has nothing to do with UnitIndexer its just a static thistype array. You have to register every unit individually to the system. When registering all previous values will be overwritten. EDIT:Changed name to pointer.

[hidden="Quote]
the values don't stack properly together IMO. For one, 10% crit rate + 15% crit rate shouldn't be 25% as this will make stacking incredibilly imba. (10% + 15% should be 22.75%, which is n + (n#2 * (1 - n)) + (n#3 * (1-n-(n#2*(1-n)))) + . . . (for the infinite series form, theres a formula to do it without an infinite series) (though thats my personal opinion. If you ever played the game rappelz, recent updates totally destroyed the game because they could get 100% 6x crit rate and just do infinite amounts of damage)
I would recommend writing an isolated crit system, if you want to use a specific algoritm.[/hidden]
I also feel like this system is too raw in its current form
It is simple and versatile in one and that's effective, at least in my opinion.
than you should do stuff like increasing health if the type of power is health
That's what bonus libs are/were for. (If Lua ever get fixed)

EDIT:
applying the resistance / crit bonuses on damage event, yada.
Yes you are right, in v.1.0.0.0 I've added a small prototype of how to you this system with DDS, but deleted it later because the map is already packed full of libraries from MUI Advanced EquipSystem.
That's the reason I designed two small spells (Cleave and missle) and coupled them with ItemPower.
I'll improve their readability.
 
Last edited:
Level 19
Joined
Aug 8, 2007
Messages
2,765
Hm, if your looking to overhaul it...

Set System

0) It looks nice, not really much more you can do with it, but
JASS:
*           static method init takes integer Set, string s, string e, string a returns nothing
*               - Init a new ItemSet

Some more documentation on those arguments would be nice. (see #8)

2) (isn't really a needed change but) I don't like the usage of the "Set" variable. Your essentially emulating the use of the "this" variable, which vJass is designed to do. Why not just make it a library, make all of the variables arrays and do name[Set], for example.

3) the entire addBonus method should be in a static if.

4) I'd change the name of the "Bonus" struct to something else, to clarify that its a specific bonus (ex. AtCountBonus)

5) Set bonuses should be re-evaluated when removeBonus is called

6) Static if's hold the same indentation principles as normal if's, the way you did it is strange

7) camelcase. If its not a global or a static method name, it should be lowerCamelCase.

8) make your variable names longer. (ex. the Bonus.p should be like Bonus.appliedAbility)

ItemPower

0) Personal opinion, but I'd remove the integer arguments when adding bonuses. You can still return integer values, but just return R2I(whatever)

1) As i said before, needs more overall functionality. Good things to do would be stacking percentage management (evasion, block, crit rate, resistances, etc you dont want going above/reaching 100), damage event integration, constants tables (a table of good bonuses to be implemented into an RPG, can be modified upon desire of the mapmaker)

2) More documentation for the ManipulateItemPower function (and more documentation overall)
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I'm guessing by item set you mean item recipes ? or do you mean item restrictions. (left right hand armor helmet and other things ?)

No, if a unit has x Items of an ItemSet in his inventory a bonus is applied. Note that it uses a custom inventory system.
Have you ever played Diablo 2, it works 100 % the same as Sets work there.

Example: an ItemSet contains out of a helmet, boot and a sword.
With example names: ItemSet Harold's Kitchen contains Harold's Cleaver, Harold's Helmet and Harold's Footprints.
Bonuses are: with 2 Items hold +2 Defense, with 3 items +2 Defenses (This Bonus stays), +10 Damage, +150 Life.
  • 2 Items of this Set in the inventory --> + 2 Defense
  • 3 Items of this Set in the inventory --> + 10 Damage
  • 3 Items of this Set in the inventory --> + 150 Life
  • Unit drops one of those item --> 10 Damage and 150 Life will be removed
  • 2 Items of this Set in the inventory --> 2 Defense stay's

Suggest a more distinct library name, if you don't like ItemSet.
Best would be to just test the map.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
@Arhowk

I don't like the usage of the "Set" variable.
Me too. It was the first variable name that came into my mind. I'll change it.
the entire addBonus method should be in a static if.

Both Bonuses for the ItemPower library or simple ability bonuses are applied with this method. The Boolean true/ false determines which one to use.
JASS:
    public function ItemSetAddAbility takes integer Set, integer ItemCount, integer id returns nothing
        call ItemSet.addBonus(Set, ItemCount, false, id, 0., 0)
    endfunction
   
    public function ItemSetAddPower takes integer Set, integer ItemCount, integer Power, real Real, integer Int returns nothing
        call ItemSet.addBonus(Set, ItemCount, true, Power, Real, Int)
    endfunction

I'd change the name of the "Bonus" struct to something else, to clarify that its a specific bonus (ex. AtCountBonus)
It's a private struct with no access from outside the library and I think the name Bonus fits very well. The function is Bonus.create(..) what yould be more distinct?

5) Set bonuses should be re-evaluated when removeBonus is called
Yes I know, I would rather remove the whole function as I've also seen that problem.
It could only work this way: Unequip complete inventory, remove the Bonus reequip the inventory.
camelcase. If its not a global or a static method name, it should be lowerCamelCase.
Yeah, after all its v1.0.0.0 I'll fix that.
make your variable names longer. (ex. the Bonus.p should be like Bonus.appliedAbility)
I seldom use more than 4 characters outside the API. I'll try to make them more distinct, but not longer.
Personal opinion, but I'd remove the integer arguments when adding bonuses. You can still return integer values, but just return R2I(whatever)
In the first version of ItemPower I didn't use integers and run into a nasty rounding issue. Have you ever used I2S(R2I(0.99)), well it is 0. Most times the returned integer will be smaller than the real, for instance R2I(0.15*100) may be sometimes 14 if composed out of two values ( 0.1+0.05). Although integers are redundant, when it comes to damage calculations there are still few cases you want to return the exact integer ( not rounded), for instance on a multiboard.
2) More documentation for the ManipulateItemPower function (and more documentation overall)
Okey-dokey! I'll do that.

Thank you for your opinion.:thumbs_up:

Edit: I know you don't like the math used in ItemPower, but that is not discussible. :/

EDIT: Updated
  • Improved the documentation
  • Exchanged dubious variables
  • Fixed typos and all methods now follow the JPAG convention
  • Added debug part for the removeBonus method
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
1) As i said before, needs more overall functionality. Good things to do would be stacking percentage management (evasion, block, crit rate, resistances, etc you dont want going above/reaching 100), damage event integration, constants tables (a table of good bonuses to be implemented into an RPG, can be modified upon desire of the mapmaker)
Right now an idea is developing in my head. I will add a Table or use an existing if possible, which offers the following: add specific algorithm. So each value can be calculated different. At the moment I'm not sure if either it can be done in one function or the user has to add a specific math and connect it with a booleanexpression --> if boolexpr (myExpr != null) call myMath(..) else use default math. Something like this.

Edit: it will be a module --> implement optional ItemPowerAlgorithm. I just need to figure out which way is the best to determine if the system should suppress the origional math function and instead run the specific one or not. Also I need a counter for counting ho many Items of each Power a unit has.

At first I didn't understand your argument, but now I'm hooked by this idea.
 
Last edited:
Level 19
Joined
Aug 8, 2007
Messages
2,765
I don't feel like individually qouting each of those atm but...

In the first version of ItemPower I didn't use integers and run into a nasty rounding issue. Have you ever used I2S(R2I(0.99)) , well it is 0. Most times the returned integer will be smaller than the real, for instance R2I(0.15*100) may be sometimes 14 if composed out of two values ( 0.1+0.05). Although integers are redundant, when it comes to damage calculations there are still few cases you want to return the exact integer ( not rounded), for instance on a multiboard.

R2I() is also known as trunc(), meaning it just gets rid of the decimal point

if you want to round it to the nearest whole number, use R2I(x+0.5)

As far as what your talking about with the algorithm, not too bad of an idea. What you could do is pass a trigger with one global variable being the input and another being the output. You can have the user call TriggerAddCondition() that will take in the input and set the output to whatever than add TriggerAddAction() to manipulate the output / reset the input. For example

JASS:
globals
    integer input
    integer output
endglobals

function manipulateInput takes nothing returns nothing
    set output = input * 2
endfunction

function init takes nothing returns nothing
    local trigger t = AddCustomAlgorithm(...)
    call TriggerAddCondition(t, function manipulateInput)
    set t = null
endfunction
function manipulateOutut takes nothing returns nothing
    call printi(output)
endfunction

function AddCustomAlgorithm takes /*whatever used to keep track of the differnent algorithms*/ returns trigger
    local trigger t = CreateTrigger()
    call TriggerAddAction(function manipulateOutput)
    return t
endfunction

Also, if your interested, the formula for what I've been talking about goes something like this

JASS:
        static method getUnitResistance takes unit u, integer resistance returns real
            local real a = (100-customBonuses[resistance].real[GetUnitUserData(u)])/100
            local real b = (100-customBonuses[TRAIT_TYPE_ALL_RESISTANCE].real[GetUnitUserData(u)])/100
            return 100-((a*b)*100)
        endmethod

this is just a small example, the customBonus[][] values were pre-compiled via

JASS:
        private static method convert takes integer i returns integer
            if i == 0 then
                return TRAIT_TYPE_FIRE_RESISTANCE
            elseif i == 1 then
                return TRAIT_TYPE_LIGHTNING_RESISTANCE
            elseif i == 2 then
                return TRAIT_TYPE_COLD_RESISTANCE
            elseif i == 3 then
                return TRAIT_TYPE_POISON_RESISTANCE
            elseif i == 4 then
                return TRAIT_TYPE_DARKNESS_RESISTANCE
            elseif i == 5 then
                return TRAIT_TYPE_HOLY_RESISTANCE
            elseif i == 6 then
                return TRAIT_TYPE_PHYSICAL_RESISTANCE
            elseif i == 7 then
                return TRAIT_TYPE_ALL_RESISTANCE
            elseif i == 8 then
                return TRAIT_TYPE_SPELL_RESISTANCE
            endif
            return -1
        endmethod
        
        private static method r_compile takes unit u, integer r returns nothing
            local integer i = 0
            local integer id = GetUnitUserData(u)
            local integer id2 = id*8192
            local integer max = currentResistancePoint[r+id2]
            local real ti
            local real tt = 1
            local real array n
            loop
                exitwhen i > max
                set ti = resistances[r].real[i+id2]
                set tt = ((100-ti)/100)*tt
                set i = i + 1
            endloop
            set customBonuses[convert(r)].real[id] = 100-((tt)*100)
        endmethod
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
The prototype of IPMath looks like this:
You have 2 arguments to work with:
real old is the value before equipping and counter to keep track of the Items with have influence of the specific power. ( for example 3 items in your inventory have crit --> counter = 3
Also real new which is the new real value.
IPMHead takes function name and which power, if I get it to work you can paste any caculation inside.
JASS:
            //! runtextmacro IPMHead("increaseCrit", "4")
                set old = old*1000
                set new = old + 1*counter
                //debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,2,R2S(new))
            //! runtextmacro IPMIncreaseEnd()

Edit: It's done, hopefully I get the API finished until the weekend :)
Edit 2: I've change the API of both systems, they now work perfectly. I just need more some time to make a proper demonstration. :thumbs_up:

EDIT 3: Its done, I'll upload it later tonight. If someone feels more variables are required for any custom calculation plz tell me.

JASS:
*   Any custom formulas will be added here, the design is always the same
*   An example is worth more than 1000 Words. 
*/
    //Now you have 4 variables to your need: value, old, new and counter
    
    //value --> The value the current item has.
    //          For instance +20% MagicDamage
    //old --> is the current total value for your unit.
    //          For instance +160% Magic Damage composed out of 3 items 
    //new --> is the value which will be saved in the end
    //          For instance new = old + value --> 180% = 160% + 20% 
    //counter --> is the number of items hold by your unit, which increase that ItemPower
    //          For instance 1.Boots +100% 2.Gloves +20% 3.Sword +40% --> counter = 3
    
    //My custom formula for MagicDamage. --> //! textmacro IPMIncreaseHead takes TYPE, INTEGER
    
    //Scenario Increase value:
    //Step 1: type runtextmacro IPMIncreaseHead("NameOfYourFunction", "WhichItemPower")
        //! runtextmacro IPMIncreaseHead("increaseMagicDamage", "2")
                set new = old + (value*counter)//Step 2: define your very own formula
        //! runtextmacro IPMIncreaseEnd()
    //Step 3: type runtextmacro IMPIncreaseEnd()
    
    //And the other way around.
    
    //Scenario Decrease value: 
    //Step 1: type runtextmacro IPMDecreaseHead("NameOfYourFunction", "WhichItemPower")
        //! runtextmacro IPMDecreaseHead("decreaseMagicDamage", "2")
                set new = old - (value*counter)//Step 2: define your very own formula
        //! runtextmacro IPMDecreaseEnd()
        //Step 3: type runtextmacro IMPDecreaseEnd()
        
    //Step 4: register your function name with the equipsystem within the onInit method.
        private static method onInit takes nothing returns nothing
            implement ItemPowerInitHack//Don't touch
            call RegisterItemEquipEvent(function thistype.increaseMagicDamage)//We register the method with the increase formula to the equip event
            call RegisterItemUnequipEvent(function thistype.decreaseMagicDamage)//We register the method with the decrease formula to the unequip event

The exmaple is simple, but shows the purpose very well.

Opinions and feedbacks are very welcome especially concerning the following issues:
  • Logical mistakes within the calculation part
  • Missing variables to ensure any calculation is possible (for example saving the value an item gave to a unit if a custom formula was used)
  • Reorganization of the whole library or the modules to improve readability
  • Custom formulas, so I can provide a better example than the actual one
  • Any additional features this system should provide
  • typos

Upate:
Commented the exmaple for a custom formula, as it should not be used by default and added a small manual. Also did some reorganization, I hope its now easier to understand.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
I never intended to impose certain bonuses. As they are "only" real numbers, a bonus could be anything (Increase raidus for group enumaration, Unit scale value, Increase Chances of any type, ...)
I've chosen crit as example, just because everyone is familiar with it. But I do agree that the configuration part is user unfriendly in its current state.
Bonus strength is a bad example, because the system can't provide such bonuses as Armor, Strength, Agility, Int, Movement Speed, Abilities. But the Witcher's equipsystem can.

JASS:
if (Many == .count) then//Is the set completed?
    call this.itemSetComplete(u)
endif
private method itemSetIncomplete takes unit u returns nothing
    static if ATTACH_EFFECT then
        local integer i = GetUnitId(u)
        call DestroyEffect(ind[i].effect[this])
    endif
    call DisplayTimedTextToPlayer(GetOwningPlayer(u), 0,0,TIME, MESSAGE_COLOR + INCOMPLETE  + "|r" + ITEM_SET_COLOR +this.name)
endmethod
On the next update, I'll change it to:
JASS:
if (Many == .count) then//Is the set completed?
    static if ATTACH_EFFECT then
        call DestroyEffect(ind[GetUnitId(u)].effect[this])
    endif
    call DisplayTimedTextToPlayer(GetOwningPlayer(u), 0,0,TIME, MESSAGE_COLOR + INCOMPLETE  + "|r" + ITEM_SET_COLOR +this.name)
endif
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Thank you
I feel like people are trying to make this system over and over again.
I never intended to do so. I haven't even tried your system, yet. :)

Update:
I've updated the ItemSet library and reworked the docs for both systems. I think they are now very solid and ready for approval.

Another update:
Made Alloc and ErrorMessage optional and erased some typos.

Update No.3:
Replace GetUnitId with GetUnitUserData. Now both systems can use any UnitIndexer independent of its API.


Update No.4:
Seperated the textmacros from the ItemPower library. They are now 100% optional.
Added lots of example how to use this system.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Thanks for pointing it out, it is now fixed.

Btw do you need the ItemPowerMacros for your map. In other words do you plan to use different calculations than simple addition and substraction?
If not don't copy and paste the ItemPowerMacros library, because it is only there for that very specific purpose and 100% optional.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I'll update ItemPower and ItemSet very soon. The new changelog will be:
  • Much more lightweight, as the current version takes a huge dump on Table instances.
  • The allocation will now be directly based on UnitUserData and no longer with the Alloc module.
  • Less code same functionality.
  • ItemPowerMacros will be completly outsourced in form of a module. Also completly removed and only be given out on request. This expansion is just too specific.
  • AdvancedEquipmentSystem will be replaced by library Equipment which is an re-coded cleaner version of it.
  • Better demo examples.
  • Heavy documentation in case one has to adjust the code to his very own equipment system.
  • Slight API change: getPower will become get and changeOwner will be completly removed.
  • Powers now have to be register manually and start of with 0.

Improved all examples as requested.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
You should remove Nes' DDS from al list. Link goes to nowhere basically - author, as we all know, removed most of his resources from hive. Instead, you could add link to looking_for_help' damage event. It works perfectly and has both, GUI and vJass version.
Yes true. I've just added it back then to demonstrate several ways to use this library.
Advantage vs PII?
What is PII?
 

Deleted member 219079

D

Deleted member 219079

There is basically no connection between this and PII - those are completely different resources.
Are you trolling?
 
Nope, but you do.

PII is an Item Indexer, while BPower's snippet assignins real values to item and uses unit indexer for proper behaviour. Is it used to attach values such as block chance, magic find etc. instead of providing unique integer data per item handle.

ItemSet is completely different resource.
You think "Advatage vs PII?" sounds smart, yet, without proper preparation the effect is exactly opposite.
 

Deleted member 219079

D

Deleted member 219079

ItemPower allows to assigns real values to items.
So this isn't a typo?
I thought it as
ItemPower allows you to assign real value to item

edit: so what does it do? I don't understand "ItemPower allows to assigns real values to items."
 

Deleted member 219079

D

Deleted member 219079

So it's like, TimerUtils ? =D

Imma take a look, here's a rep for explanation
 
Yeah.. but a bit more powerfull.
Imagen a replacement for item type with this as property struct.

As seem in example, you can easily retrive item's properties via ItemPower type:
JASS:
            set this.minDamage   = GetItemPowerByIndex(this, 0)
            set this.maxDamage   = GetItemPowerByIndex(this, 1)
            set this.critChance  = GetItemPowerByIndex(this, 2)
            set this.critDamage  = GetItemPowerByIndex(this, 3)
            set this.blockChance = GetItemPowerByIndex(this, 4)
            set this.minBlock    = GetItemPowerByIndex(this, 5)
            set this.maxBlock    = GetItemPowerByIndex(this, 6)
Again, instead of implementing random stuff for every item in your map, you extend this and just spam myItem.myStat - where myItem is member of ItemPower or child struct extending it publicly as a parent - everywhere for everything you need.

Offtop: BPower's resource-castle has been successfully defended :D
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Update
  • Replaced AdvancedEquipmentSystem with library Equipment.
    They share the same API and have the same behaviour, however Equipment
    is faster, safer to use, requires and checks correct usage (debug only) and has additional features.
    It is also less code.
    Equipment does neither require LinkedListModule nor RegisterPlayerUnitEvent (optional).
    .
  • Equipment however can use SoundTools to play an error sound. (optional)
    .
  • Equipment has 100% backwards compatibility to AdvancedEquipmentSystem.
    .
  • ItemSet fires now an onCompleted/onIncompleted (previously completed) trigger.
    Two event response wrapper functions added to get the event ItemSet and event unit.
    .
  • Since ItemSet indexing does not require destroy(), Alloc has been removed.
    .
  • Everything still works with AdvancedEquipmentSystem.

Uploaded Equipment, because it's the inventory system I'm using myself.
It has never been approved by a (nother) moderator, but I couldn't find any mistake so far.
AdvancedEquipmentSystem however has a few minor ones!

Feel free to ask questions, if someting is not clear to you.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I'm planing to add a library called Backpack. The name is self-explanatory.
It will consume 1/2 inventory slots for switching inventory pages.
99 pages maximum. --> 99*5 slots available.
Also a function to sort the inventory, optionally an auto-sort implementation.

The API will be. create/destroy/manipulate (bags) /sort/UnitHasItemInBackpack/
dropItem
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I'm fixing the whole package. API will change (slightly), however I recommend to use
the new uploaded once I'm finished coding.

Huge if not giant update

ItemPower
  • Everything is new and better.
  • Not struct anymore, just normal functions
  • No UnitIndexer - UnitUserData, just HandleId(unit)
  • Better API, 100% fail safe ....
  • optionally uses ErrorMessage for best debug results

ItemSet

  • Works now flawless
  • Adds buff indicator to units, if wanted
  • Can use Bonus (Nestharus), if wanted and installed
  • Can use ItemPower (BPower), if wanted and imported
  • optionally uses ErrorMessage for best debug results
ItemSetBonus type is now identified via key
JASS:
        key ITEM_SET_BONUS_TYPE_ABILITY    
        key ITEM_SET_BONUS_TYPE_ITEM_POWER  
        key ITEM_SET_BONUS_TYPE_BONUS
You now have to run onItemEquip and onItemUnequip manually via:

static method onItemUnequip takes unit whichUnit, integer itemId returns nothing
static method onItemEquip takes unit whichUnit, integer itemId returns nothing

Equipment integrates these two methods into its system.
--> Works fully automatic with Equipment


Equipment
  • Code improvement
  • Can use Bonus, ItemPower and ItemSet if they exists
  • More and better API
  • Can now disable animationTag feature for units if wanted.
  • optionally uses ErrorMessage for best debug results
  • Works now also with Chaos ability (Metamorphosis).

I think these 3 systems are now really nice and extremly useful for anyone
who want to use an advanced inventory system.

Update:

Added BonusMod as optional requirement. It can be used as
alternative to Bonus (which is much harder to install. I tried myself)
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Settings in the demo map were invalid ( fixed that )

Fixed a small bug in ItemSet:
bugs
- Sets could be completed, by equipping the same item type multiple times.
--> Now the system checks if an item type is already equipped.
--> I missed that, because I designed ItemSet for an custom equip system, where items
have distinct item classes and only one valid item slot ( weapon, shield, .... ) in that scenario
one item type can't be equipped twice, but in normal wc3 inventory it can happen.

- ItemSet no longer requires Table. Everything runs now over one hashtable.
--> An hashtable was required to keep track of equipped items.

- Added GetTriggerItemSetEventId function to get the proper ItemSet event id when the event trigger fires.
--> Returns either INCOMPLETE or COMPLETE
I was surprise that nobody reported that bug. It also shows that this resource is not as
attractive as I though it is. Another reason could be that it is too hard to setup.
Hence I will improve the demonstration and re-submit ItemSet as it is a stand-alone resource
which is just an optional requirement of Equipment.
 
Last edited:
Top