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

ItemRestriction

  • Like
Reactions: deepstrasz
For restricting or limiting items from being equipped.
Used in Island Troll Tribes and Night of the Dead: Aftermath for restriction handling.

Exposes two classes, UnitRequirement and ItemRestriction.
UnitRequirement sets unit criteria that have to be met in order for unit to pickup given item:
- hero level
- statistics
- unit type
- arbitrary conditions e.g.: no trolls alive on the map

ItemRestriction enforces item limits:
- set item count limits
- group items by type
- set exceptions, e.g.: berserkers can wield up to two 2H weapons rather than one
- set exclusives, e.g.: 1H weapons and 2H weapons exclude each other
- customize error messages

Available in vJass and Wurst versions. Checkout code documentation for more info and play with demo map to get a general idea of how powerful this tool is.

Visit main repository if you want to know full history of the library. For setting Wurst-only dependency point, please use wurst repository.

Testing vJass:
- simply download the demo map and launch it
- test map contains all dependancies and optional requirements listed in Trigger Editor

Testing Wurst:
- clone repository using git and uncomment demo (InitDemos.wurst) for system you wish to test
- use 'Run a Wurst map' VSCode task for running the map

ItemRestriction (vJass):
JASS:
/*****************************************************************************
*
*    ItemRestriction v1.1.2.1
*       by Bannar
*
*    For restricting or limiting items from being equipped.
*
******************************************************************************
*
*    Requirements:
*
*       ListT by Bannar
*          hiveworkshop.com/threads/containers-list-t.249011/
*
*       RegisterPlayerUnitEvent by Bannar
*          hiveworkshop.com/threads/snippet-registerevent-pack.250266/
*
*       UnitDex by TriggerHappy
*          hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
*
*
*    Optional requirement:
*
*       SimError by Vexorian
*          wc3c.net/showthread.php?t=101260&highlight=SimError
*
******************************************************************************
*
*    Configurable error messages:
*
*       function GetUnitTypeErrorMessage takes UnitRequirement requirement, integer unitId returns string
*       function GetLevelErrorMessage takes UnitRequirement requirement, integer level returns string
*       function GetStatisticErrorMessage takes UnitRequirement requirement, integer value, string statistic returns string
*       function GetLimitErrorMessage takes ItemRestriction restriction, integer limit returns string
*       function GetExclusiveErrorMessage takes ItemRestriction first, ItemRestriction second returns string
*       function GetForbiddenErrorMessage takes ItemRestriction restriction returns string
*       function PrintErrorMessage takes player whichPlayer, string message returns nothing
*
******************************************************************************
*
*    Interface UnitRequirementPredicate:
*
*       static method isMet takes unit whichUnit returns string
*          Returns null on success or error message if unit does not meet predicate criteria.
*
*       module UnitRequirementPredicateModule
*          Declares body for new predicate type.
*
*
*    Predicate implementation example:
*
*        | struct MyPredicate extends array
*        |     static method isMet takes unit whichUnit returns string
*        |         return "This unit does not meet requirement criteria"
*        |     endmethod
*        |
*        |     implement UnitRequirementPredicateModule
*        | endstruct
*
******************************************************************************
*
*    struct UnitRequirement:
*
*       Fields:
*
*        | string name
*        |    Name associated with requirement.
*        |
*        | integer level
*        |    Unit level requirement.
*        |
*        | integer strength
*        |    Hero strength requirement.
*        |
*        | integer agility
*        |    Hero agility requirement.
*        |
*        | integer intelligence
*        |    Hero intelligence requirement.
*        |
*        | boolean includeBonuses
*        |    Whether to include bonuses when checking unit staticstics.
*
*
*       General:
*
*        | static method create takes string name returns thistype
*        |    Default ctor.
*        |
*        | method destroy takes nothing returns nothing
*        |    Default dctor.
*
*
*       Methods:
*
*        | method getUnits takes nothing returns IntegerList
*        |    Returns unit type requirement list.
*        |
*        | method has takes integer unitTypeId returns boolean
*        |    Whether specified unit type is a part of requirement.
*        |
*        | method addUnit takes integer unitTypeId returns thistype
*        |    Adds specified unit type to requirement criterias.
*        |
*        | method removeUnit takes integer unitTypeId returns thistype
*        |    Removes specified unit type from requirement criterias.
*        |
*        | method requireStat takes integer str, integer agi, integer int returns thistype
*        |    Sets hero statistic requirements to specified values.
*        |
*        | method addCondition takes UnitRequirementPredicate predicate returns thistype
*        |    Adds new criteria to requirement criterias.
*        |
*        | method removeCondition takes UnitRequirementPredicate predicate returns thistype
*        |    Removes specified condition from requirement criterias.
*        |
*        | method test takes unit whichUnit returns string
*        |    Validates whether specified unit meets this unit requirements.
*        |
*        | method filter takes unit whichUnit returns boolean
*        |    Returns value indicating whether specified unit successfully passed requirement test.
*
*
*    struct ItemRestriction:
*
*       Fields:
*
*        | string name
*        |    Name associated with restriction.
*        |
*        | integer limit
*        |    Maximum number of items a unit can carry.
*        |
*        | UnitRequirement requirement
*        |    Requirement a unit must meet to hold items.
*
*
*       General:
*
*        | static method create takes string name, integer limit returns thistype
*        |    Default ctor.
*        |
*        | method destroy takes nothing returns nothing
*        |    Default dctor.
*
*
*       Methods:
*
*        | method getItems takes nothing returns IntegerList
*        |    Item types that enforce this restriction.
*        |
*        | method has takes integer itemTypeId returns boolean
*        |    Whether specified item type is a part of restriction.
*        |
*        | method removeItem takes integer itemTypeId returns thistype
*        |    Remove specified item type from this restriction.
*        |
*        | method addItem takes integer itemTypeId returns thistype
*        |    Add specified item type to this restriction.
*        |
*        | method getExceptions takes nothing returns LimitExceptionList
*        |    Returns collection of UnitRequirement instances that may define different limits.
*        |    Example: berserker may carry two 2H-weapons, rather than one.
*        |
*        | method removeException takes UnitRequirement requirement returns thistype
*        |    Removes item limit exception for specified requirement.
*        |
*        | method addException takes UnitRequirement requirement, integer newLimit returns thistype
*        |    Adds new item limit exception for specified requirement.
*        |
*        | method getExclusives takes nothing returns ItemRestrictionList
*        |    Returns collection of ItemRestriction instances that exclude each other from being picked.
*        |    Example: a unit cannot carry both 1H-weapons and 2H-weapons at the same time.
*        |
*        | method removeExclusive takes ItemRestriction restriction returns thistype
*        |    Makes specified restriction non-exclusive with this restriction.
*        |
*        | method addExclusive takes ItemRestriction restriction returns thistype
*        |    Makes specified restriction exclusive with this restriction.
*        |
*        | method getCount takes unit whichUnit returns integer
*        |    Returns related to this restriction, current item count for specified unit.
*        |
*        | method getException takes unit whichUnit returns LimitException
*        |    Returns currently chosen limit exception if any for specified unit.
*        |
*        | method test takes unit whichUnit, item whichItem returns string
*        |    Validates whether specified unit can hold specified itm given the restriction criteria.
*        |
*        | method filter takes unit whichUnit, item whichItem returns boolean
*        |    Returns value indicating whether specified unit successfully
*        |    passed restriction test for specified item.
*
*
*****************************************************************************/
library ItemRestriction requires /*
                        */ ListT /*
                        */ RegisterPlayerUnitEvent /*
                        */ UnitDex /*
                        */ optional SimError

private function GetUnitTypeErrorMessage takes UnitRequirement requirement, integer unitId returns string
    return "This item can not be hold by this unit type."
endfunction

private function GetLevelErrorMessage takes UnitRequirement requirement, integer level returns string
    return "This item requires level " + I2S(level) + " to be picked up."
endfunction

private function GetStatisticErrorMessage takes UnitRequirement requirement, integer value, string statistic returns string
    return "This item requires " + I2S(value) + " " + statistic + "."
endfunction

private function GetLimitErrorMessage takes ItemRestriction restriction, integer limit returns string
    return "This unit can not hold more than " + I2S(limit) + " item(s) of \"" + restriction.name + "\" type."
endfunction

private function GetExclusiveErrorMessage takes ItemRestriction first, ItemRestriction second returns string
    return "This unit cannot hold items of type \"" + first.name + "\" and \"" + second.name + "\" at the same time."
endfunction

private function GetForbiddenErrorMessage takes ItemRestriction restriction returns string
    return "This item can not be picked up by this unit."
endfunction

private function PrintErrorMessage takes player whichPlayer, string message returns nothing
static if LIBRARY_SimError then
    call SimError(whichPlayer, message)
else
    call DisplayTimedTextToPlayer(whichPlayer, 0, 0, 2.0, message)
endif
endfunction

globals
    private trigger array triggers
    private unit argUnit = null
    private string retMessage = null
endglobals

struct UnitRequirementPredicate extends array
    implement Alloc

    static method isMet takes unit whichUnit returns string
        return null
    endmethod

    static method create takes nothing returns thistype
        local thistype this = allocate()
        set triggers[this] = CreateTrigger()
        return this
    endmethod

    method destroy takes nothing returns nothing
        call DestroyTrigger(triggers[this])
        set triggers[this] = null
        call deallocate()
    endmethod
endstruct

module UnitRequirementPredicateModule
    private delegate UnitRequirementPredicate predicate

    private static method onInvoke takes nothing returns boolean
        set retMessage = thistype.isMet(argUnit)
        return retMessage == null
    endmethod

    static method create takes nothing returns thistype
        local thistype this = UnitRequirementPredicate.create()
        set predicate = this
        call TriggerAddCondition(triggers[this], Condition(function thistype.onInvoke))
        return this
    endmethod

    method destroy takes nothing returns nothing
        call predicate.destroy()
    endmethod
endmodule

struct UnitRequirement extends array
    string name
    integer level
    integer strength
    integer agility
    integer intelligence
    boolean includeBonuses
    private IntegerList units
    private IntegerList conditions

    implement Alloc

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

        set units = IntegerList.create()
        set level = 0
        set strength = 0
        set agility = 0
        set intelligence = 0
        set includeBonuses = false
        set this.name = name
        set conditions = IntegerList.create()

        return this
    endmethod

    method destroy takes nothing returns nothing
        set name = null
        call units.destroy()
        call conditions.destroy()
        call deallocate()
    endmethod

    method has takes integer unitTypeId returns boolean
        return units.find(unitTypeId) != 0
    endmethod

    method requireStat takes integer str, integer agi, integer intel returns thistype
        set strength = str
        set agility = agi
        set intelligence = intel

        return this
    endmethod

    method getUnits takes nothing returns IntegerList
        return IntegerList[units]
    endmethod

    method addUnit takes integer unitTypeId returns thistype
        local IntegerListItem node = units.find(unitTypeId)
        if unitTypeId > 0 and node == 0 then
            call units.push(unitTypeId)
        endif
        return this
    endmethod

    method removeUnit takes integer unitTypeId returns thistype
        local IntegerListItem node = units.find(unitTypeId)
        if node != 0 then
            call units.erase(node)
        endif
        return this
    endmethod

    method addCondition takes UnitRequirementPredicate predicate returns thistype
        local IntegerListItem node = conditions.find(predicate)
        if predicate != 0 and node == 0 then
            call conditions.push(predicate)
        endif
        return this
    endmethod

    method removeCondition takes UnitRequirementPredicate predicate returns thistype
        local IntegerListItem node = conditions.find(predicate)
        if node != 0 then
            call conditions.erase(node)
        endif
        return this
    endmethod

    method test takes unit whichUnit returns string
        local integer unitTypeId = GetUnitTypeId(whichUnit)
        local IntegerListItem iter
        local UnitRequirementPredicate condition

        if not units.empty() and not has(unitTypeId) then
            return GetUnitTypeErrorMessage(this, unitTypeId)
        elseif level > 0 and GetHeroLevel(whichUnit) < level then
            return GetLevelErrorMessage(this, level)
        elseif strength > 0 and GetHeroStr(whichUnit, includeBonuses) < strength then
            return GetStatisticErrorMessage(this, strength, "Strength")
        elseif agility > 0 and GetHeroAgi(whichUnit, includeBonuses) < agility then
            return GetStatisticErrorMessage(this, agility, "Agility")
        elseif intelligence > 0 and GetHeroInt(whichUnit, includeBonuses) < intelligence then
            return GetStatisticErrorMessage(this, intelligence, "Intelligence")
        endif

        set argUnit = whichUnit
        set iter = conditions.first
        loop
            exitwhen iter == 0
            set condition = iter.data
            if not TriggerEvaluate(triggers[condition]) then
                return retMessage
            endif
            set iter = iter.next
        endloop

        return null
    endmethod

    method filter takes unit whichUnit returns boolean
        return test(whichUnit) == null
    endmethod
endstruct

struct LimitException extends array
    UnitRequirement requirement
    integer newLimit

    implement Alloc

    static method create takes UnitRequirement requirement, integer newLimit returns thistype
        local thistype this = allocate()
        set this.requirement = requirement
        set this.newLimit = newLimit
        return this
    endmethod

    method destroy takes nothing returns nothing
        call deallocate()
    endmethod
endstruct

//! runtextmacro DEFINE_STRUCT_LIST("", "LimitExceptionList", "LimitException")
//! runtextmacro DEFINE_STRUCT_LIST("", "ItemRestrictionList", "ItemRestriction")

private module ItemRestrictionInit
    private static method onInit takes nothing returns nothing
        set instanceTable = Table.create()

        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, function thistype.onPickup)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function thistype.onDrop)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.onTargetOrder)
        call RegisterUnitIndexEvent(Condition(function thistype.onDeindex), EVENT_UNIT_DEINDEX)
    endmethod
endmodule

struct ItemRestriction extends array
    string name
    integer limit
    UnitRequirement requirement
    private Table table
    private IntegerList items
    private LimitExceptionList exceptions
    private ItemRestrictionList exclusives
    // For extra speed, each item type involved will have separate list assigned
    private static Table instanceTable

    implement Alloc

    private static method saveRestriction takes integer index, ItemRestriction restriction returns nothing
        local ItemRestrictionList restrictions
        if not instanceTable.has(index) then
            set instanceTable[index] = ItemRestrictionList.create()
        endif
        set restrictions = instanceTable[index]
        if restrictions.find(restriction) == 0 then
            call restrictions.push(restriction)
        endif
    endmethod

    private static method flushRestriction takes integer index, ItemRestriction restriction returns nothing
        local ItemRestrictionList restrictions = instanceTable[index]
        call restrictions.erase(restrictions.find(restriction))
        if restrictions.empty() then
            call restrictions.destroy()
            call instanceTable.remove(index)
        endif
    endmethod

    static method getRestrictions takes integer index returns ItemRestrictionList
        if instanceTable.has(index) then
            return instanceTable[index]
        endif
        return 0
    endmethod

    static method create takes string name, integer limit returns thistype
        local thistype this = allocate()

        set table = Table.create()
        set items = IntegerList.create()
        set exceptions = LimitExceptionList.create()
        set exclusives = ItemRestrictionList.create()
        set this.name = name
        set this.limit = limit
        set this.requirement = 0
        // Global instance list for handling deindex event
        call saveRestriction(0, this)

        return this
    endmethod

    method destroy takes nothing returns nothing
        local ItemRestrictionList restrictions
        local ItemRestrictionListItem iter
        local ItemRestrictionListItem node
        local ItemRestriction exclusive
        call flushRestriction(0, this)

        set iter = items.first
        loop
            exitwhen iter == 0
            call flushRestriction(iter.data, this)
            set iter = iter.next
        endloop

        if not exclusives.empty() then
            set iter = exclusives.first
            loop
                exitwhen iter == 0
                set exclusive = iter.data
                set node = exclusive.exclusives.find(this)
                call exclusive.exclusives.erase(node)
                set iter = iter.next
            endloop
        endif

        call table.destroy()
        call items.destroy()
        call exceptions.destroy()
        call exclusives.destroy()
        set name = null
        call deallocate()
    endmethod

    method getItems takes nothing returns IntegerList
        return IntegerList[items]
    endmethod

    method has takes integer itemTypeId returns boolean
        return items.find(itemTypeId) != 0
    endmethod

    method removeItem takes integer itemTypeId returns thistype
        if has(itemTypeId) then
            call items.erase(items.find(itemTypeId))
            call flushRestriction(itemTypeId, this)
        endif
        return this
    endmethod

    method addItem takes integer itemTypeId returns thistype
        if itemTypeId > 0 and not has(itemTypeId) then
            call items.push(itemTypeId)
            call saveRestriction(itemTypeId, this)
        endif
        return this
    endmethod

    method getExceptions takes nothing returns LimitExceptionList
        return LimitExceptionList[exceptions]
    endmethod

    method removeException takes UnitRequirement requirement returns thistype
        local LimitExceptionListItem iter = exceptions.first
        local LimitException exception

        loop
            exitwhen iter == 0
            set exception = iter.data
            if exception.requirement == requirement then
                call exceptions.erase(iter)
                call exception.destroy()
                exitwhen true
            endif
            set iter = iter.next
        endloop

        return this
    endmethod

    method addException takes UnitRequirement requirement, integer newLimit returns thistype
        local LimitExceptionListItem iter = exceptions.first
        loop
            exitwhen iter == 0
            if iter.data.requirement == requirement then
                return this
            endif
            set iter = iter.next
        endloop
        call exceptions.push(LimitException.create(requirement, newLimit))

        return this
    endmethod

    method getExclusives takes nothing returns ItemRestrictionList
        return ItemRestrictionList[exclusives]
    endmethod

    method removeExclusive takes ItemRestriction restriction returns thistype
        local ItemRestrictionListItem node = exclusives.find(restriction)
        if node != 0 then
            call exclusives.erase(node)
            set node = restriction.exclusives.find(this)
            call restriction.exclusives.erase(node)
        endif

        return this
    endmethod

    method addExclusive takes ItemRestriction restriction returns thistype
        if restriction != 0 and exclusives.find(restriction) == 0 then
            call exclusives.push(restriction)
            call restriction.exclusives.push(this)
        endif

        return this
    endmethod

    private method geTcount takes integer index returns integer
        return table[index]
    endmethod

    private method incCount takes integer index returns nothing
        set table[index] = geTcount(index) + 1
    endmethod

    private method decCount takes integer index returns nothing
        set table[index] = geTcount(index) - 1
    endmethod

    private method geTexception takes integer index returns LimitException
        return table[-index]
    endmethod

    private method setException takes integer index, LimitException exception returns nothing
        set table[-index] = exception
    endmethod

    method getCount takes unit whichUnit returns integer
        return geTcount(GetUnitId(whichUnit))
    endmethod

    method getException takes unit whichUnit returns LimitException
        return geTexception(GetUnitId(whichUnit))
    endmethod

    method test takes unit whichUnit, item whichItem returns string
        local string errorMessage
        local IntegerListItem iter
        local ItemRestriction exclusive
        local integer index = GetUnitId(whichUnit)
        local integer threshold = limit
        local LimitException exception

        if not has(GetItemTypeId(whichItem)) then
            return null
        elseif requirement != 0 then
            set errorMessage = requirement.test(whichUnit)
            if errorMessage != null then
                return errorMessage
            endif
        endif

        set iter = exclusives.first
        loop
            exitwhen iter == 0
            set exclusive = iter.data

            if exclusive.geTcount(index) > 0 then
                return GetExclusiveErrorMessage(this, exclusive)
            endif
            set iter = iter.next
        endloop

        if not exceptions.empty() then
            set exception = geTexception(index)
            if exception == 0 or exceptions.find(exception) == 0 then
                call table.remove(-index) // clear assigned exception if any

                set iter = exceptions.first
                loop
                    exitwhen iter == 0
                    set exception = iter.data

                    if exception.requirement.filter(whichUnit) then
                        set threshold = exception.newLimit
                        call setException(index, exception)
                        exitwhen true
                    endif

                    set iter = iter.next
                endloop
            else
                set threshold = exception.newLimit
            endif
        endif

        if threshold <= 0 then
            return GetForbiddenErrorMessage(this)
        elseif geTcount(index) >= threshold then
            return GetLimitErrorMessage(this, threshold)
        endif

        return null
    endmethod

    method filter takes unit whichUnit, item whichItem returns boolean
        return test(whichUnit, whichItem) == null
    endmethod

    // Returns null (not allowed), empty list (no restrictions) or
    // non-empty list (restrictions to increase count for).
    // Caller is responsible for destroying retrieved list if any
    private static method evaluateRestrictions takes unit u, item itm returns ItemRestrictionList
        local ItemRestrictionList result = ItemRestrictionList.create()
        local ItemRestrictionList restrictions = getRestrictions(GetItemTypeId(itm))
        local ItemRestrictionListItem iter
        local ItemRestriction restriction
        local string errorMessage

        if restrictions != 0 then
            set iter = restrictions.first
            loop
                exitwhen iter == 0
                set restriction = iter.data
                set errorMessage = restriction.test(u, itm)

                if errorMessage != null then     
                    call PrintErrorMessage(GetOwningPlayer(u), errorMessage)
                    call result.destroy()
                    set result = 0
                    exitwhen true
                endif

                call result.push(restriction)
                set iter = iter.next
            endloop
        endif

        return result
    endmethod

    private static method onPickup takes nothing returns nothing
        local item itm = GetManipulatedItem()
        local unit u
        local integer index
        local ItemRestrictionList associated
        local ItemRestrictionListItem iter
        local trigger t

        if not IsItemPowerup(itm) then
            set u = GetTriggerUnit()
            set associated = evaluateRestrictions(u, itm)

            if associated != 0 then
                set index = GetUnitId(u)
                set iter = associated.first

                loop
                    exitwhen iter == 0
                    call iter.data.incCount(index)
                    set iter = iter.next
                endloop
                call associated.destroy()
            else
                set t = GetAnyPlayerUnitEventTrigger(EVENT_PLAYER_UNIT_DROP_ITEM)
                call DisableTrigger(t)
                call UnitRemoveItem(u, itm)
                call EnableTrigger(t)
                set t = null
            endif

            set u = null
        endif
        set itm = null
    endmethod

    private static method onDrop takes nothing returns nothing
        local ItemRestrictionList restrictions = getRestrictions(GetItemTypeId(GetManipulatedItem()))
        local ItemRestrictionListItem iter
        local integer index
        local integer count
        local ItemRestriction restriction

        if restrictions != 0 then
            set index = GetUnitId(GetTriggerUnit())
            set iter = restrictions.first

            loop
                exitwhen iter == 0
                set restriction = iter.data
                set count = restriction.geTcount(index)

                if count > 0 then
                    call restriction.decCount(index)
                endif
                set iter = iter.next
            endloop
        endif
    endmethod

    private static method onTargetOrder takes nothing returns nothing
        local item itm = GetOrderTargetItem()
        local unit u
        local ItemRestrictionList associated

        if GetIssuedOrderId() == 851971 and itm != null then // order smart
            set u = GetTriggerUnit()
            set associated = evaluateRestrictions(u, itm)

            if associated == 0 then
                if not IsUnitPaused(u) then
                    call PauseUnit(u, true)
                    call IssueImmediateOrderById(u, 851972) // order stop
                    call PauseUnit(u, false)
                endif
            else
                call associated.destroy()
            endif
            set u = null
        endif
        set itm = null
    endmethod

    private static method onDeindex takes nothing returns nothing
        local integer index = GetIndexedUnitId()
        local ItemRestrictionList restrictions = getRestrictions(0)
        local ItemRestrictionListItem iter
        local ItemRestriction restriction

        if restrictions != 0 then
            set iter = restrictions.first
            loop
                exitwhen iter == 0
                set restriction = iter.data
                if restriction.table.has(index) then
                    call restriction.table.flush()
                endif
                set iter = iter.next
            endloop
        endif
    endmethod

    implement ItemRestrictionInit
endstruct

endlibrary
Demo code:
JASS:
scope ItemRestrictionDemo initializer Init

native UnitAlive takes unit whichUnit returns boolean

struct AtLeastOneSpiderAlive extends array
    static method filter takes nothing returns boolean
        return GetUnitTypeId(GetFilterUnit()) == 'nssp' and UnitAlive(GetFilterUnit()) // Spitting Spider
    endmethod

    static method isMet takes unit whichUnit returns string
        local group g = CreateGroup()
        local string result = null
        local item itm = GetManipulatedItem()
        set itm = GetOrderTargetItem()
        call DisplayTimedTextToPlayer(Player(0), 0, 0, 5, "isMet: "+GetItemName(itm))

        call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, Filter(function thistype.filter))
        if FirstOfGroup(g) == null then
            set result = "There are no spider alive on the map. Spare a few."
        endif

        call DestroyGroup(g)
        set g = null
        return result
    endmethod

    implement UnitRequirementPredicateModule
endstruct

globals
    string description = "Heroes are limited to two 1H weapons.\n" + /*
    */ "Heroes are limited to one 2H weapon.\n" + /*
    */ "Tauren Chieftain is an exception, he can hold up to two 2H weapons.\n" + /*
    */ "1H weapons and 2H weapons exclude ech other.\n" + /*
    */ "Spider Ring can only be picked up if at least one spider is alive on the map."
endglobals

private function Init takes nothing returns nothing
    local ItemRestriction weapon1h = ItemRestriction.create("1H-weapon", 2)
    local ItemRestriction weapon2h = ItemRestriction.create("2H-weapon", 1)
    local UnitRequirement tauren = UnitRequirement.create("tauren")
    local ItemRestriction spiderRing = ItemRestriction.create("Friend of the spiders", 1)
    local UnitRequirement atLeastOneSpiderAlive = UnitRequirement.create("Spare the spiders")

    call spiderRing.addItem('sprn')
    call atLeastOneSpiderAlive.addCondition(AtLeastOneSpiderAlive.create())
    set spiderRing.requirement = atLeastOneSpiderAlive

    call tauren.addUnit('Otch')
    call weapon1h.addItem('desc')
    call weapon2h.addItem('mlst')
    call weapon2h.addExclusive(weapon1h)
    call weapon2h.addException(tauren, 2)

    call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 45, description)
endfunction

endscope
ItemRestriction (Wurst):
Wurst:
/*
*  ItemRestriction v1.1.2.2
*     by Bannar
*
*  For restricting or limiting items from being equipped.
*/
package ItemRestriction
import HashMap
import LinkedList
import UnitIndexer
import RegisterEvents
import Orders

@configurable function getUnitTypeErrorMessage(UnitRequirement _requirement, int _unitId) returns string
    return "This item can not be hold by this unit type."

@configurable function getLevelErrorMessage(UnitRequirement _requirement, int level) returns string
    return "This item requires level " + level.toString() + " to be picked up."

@configurable function getStatisticErrorMessage(UnitRequirement _requirement, int value, string statistic) returns string
    return "This item requires " + value.toString() + " " + statistic + "."

@configurable function getLimitErrorMessage(ItemRestriction restriction, int limit) returns string
    return "This unit can not hold more than " + limit.toString() + " item(s) of \"" + restriction.name + "\" type."

@configurable function getExclusiveErrorMessage(ItemRestriction first, ItemRestriction second) returns string
    return "This unit cannot hold items of type \"" + first.name + "\" and \"" + second.name + "\" at the same time."

@configurable function getForbiddenErrorMessage(ItemRestriction _restriction) returns string
    return "This item can not be picked up by this unit."

@configurable function printErrorMessage(player whichPlayer, string message)
    printTimedToPlayer(message, 2, whichPlayer)

public interface UnitRequirementPredicate
    /** Returns null on success or error message if unit does not meet predicate criteria. */
    function isMet(unit whichUnit) returns string

/** Set of unit-specific requirements. */
public class UnitRequirement
    /** Name associated with requirement. */
    string name
    /** Unit level requirement. */
    var level = 0
    /** Hero strength requirement. */
    var strength = 0
    /** Hero agility requirement. */
    var agility = 0
    /** Hero intelligence requirement. */
    var intelligence = 0
    /** Whether to include bonuses when checking unit statistics. */
    var includeBonuses = false

    /** Unit type requirement. Omitted if empty. */
    private constant units = new LinkedList<int>()
    private constant conditions = new LinkedList<UnitRequirementPredicate>()

    construct(string name)
        this.name = name

    ondestroy
        destroy units
        destroy conditions

    /** Whether specified unit type is a part of requirement. */
    function has(int unitTypeId) returns boolean
        return units.contains(unitTypeId)

    /** Sets hero statistic requirements to specified values. */
    function requireStat(int str, int agi, int intel)
        strength = str
        agility = agi
        intelligence = intel

    /** Returns unit type requirement list. */
    function getUnits() returns LinkedList<int>
        return units.copy()

    /** Adds specified unit type to requirement criterias. */
    function addUnit(int unitTypeId)
        if unitTypeId > 0 and not units.contains(unitTypeId)
            units.push(unitTypeId)

    /** Removes specified unit type from requirement criterias. */
    function removeUnit(int unitTypeId)
        if unitTypeId > 0
            units.remove(unitTypeId)

    /** Adds new criteria to requirement criterias. */
    function addCondition(UnitRequirementPredicate predicate)
        if predicate != null and not conditions.contains(predicate)
            conditions.add(predicate)

    /** Removes specified condition from requirement criterias. */
    function removeCondition(UnitRequirementPredicate predicate)
        if predicate != null
            conditions.remove(predicate)

    /** Validates whether specified unit meets this unit requirements. */
    function test(unit whichUnit) returns string
        var unitTypeId = whichUnit.getTypeId()

        if not units.isEmpty() and not has(unitTypeId)
            return getUnitTypeErrorMessage(this, unitTypeId)
        if level > 0 and whichUnit.getLevel() < level
            return getLevelErrorMessage(this, level)
        if strength > 0 and whichUnit.getStr(includeBonuses) < strength
            return getStatisticErrorMessage(this, strength, "Strength")
        if agility > 0 and whichUnit.getAgi(includeBonuses) < agility
            return getStatisticErrorMessage(this, agility, "Agility")
        if intelligence > 0 and whichUnit.getInt(includeBonuses) < intelligence
            return getStatisticErrorMessage(this, intelligence, "Intelligence")

        for condition in conditions
            var errorMessage = condition.isMet(whichUnit)
            if errorMessage != null
                return errorMessage

        return null

    /** Returns value indicating whether specified unit successfully passed requirement test. */
    function filter(unit whichUnit) returns bool
        return test(whichUnit) == null

public class LimitException
    UnitRequirement requirement
    int newLimit

    construct(UnitRequirement requirement, int newLimit)
        this.requirement = requirement
        this.newLimit = newLimit

/** Allows to restrict or limit the number of item equipped by unit. */
public class ItemRestriction
    /** Name associated with restriction. */
    string name
    /** Maximum number of items a unit can carry. */
    int limit
    /** Requirement a unit must meet to hold items. */
    UnitRequirement requirement = null

    protected constant table = new Table()
    private constant items = new LinkedList<int>()
    private constant exceptions = new LinkedList<LimitException>()
    private constant exclusives = new LinkedList<ItemRestriction>()
    // For extra speed, each item type involved will have separate list assigned
    private static constant instances = new HashMap<int, LinkedList<ItemRestriction>>()

    private static function saveRestriction(int index, ItemRestriction restriction)
        if not instances.has(index)
            instances.put(index, new LinkedList<ItemRestriction>())
        var restrictions = instances.get(index)
        if not restrictions.contains(restriction)
            restrictions.push(restriction)

    private static function flushRestriction(int index, ItemRestriction restriction)
        var restrictions = instances.get(index)
        restrictions.remove(restriction)
        if restrictions.isEmpty()
            destroy restrictions
            instances.remove(index)

    /** Returns restrictions associated with specified item type. */
    static function getRestrictions(int index) returns LinkedList<ItemRestriction>
        LinkedList<ItemRestriction> result = null
        if instances.has(index)
            result = instances.get(index)
        return result

    construct(string name, int limit)
        this.name = name
        this.limit = limit
        // Global instance list for handling deindex event
        saveRestriction(0, this)

    ondestroy
        flushRestriction(0, this)
        items.forEach(e -> flushRestriction(e, this))
        exclusives.forEach(e -> e.exclusives.remove(this))

        destroy table
        destroy items
        destroy exceptions
        destroy exclusives

    /** Item types that enforce this restriction. */
    function getItems() returns LinkedList<int>
        return items.copy()

    /** Whether specified item type is a part of restriction. */
    function has(int itemTypeId) returns boolean
        return items.contains(itemTypeId)

    /** Remove specified item type from this restriction. */
    function removeItem(int itemTypeId)
        if has(itemTypeId)
            items.remove(itemTypeId)
            flushRestriction(itemTypeId, this)

    /** Add specified item type to this restriction. */
    function addItem(int itemTypeId)
        if itemTypeId > 0 and not has(itemTypeId)
            items.push(itemTypeId)
            saveRestriction(itemTypeId, this)

    /** Returns collection of UnitRequirement instances that may define different limits.
        Example: berserker may carry two 2H-weapons, rather than one. */
    function getExceptions() returns LinkedList<LimitException>
        return exceptions.copy()

    /** Removes item limit exception for specified requirement. */
    function removeException(UnitRequirement requirement)
        var iter = exceptions.iterator()
        for exception from iter
            if exception.requirement == requirement
                iter.remove()
                destroy exception
                break
        iter.close()

    /** Adds new item limit exception for specified requirement. */
    function addException(UnitRequirement requirement, int newLimit)
        if requirement != null
            for exception from exceptions.staticItr()
                if exception.requirement == requirement
                    return
            exceptions.push(new LimitException(requirement, newLimit))

    /** Returns collection of ItemRestriction instances that exclude each other from being picked.
        Example: a unit cannot carry both 1H-weapons and 2H-weapons at the same time. */
    function getExclusives() returns LinkedList<ItemRestriction>
        return exclusives.copy()

    /** Makes specified restriction non-exclusive with this restriction. */
    function removeExclusive(ItemRestriction restriction)
        if exclusives.contains(restriction)
            exclusives.remove(restriction)
            restriction.exclusives.remove(this)

    /** Makes specified restriction exclusive with this restriction. */
    function addExclusive(ItemRestriction restriction)
        if restriction != null and not exclusives.contains(restriction)
            exclusives.push(restriction)
            restriction.exclusives.push(this)

    protected function getCount(int index) returns int
        return table.loadInt(index)

    protected function incCount(int index)
        table.saveInt(index, getCount(index) + 1)

    protected function decCount(int index)
        table.saveInt(index, getCount(index) - 1)

    protected function getException(int index) returns LimitException
        return table.loadInt(-index) castTo LimitException

    protected function setException(int index, LimitException exception)
        table.saveInt(-index, exception castTo int)

    /** Returns related to this restriction, current item count for specified unit. */
    function getCount(unit whichUnit) returns int
        return getCount(whichUnit.getIndex())

    /** Returns currently chosen limit exception if any for specified unit. */
    function getException(unit whichUnit) returns LimitException
        return getException(whichUnit.getIndex())

    /** Validates whether specified unit can hold specified itm given the restriction criteria. */
    function test(unit whichUnit, item whichItem) returns string
        if not has(whichItem.getTypeId())
            return null
        else if requirement != null
            var errorMessage = requirement.test(whichUnit)
            if errorMessage != null
                return errorMessage

        var index = whichUnit.getIndex()
        for exclusive in exclusives
            if exclusive.getCount(index) > 0
                return getExclusiveErrorMessage(this, exclusive)

        var threshold = limit
        if not exceptions.isEmpty()
            var exception = getException(index)
            if exception == null or not exceptions.contains(exception)
                table.removeInt(-index) // clear assigned exception if any

                var iter = exceptions.iterator()
                while iter.hasNext()
                    exception = iter.next()
                    if exception.requirement.filter(whichUnit)
                        threshold = exception.newLimit
                        setException(index, exception)
                        break
                iter.close()
            else
                threshold = exception.newLimit

        if threshold <= 0
            return getForbiddenErrorMessage(this)
        if getCount(index) >= threshold
            return getLimitErrorMessage(this, threshold)
        return null
   
    /** Returns value indicating whether specified unit successfully passed restriction test for specified item. */
    function filter(unit whichUnit, item whichItem) returns bool
        return test(whichUnit, whichItem) == null

// Returns null (not allowed), empty list (no restrictions) or non-empty list (restrictions to increase count for).
// Caller is responsible for destroying retrieved list if any
function evaluateRestrictions(unit u, item itm) returns LinkedList<ItemRestriction>
    var result = new LinkedList<ItemRestriction>()
    var restrictions = ItemRestriction.getRestrictions(itm.getTypeId())

    if restrictions != null
        for restriction in restrictions
            var errorMessage = restriction.test(u, itm)
            if errorMessage != null
                printErrorMessage(u.getOwner(), errorMessage)
                destroy result
                result = null
                break
            result.push(restriction)
    return result

function onPickup()
    var itm = GetManipulatedItem()
    if not itm.isPowerup()
        var u = GetTriggerUnit()
        var associated = evaluateRestrictions(u, itm)

        if associated != null
            var index = u.getIndex()
            associated.forEach(e -> e.incCount(index))
            destroy associated
        else
            var t = getPlayerUnitEventTrigger(EVENT_PLAYER_UNIT_DROP_ITEM)
            t.disable()
            u.removeItem(itm)
            t.enable()

function onDrop()
    var itemTypeId = GetManipulatedItem().getTypeId()
    var restrictions = ItemRestriction.getRestrictions(itemTypeId)

    if restrictions != null
        var index = GetTriggerUnit().getIndex()
        for restriction from restrictions.staticItr()
            var count = restriction.getCount(index)
            if count > 0
                restriction.decCount(index)

function onTargetOrder()
    var itm = GetOrderTargetItem()
    if GetIssuedOrderId() == SpecialOrders.smart and itm != null
        var u = GetTriggerUnit()
        var associated = evaluateRestrictions(u, itm)
        if associated == null
            u.abortOrder()
        else
            destroy associated

function onDeindex()
    var index = getDeindexedUnit().getIndex()
    var restrictions = ItemRestriction.getRestrictions(0)

    if restrictions != null
        for restriction from restrictions.staticItr()
            if restriction.table.hasInt(index)
                restriction.table.flush()

init
    registerPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, () -> onPickup())
    registerPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, () -> onDrop())
    registerPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, () -> onTargetOrder())
    onUnitDeindex(() -> onDeindex())
Demo code:
Wurst:
package ItemRestrictionDemo
import ItemRestriction

class AtLeastOneSpiderAlive implements UnitRequirementPredicate
    static function filter() returns boolean
        return GetFilterUnit().getTypeId() == 'nssp' and GetFilterUnit().isAlive() // Spitting Spider

    function isMet(unit _whichUnit) returns string
        group g = CreateGroup()
        string result = null

        g.enumUnitsInRect(bj_mapInitialPlayableArea, Filter(function filter))
        if not g.hasNext()
            result = "There are no spider alive on the map. Spare a few."

        g.destr()
        return result

string description = "Heroes are limited to two 1H weapons.\n" +
    "Heroes are limited to one 2H weapon.\n" +
    "Tauren Chieftain is an exception, he can hold up to two 2H weapons.\n" +
    "1H weapons and 2H weapons exclude ech other.\n" +
    "Spider Ring can only be picked up if at least one spider is alive on the map."

function createAllItems()
    CreateItem('desc', - 254.5, 325.1)
    CreateItem('desc', - 145.3, 328.8)
    CreateItem('desc', - 145.9, 224.0)
    CreateItem('desc', - 255.3, 221.0)
    CreateItem('mlst', - 533.7, - 124.3)
    CreateItem('mlst', - 533.1, - 15.6)
    CreateItem('mlst', - 634.2, - 24.8)
    CreateItem('mlst', - 637.4, - 136.9)
    CreateItem('sprn', - 291.9, 11.3)

public function initItemRestrictionDemo()
    createAllItems()

    var weapon1h = new ItemRestriction("1H-weapon", 2)
    var weapon2h = new ItemRestriction("2H-weapon", 1)
    var tauren = new UnitRequirement("tauren")
    var spiderRing = new ItemRestriction("Friend of the spiders", 1)
    var atLeastOneSpiderAlive = new UnitRequirement("Spare the spiders")

    spiderRing.addItem('sprn')
    atLeastOneSpiderAlive.addCondition(new AtLeastOneSpiderAlive())
    spiderRing.requirement = atLeastOneSpiderAlive

    tauren.addUnit('Otch')
    weapon1h.addItem('desc')
    weapon2h.addItem('mlst')
    weapon2h.addExclusive(weapon1h)
    weapon2h.addException(tauren, 2)

    printTimed(description, 45)
Contents

Test Map (Map)

Reviews
MyPad
Nitpicks: The vJASS snippet in description can be removed, since the trigger viewer functionality can expose it anyway. In ItemRestriction.onTargetOrder, why pause the unit before stopping it? call PauseUnit(u, true) call IssueImmediateOrderById(u...

Nitpicks:

  • The vJASS snippet in description can be removed, since the trigger viewer functionality can expose it anyway.
  • In ItemRestriction.onTargetOrder, why pause the unit before stopping it?
JASS:
call PauseUnit(u, true)
call IssueImmediateOrderById(u, 851972) // order stop
call PauseUnit(u, false)

Status:

  • Approved
 
Last edited:
Bannar is right I think, it is a problem for example if "stop" gets ordered at non-instant order events. For example, in "Target Order Event" or "Point Order Event", we try to order (the instant order) "stop", and indeed, it gets instantly executed. But then afterwards, after the "stop" was already executed, the move order (from event) just kicks in and overrides the stop order, as it is executed not instantly. With pausing the unit before stop, the order queue gets cleaned, so the move order won't be executed afterwards.
 
Level 2
Joined
Apr 16, 2017
Messages
13
If I want to set this data dynamically. I should do this. such as. I want to bind items to pick up players. When this player loses the item, other players cannot pick it up.

private function Init takes nothing returns nothing
local ItemRestriction weapon1h = ItemRestriction.create("1H-weapon", 2)
local ItemRestriction weapon2h = ItemRestriction.create("2H-weapon", 1)
local UnitRequirement tauren = UnitRequirement.create("tauren")
local ItemRestriction spiderRing = ItemRestriction.create("Friend of the spiders", 1)
local UnitRequirement atLeastOneSpiderAlive = UnitRequirement.create("Spare the spiders")

call spiderRing.addItem('sprn')
call atLeastOneSpiderAlive.addCondition(AtLeastOneSpiderAlive.create())
set spiderRing.requirement = atLeastOneSpiderAlive

call tauren.units.push('Otch')
call weapon1h.addItem('desc')
call weapon2h.addItem('mlst')
call weapon2h.addExclusive(weapon1h)
call weapon2h.addException(tauren, 2)

call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 45, description)
endfunction
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Player 1 cannot pick up items that Player 2 has discarded
Let me rephrase to make sure we understand each other:
What you speak about is not a bug report, but a request for a feature. Items picked by a player, are bound to that player, thus cannot be picked up by unit that is not owned by him or her.

What you need is a custom predicate struct and a separate snippet for providing you with GetItemOwner functionality - such feature is outside of ItemRestriction scope. Example:
JASS:
struct MyPredicate extends array
    static method isMet takes unit whichUnit returns string
        local item itm = GetOrderTargetItem()
        local string result = null

        if itm != null and GetOwningPlayer(whichUnit) != GetItemOwner(itm) then
            set result = "my error message"
        endif
        set itm = null
        return result
    endmethod

    implement UnitRequirementPredicateModule
endstruct
 
Last edited:
Level 2
Joined
Apr 16, 2017
Messages
13
Let me rephrase to make sure we understand each other:
What you speak about is not a bug report, but a request for a feature. Items picked by a player, are bound to that player, thus cannot be picked up by unit that is not owned by him or her.

What you need is a custom predicate struct and a separate snippet for providing you with GetItemOwner functionality - such feature is outside of ItemRestriction scope. Example:
JASS:
struct MyPredicate extends array
    static method isMet takes unit whichUnit returns string
        local item itm = GetOrderTargetItem()
        local string result = null

        if itm != null and GetOwningPlayer(whichUnit) != GetItemOwner(itm) then
            set result = "my error message"
        endif
        set itm = null
        return result
    endmethod

    implement UnitRequirementPredicateModule
endstruct
Yes, I want to combine the constraints with your ItemRestriction. When you click on a product that does not belong to you, it pops up.
 
Top