• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Unit's equipment/inventory from equipment/inventory system removed only on first teleportation using JASS TP spell

Status
Not open for further replies.
Level 6
Joined
Jun 18, 2011
Messages
147
I am not very knowledgable on all things JASS and vJASS and I do not have the best understanding how a lot of the libraries I use work. I try to, but nonetheless I am still far behind the experience of many. Still, I put my thoughts in the spoilers about possible issues with the libraries. Furthermore, here is my best explanation as to what I think could be happening.

I know the Equipment/Inventory System UI uses dummy units that are called upon when the UI is opened. So, when the hero is teleported by his ally, the dummy units for the UI know how to update to the location of the hero. However the destructibles used to make the icon textures for the items do not. The hero keeps all of the stat bonuses from the items (I did not include the triggers Item Setups which shows how an item gains bonuses and is linked to a destructible that has the texture for it's icon nor did I include the BonusMod Library). So, to sum up the hero appears in a new location via the teleport spell without the textures in his inventory and equipment (the hero keeps the bonus stats, but the equipment/inv slots become empty and new items can be placed in). If new items are placed in the hero can then be teleported with the Mythical Scroll spell without causing the items to dissapear. Therefore, I could possibly workaround this issue by just having the unit get teleported by an invisible dummy version of this spell after his UI is created.

I may find a solution on my own and post it here, but any help would be greatly appreciated. Here is a test version of my map if you are interested...

Test_Map - Warcraft 3 Maps - Epic War.com

(Only King Arthur is fully set up to use the UI and Merlin has the teleport spell. Type "lvl" to quick level all allied heroes to learn the spell. Use Merlin's staff to teleport to Arthur (they start in different locations). Then select where you'd like to teleport with the spell and then recast the spell on Arthur or Arthur/Merlin together to teleport him/them.)

JASS:
scope MythicalScroll initializer Init_MythicalScroll

//////////// START OF CONF PART ////////////


    globals
        private constant integer SPELL_PORTAL = 'A04O'
        private constant integer SPELL_TELEPORT = 'A04N'

        private constant real INTERVAL_PORTAL = 0.5

        private constant string EFFECT_PORTAL_PATH = "Eldritch Scroll.mdx"
        private constant real EFFECT_PORTAL_SIZE = 1
        private constant real EFFECT_PORTAL_HEIGHT = 0

        private constant real RADIUS = 300

        private constant string EFFECT_SCROLL_PATH = "Boomer Scroll SD.mdl"
        private constant real EFFECT_SCROLL_SIZE = 1
        private constant real EFFECT_SCROLL_HEIGHT = 0

        private constant string EFFECT_TELEPORT_START_PATH = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
        private constant string EFFECT_TELEPORT_ARRIVE_PATH = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"

        private constant real FLY_DURATION_SCROLL = 1
        private constant real INTERVAL_SCROLL = 0.01

        private constant string ERROR_MESSAGE_TEXT = "[Error] - The Portal does not exist anymore."
        private constant string ERROR_MESSAGE_COLOR = "ff0000"

        private group G = CreateGroup()
    endglobals

private function getDurationMax takes real lvl returns real
    return 7 + (2 * lvl)
endfunction

//////////// END OF CONF PART ////////////

private keyword Portal

private function randomRadian takes nothing returns real
    return GetRandomReal(0, 6.283185)
endfunction

private function distancePoints takes real X, real Y, real X1, real Y1 returns real
    return SquareRoot(((Y1-Y)*(Y1-Y))+((X1-X)*(X1-X)))
endfunction

private function createSfx takes string Path, real X, real Y, real A, real Scale, real Height returns effect
    set bj_lastCreatedEffect = AddSpecialEffect(Path, X, Y)

    call BlzSetSpecialEffectZ(bj_lastCreatedEffect, Height)

    if Scale != 1 then
      call BlzSetSpecialEffectScale(bj_lastCreatedEffect, Scale)
    endif

    call BlzSetSpecialEffectYaw(bj_lastCreatedEffect, A)

    return bj_lastCreatedEffect
endfunction

private function effectPoint takes string Path, real X, real Y returns nothing
    local effect sfx = AddSpecialEffect(Path, X, Y)
        call DestroyEffect(sfx)
    set sfx = null
endfunction

private function moveSfx takes effect Sfx, real X, real Y, real A returns nothing
    call BlzSetSpecialEffectX(Sfx, X)
    call BlzSetSpecialEffectY(Sfx, Y)
    call BlzSetSpecialEffectYaw(Sfx, A)

endfunction

private function addSpell takes unit U, integer Id returns nothing
    call UnitAddAbility(U, Id)
    call UnitMakeAbilityPermanent(U, true, Id)
endfunction

private function removeSpell takes unit U, integer Id returns nothing
    call UnitMakeAbilityPermanent(U, false, Id)
    call UnitRemoveAbility(U, Id)
endfunction

private function colorTxt takes string Message, string ColorCode returns string
    if ColorCode == "" or ColorCode == null then
        return Message
    else
        return "|cff" + ColorCode + Message + "|r"
    endif
endfunction

private function nullFilter takes nothing returns boolean
    return true
endfunction

private struct Scroll
    static Scroll array S
    static integer ST = 0
    static timer T = null

    unit caster
    real x
    real y
    real dx
    real dy
    real dist
    real angle
    real duration
    effect sfx
    player p
    Portal pt

    private method onDestroy takes nothing returns nothing
        call DestroyEffect(.sfx)

        set .caster = null
        set .p = null
        set .sfx = null

    endmethod

    private method scrollTeleport takes nothing returns nothing
        local unit U2
        local real X1
        local real Y1
        local real Dist
        local real A

        call GroupEnumUnitsInRange(G, .x, .y, RADIUS, Condition(function nullFilter))

        loop
            set U2 = FirstOfGroup(G)
            exitwhen U2 == null
            call GroupRemoveUnit(G, U2)

            if IsUnitAlly(U2, .p) then
                set X1 = GetUnitX(U2)
                set Y1 = GetUnitY(U2)
                set Dist = distancePoints(.x, .y, X1, Y1)
                set A = Atan2(Y1-.y, X1-.x)

                call effectPoint(EFFECT_TELEPORT_START_PATH, X1, Y1)
             
                call SetUnitPosition(U2, (pt.x + (Cos(A) * Dist)), (pt.y + (Sin(A) * Dist)))

                call effectPoint(EFFECT_TELEPORT_ARRIVE_PATH, GetUnitX(U2), GetUnitY(U2))
            endif
        endloop

    endmethod

    private method moveScroll takes nothing returns nothing
        set .duration = .duration + INTERVAL_SCROLL

        if .dist > 0 then
            set .x = .x + .dx
            set .y = .y + .dy

            call moveSfx(.sfx, .x, .y, .angle)
        endif

    endmethod

    static method update takes nothing returns nothing
        local Scroll s
        local integer I = 0

        loop
            set I = I + 1
            set s = .S[I]

            call s.moveScroll()

            if s.duration >= FLY_DURATION_SCROLL then
                call s.scrollTeleport()

                call s.destroy()

                set .S[I] = .S[.ST]
                set .ST = .ST - 1
                set I = I - 1
            endif

            exitwhen I >= .ST
        endloop

        if .ST <= 0 then
            call PauseTimer(.T)
            set .ST = 0
        endif

    endmethod

    static method addScroll takes unit U, real X, real Y, real A, real Distance, player P, Portal PT returns nothing
        local Scroll s = Scroll.allocate()
        local real dist = Distance / FLY_DURATION_SCROLL * INTERVAL_SCROLL

        set s.caster = U
        set s.x = X
        set s.y = Y
        set s.dx = Cos(A) * dist
        set s.dy = Sin(A) * dist
        set s.dist = dist
        set s.angle = A
        set s.duration = 0
        set s.sfx = createSfx(EFFECT_SCROLL_PATH, X, Y, A, EFFECT_SCROLL_SIZE, EFFECT_SCROLL_HEIGHT)
        set s.p = P
        set s.pt = PT

        set .ST = .ST + 1
        set .S[.ST] = s

        if .ST == 1 then
            call TimerStart(.T, INTERVAL_SCROLL, true, function Scroll.update)
        endif

    endmethod
endstruct

private struct Portal
    static Portal array P
    static integer PT = 0
    static timer T = null

    unit caster
    real x
    real y
    player p
    real duration
    real durationMax
    real lvl
    effect sfx
    boolean done

    private method swapSpells takes boolean showTeleportSpell returns nothing
        if showTeleportSpell then
            call BlzUnitHideAbility(.caster, SPELL_PORTAL, showTeleportSpell)
            call addSpell(.caster, SPELL_TELEPORT)
            call SetUnitAbilityLevel(.caster, SPELL_TELEPORT, R2I(.lvl))
        else
            call removeSpell(.caster, SPELL_TELEPORT)
            call BlzUnitHideAbility(.caster, SPELL_PORTAL, showTeleportSpell)
        endif

    endmethod

    private method onDestroy takes nothing returns nothing
        call .swapSpells(false)
        set .done = true

        call DestroyEffect(.sfx)

        set .caster = null
        set .sfx = null
        set .p = null

    endmethod

    static method update takes nothing returns nothing
        local Portal p
        local integer I = 0

        loop
            set I = I + 1
            set p = .P[I]

            set p.duration = p.duration + INTERVAL_PORTAL

            if p.duration >= p.durationMax then
                call p.destroy()

                set .P[I] = .P[.PT]
                set .PT = .PT - 1
                set I = I - 1
            endif

            exitwhen I >= .PT
        endloop

        if .PT <= 0 then
            call PauseTimer(.T)
            set .PT = 0
        endif

    endmethod

    static method getByCaster takes unit U returns Portal
        local Portal p
        local Portal portalToReturn = 0
        local integer I = 1

        loop
            exitwhen I > .PT
            set p = .P[I]

            if p.caster == U then
                set I = .PT
                set portalToReturn = p
            endif

            set I = I + 1
        endloop

        return portalToReturn

    endmethod

    static method addPortal takes unit U, real X, real Y, real lvl returns nothing
        local Portal p
        local boolean found = false
        local integer I = 1

        loop
            exitwhen I > .PT
            set p = .P[I]

            if p.caster == U then
                set I = .PT
                set found = true

                set p.duration = 0
                set p.x = X
                set p.y = Y

                call BlzSetSpecialEffectX(p.sfx, X)
                call BlzSetSpecialEffectY(p.sfx, Y)

                if p.lvl != lvl then
                    set p.lvl = lvl
                    set p.durationMax = getDurationMax(lvl)
                endif
            endif

            set I = I + 1
        endloop

        if not(found) then
            set p = Portal.allocate()

            set p.caster = U
            set p.x = X
            set p.y = Y
            set p.p = GetOwningPlayer(U)
            set p.duration = 0
            set p.durationMax = getDurationMax(lvl)
            set p.lvl = lvl
            set p.sfx = createSfx(EFFECT_PORTAL_PATH, X, Y, randomRadian(), EFFECT_PORTAL_SIZE, EFFECT_SCROLL_HEIGHT)

            call p.swapSpells(true)

            set .PT = .PT + 1
            set .P[.PT] = p

            if .PT == 1 then
                call TimerStart(.T, INTERVAL_PORTAL, true, function Portal.update)
            endif
        endif

    endmethod
endstruct

function Trig_MythicalScrollTeleport_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_TELEPORT
endfunction

function Trig_MythicalScrollTeleport_Actions takes nothing returns nothing
    local unit U = GetTriggerUnit()
    local real XCaster = GetUnitX(U)
    local real YCaster = GetUnitY(U)
    local real XTarget = GetSpellTargetX()
    local real YTarget = GetSpellTargetY()
    local player P = GetOwningPlayer(U)
    local Portal PT = Portal.getByCaster(U)
    local real A = Atan2(YTarget-YCaster, XTarget-XCaster)
    local real distance = distancePoints(XCaster, YCaster, XTarget, YTarget)

    if PT != 0 then
        call Scroll.addScroll(U, XCaster, YCaster, A, distance, P, PT)
    else
        call DisplayTextToPlayer(P, 0, 0, colorTxt(ERROR_MESSAGE_TEXT, ERROR_MESSAGE_COLOR))
    endif

    set U = null
    set P = null

endfunction

function Trig_MythicalScrollPortal_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_PORTAL
endfunction

function Trig_MythicalScrollPortal_Actions takes nothing returns nothing
    local unit U = GetTriggerUnit()
    local real XTarget = GetSpellTargetX()
    local real YTarget = GetSpellTargetY()
    local real lvl = GetUnitAbilityLevel(U, GetSpellAbilityId())

    call Portal.addPortal(U, XTarget, YTarget, lvl)

    set U = null

endfunction

//===========================================================================
function Init_MythicalScroll takes nothing returns nothing
    local trigger T = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( T, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( T, Condition( function Trig_MythicalScrollPortal_Conditions ) )
    call TriggerAddAction( T, function Trig_MythicalScrollPortal_Actions )
    set T = null

    set T = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( T, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( T, Condition( function Trig_MythicalScrollTeleport_Conditions ) )
    call TriggerAddAction( T, function Trig_MythicalScrollTeleport_Actions )
    set T = null

    set Scroll.T = CreateTimer()
    set Portal.T = CreateTimer()

    call Preload(EFFECT_TELEPORT_START_PATH)
    call Preload(EFFECT_TELEPORT_ARRIVE_PATH)
    call Preload(EFFECT_SCROLL_PATH)
    call Preload(EFFECT_PORTAL_PATH)

endfunction

endscope


From TriggerHappy's Equipment/Inventory System v1.43

JASS:
library UserInterface initializer Init requires optional UnitDex/*or any unit indexer*/, Camera
/***************************************************************
*
*   v1.0.6, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*
*   - Create UI elements based on dummy units.
*
*   - Elements are overlay onto the current camera keeping you in gameplay.
*
*   - The camera can be modified to any view including third person, however
*     it must be locked.
*
*   - Dynamic textures are applied to dummies through an ability based off
*     off War Clib, using destructables with their replaceable texture field
*     set to the desired texture. (more: http://www.wc3c.net/showthread.php?p=1043980)
*
*   - Detect left and right clicks.
*
*   - Works in multiplayer (and single) without lag.
*   _________________________________________________________________________
*   1. Installation
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       1. Copy the script to your map and save it (requires JassHelper *or* JNGP)
*       2. Copy the object editor data and paste the correct raw codes into the configuration.
*   _________________________________________________________________________
*   2. API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       struct UIButton
*
*           static method create takes real minx, real maxy, real W, real H, real z, integer texture returns thistype
*           static method updateAll takes nothing returns nothing
*           static method click takes unit u, boolean isLeft returns boolean
*           static method clickEx takes real x, real y, boolean isLeft returns boolean
*           static method clickPeriodicSelect takes player p, boolean isLeft returns boolean
*
*           method destroy takes nothing returns nothing
*           method update takes nothing returns nothing
*           method show takes boolean flag, Camera Cam returns nothing
*           method showPlayer takes player p, boolean flag, Camera cam returns nothing
*           method isClicked takes real x, real y returns boolean
*           method clickL takes nothing returns nothing
*           method clickR takes nothing returns nothing
*           method setPosition takes real minx, real maxy returns nothing
*           method setTexture takes integer texture returns nothing
*
*           -----------------------
*
*           integer customValue
*           integer animIndex
*           trigger triggerL
*           trigger triggerR
*           filterfunc onLeftClick
*           filterfunc onRightClick
*
*
*       struct UIPicture
*
*           static method create takes real minx, real maxy, real w, real h, real z, integer texture returns thistype
*           static method createEx takes real minx, real maxy, real z, real scale, integer unitId, real modelWidth, real modelHeight, integer texture returns thistype
*           static method updateAll takes nothing returns nothing

*           method destroy takes nothing returns nothing
*           method update takes nothing returns nothing
*           method show takes boolean show, Camera Cam returns nothing
*           method showPlayer takes player p, boolean flag, Camera cam returns nothing
*           method setPosition takes real minx, real maxy returns nothing
*           method setTexture takes integer texture returns nothing
*
*           -----------------------
*
*           integer customValue
*           integer animIndex
*
*       struct UIText
*
*           static method create takes real minx, real maxy, real z returns thistype
*           static method updateAll takes nothing returns nothing
*
*           method destroy takes nothing returns nothing
*           method update takes nothing returns nothing
*           method setPosition takes real minx, real maxy returns nothing
*           method show takes boolean show, Camera Cam returns nothing
*
*           -----------------------
*
*           integer customValue
*
*       function GetTriggerButton takes nothing returns UIButton
*       function GetClickingPlayer takes nothing returns player
*       function GetCameraDeltaZ takes nothing returns real
*       function CreateWindow takes real x, real y, real size, real width, real height, race playerRace returns UIPicture
*
*       public function ApplySkin takes unit u, integer texture returns boolean
*
***************************************************************/

    // configuration
    private module InterfaceConfig
 
        // dummy unit with pitch angle animations
        static constant integer DUMMY_TYPE          = 'uidm'
     
        // model used in CreateWindow function
        static constant integer WINDOW_DUMMY        = 'uiwn'
     
        // destructable ID of the human border texture. the
        // rest of the races should have consecutive ids.
        static constant integer RACE_BORDERS_START  = 'D201'
     
        // run a timer to check for unit selections.
        // faster but more cpu intensive.
        static constant boolean LEFT_CLICK_TIMER    = true

            // really low timer intervals remove the flicker
            // effect, most of the time.
            static constant real LEFT_CLICK_TIMER_RATE  = 0.01

            // in multiplayer, IsUnitSelected is synchronous
            // so it doesn't instantly return false when you use
            // SelectUnit(u, false). So we must disable the button
            // for the clicking player for a brief period or else
            // the event will be spammed.
            static constant real LEFT_CLICK_DELAY   = 0.6
     
        // ability id's
        static constant integer DESTROYER_FORM_ID   = 'Aave'
        static constant integer LOCUST_ID           = 'Aloc'
     
        // ability used to change dummy unit textures
        static constant integer SKIN_CHANGE_ABILITY = 'Skin'
     
        // removes pathing and possibly other things from the unit.
        // similar to locust, but allows clicking.
        static constant integer GHOST_ABILITY       = 'Aeth'

        // dummy player
        static constant player PLAYER               = CAMERA_DUMMY_PLAYER // from Camera library
    endmodule
    // end configuration

    globals
        public destructable DummyTree = null
        private trigger Trig = CreateTrigger()
        private UIButton ClickedButton = 0
        private player ClickingPlayer = null
        private location DummyLoc = Location(0,0)
    endglobals

    function GetTriggerButton takes nothing returns UIButton
        return ClickedButton
    endfunction
 
    function GetClickingPlayer takes nothing returns player
        return ClickingPlayer
    endfunction

    public function Eval takes filterfunc func returns boolean
        call TriggerClearConditions(Trig)
        call TriggerAddCondition(Trig, func)
        return TriggerEvaluate(Trig)
    endfunction

    public function GetTerrainZ takes real x, real y returns real
        call MoveLocation(DummyLoc, x, y)
        return GetLocationZ(DummyLoc)
    endfunction
 
    // adjusts
    public function FindModelAnimIndex takes real w, real h, real z returns integer
        local real W = w*SCREEN_WIDTH*z
        local real H = h*SCREEN_HEIGHT*z
        local real anim
        if (H<W) then
            set anim = 10*(W/H-1)
            return UIMath_R2I_N(anim)
        else
            set anim = 10*(H/W-1)
            if anim >= 0.5 then
                return 100+UIMath_R2I_N(anim)
            endif
        endif
        return 0
    endfunction

    public function FindModelSize takes real w, real h, real z returns real
        local real W = w*SCREEN_WIDTH*z
        local real H = h*SCREEN_HEIGHT*z
        if (H<W) then
            return 0.5*H
        endif
        return 0.5*W
    endfunction

    private function LocalReal takes player p, real forPlayer, real default returns real
        if (User.Local == p) then
            return forPlayer
        endif
        return default
    endfunction
 
    public function ApplySkin takes unit u, integer texture returns boolean
        local boolean out = false
     
        if (texture != 0) then
         
            call UnitAddAbility(u, Interface.SKIN_CHANGE_ABILITY)
            set DummyTree = CreateDestructable(texture,GetUnitX(u),GetUnitY(u),0,0,1)
            set out = IssueTargetOrder(u, "grabtree", DummyTree)
            call RemoveDestructable(DummyTree)
            set DummyTree = null
            call UnitRemoveAbility(u, Interface.SKIN_CHANGE_ABILITY)
        endif
     
        return out
    endfunction

    struct UIButton
        integer customValue
        integer animIndex
        trigger triggerL
        trigger triggerR
        filterfunc onLeftClick
        filterfunc onRightClick
        boolean disabled
        unit selectUnit

        static boolean useDelay = false

        readonly static thistype array AllShow
        readonly static integer CountShow = 0

        readonly integer index
        readonly Camera camera
     
        readonly real scale
        readonly real centerx
        readonly real centery
        readonly real minx
        readonly real maxx
        readonly real miny
        readonly real maxy
        readonly real width
        readonly real height
        readonly real z
        readonly unit picture
        readonly effect model
     
        readonly boolean displayed
        readonly integer texture
     
        private integer unitId
        private static thistype array Unit2Button
     
        static method create takes real minx, real maxy, real W, real H, real z, integer texture returns thistype
            local thistype this = thistype.allocate()

            set .texture        = texture
            set .customValue    = 0
            set .camera         = 0
            set .width          = W
            set .height         = H
            set .minx           = minx
            set .maxx           = minx+W
            set .maxy           = maxy
            set .miny           = maxy-H
            set .z              = 100.2+z
            set .centerx        = minx+W/2.0
            set .centery        = maxy-H/2.0
            set .displayed      = false
            set .scale          = FindModelSize(W, H, .z)
            set .triggerL       = null
            set .triggerR       = null
            set .onLeftClick    = null
            set .onRightClick   = null
            set .selectUnit     = null
            set .picture        = CreateUnit(Interface.PLAYER, Interface.DUMMY_TYPE, 0, 0, 0)
         
            // apply texture
            call ApplySkin(.picture, texture)
         
            set .animIndex = FindModelAnimIndex(W, H, .z)
            call SetUnitAnimationByIndex(.picture, .animIndex)
            call UnitAddAbility(.picture, Interface.DESTROYER_FORM_ID)
            call UnitRemoveAbility(.picture, Interface.DESTROYER_FORM_ID)
            call UnitAddAbility(.picture, Interface.SKIN_CHANGE_ABILITY)
            call UnitAddAbility(.picture, Interface.GHOST_ABILITY)
         
            set this.unitId = GetUnitUserData(.picture)
            set Unit2Button[this.unitId] = this
         
            call SetUnitScale(.picture, 0, 0, 0)
         
            return this
        endmethod
     
        method destroy takes nothing returns nothing
            call RemoveUnit(.picture)
         
            if .displayed then
                set .AllShow[.index] = .AllShow[.CountShow]
                set .AllShow[.index].index = .index
                set .CountShow = .CountShow - 1
            endif
        endmethod

        method update takes nothing returns nothing
            local VECTOR3 Pos
         
            if (.displayed) then
                set Pos = .camera.win2World(.centerx, .centery, .z)
                call SetUnitX(.picture, Pos.x)
                call SetUnitY(.picture, Pos.y)
                call SetUnitFlyHeight(.picture, Pos.z-GetTerrainZ(Pos.x, Pos.y), 0)
                call Pos.destroy()
            endif
        endmethod
     
        method show takes boolean flag, Camera Cam returns nothing
            if Cam != -1 then
                set .camera = Cam
            endif
         
            if flag != .displayed then
                if flag then
                    set .AllShow[.CountShow] = this
                    set .index = .CountShow
                    set .CountShow = .CountShow + 1
                    call .update()
                    call SetUnitAnimationByIndex(.picture, .animIndex)
                 
                    call SetUnitScale(.picture, .scale, 0, 0)
                else
                    set .CountShow = .CountShow - 1
                    set .AllShow[.index] = .AllShow[.CountShow]
                    set .AllShow[.index].index = .index
                 
                    call SetUnitX(.picture, 0)
                    call SetUnitY(.picture, 0)
                 
                    call SetUnitScale(.picture, 0, 0, 0)
                endif
            endif
         
            set .displayed = flag
        endmethod
     
        method showPlayer takes player p, boolean flag, Camera cam returns nothing
            call .show(flag, cam)
         
            if (flag) then
                call SetUnitScale(.picture, LocalReal(p, .scale, 0), 0, 0)
            endif
        endmethod

        method isClicked takes real x, real y returns boolean
            return .minx < x and x < .maxx and .miny < y  and y < .maxy
        endmethod
     
        method clickL takes nothing returns nothing
            set ClickedButton = this

            if (.triggerL != null and TriggerEvaluate(.triggerL)) then
                call TriggerExecute(.triggerL)
            endif

            if (.onLeftClick != null) then
                 call Eval(.onLeftClick)
            endif
        endmethod
     
        method clickR takes nothing returns nothing
            set ClickedButton = this

            if (.triggerR != null and TriggerEvaluate(.triggerR)) then
                call TriggerExecute(.triggerR)
            endif
         
            if (.onRightClick != null) then
                 call Eval(.onRightClick)
            endif
        endmethod
     
        method setPosition takes real minx, real maxy returns nothing
            set .minx = minx
            set .maxx = minx+.width
            set .maxy = maxy
            set .miny = maxy-.height
            set .centerx = minx+.width/2.0
            set .centery = maxy-.height/2.0
         
            if .displayed then
                call .update()
            endif
        endmethod
     
        method setTexture takes integer texture returns nothing
            call SetUnitX(.picture, 0)
            call SetUnitY(.picture, 0)
            call ApplySkin(.picture, texture)
         
            if .displayed then
                call .update()
            endif
         
            set .texture = texture
        endmethod
     
        static method updateAll takes nothing returns nothing
            local integer i = .CountShow
            loop
                exitwhen i < 0
                call .AllShow[i].update()
                set i = i - 1
            endloop
        endmethod
     
        static method click takes unit u, boolean isLeft returns boolean
            local thistype b
         
            if GetUnitTypeId(u) == Interface.DUMMY_TYPE then
                set b = Unit2Button[GetUnitUserData(u)]

                if b > 0 and b.displayed then
                    call SelectUnit(u, false)

                    if isLeft then
                        call b.clickL()
                    else
                        call b.clickR()
                    endif
                 
                    return true
                endif
            endif
         
            return false
        endmethod
     
        static method clickEx takes real x, real y, boolean isLeft returns boolean
            local integer i = .CountShow
         
            loop
                exitwhen i < 0

                if .AllShow[i].isClicked(x, y) then
                    if isLeft then
                        call .AllShow[i].clickL()
                    else
                        call .AllShow[i].clickR()
                    endif
                 
                    return true
                endif
             
                set i = i - 1
            endloop
         
            return false
        endmethod
     
        private static method enableButtonCallback takes nothing returns nothing
            local timer t = GetExpiredTimer()

            static if (LIBRARY_TimerUtils) then
                local thistype btn = thistype(GetTimerData(t))
            else
                local thistype btn = thistype(R2I(TimerGetRemaining(t) + 0.5))
            endif

            set btn.disabled = false
            set t = null
        endmethod

        static method clickPeriodicSelect takes player p, boolean isLeft returns boolean
            local integer i = .CountShow
            local timer t
         
            loop
                exitwhen i < 0

                if (IsUnitSelected(.AllShow[i].picture, p)) then
                 
                    if (not .AllShow[i].disabled) then

                        if isLeft then
                            call .AllShow[i].clickL()
                        else
                            call .AllShow[i].clickR()
                        endif

                     
                        if (thistype.useDelay) then // static if doesn't work..
                            set .AllShow[i].disabled = true

                            static if (LIBRARY_TimerUtils) then
                                call TimerStart(NewTimerEx(.AllShow[i]), Interface.LEFT_CLICK_DELAY, false, function thistype.enableButtonCallback)
                            else
                                set t = CreateTimer()
                                call TimerStart(t, .AllShow[i], false, null)
                                call PauseTimer(t)
                                call TimerStart(t, Interface.LEFT_CLICK_DELAY, false, function thistype.enableButtonCallback)
                                set t = null
                            endif
                        endif
                     
                        return true
                    endif

                    call SelectUnit(.AllShow[i].picture, false)

                    if (.AllShow[i].selectUnit != null and User.Local == p) then
                        call SelectUnit(.AllShow[i].selectUnit, true)
                    endif

                endif
             
                set i = i - 1
            endloop
         
            return false
        endmethod

    endstruct

    struct UIPicture
        integer customValue
        integer animIndex
     
        readonly static thistype array AllShow
        readonly static integer CountShow = 0
     
        readonly integer index
        readonly Camera camera
     
        readonly real scale
        readonly real centerx
        readonly real centery
        readonly real width
        readonly real height
        readonly real z
        readonly unit picture
        readonly boolean displayed
        readonly integer texture

        static method create takes real minx, real maxy, real w, real h, real z, integer texture returns thistype
            local thistype this = thistype.allocate()

            set .texture        = texture
            set .customValue    = 0
            set .camera         = 0
            set .width          = w
            set .height         = h
            set .centerx        = minx+w/2.0
            set .centery        = maxy-h/2.0
            set .z              = 100.2+z
            set .displayed      = false
            set .picture        = CreateUnit(Interface.PLAYER, Interface.DUMMY_TYPE, 0, 0, 0)
            set .animIndex      = FindModelAnimIndex(w, h, .z)
            set .scale          = FindModelSize(w, h, .z)
         
            call ApplySkin(.picture, texture)
         
            call SetUnitAnimationByIndex(.picture, .animIndex)
         
            call UnitAddAbility(.picture, Interface.DESTROYER_FORM_ID)
            call UnitRemoveAbility(.picture, Interface.DESTROYER_FORM_ID)
            call UnitAddAbility(.picture, Interface.LOCUST_ID)
            call UnitRemoveAbility(.picture, Interface.LOCUST_ID)
         
            // hide unit
            call SetUnitScale(.picture, 0, 0, 0)
         
            return this
        endmethod
     
        static method createEx takes real minx, real maxy, real z, real scale, integer unitId, real modelWidth, real modelHeight, integer texture returns thistype
            local thistype this = thistype.allocate()

            set .customValue    = 0
            set .camera         = 0
            set .z              = 100.2 + z
            set .width          = (modelWidth*scale) / (SCREEN_WIDTH*.z)
            set .height         = (modelHeight*scale) / (SCREEN_HEIGHT*.z)
            set .scale          = scale
            set .centerx        = minx+.width/2.0
            set .centery        = maxy-.height/2.0
            set .displayed      = false
            set .picture        = CreateUnit(Interface.PLAYER, unitId, 0, 0, 270)
         
            call ApplySkin(.picture, texture)
         
            call UnitAddAbility(.picture, Interface.DESTROYER_FORM_ID)
            call UnitRemoveAbility(.picture, Interface.DESTROYER_FORM_ID)
            call UnitAddAbility(.picture, Interface.LOCUST_ID)
            call UnitRemoveAbility(.picture, Interface.LOCUST_ID)
         
            // hide unit
            call SetUnitScale(.picture, 0, 0, 0)
         
            return this
        endmethod
     
        method destroy takes nothing returns nothing
            call RemoveUnit(.picture)
         
            if .displayed then
                set .AllShow[.index] = .AllShow[.CountShow]
                set .AllShow[.index].index = .index
                set .CountShow = .CountShow - 1
            endif
        endmethod

        method update takes nothing returns nothing
            local VECTOR3 Pos

            if (this.displayed) then
                set Pos = this.camera.win2World(.centerx, .centery, .z)
                call SetUnitX(.picture, Pos.x)
                call SetUnitY(.picture, Pos.y)
                call SetUnitFlyHeight(.picture, Pos.z - GetTerrainZ(Pos.x, Pos.y), 0)
                call Pos.destroy()
            endif
        endmethod
     
        method show takes boolean show, Camera Cam returns nothing
            if Cam != -1 then
                set this.camera = Cam
            endif

            if show != .displayed then
                if show then
                    set .AllShow[.CountShow] = this
                    set .index = .CountShow
                    set .CountShow = .CountShow + 1
                    call .update()
                    call SetUnitAnimationByIndex(.picture, .animIndex)
                    call SetUnitScale(.picture, .scale, 0, 0)
                else
                    set .CountShow = .CountShow - 1
                    set .AllShow[.index] = .AllShow[.CountShow]
                    set .AllShow[.index].index = .index
                    call SetUnitX(.picture, 0)
                    call SetUnitY(.picture, 0)
                    call SetUnitScale(.picture, 0, 0, 0)
                endif
            endif
         
            set .displayed = show
        endmethod
     
        method showPlayer takes player p, boolean flag, Camera cam returns nothing
            call .show(flag, cam)
         
            if (flag) then
                call SetUnitScale(.picture, LocalReal(p, .scale, 0), 0, 0)
            endif
        endmethod
     
        method setPosition takes real minx, real maxy returns nothing
            set .centerx = minx+.width/2.0
            set .centery = maxy-.height/2.0
            if .displayed then
                call .update()
            endif
        endmethod
     
        method setTexture takes integer texture returns nothing
            call SetUnitX(.picture, 0)
            call SetUnitY(.picture, 0)
            call ApplySkin(.picture, texture)
         
            if .displayed then
                call .update()
            endif
         
            set .texture = texture
        endmethod
     
        static method updateAll takes nothing returns nothing
            local integer i = .CountShow

            loop
                exitwhen i < 0
                call .AllShow[i].update()
                set i = i - 1
            endloop
        endmethod

    endstruct

    struct UIText
        integer customValue
     
        readonly unit dummy
        readonly static thistype array AllShow
        readonly static integer CountShow = 0
        readonly integer index
        readonly Camera camera
        readonly real minx
        readonly real maxy
        readonly real z
        readonly texttag text
        readonly boolean displayed
     
        static method create takes real minx, real maxy, real z returns thistype
            local thistype this = thistype.allocate()
         
            set .customValue    = 0
            set .camera         = 0
            set .minx           = minx
            set .maxy           = maxy
            set .z              = 100 + z
            set .displayed      = false
            set .text           = CreateTextTag()
            set .dummy          = CreateUnit(Interface.PLAYER, Interface.DUMMY_TYPE, 0, 0, 0)

            call SetUnitScale(.dummy, 0, 0, 0)

            call UnitAddAbility(.dummy, Interface.LOCUST_ID)
            call UnitRemoveAbility(.dummy, Interface.LOCUST_ID)
         
            call SetTextTagVisibility(.text, false)
            call SetTextTagPosUnit(.text, .dummy, 0)
         
            return this
        endmethod
     
        static method createEx takes player p, real minx, real maxy, real z returns thistype
            local thistype this = thistype.allocate()
         
            set .customValue    = 0
            set .camera         = 0
            set .minx           = minx
            set .maxy           = maxy
            set .z              = 100 + z
            set .displayed      = false
            set .dummy          = CreateUnit(Interface.PLAYER, Interface.DUMMY_TYPE, 0, 0, 0)

            if (User.Local == p) then
                set .text = CreateTextTag()
            endif
         
            call SetUnitScale(.dummy, 0, 0, 0)

            call UnitAddAbility(.dummy, Interface.LOCUST_ID)
            call UnitRemoveAbility(.dummy, Interface.LOCUST_ID)
         
            call SetTextTagVisibility(.text, false)
            call SetTextTagPosUnit(.text, .dummy, 0)
         
            return this
        endmethod
     
        method destroy takes nothing returns nothing
            call DestroyTextTag(.text)
         
            if .displayed then
                set .AllShow[.index] = .AllShow[.CountShow]
                set .AllShow[.index].index = .index
                set .CountShow = .CountShow - 1
            endif
        endmethod
     
        method update takes nothing returns nothing
            local VECTOR3 Pos = .camera.win2World(.minx, .maxy, .z)
            call SetUnitX(.dummy, Pos.x)
            call SetUnitY(.dummy, Pos.y)
            call SetTextTagPosUnit(.text, .dummy, (Pos.z-GetTerrainZ(Pos.x,Pos.y))-14.8)
            call Pos.destroy()
        endmethod
     
        method setPosition takes real minx, real maxy returns nothing
            set .minx = minx
            set .maxy = maxy
         
            if .displayed then
                call .update()
            endif
        endmethod
     
        method show takes boolean show, Camera Cam returns nothing
            if Cam != -1 then
                set .camera = Cam
            endif
         
            call SetTextTagVisibility(.text, show)
         
            if show != .displayed then
                if show then
                    set .AllShow[.CountShow] = this
                    set .index = .CountShow
                    set .CountShow = .CountShow + 1
                    call .update()
                else
                    set .CountShow = .CountShow - 1
                    set .AllShow[.index] = .AllShow[.CountShow]
                    set .AllShow[.index].index = .index
                endif
            endif
         
            set .displayed = show
        endmethod
     
        static method updateAll takes nothing returns nothing
            local integer i = .CountShow
            loop
                exitwhen i < 0
                call .AllShow[i].update()
                set i = i - 1
            endloop
        endmethod

    endstruct

    struct Interface extends array
     
        implement InterfaceConfig
     
       static method getRaceBorders takes race r returns integer
            return RACE_BORDERS_START + (GetHandleId(r)-1)
        endmethod
     
        static method updateAll takes boolean but, boolean pic, boolean tex returns nothing
            if but then
                call UIButton.updateAll()
            endif
            if pic then
                call UIPicture.updateAll()
            endif
            if tex then
                call UIText.updateAll()
            endif
        endmethod

    endstruct

    function CreateWindow takes real x, real y, real size, real width, real height, race playerRace returns UIPicture
        return UIPicture.createEx(x, y, /*z=*/2, size, Interface.WINDOW_DUMMY, width, height, Interface.getRaceBorders(playerRace))
    endfunction
 
    private function InterfaceClickR takes nothing returns nothing
        local unit clicker = GetTriggerUnit()
        local unit btnUnit = GetOrderTargetUnit()
     
        set ClickingPlayer = GetOwningPlayer(clicker)
     
        if (UIButton.click(btnUnit, false)) then
            call PauseUnit(clicker, true)
            call IssueImmediateOrderById(clicker, 851973)
            call PauseUnit(clicker, false)
        endif
     
        set clicker = null
    endfunction
 
    private function InterfaceClickL takes nothing returns nothing
        set ClickingPlayer = GetTriggerPlayer()
        call UIButton.click(GetTriggerUnit(), true)
    endfunction

    private function InterfaceClickL_Timer takes nothing returns nothing
        local integer i = 0
        local integer c = UIButton.CountShow
        local User user = User.first
     
        loop
            exitwhen user == User.NULL
 
            set ClickingPlayer = user.handle
         
            call UIButton.clickPeriodicSelect(user.handle, true)

            set user = user.next
        endloop
    endfunction
 
    private function Init takes nothing returns nothing
         local trigger t = CreateTrigger()
         local trigger t2 = CreateTrigger()
         local integer i = 0
         local User user = User.first
     
         loop
            exitwhen user == User.NULL
         
            call TriggerRegisterPlayerUnitEvent(t, user.handle, EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, null)
         
            static if (not Interface.LEFT_CLICK_TIMER) then
                call TriggerRegisterPlayerUnitEvent(t2, user.handle, EVENT_PLAYER_UNIT_SELECTED, null)
            endif
         
            set user = user.next
        endloop

        call TriggerAddAction(t, function InterfaceClickR)
     
        static if (Interface.LEFT_CLICK_TIMER) then
            call TimerStart(CreateTimer(), Interface.LEFT_CLICK_TIMER_RATE, true, function InterfaceClickL_Timer)
        else
            call TriggerAddAction(t2, function InterfaceClickL)
        endif

        set UIButton.useDelay = Interface.LEFT_CLICK_TIMER
    endfunction
 
endlibrary

JASS:
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
*   v1.2.1, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   UnitDex assigns every unit an unique integer. It attempts to make that number within the
*   maximum array limit so you can associate it with one.
*   _________________________________________________________________________
*   1. Installation
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
    ///! external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/*  ________________________________________________________________________
*   2. Configuration
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
    private module UnitDexConfig
 
        // The raw code of the leave detection ability.
        static constant integer DETECT_LEAVE_ABILITY = 'uDex'
     
        // Allow debug messages (debug mode must also be on)
        static constant boolean ALLOW_DEBUGGING      = true
     
        // Uncomment the lines below to define a filter for units being indexed
     
        /*static method onFilter takes unit u returns boolean
            return true
        endmethod*/
     
    endmodule
/*  _________________________________________________________________________
*   3. Function API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Every function inlines except for UnitDexRemove
*
*       function GetUnitId takes unit whichUnit returns integer
*       function GetUnitById takes integer index returns unit
* 
*       function UnitDexEnable takes boolean flag returns nothing
*       function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
*       function IsUnitIndexed takes unit u returns boolean
*       function IsIndexingEnabled takes nothing returns boolean
*
*       function GetIndexedUnit takes nothing returns unit
*       function GetIndexedUnitId takes nothing returns integer
*    
*       function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
*       function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
*       function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
*       function OnUnitIndex takes code func returns triggercondition
*       function OnUnitDeidex takes code func returns triggercondition
*   _________________________________________________________________________
*   4. Struct API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       UnitDex.Enabled = false // toggle the indexer
*       UnitDex.Initialized     // returns true if the preload timer has finished
*       UnitDex.Count           // returns the amount of units indexed
*       UnitDex.Unit[0]         // access the UnitDex array directly
*       UnitDex.Group           // a unit group containing every indexed unit (for enumeration)
*       UnitDex.LastIndex       // returns the last indexed unit's id
*   _________________________________________________________________________
*   5. Public Variables
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       These are to be used with the "eventtype" argument of the event API:
*
*           constant integer EVENT_UNIT_INDEX     = 0
*           constant integer EVENT_UNIT_DEINDEX   = 1
*   _________________________________________________________________________
*   6. Examples
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       1. Associate a unit with a variable
*
*           set UnitFlag[GetUnitId(yourUnit)] = true
*
*       2. Allocate a struct instance using a units assigned ID
*
*           local somestruct data = GetUnitId(yourUnit)
*
*       3. Detect when a unit leaves the map
*
*           function Exit takes nothing returns nothing
*               call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
*           endfunction
*
*           call OnUnitDeindex(function Exit)
*           // or
*           call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
*   _________________________________________________________________________
*   7. How it works
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       1. Enumerate all preplaced units
*       2. TriggerRegisterEnterRegion native to detect when a unit enters the map
*       3. Assign each unit that enters the map a unique integer
*       4. Give every unit an ability based off of defend. There is no native to accurately
*          detect when a unit leaves the map but when a unit dies or is removed from the game
*          they are issued the "undefend" order
*       5. Catch the "undefend" order and remove unit from the queue
*   _________________________________________________________________________
*   8. Notes
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
*       - The object merger line should be commented out after saving and restarting.
*       - All public functions are inlined except UnitDexRemove.
*
*   -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
 
    globals
        // Event types
        constant integer EVENT_UNIT_INDEX     = 0
        constant integer EVENT_UNIT_DEINDEX   = 1
     
        // System variables
        private trigger array IndexTrig
        private integer Index = 0
        private real E=-1
        private boolexpr FilterEnter
    endglobals
 
    function GetUnitId takes unit whichUnit returns integer
        return GetUnitUserData(whichUnit)
    endfunction
 
    function GetUnitById takes integer index returns unit
        return UnitDex.Unit[index]
    endfunction
 
    function GetIndexedUnit takes nothing returns unit
        return UnitDex.Unit[UnitDex.LastIndex]
    endfunction
 
    function GetIndexedUnitId takes nothing returns integer
        return UnitDex.LastIndex
    endfunction
 
    function IsUnitIndexed takes unit u returns boolean
        return (GetUnitById(GetUnitId(u)) != null)
    endfunction
 
    function UnitDexEnable takes boolean flag returns nothing
        set UnitDex.Enabled = flag
    endfunction
 
    function IsIndexingEnabled takes nothing returns boolean
        return UnitDex.Enabled
    endfunction
 
    function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
        return TriggerAddCondition(IndexTrig[eventtype], func)
    endfunction
 
    function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
        call TriggerRemoveCondition(IndexTrig[eventtype], c)
    endfunction
 
    function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
        call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
    endfunction
 
    function OnUnitIndex takes code func returns triggercondition
        return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
    endfunction

    function OnUnitDeindex takes code func returns triggercondition
        return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
    endfunction
 
    function UnitDexRemove takes unit u, boolean runEvents returns boolean
        return UnitDex.Remove(u, runEvents)
    endfunction
 
    /****************************************************************/
 
    private keyword UnitDexCore
 
    struct UnitDex extends array
        static boolean Enabled = true
     
        readonly static integer LastIndex
        readonly static boolean Initialized=false
        readonly static group Group=CreateGroup()
        readonly static unit array Unit
        readonly static integer Count = 0
        readonly static integer array List
     
        implement UnitDexConfig
     
        private static integer Counter = 0
     
        implement UnitDexCore
    endstruct
 
    /****************************************************************/
  
    private module UnitDexCore
 
        static method Remove takes unit u, boolean runEvents returns boolean
            local integer i
         
            if (IsUnitIndexed(u)) then
                set i = GetUnitId(u)
                set UnitDex.List[i] = Index
                set Index = i
             
                call GroupRemoveUnit(UnitDex.Group, u)
                call SetUnitUserData(u, 0)
         
                if (runEvents) then
                    set UnitDex.LastIndex = i
                    set E = EVENT_UNIT_DEINDEX
                    call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
                    set E = -1
                endif
             
                set UnitDex.Unit[i] = null
                set UnitDex.Count = UnitDex.Count - 1
             
                return true
            endif
         
            return false
        endmethod
     
        private static method onGameStart takes nothing returns nothing
            local integer i = 0
            static if (not LIBRARY_GroupUtils) then // Check if GroupUtils exists so we can use it's enumeration group.
                local group ENUM_GROUP = CreateGroup() // If not, create the group.
            endif
         
            // Index preplaced units
            loop
                call GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(i), FilterEnter)
             
                set i = i + 1
             
                exitwhen i == bj_MAX_PLAYER_SLOTS
            endloop
         
            static if (not LIBRARY_GroupUtils) then
                call DestroyGroup(ENUM_GROUP)
                set ENUM_GROUP = null
            endif
         
            // run init triggers
            set i = 1
            loop
                exitwhen i > Counter
             
                set LastIndex = i
             
                call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
                set E = EVENT_UNIT_INDEX
                set E = -1
             
                set i = i + 1
            endloop

            set LastIndex   = Counter
            set Initialized = true
            set FilterEnter = null
         
            call DestroyTimer(GetExpiredTimer())
        endmethod
     
        private static method onEnter takes nothing returns boolean
            local unit    u = GetFilterUnit()
            local integer i = GetUnitId(u)
            local integer t = Index
         
            if (i == 0 and thistype.Enabled) then
             
                // If a filter was defined pass the unit through it.
                static if (thistype.onFilter.exists) then
                    if (not thistype.onFilter(u)) then
                        set u = null
                        return false // check failed
                    endif
                endif
             
                // Handle debugging
                static if (thistype.DEBUG_MODE and thistype.ALLOW_DEBUGGING) then
                    if (t == 0 and Counter+1 >= JASS_MAX_ARRAY_SIZE) then
                        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached!")
                        set u = null
                        return false
                    endif
                endif
             
                // Add to group of indexed units
                call GroupAddUnit(thistype.Group, u)
             
                // Give unit the leave detection ability
                call UnitAddAbility(u, thistype.DETECT_LEAVE_ABILITY)
                call UnitMakeAbilityPermanent(u, true, thistype.DETECT_LEAVE_ABILITY)
             
                // Allocate index
                if (Index != 0) then
                    set Index = List[t]
                else
                    set Counter = Counter + 1
                    set t = Counter
                endif
             
                set List[t] = -1
                set LastIndex = t
                set thistype.Unit[t] = u
                set Count = Count + 1
             
                // Store the index
                call SetUnitUserData(u, t)
             
                if (thistype.Initialized) then
                    // Execute custom events registered with RegisterUnitIndexEvent
                    call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
                 
                    // Handle TriggerRegisterUnitIndexEvent
                    set E = EVENT_UNIT_INDEX

                    // Reset so the event can occur again
                    set E = -1
                endif
            endif
         
            set u = null
         
            return false
        endmethod

        private static method onLeave takes nothing returns boolean
            local unit    u
            local integer i
         
            // Check if order is undefend.
            if (thistype.Enabled and GetIssuedOrderId() == 852056) then
             
                set u = GetTriggerUnit()
             
                // If unit was killed (not removed) then don't continue
                if (GetUnitAbilityLevel(u, thistype.DETECT_LEAVE_ABILITY) != 0) then
                    set u = null
                    return false
                endif
             
                set i = GetUnitId(u)

                // If unit has been indexed then deindex it
                if (i > 0 and i <= Counter and u == GetUnitById(i)) then
                 
                    // Recycle the index
                    set List[i]   = Index
                    set Index     = i
                    set LastIndex = i
                 
                    // Remove to group of indexed units
                    call GroupRemoveUnit(thistype.Group, u)
             
                    // Execute custom events without any associated triggers
                    call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
                 
                    // Handle TriggerRegisterUnitIndexEvent
                    set E = EVENT_UNIT_DEINDEX
                 
                    // Remove entry
                    call SetUnitUserData(u, 0)
                    set thistype.Unit[i] = null
                 
                    // Decrement unit count
                    set Count = Count - 1
            
                    // Reset so the event can occur again
                    set E = -1
                endif
             
                set u = null
            endif
         
            return false
        endmethod
     
        private static method onInit takes nothing returns nothing
            local trigger t         = CreateTrigger()
            local integer i         = 0
            local player p
            local unit u
         
            static if (not LIBRARY_WorldBounds) then // Check if WorldBounts exists, if not then define the necessary vars
                local region reg = CreateRegion() // If WorldBounds wasn't found, create the region manually
                local rect world = GetWorldBounds()
            endif
         
            set FilterEnter = Filter(function thistype.onEnter)
         
            // Begin to index units when they enter the map
            static if (LIBRARY_WorldBounds) then
                call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, FilterEnter)
            else
                call RegionAddRect(reg, world)
                call TriggerRegisterEnterRegion(CreateTrigger(), reg, FilterEnter)
                call RemoveRect(world)
                set world = null
            endif
         
            call TriggerAddCondition(t, Filter(function thistype.onLeave))
         
            set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
            set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
         
            loop
                set p = Player(i)
             
                // Detect "undefend"
                call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
             
                // Hide the detect ability from players
                call SetPlayerAbilityAvailable(p, thistype.DETECT_LEAVE_ABILITY, false)
             
                set i = i + 1
                exitwhen i == bj_MAX_PLAYER_SLOTS
            endloop
         
            call TimerStart(CreateTimer(), 0, false, function thistype.onGameStart)
        endmethod
 
    endmodule
 
endlibrary

  • Unit Indexer
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Custom script: call ExecuteFunc("InitializeUnitIndexer")
      • Custom script: endfunction
      • -------- --------
      • -------- This is the core function - it provides an index all existing units and for units as they enter the map --------
      • -------- --------
      • Custom script: function IndexUnit takes nothing returns boolean
      • Custom script: local integer pdex = udg_UDex
      • Custom script: local integer ndex
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • IsUnitPreplaced[0] Equal to False
        • Then - Actions
          • -------- --------
          • -------- Check for removed units for every (32) new units created --------
          • -------- --------
          • Set VariableSet UDexWasted = (UDexWasted + 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • UDexWasted Equal to 32
            • Then - Actions
              • Set VariableSet UDexWasted = 0
              • Set VariableSet UDex = UDexNext[0]
              • Custom script: loop
              • Custom script: exitwhen udg_UDex == 0
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Custom value of UDexUnits[UDex]) Equal to 0
                • Then - Actions
                  • -------- --------
                  • -------- Remove index from linked list --------
                  • -------- --------
                  • Custom script: set ndex = udg_UDexNext[udg_UDex]
                  • Custom script: set udg_UDexNext[udg_UDexPrev[udg_UDex]] = ndex
                  • Custom script: set udg_UDexPrev[ndex] = udg_UDexPrev[udg_UDex]
                  • Set VariableSet UDexPrev[UDex] = 0
                  • Set VariableSet IsUnitPreplaced[UDex] = False
                  • -------- --------
                  • -------- Fire deindex event for UDex --------
                  • -------- --------
                  • Set VariableSet UnitIndexEvent = 2.00
                  • Set VariableSet UnitIndexEvent = 0.00
                  • -------- --------
                  • -------- Recycle the index for later use --------
                  • -------- --------
                  • Set VariableSet UDexUnits[UDex] = No unit
                  • Set VariableSet UDexNext[UDex] = UDexRecycle
                  • Set VariableSet UDexRecycle = UDex
                  • Custom script: set udg_UDex = ndex
                • Else - Actions
                  • Set VariableSet UDex = UDexNext[UDex]
              • Custom script: endloop
            • Else - Actions
        • Else - Actions
      • -------- --------
      • -------- You can use the boolean UnitIndexerEnabled to protect some of your undesirable units from being indexed --------
      • -------- - Example: --------
      • -------- -- Set UnitIndexerEnabled = False --------
      • -------- -- Unit - Create 1 Dummy for (Triggering player) at TempLoc facing 0.00 degrees --------
      • -------- -- Set UnitIndexerEnabled = True --------
      • -------- --------
      • -------- You can also customize the following block - if conditions are false the (Matching unit) won't be indexed. --------
      • -------- --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • UnitIndexerEnabled Equal to True
          • (Custom value of (Matching unit)) Equal to 0
        • Then - Actions
          • -------- --------
          • -------- Generate a unique integer index for this unit --------
          • -------- --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • UDexRecycle Equal to 0
            • Then - Actions
              • Set VariableSet UDex = (UDexGen + 1)
              • Set VariableSet UDexGen = UDex
            • Else - Actions
              • Set VariableSet UDex = UDexRecycle
              • Set VariableSet UDexRecycle = UDexNext[UDex]
          • -------- --------
          • -------- Link index to unit, unit to index --------
          • -------- --------
          • Set VariableSet UDexUnits[UDex] = (Matching unit)
          • Unit - Set the custom value of UDexUnits[UDex] to UDex
          • Set VariableSet IsUnitPreplaced[UDex] = IsUnitPreplaced[0]
          • -------- --------
          • -------- Use a doubly-linked list to store all active indexes --------
          • -------- --------
          • Set VariableSet UDexPrev[UDexNext[0]] = UDex
          • Set VariableSet UDexNext[UDex] = UDexNext[0]
          • Set VariableSet UDexNext[0] = UDex
          • -------- --------
          • -------- Fire index event for UDex --------
          • -------- --------
          • Set VariableSet UnitIndexEvent = 0.00
          • Set VariableSet UnitIndexEvent = 1.00
          • Set VariableSet UnitIndexEvent = 0.00
        • Else - Actions
      • Custom script: set udg_UDex = pdex
      • Custom script: return false
      • Custom script: endfunction
      • -------- --------
      • -------- The next function initializes the core of the system --------
      • -------- --------
      • Custom script: function InitializeUnitIndexer takes nothing returns nothing
      • Custom script: local integer i = 0
      • Custom script: local region re = CreateRegion()
      • Custom script: local rect r = GetWorldBounds()
      • Custom script: local boolexpr b = Filter(function IndexUnit)
      • Set VariableSet UnitIndexEvent = -1.00
      • Set VariableSet UnitIndexerEnabled = True
      • Set VariableSet IsUnitPreplaced[0] = True
      • Custom script: call RegionAddRect(re, r)
      • Custom script: call TriggerRegisterEnterRegion(CreateTrigger(), re, b)
      • Custom script: call RemoveRect(r)
      • Custom script: set re = null
      • Custom script: set r = null
      • Custom script: loop
      • Custom script: call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(i), b)
      • Custom script: set i = i + 1
      • Custom script: exitwhen i == bj_MAX_PLAYER_SLOTS
      • Custom script: endloop
      • Custom script: set b = null
      • -------- --------
      • -------- This is the "Unit Indexer Initialized" event, use it instead of "Map Initialization" for best results --------
      • -------- --------
      • Set VariableSet IsUnitPreplaced[0] = False
      • Set VariableSet UnitIndexEvent = 3.00

JASS:
//TESH.scrollpos=0
//TESH.alwaysfold=1
library PlayerUtils
/**************************************************************
*
*   v1.2.8 by TriggerHappy
*
*   This library provides a struct which caches data about players
*   as well as provides functionality for manipulating player colors.
*
*   Constants
*   ------------------
*
*       force FORCE_PLAYING - Player group of everyone who is playing.
*
*   Struct API
*   -------------------
*     struct User
*
*       static method fromIndex takes integer i returns User
*       static method fromLocal takes nothing returns User
*       static method fromPlaying takes integer id returns User
*
*       static method operator []    takes integer id returns User
*       static method operator count takes nothing returns integer
*
*       method operator name         takes nothing returns string
*       method operator name=        takes string name returns nothing
*       method operator color        takes nothing returns playercolor
*       method operator color=       takes playercolor c returns nothing
*       method operator defaultColor takes nothing returns playercolor
*       method operator hex          takes nothing returns string
*       method operator nameColored  takes nothing returns string
*
*       method toPlayer takes nothing returns player
*       method colorUnits takes playercolor c returns nothing
*
*       readonly string originalName
*       readonly boolean isPlaying
*       readonly static player Local
*       readonly static integer LocalId
*       readonly static integer AmountPlaying
*       readonly static playercolor array Color
*       readonly static player array PlayingPlayer
*
**************************************************************/

    globals
        // automatically change unit colors when changing player color
        private constant boolean AUTO_COLOR_UNITS = true
 
        // use an array for name / color lookups (instead of function calls)
        private constant boolean ARRAY_LOOKUP     = false
 
        // this only applies if ARRAY_LOOKUP is true
        private constant boolean HOOK_SAFETY      = false // disable for speed, but only use the struct to change name/color safely
 
        constant force FORCE_PLAYING = CreateForce()
 
        private string array Name
        private string array Hex
        private string array OriginalHex
        private playercolor array CurrentColor
    endglobals

    private keyword PlayerUtilsInit

    struct User extends array
  
        static constant integer NULL = 15
    
        readonly player handle
        readonly integer id
        readonly thistype next
        readonly thistype prev

        readonly string originalName
        readonly boolean isPlaying
 
        readonly static thistype first
        readonly static thistype last
        readonly static player Local
        readonly static integer LocalId
        readonly static integer AmountPlaying = 0
        readonly static playercolor array Color

        static if not (LIBRARY_GroupUtils) then
            readonly static group ENUM_GROUP = CreateGroup()
        endif

        private static thistype array PlayingPlayer
        private static integer array PlayingPlayerIndex
 
        // similar to Player(#)
        static method fromIndex takes integer i returns thistype
            return thistype(i)
        endmethod
 
        // similar to GetLocalPlayer
        static method fromLocal takes nothing returns thistype
            return thistype(thistype.LocalId)
        endmethod
 
        // access active players array
        static method fromPlaying takes integer index returns thistype
            return PlayingPlayer[index]
        endmethod
 
        static method operator [] takes player p returns thistype
            return thistype(GetPlayerId(p))
        endmethod
 
        method toPlayer takes nothing returns player
            return this.handle
        endmethod
  
        method operator name takes nothing returns string
            static if (ARRAY_LOOKUP) then
                return Name[this]
            else
                return GetPlayerName(this.handle)
            endif
        endmethod
 
        method operator name= takes string newName returns nothing
            call SetPlayerName(this.handle, newName)
            static if (ARRAY_LOOKUP) then
                static if not (HOOK_SAFETY) then
                    set Name[this] = newName
                endif
            endif
        endmethod
 
        method operator color takes nothing returns playercolor
            static if (ARRAY_LOOKUP) then
                return CurrentColor[this]
            else
                return GetPlayerColor(this.handle)
            endif
        endmethod
 
        method operator hex takes nothing returns string
            return OriginalHex[GetHandleId(this.color)]
        endmethod
 
        method operator color= takes playercolor c returns nothing
            call SetPlayerColor(this.handle, c)
    
            static if (ARRAY_LOOKUP) then
                set CurrentColor[this] = c
                static if not (HOOK_SAFETY) then
                    static if (AUTO_COLOR_UNITS) then
                        call this.colorUnits(color)
                    endif
                endif
            endif
        endmethod
 
        method operator defaultColor takes nothing returns playercolor
            return Color[this]
        endmethod
 
        method operator nameColored takes nothing returns string
            return hex + this.name + "|r"
        endmethod
 
        method colorUnits takes playercolor c returns nothing
            local unit u
    
            call GroupEnumUnitsOfPlayer(ENUM_GROUP, this.handle, null)
    
            loop
                set u = FirstOfGroup(ENUM_GROUP)
                exitwhen u == null
                call SetUnitColor(u, c)
                call GroupRemoveUnit(ENUM_GROUP, u)
            endloop
        endmethod
 
        static method onLeave takes nothing returns boolean
            local thistype p  = thistype[GetTriggerPlayer()]
            local integer i   = .PlayingPlayerIndex[p.id]
    
            // clean up
            call ForceRemovePlayer(FORCE_PLAYING, p.toPlayer())
    
            // recycle index
            set .AmountPlaying = .AmountPlaying - 1
            set .PlayingPlayerIndex[i] = .PlayingPlayerIndex[.AmountPlaying]
            set .PlayingPlayer[i] = .PlayingPlayer[.AmountPlaying]
        
            if (.AmountPlaying == 1) then
                set p.prev.next = User.NULL
                set p.next.prev = User.NULL
            else
                set p.prev.next = p.next
                set p.next.prev = p.prev
            endif

            set .last = .PlayingPlayer[.AmountPlaying]
        
            set p.isPlaying = false
    
            return false
        endmethod
 
        implement PlayerUtilsInit
 
    endstruct

    private module PlayerUtilsInit
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local integer i = 0
            local thistype p
    
            set thistype.Local   = GetLocalPlayer()
            set thistype.LocalId = GetPlayerId(thistype.Local)
    
            set OriginalHex[0]  = "|cffff0303"
            set OriginalHex[1]  = "|cff0042ff"
            set OriginalHex[2]  = "|cff1ce6b9"
            set OriginalHex[3]  = "|cff540081"
            set OriginalHex[4]  = "|cfffffc01"
            set OriginalHex[5]  = "|cfffe8a0e"
            set OriginalHex[6]  = "|cff20c000"
            set OriginalHex[7]  = "|cffe55bb0"
            set OriginalHex[8]  = "|cff959697"
            set OriginalHex[9]  = "|cff7ebff1"
            set OriginalHex[10] = "|cff106246"
            set OriginalHex[11] = "|cff4e2a04"
      
            set thistype.first = User.NULL

            loop
                exitwhen i == 12

                set p         = User(i)
                set p.handle  = Player(i)
                set p.id      = i
        
                set thistype.Color[i] = GetPlayerColor(p.handle)
                set CurrentColor[i] = thistype.Color[i]
          
                if (GetPlayerController(p.handle) == MAP_CONTROL_USER and GetPlayerSlotState(p.handle) == PLAYER_SLOT_STATE_PLAYING) then

                    set .PlayingPlayer[AmountPlaying] = p
                    set .PlayingPlayerIndex[i] = .AmountPlaying
                
                   set .last = i
                
                    if (.first == User.NULL) then
                        set .first = i
                        set User(i).next = User.NULL
                        set User(i).prev = User.NULL
                    else
                        set User(i).prev = PlayingPlayer[AmountPlaying-1].id
                        set PlayingPlayer[AmountPlaying-1].next = User(i)
                        set User(i).next = User.NULL
                    endif

                    set p.isPlaying = true
            
                    call TriggerRegisterPlayerEvent(t, p.handle, EVENT_PLAYER_LEAVE)
                    call ForceAddPlayer(FORCE_PLAYING, p.handle)
            
                    set Hex[p] = OriginalHex[GetHandleId(thistype.Color[i])]
            
                    set .AmountPlaying = .AmountPlaying + 1

                endif
        
                set Name[p] = GetPlayerName(p.handle)
                set p.originalName=Name[p]
        
                set i = i + 1
            endloop
    
            call TriggerAddCondition(t, Filter(function thistype.onLeave))
        endmethod
    endmodule

    //===========================================================================
endlibrary

JASS:
library Camera requires UIMath, PlayerUtils

    globals
        // configuration
     
        // location on the map where the terrain is the default level
    // UI placement on screen X,Y
        constant real CHECK_DELTAZ_X = -300
        constant real CHECK_DELTAZ_Y = 500
     
        constant real SCREEN_WIDTH  = 0.544
        constant real SCREEN_HEIGHT = 0.302
     
        constant real SCREEN_ASPECT_RATIO = SCREEN_WIDTH/SCREEN_HEIGHT
     
        constant integer CAMERA_DUMMY_TYPE  = 'e00D'
        constant player CAMERA_DUMMY_PLAYER = Player(bj_PLAYER_NEUTRAL_EXTRA)
     
        private real DeltaZ = 0
        // endconfig
     
        public unit array DummyUnit
        public location DummyLoc = Location(0,0)
    endglobals

    function GetTerrainZ takes real x, real y returns real
        call MoveLocation(DummyLoc, x, y)
        return GetLocationZ(DummyLoc)
    endfunction
 
    function GetCameraDeltaZ takes nothing returns real
        return DeltaZ
    endfunction
 
    private function Matrix4Perspective1 takes MATRIX4 Output, real fovy, real Aspect, real zn, real zf returns MATRIX4
        return Output.SetValues(2*zn/fovy,0,0,0,0,2*zn/Aspect,0,0,0,0,zf/(zf-zn),1,0,0,zn*zf/(zn-zf),0)
    endfunction

    private function Matrix4Perspective2 takes MATRIX4 Output, real n, real f, real r, real l, real t, real b returns MATRIX4
        return Output.SetValues(2*n/(r-l), 0, (r+l)/(r-l), 0, 0, 2*n/(t-b), (t+b)/(t-b), 0, 0, 0, -(f+n)/(f-n), -2*f*n/(f-n), 0, 0, -1, 0)
    endfunction

    private function Matrix4Look takes MATRIX4 Output, VECTOR3 PosCamera, VECTOR3 AxisX, VECTOR3 AxisY, VECTOR3 AxisZ returns MATRIX4
        return Output.SetValues(AxisX.x,AxisY.x,AxisZ.x,0,AxisX.y,AxisY.y,AxisZ.y,0,AxisX.z,AxisY.z,AxisZ.z,0,-Vec3Dot(AxisX, PosCamera),-Vec3Dot(AxisY, PosCamera),-Vec3Dot(AxisZ, PosCamera),1)
    endfunction

    struct Camera
        VECTOR3 eye
        VECTOR3 at
        unit target
        real distance
        real yaw
        real pitch
        real roll
        VECTOR3 axisX
        VECTOR3 axisY
        VECTOR3 axisZ
        private MATRIX4 view
        private MATRIX4 projection
        private boolean change
        integer customValue
     
        method win2World takes real X, real Y, real Range returns VECTOR3
            local VECTOR3 Output = VECTOR3.create()
            set Output.x = .eye.x+.axisZ.x*Range+X*.axisX.x*SCREEN_WIDTH*Range+Y*.axisY.x*SCREEN_HEIGHT*Range
            set Output.y = .eye.y+.axisZ.y*Range+X*.axisX.y*SCREEN_WIDTH*Range+Y*.axisY.y*SCREEN_HEIGHT*Range
            set Output.z = .eye.z+.axisZ.z*Range+X*.axisX.z*SCREEN_WIDTH*Range+Y*.axisY.z*SCREEN_HEIGHT*Range
            return Output
        endmethod

        method world2Win takes real X, real Y, real Z returns VECTOR3
            local VECTOR3 Pos = VECTOR3.New_1(X, Y, Z)
            local boolean b
            call Vec3Transform_2(Pos, Pos, .view)
            set b = Pos.z < 0
            call Vec3Transform_2(Pos, Pos, .projection)
            if b then
                set Pos.z = -Pos.z
            endif
            return Pos
        endmethod
     
        private method updateDistanceYawPitch takes nothing returns nothing
            local real dx = .at.x-.eye.x
            local real dy = .at.y-.eye.y
            local real dz = .at.z-.eye.z
            local real len2d
            set .distance = SquareRoot(dx*dx+dy*dy+dz*dz)
            set .yaw = Atan2(dy, dx)
            set len2d = SquareRoot(dx*dx+dy*dy)
            set .pitch = Atan2(dz, len2d)
        endmethod
     
        private method updateAxisMatrix takes nothing returns nothing
            local MATRIX3 mat
            call Vec3Normalize(.axisZ, Vec3Subtract(.axisZ, .at, .eye))
            set mat = Matrix3RotationAxis(MATRIX3.create(), .axisZ, -.roll)
            call Vec3Normalize(.axisX, Vec3Cross(.axisX, .axisZ, VECTOR3.oneZ))
            call Vec3Transform_1(.axisY, Vec3Cross(.axisY, .axisX, .axisZ), mat)
            call Vec3Transform_1(.axisX, .axisX, mat)
            call Matrix4Look(.view, .eye, .axisX, .axisY, .axisZ)
            call mat.destroy()
        endmethod

        method applyCameraForPlayer takes player p, boolean ignoreChange returns boolean
            if GetLocalPlayer() == p then
                call SetCameraField(CAMERA_FIELD_ROTATION, .yaw*bj_RADTODEG, 0)
                call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, .pitch*bj_RADTODEG, 0)
                call SetCameraField(CAMERA_FIELD_ROLL, .roll*bj_RADTODEG, 0)
                call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, .distance, 0)
                call SetCameraTargetController(DummyUnit[GetPlayerId(p)], .at.x, .at.y, false)
                call SetCameraField(CAMERA_FIELD_ZOFFSET, .at.z-DeltaZ, 0)
            endif
            if .change or ignoreChange then
                set .change = false
                return true
            endif
            return false
        endmethod

        method setPosition takes real x, real y, real z returns nothing
            local real dx = x-.at.x
            local real dy = y-.at.y
            local real dz = z-.at.z
            set .eye.x = .eye.x+dx
            set .eye.y = .eye.y+dy
            set .eye.z = .eye.z+dz
            set .at.x = x
            set .at.y = y
            set .at.z = z
            set .change = true
        endmethod
     
        method setEyeAndAt takes real ex, real ey, real ez, real tx, real ty, real tz returns nothing
            set .eye.x = ex
            set .eye.y = ey
            set .eye.z = ez
            set .at.x = tx
            set .at.y = ty
            set .at.z = tz
            call .updateDistanceYawPitch()
            call .updateAxisMatrix()
            set .change = true
        endmethod
     
        method setYawPitchRoll takes real yaw, real pitch, real roll, boolean EyeLock returns nothing
            local real Z = .distance*Sin(pitch)
            local real XY = .distance*Cos(pitch)
            local real X = XY*Cos(yaw)
            local real Y = XY*Sin(yaw)
            set .yaw = yaw
            set .pitch = pitch
            set .roll = roll
            if EyeLock then
                set .at.x = .eye.x+X
                set .at.y = .eye.y+Y
                set .at.z = .eye.z+Z
            else
                set .eye.x = .at.x-X
                set .eye.y = .at.y-Y
                set .eye.z = .at.z-Z
            endif
            call .updateAxisMatrix()
            set .change = true
        endmethod
     
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
         
            set .customValue = 0
            set .change = true
            set .eye = VECTOR3.New_1(0.0,-922.668,DeltaZ+1367.912)
            set .at = VECTOR3.New_1(0, 0, DeltaZ)
            set .distance = 0
            set .yaw = 0
            set .pitch = 0
            set .roll = 0
            set .axisX = VECTOR3.create()
            set .axisY = VECTOR3.create()
            set .axisZ = VECTOR3.create()
            set .view  = MATRIX4.create()
            set .projection = Matrix4Perspective2(MATRIX4.create(), 0.5, 10000, -SCREEN_WIDTH/2, SCREEN_WIDTH/2, -SCREEN_HEIGHT/2, SCREEN_HEIGHT/2)
            call .updateDistanceYawPitch()
            call .updateAxisMatrix()
         
            return this
        endmethod
     
        method destroy takes nothing returns nothing
            call .eye.destroy()
            call .at.destroy()
            call .axisX.destroy()
            call .axisY.destroy()
            call .axisZ.destroy()
            call .view.destroy()
            call .projection.destroy()
            call this.destroy()
        endmethod
     
    endstruct

    private module CamInitModule
        private static method onInit takes nothing returns nothing
            local integer i = 0
            local User user
         
            loop
                exitwhen i == User.AmountPlaying
             
                set user = User.fromPlaying(i)
             
                set DummyUnit[user.id] = CreateUnit(CAMERA_DUMMY_PLAYER, CAMERA_DUMMY_TYPE, 0, 0, 0)
                call ShowUnit(DummyUnit[user.id], false)
                call PauseUnit(DummyUnit[user.id], true)
             
                set i = i + 1
            endloop
         
            // init delta z
            call SetCameraPosition(CHECK_DELTAZ_X, CHECK_DELTAZ_Y)
            set DeltaZ = GetCameraTargetPositionZ() - 6
        endmethod
    endmodule
 
    private struct CamInit
        implement CamInitModule
    endstruct
 
endlibrary

JASS:
library UIMath initializer Init

    public function R2I_N takes real r returns integer
        local integer i = R2I(r)
        if (r < I2R(i)+0.5) then
            return i
        else
            return i+1
        endif
    endfunction

    struct VECTOR3
        static VECTOR3 Zero
        static VECTOR3 oneX
        static VECTOR3 oneY
        static VECTOR3 oneZ
        real x
        real y
        real z
     
        static method New_0 takes nothing returns VECTOR3
            local VECTOR3 this = VECTOR3.create()
            set .x = 0
            set .y = 0
            set .z = 0
            return this
        endmethod
     
        static method New_1 takes real x, real y, real z returns VECTOR3
            local VECTOR3 this = VECTOR3.create()
            set .x = x
            set .y = y
            set .z = z
            return this
        endmethod
     
        static method New_2 takes VECTOR3 v returns VECTOR3
            local VECTOR3 this = VECTOR3.create()
            set .x = v.x
            set .y = v.y
            set .z = v.z
            return this
        endmethod
     
        method SetValues takes real x, real y, real z returns VECTOR3
            set .x = x
            set .y = y
            set .z = z
            return this
        endmethod
     
        method Length takes nothing returns real
            return SquareRoot(.x*.x+.y*.y+.z*.z)
        endmethod
     
        method LengthSq takes nothing returns real
            return .x*.x+.y*.y+.z*.z
        endmethod
     
        method ToString takes nothing returns string
            return "Vector3 id "+I2S(this) + "\nx = "+R2S(.x)+"   y = "+R2S(.y)+"   z = "+R2S(.z)
        endmethod
     
    endstruct

    function Vec3Add takes VECTOR3 Output, VECTOR3 v1, VECTOR3 v2 returns VECTOR3
        set Output.x = v1.x + v2.x
        set Output.y = v1.y + v2.y
        set Output.z = v1.z + v2.z
        return Output
    endfunction

    function Vec3Subtract takes VECTOR3 Output, VECTOR3 v1, VECTOR3 v2 returns VECTOR3
        set Output.x = v1.x - v2.x
        set Output.y = v1.y - v2.y
        set Output.z = v1.z - v2.z
        return Output
    endfunction
     
    function Vec3Scale takes VECTOR3 Output, VECTOR3 v, real r returns VECTOR3
        set Output.x = v.x * r
        set Output.y = v.y * r
        set Output.z = v.z * r
        return Output
    endfunction
     
    function Vec3Division takes VECTOR3 Output, VECTOR3 v, real r returns VECTOR3
        set Output.x = v.x / r
        set Output.y = v.y / r
        set Output.z = v.z / r
        return Output
    endfunction

    function Vec3Length takes VECTOR3 v returns real
        return SquareRoot(v.x*v.x+v.y*v.y+v.z*v.z)
    endfunction

    function Vec3LengthSq takes VECTOR3 v returns real
        return v.x*v.x+v.y*v.y+v.z*v.z
    endfunction

    function Vec3Normalize takes VECTOR3 Output, VECTOR3 v returns VECTOR3
        local real len = SquareRoot(v.x*v.x+v.y*v.y+v.z*v.z)
        set Output.x = v.x/len
        set Output.y = v.y/len
        set Output.z = v.z/len
        return Output
    endfunction

    function Vec3Dot takes VECTOR3 v1, VECTOR3 v2 returns real
        return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z
    endfunction

    function Vec3Cross takes VECTOR3 Output, VECTOR3 v1, VECTOR3 v2 returns VECTOR3
        local real y  = v1.z * v2.x - v1.x * v2.z
        local real z = v1.x * v2.y - v1.y * v2.x
        set Output.x = v1.y * v2.z - v1.z * v2.y
        set Output.y = y
        set Output.z = z
        return Output
    endfunction

    function Vec3Transform_1 takes VECTOR3 Output, VECTOR3 v, MATRIX3 m returns VECTOR3
        local real y = v.x*m.m12+v.y*m.m22+v.z*m.m32
        local real z = v.x*m.m13+v.y*m.m23+v.z*m.m33
        set Output.x = v.x*m.m11+v.y*m.m21+v.z*m.m31
        set Output.y = y
        set Output.z = z
        return Output
    endfunction

    function Vec3Transform_2 takes VECTOR3 Output, VECTOR3 v, MATRIX4 m returns VECTOR3
        local real y = v.x*m.m12+v.y*m.m22+v.z*m.m32+m.m42
        local real z = v.x*m.m13+v.y*m.m23+v.z*m.m33+m.m43
        local real w = v.x*m.m14+v.y*m.m24+v.z*m.m34+m.m44
        set Output.x = (v.x*m.m11+v.y*m.m21+v.z*m.m31+m.m41)/w
        set Output.y = y/w
        set Output.z = z/w
        return Output
    endfunction


    struct MATRIX3
        static MATRIX3 Zero
        static MATRIX3 E
        real m11
        real m12
        real m13
        real m21
        real m22
        real m23
        real m31
        real m32
        real m33
     
        static method New_0 takes nothing returns MATRIX3
            local MATRIX3 this = MATRIX3.create()
            set .m11 = 0
            set .m12 = 0
            set .m13 = 0
            set .m21 = 0
            set .m22 = 0
            set .m23 = 0
            set .m31 = 0
            set .m32 = 0
            set .m33 = 0
            return this
        endmethod
     
        static method New_1 takes real r11, real r12, real r13, real r21, real r22, real r23, real r31, real r32, real r33 returns MATRIX3
            local MATRIX3 this = MATRIX3.create()
            set .m11 = r11
            set .m12 = r12
            set .m13 = r13
            set .m21 = r21
            set .m22 = r22
            set .m23 = r23
            set .m31 = r31
            set .m32 = r32
            set .m33 = r33
            return this
        endmethod
     
        static method New_2 takes MATRIX3 m returns MATRIX3
            local MATRIX3 this = MATRIX3.create()
            set .m11 = m.m11
            set .m12 = m.m12
            set .m13 = m.m13
            set .m21 = m.m21
            set .m22 = m.m22
            set .m23 = m.m23
            set .m31 = m.m31
            set .m32 = m.m32
            set .m33 = m.m33
            return this
        endmethod
     
        method SetValues takes real r11, real r12, real r13, real r21, real r22, real r23, real r31, real r32, real r33 returns MATRIX3
            set .m11 = r11
            set .m12 = r12
            set .m13 = r13
            set .m21 = r21
            set .m22 = r22
            set .m23 = r23
            set .m31 = r31
            set .m32 = r32
            set .m33 = r33
            return this
        endmethod
  
        method ToString takes nothing returns string
            return "Matrux3 id "+I2S(this)+"\n"+R2S(.m11)+"  "+R2S(.m12)+"  "+R2S(.m13)+"\n"+R2S(.m21)+"  "+R2S(.m22)+"  "+R2S(.m23)+"\n"+R2S(.m31)+"  "+R2S(.m32)+"  "+R2S(.m33)
        endmethod
     
    endstruct

    function Matrix3Multiply takes MATRIX3 Output, MATRIX3 M1, MATRIX3 M2 returns MATRIX3
        return Output.SetValues(M1.m11*M2.m11+M1.m21*M2.m12+M1.m31*M2.m13,M1.m12*M2.m11+M1.m22*M2.m12+M1.m32*M2.m13,M1.m13*M2.m11+M1.m23*M2.m12+M1.m33*M2.m13,M1.m11*M2.m21+M1.m21*M2.m22+M1.m31*M2.m23,M1.m12*M2.m21+M1.m22*M2.m22+M1.m32*M2.m23,M1.m13*M2.m21+M1.m23*M2.m22+M1.m33*M2.m23,M1.m11*M2.m31+M1.m21*M2.m32+M1.m31*M2.m33,M1.m12*M2.m31+M1.m22*M2.m32+M1.m32*M2.m33,M1.m13*M2.m31+M1.m23*M2.m32+M1.m33*M2.m33)
    endfunction

    function Matrix3Scaling takes MATRIX3 Output, real x, real y, real z returns MATRIX3
        return Output.SetValues(x,0,0,0,y,0,0,0,z)
    endfunction

    function Matrix3RotationX takes MATRIX3 Output, real a returns MATRIX3
        return Output.SetValues(1,0,0,0,Cos(a),-Sin(a),0,Sin(a),Cos(a))
    endfunction

    function Matrix3RotationY takes MATRIX3 Output, real a returns MATRIX3
        return Output.SetValues(Cos(a),0,Sin(a),0,1,0,-Sin(a),0,Cos(a))
    endfunction

    function Matrix3RotationZ takes MATRIX3 Output, real a returns MATRIX3
        return Output.SetValues(Cos(a),-Sin(a),0,Sin(a),Cos(a),0,0,0,1)
    endfunction

    function Matrix3RotationAxis takes MATRIX3 Output, VECTOR3 v, real a returns MATRIX3
        local real cosa = Cos(a)
        local real sina = Sin(a)
        return Output.SetValues(cosa+(1-cosa)*v.x*v.x,(1-cosa)*v.x*v.y-sina*v.z,(1-cosa)*v.x*v.z+sina*v.y,(1-cosa)*v.y*v.x+sina*v.z,cosa+(1-cosa)*v.y*v.y,(1-cosa)*v.y*v.z-sina*v.x,(1-cosa)*v.z*v.x-sina*v.y,(1-cosa)*v.z*v.y+sina*v.x,cosa+(1-cosa)*v.z*v.z)
    endfunction

    function Matrix3RotationYawPitchRoll takes MATRIX3 Output, real Yaw, real Pitch, real Roll returns MATRIX3
        local real cosa = Cos(Yaw)
        local real sina = Sin(Yaw)
        local real cosb = Cos(Pitch)
        local real sinb = Sin(Pitch)
        local real cosy = Cos(Roll)
        local real siny = Sin(Roll)
        return Output.SetValues(cosa*cosb,cosa*sinb*siny-sina*cosy,cosa*sinb*cosy+sina*siny,sina*cosb,sina*sinb*siny+cosa*cosy,sina*sinb*cosy-cosa*siny,-sinb,cosb*siny,cosb*cosy)
    endfunction

    struct MATRIX4
        static MATRIX4 Zero
        static MATRIX4 E
        real m11
        real m12
        real m13
        real m14
        real m21
        real m22
        real m23
        real m24
        real m31
        real m32
        real m33
        real m34
        real m41
        real m42
        real m43
        real m44
     
        static method New_0 takes nothing returns MATRIX4
            local MATRIX4 this = MATRIX4.create()
            set .m11 = 0
            set .m12 = 0
            set .m13 = 0
            set .m14 = 0
            set .m21 = 0
            set .m22 = 0
            set .m23 = 0
            set .m24 = 0
            set .m31 = 0
            set .m32 = 0
            set .m33 = 0
            set .m34 = 0
            set .m41 = 0
            set .m42 = 0
            set .m43 = 0
            set .m44 = 0
            return this
        endmethod
     
        static method New_1 takes real r11, real r12, real r13, real r14, real r21, real r22, real r23, real r24, real r31, real r32, real r33, real r34, real r41, real r42, real r43, real r44 returns MATRIX4
            local MATRIX4 this = MATRIX4.create()
            set .m11 = r11
            set .m12 = r12
            set .m13 = r13
            set .m14 = r14
            set .m21 = r21
            set .m22 = r22
            set .m23 = r23
            set .m24 = r24
            set .m31 = r31
            set .m32 = r32
            set .m33 = r33
            set .m34 = r34
            set .m41 = r41
            set .m42 = r42
            set .m43 = r43
            set .m44 = r44
            return this
        endmethod
     
        static method New_2 takes MATRIX4 m returns MATRIX4
            local MATRIX4 this = MATRIX4.create()
            set .m11 = m.m11
            set .m12 = m.m12
            set .m13 = m.m13
            set .m14 = m.m14
            set .m21 = m.m21
            set .m22 = m.m22
            set .m23 = m.m23
            set .m24 = m.m24
            set .m31 = m.m31
            set .m32 = m.m32
            set .m33 = m.m33
            set .m34 = m.m34
            set .m41 = m.m41
            set .m42 = m.m42
            set .m43 = m.m43
            set .m44 = m.m44
            return this
        endmethod
     
        static method New_3 takes MATRIX3 m returns MATRIX4
            local MATRIX4 this = MATRIX4.create()
            set .m11 = m.m11
            set .m12 = m.m12
            set .m13 = m.m13
            set .m14 = 0
            set .m21 = m.m21
            set .m22 = m.m22
            set .m23 = m.m23
            set .m24 = 0
            set .m31 = m.m31
            set .m32 = m.m32
            set .m33 = m.m33
            set .m34 = 0
            set .m41 = 0
            set .m42 = 0
            set .m43 = 0
            set .m44 = 1
            return this
        endmethod
     
        method SetValues takes real r11, real r12, real r13, real r14, real r21, real r22, real r23, real r24, real r31, real r32, real r33, real r34, real r41, real r42, real r43, real r44 returns MATRIX4
            set .m11 = r11
            set .m12 = r12
            set .m13 = r13
            set .m14 = r14
            set .m21 = r21
            set .m22 = r22
            set .m23 = r23
            set .m24 = r24
            set .m31 = r31
            set .m32 = r32
            set .m33 = r33
            set .m34 = r34
            set .m41 = r41
            set .m42 = r42
            set .m43 = r43
            set .m44 = r44
            return this
        endmethod
  
        method ToString takes nothing returns string
            return "Matrux4 id "+I2S(this)+"\n"+R2S(.m11)+"  "+R2S(.m12)+"  "+R2S(.m13)+"  "+R2S(.m14)+"\n"+R2S(.m21)+"  "+R2S(.m22)+"  "+R2S(.m23)+"  "+R2S(.m24)+"\n"+R2S(.m31)+"  "+R2S(.m32)+"  "+R2S(.m33)+"  "+R2S(.m34)+"\n"+R2S(.m41)+"  "+R2S(.m42)+"  "+R2S(.m43)+"  "+R2S(.m44)
        endmethod
     
    endstruct

    function Matrix4Multiply takes MATRIX4 Output, MATRIX4 M1, MATRIX4 M2 returns MATRIX4
        return Output.SetValues(M1.m11*M2.m11+M1.m21*M2.m12+M1.m31*M2.m13+M1.m41*M2.m14,M1.m12*M2.m11+M1.m22*M2.m12+M1.m32*M2.m13+M1.m42*M2.m14,M1.m13*M2.m11+M1.m23*M2.m12+M1.m33*M2.m13+M1.m43*M2.m14,M1.m14*M2.m11+M1.m24*M2.m12+M1.m34*M2.m13+M1.m44*M2.m14,M1.m11*M2.m21+M1.m21*M2.m22+M1.m31*M2.m23+M1.m41*M2.m24,M1.m12*M2.m21+M1.m22*M2.m22+M1.m32*M2.m23+M1.m42*M2.m24,M1.m13*M2.m21+M1.m23*M2.m22+M1.m33*M2.m23+M1.m43*M2.m24,M1.m14*M2.m21+M1.m24*M2.m22+M1.m34*M2.m23+M1.m44*M2.m24,M1.m11*M2.m31+M1.m21*M2.m32+M1.m31*M2.m33+M1.m41*M2.m34,M1.m12*M2.m31+M1.m22*M2.m32+M1.m32*M2.m33+M1.m42*M2.m34,M1.m13*M2.m31+M1.m23*M2.m32+M1.m33*M2.m33+M1.m43*M2.m34,M1.m14*M2.m31+M1.m24*M2.m32+M1.m34*M2.m33+M1.m44*M2.m34,M1.m11*M2.m41+M1.m21*M2.m42+M1.m31*M2.m43+M1.m41*M2.m44,M1.m12*M2.m41+M1.m22*M2.m42+M1.m32*M2.m43+M1.m42*M2.m44,M1.m13*M2.m41+M1.m23*M2.m42+M1.m33*M2.m43+M1.m43*M2.m44,M1.m14*M2.m41+M1.m24*M2.m42+M1.m34*M2.m43+M1.m44*M2.m44)
    endfunction

    private function Init takes nothing returns nothing
       set VECTOR3.Zero = VECTOR3.New_0()
       set VECTOR3.oneX = VECTOR3.New_1(1,0,0)
       set VECTOR3.oneY = VECTOR3.New_1(0,1,0)
       set VECTOR3.oneZ = VECTOR3.New_1(0,0,1)
       set MATRIX3.Zero = MATRIX3.New_0()
       set MATRIX3.E = MATRIX3.New_1(1,0,0,0,1,0,0,0,1)
       set MATRIX4.Zero = MATRIX4.New_0()
       set MATRIX4.E = MATRIX4.New_1(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)
    endfunction

endlibrary

JASS:
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//*  To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//*  To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass)   More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//*  * Attaching
//*  * Recycling (with double-free protection)
//*
//* set t=NewTimer()      : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x)   : Get a timer (alternative to CreateTimer), call
//*                            Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t)       : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2)     : Attach value 2 to timer
//* GetTimerData(t)       : Get the timer's value.
//*                         You can assume a timer's value is 0
//*                         after NewTimer.
//*
//* Multi-flavor:
//*    Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************

//================================================================
    globals
        //How to tweak timer utils:
        // USE_HASH_TABLE = true  (new blue)
        //  * SAFEST
        //  * SLOWEST (though hash tables are kind of fast)
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true  (orange)
        //  * kinda safe (except there is a limit in the number of timers)
        //  * ALMOST FAST
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
        //  * THE FASTEST (though is only  faster than the previous method
        //                  after using the optimizer on the map)
        //  * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
        //                     work)
        //
        private constant boolean USE_HASH_TABLE      = true
        private constant boolean USE_FLEXIBLE_OFFSET = false

        private constant integer OFFSET     = 0x100000
        private          integer VOFFSET    = OFFSET
           
        //Timers to preload at map init:
        private constant integer QUANTITY   = 256
     
        //Changing this  to something big will allow you to keep recycling
        // timers even when there are already AN INCREDIBLE AMOUNT of timers in
        // the stack. But it will make things far slower so that's probably a bad idea...
        private constant integer ARRAY_SIZE = 8190

    endglobals

    //==================================================================================================
    globals
        private integer array data[ARRAY_SIZE]
        private hashtable     ht
    endglobals
 
 

    //It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
    function SetTimerData takes timer t, integer value returns nothing
        static if(USE_HASH_TABLE) then
            // new blue
            call SaveInteger(ht,0,GetHandleId(t), value)
         
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-VOFFSET]=value
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-OFFSET]=value
        endif     
    endfunction

    function GetTimerData takes timer t returns integer
        static if(USE_HASH_TABLE) then
            // new blue
            return LoadInteger(ht,0,GetHandleId(t) )
         
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-VOFFSET]
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-OFFSET]
        endif     
    endfunction

    //==========================================================================================
    globals
        private timer array tT[ARRAY_SIZE]
        private integer tN = 0
        private constant integer HELD=0x28829022
        //use a totally random number here, the more improbable someone uses it, the better.
     
        private boolean       didinit = false
    endglobals
    private keyword init

    //==========================================================================================
    // I needed to decide between duplicating code ignoring the "Once and only once" rule
    // and using the ugly textmacros. I guess textmacros won.
    //
    //! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
    // On second thought, no.
    //! endtextmacro

    function NewTimerEx takes integer value returns timer
        if (tN==0) then
            if (not didinit) then
                //This extra if shouldn't represent a major performance drawback
                //because QUANTITY rule is not supposed to be broken every day.
                call init.evaluate()
                set tN = tN - 1
            else
                //If this happens then the QUANTITY rule has already been broken, try to fix the
                // issue, else fail.
                debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
                set tT[0]=CreateTimer()
                static if( not USE_HASH_TABLE) then
                    debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
                    static if( USE_FLEXIBLE_OFFSET) then
                        if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    else
                        if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    endif
                endif
            endif
        else
            set tN=tN-1
        endif
        call SetTimerData(tT[tN],value)
     return tT[tN]
    endfunction
 
    function NewTimer takes nothing returns timer
        return NewTimerEx(0)
    endfunction


    //==========================================================================================
    function ReleaseTimer takes timer t returns nothing
        if(t==null) then
            debug call BJDebugMsg("Warning: attempt to release a null timer")
            return
        endif
        if (tN==ARRAY_SIZE) then
            debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

            //stack is full, the map already has much more troubles than the chance of bug
            call DestroyTimer(t)
        else
            call PauseTimer(t)
            if(GetTimerData(t)==HELD) then
                debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
                return
            endif
            call SetTimerData(t,HELD)
            set tT[tN]=t
            set tN=tN+1
        endif 
    endfunction

    private function init takes nothing returns nothing
     local integer i=0
     local integer o=-1
     local boolean oops = false
        if ( didinit ) then
            return
        else
            set didinit = true
        endif
  
        static if( USE_HASH_TABLE ) then
            set ht = InitHashtable()
            loop
                exitwhen(i==QUANTITY)
                set tT[i]=CreateTimer()
                call SetTimerData(tT[i], HELD)
                set i=i+1
            endloop
            set tN = QUANTITY
        else
            loop
                set i=0
                loop
                    exitwhen (i==QUANTITY)
                    set tT[i] = CreateTimer()
                    if(i==0) then
                        set VOFFSET = GetHandleId(tT[i])
                        static if(USE_FLEXIBLE_OFFSET) then
                            set o=VOFFSET
                        else
                            set o=OFFSET
                        endif
                    endif
                    if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
                        exitwhen true
                    endif
                    if (GetHandleId(tT[i])-o>=0)  then
                        set i=i+1
                    endif
                endloop
                set tN = i
                exitwhen(tN == QUANTITY)
                set oops = true
                exitwhen not USE_FLEXIBLE_OFFSET
                debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")            
            endloop
         
            if(oops) then
                static if ( USE_FLEXIBLE_OFFSET) then
                    debug call BJDebugMsg("The problem has been fixed.")
                    //If this message doesn't appear then there is so much
                    //handle id fragmentation that it was impossible to preload
                    //so many timers and the thread crashed! Therefore this
                    //debug message is useful.
                elseif(DEBUG_MODE) then
                    call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
                    call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
                    call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
                    call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
                endif
            endif
        endif

    endfunction

endlibrary

JASS:
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
 
    One map, one hashtable. Welcome to NewTable 4.1.0.1

    This newest iteration of Table introduces the new HashTable struct.
    You can now instantiate HashTables which enables the use of large
    parent and large child keys, just like a standard hashtable. Previously,
    the user would have to instantiate a Table to do this on their own which -
    while doable - is something the user should not have to do if I can add it
    to this resource myself (especially if they are inexperienced).

    This library was originally called NewTable so it didn't conflict with
    the API of Table by Vexorian. However, the damage is done and it's too
    late to change the library name now. To help with damage control, I
    have provided an extension library called TableBC, which bridges all
    the functionality of Vexorian's Table except for 2-D string arrays &
    the ".flush(integer)" method. I use ".flush()" to flush a child hash-
    table, because I wanted the API in NewTable to reflect the API of real
    hashtables (I thought this would be more intuitive).

    API

    ------------
    struct Table
    | static method create takes nothing returns Table
    |     create a new Table
    |
    | method destroy takes nothing returns nothing
    |     destroy it
    |
    | method flush takes nothing returns nothing
    |     flush all stored values inside of it
    |
    | method remove takes integer key returns nothing
    |     remove the value at index "key"
    |
    | method operator []= takes integer key, $TYPE$ value returns nothing
    |     assign "value" to index "key"
    |
    | method operator [] takes integer key returns $TYPE$
    |     load the value at index "key"
    |
    | method has takes integer key returns boolean
    |     whether or not the key was assigned
    |
    ----------------
    struct TableArray
    | static method operator [] takes integer array_size returns TableArray
    |     create a new array of Tables of size "array_size"
    |
    | method destroy takes nothing returns nothing
    |     destroy it
    |
    | method flush takes nothing returns nothing
    |     flush and destroy it
    |
    | method operator size takes nothing returns integer
    |     returns the size of the TableArray
    |
    | method operator [] takes integer key returns Table
    |     returns a Table accessible exclusively to index "key"
*/
 
globals
    private integer less = 0    //Index generation for TableArrays (below 0).
    private integer more = 8190 //Index generation for Tables.
    //Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
 
    private hashtable ht = InitHashtable()
    private key sizeK
    private key listK
endglobals

private struct dex extends array
    static method operator size takes nothing returns Table
        return sizeK
    endmethod
    static method operator list takes nothing returns Table
        return listK
    endmethod
endstruct

private struct handles extends array
    method has takes integer key returns boolean
        return HaveSavedHandle(ht, this, key)
    endmethod
    method remove takes integer key returns nothing
        call RemoveSavedHandle(ht, this, key)
    endmethod
endstruct

private struct agents extends array
    method operator []= takes integer key, agent value returns nothing
        call SaveAgentHandle(ht, this, key, value)
    endmethod
endstruct

//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
    method operator [] takes integer key returns $TYPE$
        return Load$FUNC$(ht, this, key)
    endmethod
    method operator []= takes integer key, $TYPE$ value returns nothing
        call Save$FUNC$(ht, this, key, value)
    endmethod
    method has takes integer key returns boolean
        return HaveSaved$SUPER$(ht, this, key)
    endmethod
    method remove takes integer key returns nothing
        call RemoveSaved$SUPER$(ht, this, key)
    endmethod
endstruct
private module $TYPE$m
    method operator $TYPE$ takes nothing returns $TYPE$s
        return this
    endmethod
endmodule
//! endtextmacro
 
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
    method operator [] takes integer key returns $TYPE$
        return Load$FUNC$Handle(ht, this, key)
    endmethod
    method operator []= takes integer key, $TYPE$ value returns nothing
        call Save$FUNC$Handle(ht, this, key, value)
    endmethod
    method has takes integer key returns boolean
        return HaveSavedHandle(ht, this, key)
    endmethod
    method remove takes integer key returns nothing
        call RemoveSavedHandle(ht, this, key)
    endmethod
endstruct
private module $TYPE$m
    method operator $TYPE$ takes nothing returns $TYPE$s
        return this
    endmethod
endmodule
//! endtextmacro

//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")

//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")

struct Table extends array

    // Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
    implement realm
    implement integerm
    implement booleanm
    implement stringm
    implement playerm
    implement widgetm
    implement destructablem
    implement itemm
    implement unitm
    implement abilitym
    implement timerm
    implement triggerm
    implement triggerconditionm
    implement triggeractionm
    implement eventm
    implement forcem
    implement groupm
    implement locationm
    implement rectm
    implement boolexprm
    implement soundm
    implement effectm
    implement unitpoolm
    implement itempoolm
    implement questm
    implement questitemm
    implement defeatconditionm
    implement timerdialogm
    implement leaderboardm
    implement multiboardm
    implement multiboarditemm
    implement trackablem
    implement dialogm
    implement buttonm
    implement texttagm
    implement lightningm
    implement imagem
    implement ubersplatm
    implement regionm
    implement fogstatem
    implement fogmodifierm
    implement hashtablem

    method operator handle takes nothing returns handles
        return this
    endmethod

    method operator agent takes nothing returns agents
        return this
    endmethod

    //set this = tb[GetSpellAbilityId()]
    method operator [] takes integer key returns Table
        return LoadInteger(ht, this, key) //return this.integer[key]
    endmethod

    //set tb[389034] = 8192
    method operator []= takes integer key, Table tb returns nothing
        call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
    endmethod

    //set b = tb.has(2493223)
    method has takes integer key returns boolean
        return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
    endmethod

    //call tb.remove(294080)
    method remove takes integer key returns nothing
        call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
    endmethod

    //Remove all data from a Table instance
    method flush takes nothing returns nothing
        call FlushChildHashtable(ht, this)
    endmethod

    //local Table tb = Table.create()
    static method create takes nothing returns Table
        local Table this = dex.list[0]
     
        if this == 0 then
            set this = more + 1
            set more = this
        else
            set dex.list[0] = dex.list[this]
            call dex.list.remove(this) //Clear hashed memory
        endif
     
        debug set dex.list[this] = -1
        return this
    endmethod

    // Removes all data from a Table instance and recycles its index.
    //
    //     call tb.destroy()
    //
    method destroy takes nothing returns nothing
        debug if dex.list[this] != -1 then
            debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
            debug return
        debug endif

        call this.flush()

        set dex.list[this] = dex.list[0]
        set dex.list[0] = this
    endmethod

    //! runtextmacro optional TABLE_BC_METHODS()
endstruct
 
//! runtextmacro optional TABLE_BC_STRUCTS()
 
struct TableArray extends array
 
    //Returns a new TableArray to do your bidding. Simply use:
    //
    //    local TableArray ta = TableArray[array_size]
    //
    static method operator [] takes integer array_size returns TableArray
        local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
        local TableArray this = tb[0]         //The last-destroyed TableArray that had this array size
     
        debug if array_size <= 0 then
            debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
            debug return 0
        debug endif
     
        if this == 0 then
            set this = less - array_size
            set less = this
        else
            set tb[0] = tb[this]  //Set the last destroyed to the last-last destroyed
            call tb.remove(this)  //Clear hashed memory
        endif
     
        set dex.size[this] = array_size //This remembers the array size
        return this
    endmethod
 
    //Returns the size of the TableArray
    method operator size takes nothing returns integer
        return dex.size[this]
    endmethod
 
    //This magic method enables two-dimensional[array][syntax] for Tables,
    //similar to the two-dimensional utility provided by hashtables them-
    //selves.
    //
    //ta[integer a].unit[integer b] = unit u
    //ta[integer a][integer c] = integer d
    //
    //Inline-friendly when not running in debug mode
    //
    method operator [] takes integer key returns Table
        static if DEBUG_MODE then
            local integer i = this.size
            if i == 0 then
                call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
                return 0
            elseif key < 0 or key >= i then
                call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
                return 0
            endif
        endif
        return this + key
    endmethod
 
    //Destroys a TableArray without flushing it; I assume you call .flush()
    //if you want it flushed too. This is a public method so that you don't
    //have to loop through all TableArray indices to flush them if you don't
    //need to (ie. if you were flushing all child-keys as you used them).
    //
    method destroy takes nothing returns nothing
        local Table tb = dex.size[this.size]
     
        debug if this.size == 0 then
            debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
            debug return
        debug endif
     
        if tb == 0 then
            //Create a Table to index recycled instances with their array size
            set tb = Table.create()
            set dex.size[this.size] = tb
        endif
     
        call dex.size.remove(this) //Clear the array size from hash memory
     
        set tb[this] = tb[0]
        set tb[0] = this
    endmethod
 
    private static Table tempTable
    private static integer tempEnd
 
    //Avoids hitting the op limit
    private static method clean takes nothing returns nothing
        local Table tb = .tempTable
        local integer end = tb + 0x1000
        if end < .tempEnd then
            set .tempTable = end
            call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
        else
            set end = .tempEnd
        endif
        loop
            call tb.flush()
            set tb = tb + 1
            exitwhen tb == end
        endloop
    endmethod
 
    //Flushes the TableArray and also destroys it. Doesn't get any more
    //similar to the FlushParentHashtable native than this.
    //
    method flush takes nothing returns nothing
        debug if this.size == 0 then
            debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
            debug return
        debug endif
        set .tempTable = this
        set .tempEnd = this + this.size
        call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
        call this.destroy()
    endmethod
 
endstruct
 
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array

    //Enables myHash[parentKey][childKey] syntax.
    //Basically, it creates a Table in the place of the parent key if
    //it didn't already get created earlier.
    method operator [] takes integer index returns Table
        local Table t = Table(this)[index]
        if t == 0 then
            set t = Table.create()
            set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
        endif
        return t
    endmethod

    //You need to call this on each parent key that you used if you
    //intend to destroy the HashTable or simply no longer need that key.
    method remove takes integer index returns nothing
        local Table t = Table(this)[index]
        if t != 0 then
            call t.destroy()
            call Table(this).remove(index)
        endif
    endmethod

    //Added in version 4.1
    method has takes integer index returns boolean
        return Table(this).has(index)
    endmethod

    //HashTables are just fancy Table indices.
    method destroy takes nothing returns nothing
        call Table(this).destroy()
    endmethod

    //Like I said above...
    static method create takes nothing returns thistype
        return Table.create()
    endmethod

endstruct

endlibrary

JASS:
library Table
//***************************************************************
//* Table object 3.1
//* ------------
//*
//*   set t=Table.create() - instanceates a new table object
//*   call t.destroy()     - destroys it
//*   t[1234567]           - Get value for key 1234567
//*                          (zero if not assigned previously)
//*   set t[12341]=32      - Assigning it.
//*   call t.flush(12341)  - Flushes the stored value, so it
//*                          doesn't use any more memory
//*   t.exists(32)         - Was key 32 assigned? Notice
//*                          that flush() unassigns values.
//*   call t.reset()       - Flushes the whole contents of the
//*                          Table.
//*
//*   call t.destroy()     - Does reset() and also recycles the id.
//*
//*   If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//*  You can use Table on structs' onInit  if the struct is
//* placed in a library that requires Table or outside a library.
//*
//*  You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//*  set Table["thisstring"][ 7 ] = 2
//*  set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************

//=============================================================
    globals
        private constant integer MAX_INSTANCES=8100 //400000
        //Feel free to change max instances if necessary, it will only affect allocation
        //speed which shouldn't matter that much.

    //=========================================================
        private hashtable ht = InitHashtable()
    endglobals

    private struct GTable[MAX_INSTANCES]

        method reset takes nothing returns nothing
            call FlushChildHashtable(ht, integer(this) )
        endmethod

        private method onDestroy takes nothing returns nothing
            call this.reset()
        endmethod

    endstruct

    //Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
    //! textmacro Table__make takes name, type, key
    struct $name$ extends GTable

        method operator [] takes $type$ key returns integer
            return LoadInteger(ht, integer(this), $key$)
        endmethod

        method operator []= takes $type$ key, integer value returns nothing
            call SaveInteger(ht,  integer(this)  ,$key$, value)
        endmethod

        method flush takes $type$ key returns nothing
            call RemoveSavedInteger(ht, integer(this), $key$)
        endmethod

        method exists takes $type$ key returns boolean
            return HaveSavedInteger( ht,  integer(this)  ,$key$)
        endmethod

        static method flush2D takes string firstkey returns nothing
            call $name$(- StringHash(firstkey)).reset()
        endmethod

        static method operator [] takes string firstkey returns $name$
            return $name$(- StringHash(firstkey) )
        endmethod

    endstruct
    //! endtextmacro

    //! runtextmacro Table__make("Table","integer","key" )
    //! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
    //! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )

endlibrary

JASS:
//TESH.scrollpos=84
//TESH.alwaysfold=0
library Table2
//***************************************************************
//* Table2 object 3.0
//* ------------
//*
//*   set t=Table2.create() - instanceates a new table object
//*   call t.destroy()     - destroys it
//*   t[1234567]           - Get value for key 1234567
//*                          (zero if not assigned previously)
//*   set t[12341]=32      - Assigning it.
//*   call t.flush(12341)  - Flushes the stored value, so it
//*                          doesn't use any more memory
//*   t.exists(32)         - Was key 32 assigned? Notice
//*                          that flush() unassigns values.
//*   call t.reset()       - Flushes the whole contents of the
//*                          Table.
//*
//*   call t.destroy()     - Does reset() and also recycles the id.
//*
//*   If you use HandleTable2 instead of Table2, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//*  You can use Table2 on structs' onInit  if the struct is
//* placed in a library that requires Table or outside a library.
//*
//*  You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//*  set Table2["thisstring"][ 7 ] = 2
//*  set Table2["thisstring"][ 5 ] = Table2["thisstring"][7]
//*
//***************************************************************

//=============================================================
    globals
        private constant integer MAX_INSTANCES=8100 //400000
        //Feel free to change max instances if necessary, it will only affect allocation
        //speed which shouldn't matter that much.

    //=========================================================
        private hashtable ht2
    endglobals

    private struct GTable[MAX_INSTANCES]

        method reset takes nothing returns nothing
            call FlushChildHashtable(ht2, integer(this) )
        endmethod

        private method onDestroy takes nothing returns nothing
            call this.reset()
        endmethod

        //=============================================================
        // initialize it all.
        //
        private static method onInit takes nothing returns nothing
            set ht2 = InitHashtable()
        endmethod

    endstruct

    //Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
    //! textmacro Table2__make takes name, type, key
    struct $name$ extends GTable

        method operator [] takes $type$ key returns integer
            return LoadInteger(ht2, integer(this), $key$)
        endmethod

        method operator []= takes $type$ key, integer value returns nothing
            call SaveInteger(ht2,  integer(this)  ,$key$, value)
        endmethod

        method flush takes $type$ key returns nothing
            call RemoveSavedInteger(ht2, integer(this), $key$)
        endmethod

        method exists takes $type$ key returns boolean
            return HaveSavedInteger( ht2,  integer(this)  ,$key$)
        endmethod

        static method flush2D takes string firstkey returns nothing
            call $name$(- StringHash(firstkey)).reset()
        endmethod

        static method operator [] takes string firstkey returns $name$
            return $name$(- StringHash(firstkey) )
        endmethod

    endstruct
    //! endtextmacro

    //! runtextmacro Table2__make("Table2","integer","key" )
    //! runtextmacro Table2__make("StringTable2","string", "StringHash(key)" )
    //! runtextmacro Table2__make("HandleTable2","handle","GetHandleId(key)" )

endlibrary

JASS:
//TESH.scrollpos=4
//TESH.alwaysfold=0
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//*  Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//*  To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************

//==================================================
   globals
       // High enough so the unit is no longer visible, low enough so the
       // game doesn't crash...
       //
       // I think you need 0.0 or soemthing negative prior to patch 1.22
       //
       private constant real EXTRA = 500.0
   endglobals

   //=========================================================================================
   globals
       private real maxx
       private real maxy
       private real minx
       private real miny
   endglobals

   //=======================================================================
   private function dis takes nothing returns nothing
    local unit u=GetTriggerUnit()
    local real x=GetUnitX(u)
    local real y=GetUnitY(u)

       if(x>maxx) then
           set x=maxx
       elseif(x<minx) then
           set x=minx
       endif
       if(y>maxy) then
           set y=maxy
       elseif(y<miny) then
           set y=miny
       endif
       call SetUnitX(u,x)
       call SetUnitY(u,y)
    set u=null
   endfunction

   private function init takes nothing returns nothing
    local trigger t=CreateTrigger()
    local region  r=CreateRegion()
    local rect    rc

       set minx=GetCameraBoundMinX() - EXTRA
       set miny=GetCameraBoundMinY() - EXTRA
       set maxx=GetCameraBoundMaxX() + EXTRA
       set maxy=GetCameraBoundMaxY() + EXTRA
       set rc=Rect(minx,miny,maxx,maxy)
       call RegionAddRect(r, rc)
       call RemoveRect(rc)

       call TriggerRegisterLeaveRegion(t,r, null)
       call TriggerAddAction(t, function dis)

    //this is not necessary but I'll do it anyway:
    set t=null
    set r=null
    set rc=null
   endfunction
endlibrary

JASS:
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//*  Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//*  To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************

//==================================================
   globals
       // High enough so the unit is no longer visible, low enough so the
       // game doesn't crash...
       //
       // I think you need 0.0 or soemthing negative prior to patch 1.22
       //
       private constant real EXTRA = 700.0
   endglobals

   //=========================================================================================
   globals
       private real maxx
       private real maxy
       private real minx
       private real miny
   endglobals

   //=======================================================================
   private function dis takes nothing returns nothing
    local unit u=GetTriggerUnit()
    local real x=GetUnitX(u)
    local real y=GetUnitY(u)
    local real x2=x
    local real y2=y
       if(x>maxx) then
           set x=maxx
       elseif(x<minx) then
           set x=minx
       endif
       if(y>maxy) then
           set y=maxy
       elseif(y<miny) then
           set y=miny
       endif
       if (x != x2) then
        call SetUnitX(u,x)
       endif
       if (y != y2) then
         call SetUnitY(u,y)
       endif
    set u=null
   endfunction

   private function init takes nothing returns nothing
    local trigger t=CreateTrigger()
    local region  r=CreateRegion()
    local rect    rc

       set minx=GetCameraBoundMinX() - EXTRA
       set miny=GetCameraBoundMinY() - EXTRA
       set maxx=GetCameraBoundMaxX() + EXTRA
       set maxy=GetCameraBoundMaxY() + EXTRA
       set rc=Rect(minx,miny,maxx,maxy)
       call RegionAddRect(r, rc)
       call RemoveRect(rc)

       call TriggerRegisterLeaveRegion(t,r, null)
       call TriggerAddAction(t, function dis)

    //this is not necessary but I'll do it anyway:
    set t=null
    set r=null
    set rc=null
   endfunction
endlibrary

JASS:
library xebasicQ
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITIDQ : Rawcode of the dummy unit in your map. It should
//                   use the dummy.mdx model, so remember to import it as
//                   well, just use copy&paste to copy the dummy from the
//                   xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLERQ: Medivh's raven form ability, you may need to change
//                    this rawcode to another spell that morphs into a flier
//                    in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITIONQ: The ancients' Eat tree ability, same as with medivh
//                      raven form, you might have to change it.
//
// XE_ANIMATION_PERIODQ: The global period of animation used by whatever
//                      timer that depends on it, if you put a low value
//                      the movement will look good but it may hurt your
//                      performance, if instead you use a high value it
//                      will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZEQ: The maximum unit collision size in your map, if
//                        you got a unit bigger than 197.0 it would be
//                        a good idea to update this constant, since some
//                        enums will not find it. Likewise, if none of
//                        your units can go bellow X and X is much smaller
//                        than 197.0, it would be a good idea to update
//                        as well, since it will improve the performance
//                        those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************

//===========================================================================
globals
   constant integer XE_DUMMY_UNITIDQ       = 'e00D'
   constant integer XE_HEIGHT_ENABLERQ     = 'Amrf'
   constant integer XE_TREE_RECOGNITIONQ   = 'Aeat'
   constant real    XE_ANIMATION_PERIODQ   =  0.025
   constant real    XE_MAX_COLLISION_SIZEQ =  197.0
endglobals

endlibrary

JASS:
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xepreload requires xebasicQ, optional TimerUtils
//************************************************************************
// xepreload 0.7 // modified by triggerhappy, module initialiazer
// ---------
// Ah, the joy of preloading abilities, it is such a necessary evil...
// Notice you are not supposed to use this system in places outside map init
//
// This one does the preloading and tries to minimize the hit on loading time
// for example, it only needs one single native call per ability preloaded.
//
//************************************************************************

//===========================================================================================================
    globals
        private unit dum=null
        private constant boolean ALLOW_DEBUGGING=true
    endglobals
    private keyword DebugIdInteger2IdString

    //inline friendly (when debug mode is off..)
    function XE_PreloadAbility takes integer abilid returns nothing
        call UnitAddAbility(dum, abilid)
        static if DEBUG_MODE then
            static if ALLOW_DEBUGGING then
                if GetUnitAbilityLevel(dum, abilid) == 0 then
                    call BJDebugMsg("XE_PreloadAbility: Ability "+ GetObjectName(abilid) + " " + DebugIdInteger2IdString.evaluate(abilid)+" does not exist.")
                endif
            endif
        endif
    endfunction
    // ................................................................................

    //================================================================================
    // Convert a integer id value into a 4-letter id code.
    // * Taken from cheats.j so I don't have to code it again.
    // * Used only on debug so making a whole library for it seemed silly
    // * Private so people don't begin using xepreload just to call this function....
    // * It will not work correctly if you paste this code in the custom script section
    //   due to the infamous % bug. Then again, if you do that then you probably
    // deserve it....
    //
    private function DebugIdInteger2IdString takes integer value returns string
     local string charMap = ".................................!.#$%&'()*+,-./0123456789:;<=>.@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~................................................................................................................................."
     local string result = ""
     local integer remainingValue = value
     local integer charValue
     local integer byteno

     set byteno = 0
     loop
         set charValue = ModuloInteger(remainingValue, 256)
         set remainingValue = remainingValue / 256
         set result = SubString(charMap, charValue, charValue + 1) + result
 
         set byteno = byteno + 1
         exitwhen byteno == 4
     endloop
     return result
    endfunction

    //--------------------------------
    private function kill takes nothing returns nothing
        call RemoveUnit(dum)
        set dum=null
        static if (LIBRARY_TimerUtils ) then
            call ReleaseTimer( GetExpiredTimer() )
        else
            call DestroyTimer(GetExpiredTimer())
        endif
    endfunction

    private module PreloadInit
        private static method onInit takes nothing returns nothing
            local timer t
            set dum = CreateUnit( Player(15), XE_DUMMY_UNITIDQ, 0,0,0)
            if( dum == null) then
                debug call BJDebugMsg("xePreload : XE_DUMMY_UNITIDQ ("+DebugIdInteger2IdString.evaluate(XE_DUMMY_UNITIDQ)+") not added correctly to the map.")
            endif
            static if (LIBRARY_TimerUtils) then
                set t=NewTimer()
            else
                set t=CreateTimer()
            endif
            call TimerStart(t,0.0,false,function kill)
            set t=null
        endmethod
    endmodule

    private struct Init extends array
        implement PreloadInit
    endstruct
 
endlibrary

JASS:
library InventoryCore initializer Init requires UserInterface, InventoryItem, PlayerUtils, TimerUtils
 
    struct Inventory
        //
        // configuration
        //
     
        // location of inventory
        static real X = 0.61
        static real Y = -0.3
     
        // size of the background wwindow
        static real SCALE = 0.228
     
        // location of the tooltip information
        static real TOOLTIP_X = -0.7
        static real TOOLTIP_Y = 0.0
     
        // max # of pages the inventory can have
        static integer MAX_PAGES = 1
     
        // how many slots are on each row/column
        static constant integer BUTTON_ROWS = 6
        static constant integer BUTTON_COLS = 5
     
        // how fast the UI updates
        static constant real UI_REFRESH_RATE = 0.01
     
        // show errors
        static constant boolean SHOW_ERRORS = true

        // icon size
        static constant real SLOT_WIDTH  = 0.065
        static constant real SLOT_HEIGHT = 0.065 * SCREEN_ASPECT_RATIO
        static constant real SLOT_SPACING = 1.00 // 0%
     
        // icon offsets (from X,Y)
        static constant real SLOT_OFFSET_X  = -0.0485
        static constant real SLOT_OFFSET_Y  = 0.072
     
        // raw id's
        static constant integer ICON_EMPTY          = 'dbnk'
        static constant integer ICON_TRANSPARENT    = 'Noth'
        static constant integer ICON_PAGEUP         = 'BuUp'
        static constant integer ICON_PAGEDOWN       = 'Down'
        static constant integer ICON_SELL           = 'Sell'
        static constant integer ICON_DROP           = 'Drop'
     
        static constant integer RACE_BORDERS_START = 'D201'
     
        static constant integer BACKGROUND = 'bwn2'
     
        //
        // don't edit below here unless you know what you're doing
        //
     
        static constant integer MAX_SLOTS = BUTTON_ROWS * BUTTON_COLS
        static constant integer MAX_ITEMS = MAX_SLOTS * MAX_PAGES
     
        readonly static hashtable Hashtable = InitHashtable()
        readonly static trigger ExecL = CreateTrigger()
        readonly static trigger ExecR = CreateTrigger()
        readonly static integer DisplayCount = 0
        readonly static timer UpdateTimer = CreateTimer()
        readonly static thistype array PlayerCurrent
        readonly static UIButton array buttons
     
        readonly integer itemCount
        readonly thistype next
        readonly thistype prev
        readonly integer pid
        readonly User user
        readonly unit owner
     
        static UIPicture array background
        static UIText array pageNum
        static UIButton array btnPageUp 
        static UIButton array btnPageDown
        static UIButton array btnSell
        static UIButton array btnDrop
        static UIPicture array selector
     
        Camera camera
        Table countTable
        integer currentPage
     
        readonly boolean displayed
        readonly integer freeSlot
     
        public static trigger onSell
        public static trigger onDrop
     
        public static key KEY_ITEMS
        public static key KEY_ITEM_ID
        public static key KeyUnit
        public static constant integer KEY_UNIT = INVENTORY_KEY_START + KeyUnit
        public static key KeyItemCount
        public static constant integer KEY_ITEM_COUNT = INVENTORY_KEY_START + KeyItemCount
 
        static method localInt takes integer pid, integer value, integer other returns integer
            if (User.Local != User(pid).handle) then
                set value = other
            endif
            return value
        endmethod
     
        static method err takes player p, string s returns nothing
            static if (Inventory.SHOW_ERRORS) then
                static if (LIBRARY_SimError) then
                    call SimError(p, s)
                else
                    call DisplayTimedTextToPlayer(p, 0, 0, 15, "Error: " + s)
                endif
            endif
        endmethod
     
        static method operator [] takes unit u returns thistype
            return thistype(LoadInteger(.Hashtable, KEY_UNIT, GetHandleId(u)))
        endmethod
     
        static method operator []= takes unit u, thistype value returns nothing
            call SaveInteger(.Hashtable, KEY_UNIT, GetHandleId(u), value)
        endmethod
 
        method getButton takes integer index returns UIButton
            return this.buttons[(this.pid * MAX_SLOTS) + index]
        endmethod
     
        method setButton takes integer index, UIButton value returns nothing
            set this.buttons[(this.pid * MAX_SLOTS) + index] = value
        endmethod
     
        method getItem takes integer index returns InvItem
            return LoadInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEMS) + index)
        endmethod
     
        method itemsOfType takes integer id returns integer
            return this.countTable[id]
        endmethod
     
        static method addLeftClickHook takes code func returns nothing
            call TriggerAddAction(ExecL, func)
        endmethod
     
        static method addRightClickHook takes code func returns nothing
            call TriggerAddAction(ExecR, func)
        endmethod
     
        method getItemId takes integer index returns integer
            return LoadInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEM_ID) + index)
        endmethod
     
        method setItem takes integer index, InvItem itm returns nothing
            local UIButton btn
            local integer slot = index
            local integer id = itm.id
            local integer icon = GetItemIcon(id)
            local InvItem oldItem
         
            if (this.currentPage > 0) then
                set slot = index - (.MAX_SLOTS*this.currentPage)
            endif
         
            set btn = this.getButton(slot)

            set oldItem = this.getItem(index)
         
            call SaveInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEMS) + index, itm)
         
            if (itm.tempCustomId > 0) then
                call SaveInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEM_ID) + index, itm.tempCustomId)
            endif
         
            set itm.tempCustomId = 0
         
            if (id == 0 and HaveSavedInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEMS) + index)) then
                set id = oldItem.id
             
                set this.itemCount = this.itemCount - 1
                set this.countTable[id] = this.countTable[id] - 1
             
                call RemoveSavedInteger(.Hashtable, this, (MAX_ITEMS * .KEY_ITEMS) + index)
             
                // remove ability
                if (oldItem.ability > 0 and itemsOfType(id) <= 0) then
                    call UnitRemoveAbility(this.owner, oldItem.ability)
                endif
            elseif (id > 0) then
                set this.itemCount = this.itemCount + 1
                set this.countTable[id] = this.countTable[id] + 1
             
                // add ability
                if (itm.ability > 0 and itemsOfType(id) == 1) then
                    call UnitAddAbility(this.owner, itm.ability)
                endif
            endif
         
            call btn.setTexture(.localInt(this.pid, icon, ICON_TRANSPARENT))
            call btn.showPlayer(this.user.handle, this.displayed, this.camera)
        endmethod
     
        method findFreeSlot takes integer page returns integer //O(n)
            local integer i = 0
            loop
                exitwhen i == MAX_SLOTS
                if (this.getItem((MAX_SLOTS*page)+i) == 0) then
                    return i
                endif
                set i = i + 1
            endloop
            return -1
        endmethod

        method createButtons takes nothing returns nothing
            local integer row = 0
            local integer col = 0
            local UIButton btn = 0
            local integer i = 0

            if (btnPageUp[this.pid] == 0) then
                set btnPageUp[this.pid] = UIButton.create(X - 0.15, (Y + SLOT_OFFSET_Y) - (col * (SLOT_HEIGHT*SLOT_SPACING)), .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, thistype.localInt(this.pid, ICON_PAGEUP, ICON_TRANSPARENT))
                set btnPageDown[this.pid] = UIButton.create(X - 0.15, (Y + SLOT_OFFSET_Y) - ((.BUTTON_COLS-1) * (SLOT_HEIGHT*SLOT_SPACING)), .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, thistype.localInt(this.pid, ICON_PAGEDOWN, ICON_TRANSPARENT))
                set btnSell[this.pid] = UIButton.create(X - 0.15, (Y + SLOT_OFFSET_Y) - .300, .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, thistype.localInt(this.pid, ICON_SELL, ICON_TRANSPARENT))
                set btnDrop[this.pid] = UIButton.create(X - 0.15, (Y + SLOT_OFFSET_Y) - .150, .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, thistype.localInt(this.pid, ICON_DROP, ICON_TRANSPARENT))
             
                set btnPageUp[this.pid].onLeftClick = InvFuncLClickSlot
                set btnPageDown[this.pid].onLeftClick = InvFuncLClickSlot
                set btnSell[this.pid].onLeftClick = InvFuncLClickSlot
                set btnDrop[this.pid].onLeftClick = InvFuncLClickSlot
             
                set btnPageUp[this.pid].selectUnit = this.owner
                set btnPageDown[this.pid].selectUnit = this.owner
                set btnSell[this.pid].selectUnit = this.owner
                set btnDrop[this.pid].selectUnit = this.owner
             
                set selector[this.pid] = UIPicture.createEx(X - 0.15, (Y + SLOT_OFFSET_Y) - .250, 0, .55, 'e000', 1, 1, 0)
                set selector[this.pid].animIndex = 56
                call selector[this.user.id].show(false, this.camera)
             
                call AddSpecialEffectTarget("UI\\TRSHerolevel.mdx", selector[this.pid].picture, "origin")
            endif
         
            loop
                exitwhen col == BUTTON_COLS
             
                set row = 0
             
                loop
                    exitwhen row == BUTTON_ROWS
                 
                    if (this.getButton(i) == 0) then
                 
                        set btn = UIButton.create( (X + SLOT_OFFSET_X) + (row * (SLOT_WIDTH*SLOT_SPACING)), (Y + SLOT_OFFSET_Y) - (col * (SLOT_HEIGHT*SLOT_SPACING)), .SLOT_WIDTH, .SLOT_HEIGHT, 0.5, GetItemIcon(this.getItem(i).id))
                     
                        set btn.customValue = i
                     
                        set btn.selectUnit = this.owner
                     
                        set btn.onLeftClick = InvFuncLClickSlot
                        set btn.onRightClick = InvFuncRClickSlot
                     
                        call this.setButton(i, btn)

                        //call SetUnitVertexColor(.background[this.pid].picture, 255, 255, 255, thistype.localInt(this.pid, 255, 0))
                    endif
                    set i = i + 1
                 
                    set row = row + 1
                endloop
             
                set col = col + 1
            endloop
        endmethod
     
        implement InvPlugins
     
        static method create takes unit owner returns thistype
            local thistype this = thistype.allocate()
            local boolean showLocally
            local integer raceId
            local integer i = 0
         
            set this.itemCount = 0
            set this.owner = owner
            set this.freeSlot = 0
            set this.pid = GetPlayerId(GetOwningPlayer(this.owner))
            set this.user = User(this.pid)
            set this.countTable=Table.create()
         
            set raceId = GetHandleId(GetPlayerRace(User(this.pid).handle))-1
         
            set showLocally = (User.Local == User(this.pid).handle)
         
            call this.createButtons()
         
            static if (thistype.createTooltip.exists) then
                call thistype.createTooltip(this, raceId)
            endif
         
            if (this.pageNum[this.pid] == 0) then
                set this.pageNum[this.pid] = UIText.createEx(this.user.toPlayer(), X + 0.300, Y + .10, 1)
            endif
         
            set Inventory[this.owner] = this
         
            if (.background[this.pid] == 0) then
                set .background[this.pid] = UIPicture.createEx(X, Y, 2, SCALE, BACKGROUND, 70., 60, .localInt(this.pid, RACE_BORDERS_START + raceId, ICON_TRANSPARENT))
            endif

            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this

            set this.prev = 0

            return this
        endmethod
     
        method addItem takes InvItem itm returns boolean
            local integer slot
         
            if (itm <= 0 or itm.id <= 0) then
                return false
            endif
         
            set slot = findFreeSlot(this.currentPage)
         
            if (slot == -1) then
                call err(User(this.pid).handle, "There are no free slots on this inventory page.")
                return false
            endif
         
            if (itm.icon == ICON_EMPTY) then
                return false
            endif
         
            call this.setItem((.MAX_SLOTS * this.currentPage) + slot, itm)
         
            return true
        endmethod
     
        method destroy takes nothing returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next

            call this.deallocate()
        endmethod
     
        private static method onDisplay takes nothing returns nothing
            local thistype inv
            local real x
            local real y
            local real z
         
            local User user = User(User.LocalId)
         
            set inv = Inventory.PlayerCurrent[user.id]
         
            if (inv == 0 or not inv.displayed) then
                return
            endif
         
            set x = GetUnitX(inv.owner)
            set y = GetUnitY(inv.owner)
            set z = GetTerrainZ(x, y) + GetUnitDefaultFlyHeight(inv.owner)
         
            call inv.camera.setPosition(x, y, z)
         
            if inv.camera.applyCameraForPlayer(user.handle, false) then
                call Interface.updateAll(true, true, true)
            endif

        endmethod
     
        method show takes boolean flag, Camera cam returns nothing
            local integer i
            local thistype equip
            local real timeout
         
            if (this.currentPage >= .MAX_PAGES) then
                set this.currentPage = 0
            elseif (this.currentPage < 0) then
                set this.currentPage = .MAX_PAGES - 1
            endif
         
            if (flag) then
                if (not this.displayed) then
                    set .DisplayCount = .DisplayCount + 1
                endif
             
                if (PlayerCurrent[this.pid] > 0 and PlayerCurrent[this.pid] != this) then
                    call PlayerCurrent[this.pid].show(false, cam)
                endif
             
                set PlayerCurrent[this.pid] = this

                call PauseTimer(.UpdateTimer)
                call TimerStart(.UpdateTimer, UI_REFRESH_RATE, true, function thistype.onDisplay)
            else
                set .DisplayCount = .DisplayCount - 1
             
                if (DisplayCount == 0) then
                    call PauseTimer(.UpdateTimer)
                else
                    call TimerStart(.UpdateTimer, UI_REFRESH_RATE, true, function thistype.onDisplay)
                endif
             
                if (User.LocalId == this.pid) then
                    call ResetToGameCamera(0)
            set udg_CCSS_CamUpdateInterval[this.pid] = 0.1
                endif
             
                set PlayerCurrent[this.pid] = 0
            endif
         
            set this.displayed = flag
            set this.camera = cam
         
            call this.btnPageUp[this.pid].show(flag, cam)
            call this.btnSell[this.pid].show(flag, cam)
            call this.btnDrop[this.pid].show(flag, cam)
            if (not flag) then
                call this.selector[this.pid].show(flag, cam)
            endif
            call this.btnPageDown[this.pid].show(flag, cam)
            call this.background[this.pid].show(flag, cam)

            static if thistype.showTooltip.exists then
                if (not flag) then
                    call thistype.showTooltip(this, flag)
                endif
            endif
         
            call SetTextTagText(this.pageNum[this.pid].text, I2S(this.currentPage + 1) + "/" + I2S(.MAX_PAGES), 10 * 0.0023)
         
            call this.pageNum[this.pid].show(flag, cam)
         
            set i = 0
         
            loop
                exitwhen i == .MAX_SLOTS
             
                call this.getButton(i).setTexture(thistype.localInt(this.pid, GetItemIcon(this.getItem((.MAX_SLOTS*this.currentPage) + i).id), ICON_TRANSPARENT))

                call this.getButton(i).showPlayer(this.user.handle, flag, this.camera)
             
                call this.getButton(i).showPlayer(this.user.handle, flag, this.camera)
             
             
                set i = i + 1
            endloop

        endmethod
     
        /*private static method onInit takes nothing returns nothing
        endmethod*/
    endstruct
 
    public function GracePeriod takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local UIButton but = UIButton(GetTimerData(t))
     
        set InvButtonDisabled[but] = false
     
        call ReleaseTimer(t)
    endfunction
 
    function DisableButtonIfUsingTimer takes UIButton but, real time returns nothing
        static if (Inventory.USE_LEFT_CLICK_TIMER) then
            call TimerStart(NewTimerEx(but), time, false, function GracePeriod)
            set InvButtonDisabled[but] = true
        endif
    endfunction
 
    private function LClickItemSlot takes nothing returns boolean
        local UIButton but = GetTriggerButton()
        local player p = GetClickingPlayer()
        local integer pid = GetPlayerId(p)
        local Inventory inv =  Inventory.PlayerCurrent[pid]
        local integer slot =  but.customValue
        local integer itemId = 0
        local integer icon = 0
        local integer last = InvPlayerLastSlot[inv.pid] - 1
        local integer lastItemId
        local InvItem lastItem
        local UIButton lastButton = InvPlayerLastButton[inv.pid]

        if (User.Local == p) then
            call SelectUnit(but.picture, false)
            call SelectUnit(inv.owner, true)
        endif
     
        if (InvButtonDisabled[but]) then
            return false
        endif
     
        // sell item
        if (but == Inventory.btnSell[inv.pid]) then
            set last = InvPlayerLastSlot[inv.pid] - 1
         
            if (last < 0) then
                return false
            endif
         
           if (p != User(inv.pid).handle) then
                return false
            endif
         
            // run onSell event
            set InvEventPlayer = User(inv.pid).handle
            set InvEventItem = inv.getItem((Inventory.MAX_SLOTS*inv.currentPage)+last)
            set InvEventSlot = (Inventory.MAX_SLOTS*inv.currentPage)+last
         
            if (Inventory.onSell != null and TriggerEvaluate(Inventory.onSell)) then
                call TriggerExecute(Inventory.onSell)
            endif
         
            call DisableButtonIfUsingTimer(but, 2)
         
            return false
        elseif (but == Inventory.btnDrop[inv.pid]) then
            set last = InvPlayerLastSlot[inv.pid] - 1
         
            if (last < 0) then
                return false
            endif
         
           if (p != User(inv.pid).handle) then
                return false
            endif
         
            // run onSell event
            set InvEventPlayer = User(inv.pid).handle
            set InvEventItem = inv.getItem((Inventory.MAX_SLOTS*inv.currentPage)+last)
            set InvEventSlot = (Inventory.MAX_SLOTS*inv.currentPage)+last
         
            if (Inventory.onDrop != null and TriggerEvaluate(Inventory.onDrop)) then
                call TriggerExecute(Inventory.onDrop)
                return false
            endif
         
            call DisableButtonIfUsingTimer(but, 2)
        elseif (but == Inventory.btnPageDown[inv.pid]) then
           if (p != User(inv.pid).handle) then
                return false
            endif
     
            set inv.currentPage = inv.currentPage + 1
            call inv.show(true, inv.camera)
            call DisableButtonIfUsingTimer(but, .6)
            return false
        elseif (but == Inventory.btnPageUp[inv.pid]) then
           if (p != User(inv.pid).handle) then
                return false
            endif
         
            set inv.currentPage = inv.currentPage - 1
            call inv.show(true, inv.camera)
            call DisableButtonIfUsingTimer(but, .6)
            return false
        else
            call Inventory.selector[inv.pid].show(true, inv.camera)
            call Inventory.selector[pid].setPosition(but.centerx - 0.004, but.centery + 0.0262)
            call Inventory.selector[pid].showPlayer(Player(pid), true, inv.camera)
        endif

        if (p != User(inv.pid).handle) then
            return false
        endif
     
        if (last + 1 != slot) then
            call inv.showLines(inv.pid, false, inv.camera)
        endif
     
        call TriggerExecute(Inventory.ExecL)
     
        // get item in slot
        set itemId = inv.getItem((Inventory.MAX_SLOTS*inv.currentPage)+slot).id
     
         // check for switch
        if (last+1 != 0) then

            call SetUnitVertexColor(lastButton.picture, 255, 255, 255, 255)
            call SetUnitVertexColor(but.picture, 255, 255, 255, 255)
         
            set InvPlayerLastSlot[inv.pid] = 0
            set InvPlayerLastButton[inv.pid] = but
             
            if (itemId == 0) then
                set last = (Inventory.MAX_SLOTS * inv.currentPage) + last
             
                set lastItem = inv.getItem(last)
         
                set lastItemId = lastItem.id
             
                set lastItem.tempCustomId = inv.getItemId(last)
         
                call inv.setItem((Inventory.MAX_SLOTS*inv.currentPage)+slot, lastItem)
                call inv.setItem(last, 0)
             
                call DisableButtonIfUsingTimer(but, 2)
            elseif (inv.getItem((Inventory.MAX_SLOTS * inv.currentPage) + last).isSocket) then
             
                set last = (Inventory.MAX_SLOTS * inv.currentPage) + last
             
                set lastItem = inv.getItem(last)
                set lastItem.tempCustomId = inv.getItemId(last)
                set slot = (Inventory.MAX_SLOTS*inv.currentPage)+slot
             
                if (Equipment.onSocket != null) then
                    if (lastItem.sockets <= lastItem.maxSockets) then
                        set SocketInv = inv
                        set SocketLast = last
                        set SocketSlot = slot
                     
                        if (not inv.getItem(slot).isSocket and Equipment.onSocket != null and TriggerEvaluate(Equipment.onSocket)) then
                            call TriggerExecute(Equipment.onSocket)
                            return false
                        endif
                    endif
                else
                    set inv.getItem(slot).tempCustomId = inv.getItemId(slot)
                 
                    if (inv.getItem(slot).addSocket(lastItem)) then
                        call inv.setItem(last, 0)
                    endif
                endif
 
            endif
        else//if (itemId != 0) then
            set InvPlayerLastSlot[inv.pid] = 0
            set InvPlayerLastButton[inv.pid] = but
        endif
     
        if (itemId == 0 or InvPlayerLastSlot[inv.pid] != 0) then
        else
            set InvPlayerLastSlot[inv.pid] = slot + 1
        endif
     
        if (but != lastButton) then
            call SetUnitVertexColor(lastButton.picture, 255, 255, 255, 255)
            call SetUnitVertexColor(but.picture, 175, 175, 175, 255)
        endif
     
        if (itemId > 0) then
         
            call inv.setTooltipTitle(GetObjectName(itemId))
         
            static if (InvItem.buildDescription.exists) then
                set GetInvItem(itemId).tempCustomId = inv.getItemId((Inventory.MAX_SLOTS*inv.currentPage)+slot)

                call GetInvItem(itemId).buildDescription(inv.owner)
            else
                call inv.setTooltipInfo(GetItemDescription(itemId))
            endif
         
            call inv.setTooltipCost("|cffffcc00" + I2S(GetInvItem(itemId).cost) + "|r")
         
            call inv.setTooltipIcon(inv.localInt(inv.pid, GetItemIcon(itemId), Inventory.ICON_TRANSPARENT))
        endif
     
        set Inventory.TOOLTIP_X = but.minx - 0.4
        set Inventory.TOOLTIP_Y = but.maxy + 0.4
     
        call inv.showTooltip(inv, itemId > 0)
     
        return false
    endfunction

    private function RClickItemSlot takes nothing returns boolean
        local UIButton but = GetTriggerButton()
        local player p = GetClickingPlayer()
        local integer pid = GetPlayerId(p)
        local Inventory inv =  Inventory.PlayerCurrent[pid]
     
        local integer slot =  but.customValue
        local integer itemId
 
        if (p != User(inv.pid).handle) then
            return false
        endif
     
        call TriggerExecute(Inventory.ExecR)
     
        set slot = (Inventory.MAX_SLOTS * inv.currentPage) + slot

        set itemId = inv.getItem(slot).id

        if (itemId == 0) then
            return false
        endif
     
        call inv.showTooltip(inv, false)
     
        /*call SetItemUserData(CreateItem(itemId, GetUnitX(inv.owner), GetUnitY(inv.owner)), inv.getItemId(slot))
     
        call inv.setItem(slot, 0)*/
     
        return false
    endfunction
 
    private function Init takes nothing returns nothing
        set InvFuncLClickSlot = Filter(function LClickItemSlot)
        set InvFuncRClickSlot = Filter(function RClickItemSlot)
    endfunction
 
endlibrary

JASS:
library InventoryItem uses Table, optional InventoryBonus
 
    globals
        public constant integer SEED_START = 0
        public constant integer SEED_END   = 999
    endglobals
 
    struct InvItem
        readonly static Table Table
     
        integer id
        integer icon
        integer cost
        integer ability
        string info
        integer tempCustomId
    //item ItemTypeId
     
        implement InvItemPlugins
     
        static if not thistype.buildDescriptionLines.exists then
            method buildDescriptionLines takes integer pid, integer startLine returns integer
                return buildDescriptionLines(GetPlayerId(GetOwningPlayer(forUnit)), 0)
            endmethod
        endif
     
        static if not thistype.buildDescription.exists then
            method buildDescription takes unit forUnit returns string
                return buildDescriptionLines(GetPlayerId(GetOwningPlayer(forUnit)), 0)
            endmethod
        endif
     
        private static method onInit takes nothing returns nothing
            set InvItem.Table = Table.create()
        endmethod
    endstruct
 
    function GetInvItem takes integer id returns InvItem
        return InvItem(InvItem.Table[id])
    endfunction
 
    function GetItemInfo takes integer id returns string
        return InvItem(InvItem.Table[id]).info
    endfunction
 
    function GetItemDescription takes integer id returns string
        return InvItem(InvItem.Table[id]).info
    endfunction
 
    function GetItemIcon takes integer id returns integer
        local integer icon = InvItem(InvItem.Table[id]).icon
        if (icon == 0) then
            return Inventory.ICON_EMPTY
        endif
        return icon
    endfunction
 
    function CreateInvItem takes integer id, integer icon, integer cost, integer abil, string info returns InvItem
        local InvItem itm = InvItem.create()
        set itm.id   = id
        set itm.icon = icon
        set itm.cost = cost
        set itm.info = info
        set itm.ability = abil
        set InvItem.Table[itm.id] = itm
        return itm
    endfunction
 
    struct InvCustomItem extends array
        static integer Counter = 1
        static Table SeedTable
     
        static method create takes nothing returns thistype
            local thistype this = Counter
            set SeedTable[this] = GetRandomInt(SEED_START, SEED_END)
            set Counter = Counter + 1
            return this
        endmethod
     
        method seed takes nothing returns integer
            return SeedTable[this]
        endmethod
     
        static method onInit takes nothing returns nothing
            set SeedTable = SeedTable.create()
        endmethod
    endstruct

endlibrary

JASS:
library InventoryPlugins

    // add plugins here
    module InvPlugins
        implement InvTooltip
    endmodule
 
    module InvItemPlugins
        implement InvItemTooltip
        implement InvItemEquipment
        implement InvItemSocket
    endmodule

endlibrary

JASS:
library EquipmentCore initializer Init requires InventoryCore, EquipmentItem
 
    globals
        public filterfunc FuncLClickSlot = null
        public filterfunc FuncRClickSlot = null
        public integer array PlayerLastSlot
    endglobals
 
    public /*constant*/ function HERO_WINDOW_NAME takes unit u returns string
        return User[GetOwningPlayer(u)].nameColored
        // return GetHeroProperName(u)
    endfunction
 
    struct Equipment
        //
        // configuration
        //
        static constant real X = 0.425
        static constant real Y = .93
     
        static constant real WINDOW_SIZE = 0.28
     
        static constant real SLOT_OFFSET_Y          = 0.065
        static constant real SLOT_OFFSET_ROWRIGHT_X = 0.460
        static constant real SLOT_OFFSET_ROWLEFT_X  = 0.105
     
        static constant real HERO_NAME_Y = 0.9
     
        static constant real CHARMODEL_OFFSET_X  = X + 0.32
        static constant real CHARMODEL_OFFSET_Y  = 0.200
     
        static constant integer MAX_SLOTS = 14
        static constant real SLOT_WIDTH   = 0.090
        static constant real SLOT_HEIGHT  = 0.090 * SCREEN_ASPECT_RATIO
     
        static constant integer MODEL_DUMMY  = 'e00E' // for character model
        static constant integer WINDOW_DUMMY = 'ewin'
        //
        // end config
        //
     
        readonly static boolean Initialized = false
        readonly static integer DisplayCount = 0
        readonly static hashtable Hashtable
        readonly static timer UpdateTimer
        readonly static unit array PlayerCurrentUnit
        readonly static thistype array UnitsIndex
     
        static UIButton array buttons[.MAX_SLOTS]
        static UIPicture array slotButton[.MAX_SLOTS]
        InvItem array item[.MAX_SLOTS]
        integer array itemId[.MAX_SLOTS]
        static UIPicture array pictures[.MAX_SLOTS]
        static UIPicture array selector
        static UIText array title
        static trigger onSocket
     
        UIPicture charModel
        Camera camera
 
        unit unit
        player player
        User user
     
        readonly boolean displayed
        readonly thistype next
        readonly thistype prev
     
        static method operator [] takes unit u returns thistype
            return .UnitsIndex[GetUnitUserData(u)]
        endmethod
 
        method getButton takes integer index returns UIButton
            return this.buttons[(this.user.id * .MAX_SLOTS) + index]
        endmethod
     
        method setButton takes integer index, UIButton value returns nothing
            set this.buttons[(this.user.id * .MAX_SLOTS) + index] = value
        endmethod
     
        static method create takes unit u returns thistype
            local thistype this = thistype.allocate()
            local real x1 = X + SLOT_OFFSET_ROWLEFT_X
            local real x2 = X + SLOT_OFFSET_ROWRIGHT_X
            local real y1 = Y - SLOT_OFFSET_Y
            local integer i = 0

            set this.unit = u
            set this.player = GetOwningPlayer(u)
            set this.user = User[this.player]
         
            set .UnitsIndex[GetUnitUserData(u)] = this

            //
            // GEAR SLOTS
            //
         
            if (this.getButton(0) == 0) then
         
                set selector[this.user.id] = UIPicture.createEx(X - 0.15, (Y + SLOT_OFFSET_Y) - .250, 0, .70, MODEL_DUMMY, 1, 1, 0)
                set selector[this.user.id].animIndex = 56
                call selector[this.user.id].show(false, this.camera)

                call AddSpecialEffectTarget("UI\\TRSHerolevel.mdx", selector[this.user.id].picture, "origin")
             
                // middle
                call this.setButton(12, UIButton.create(x1 + (.SLOT_WIDTH*1.5), y1 - (.SLOT_HEIGHT*5), .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'Weap'))
                call this.setButton(13, UIButton.create(x2 - (.SLOT_WIDTH*1.5), y1 - (.SLOT_HEIGHT*5), .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'Shie'))
             
                // left side
                call this.setButton(0, UIButton.create(x1, y1, .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'Head'))
                call this.setButton(1, UIButton.create(x1, y1 - (.SLOT_HEIGHT*1), .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'Sho1'))
                call this.setButton(2, UIButton.create(x1, y1 - (.SLOT_HEIGHT*2), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Ches'))
                call this.setButton(3, UIButton.create(x1, y1 - (.SLOT_HEIGHT*3), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Belt'))
                call this.setButton(4, UIButton.create(x1, y1 - (.SLOT_HEIGHT*4), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Pant'))
                call this.setButton(5, UIButton.create(x1, y1 - (.SLOT_HEIGHT*5), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Rin1'))
             
                // right side
                call this.setButton(6, UIButton.create(x2, y1, .SLOT_WIDTH, .SLOT_HEIGHT, 10, 'Neck'))
                call this.setButton(7, UIButton.create(x2, y1 - (.SLOT_HEIGHT*1), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Sho2'))
         
                call this.setButton(8, UIButton.create(x2, y1 - (.SLOT_HEIGHT*2), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Glov'))
                call this.setButton(9, UIButton.create(x2, y1 - (.SLOT_HEIGHT*3), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Brac'))
                call this.setButton(10, UIButton.create(x2, y1 - (.SLOT_HEIGHT*4), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Boot'))
                call this.setButton(11, UIButton.create(x2, y1 - (.SLOT_HEIGHT*5), .SLOT_WIDTH, .SLOT_HEIGHT, 10,'Rin2'))
                         
                loop
                    exitwhen i == 14
                    set this.getButton(i).customValue = i
                 
                     set this.getButton(i).selectUnit = this.unit
                  
                    set this.getButton(i).onLeftClick = FuncLClickSlot
                    set this.getButton(i).onRightClick = FuncRClickSlot
                 
                    set this.slotButton[(this.user.id * MAX_SLOTS) + i] = UIPicture.create(this.getButton(i).minx + (.SLOT_WIDTH/6.3), this.getButton(i).maxy - 0.022, .SLOT_WIDTH * .7, .SLOT_HEIGHT * .7, 9, Inventory.ICON_EMPTY)
                    set this.slotButton[(this.user.id * MAX_SLOTS) + i].customValue = i

                    set i = i + 1
                endloop
             
                set .pictures[(this.user.id * MAX_SLOTS) + 0] = UIPicture.createEx(X, Y, 11, WINDOW_SIZE, .WINDOW_DUMMY, 140., 130., Inventory.localInt(this.user.id, Inventory.RACE_BORDERS_START+(GetHandleId(GetPlayerRace(this.player))-1), Inventory.ICON_TRANSPARENT))
             
            endif
         
            // unit model
            set i = EquipGetHeroModel(GetUnitTypeId(this.unit))
         
            set .charModel = UIPicture.createEx(CHARMODEL_OFFSET_X, .CHARMODEL_OFFSET_Y, 5, HeroModelData(i).scale, MODEL_DUMMY, 1, 1, 0)
            set .charModel.animIndex = 140
            call SetUnitColor(.charModel.picture, this.user.color)

            call AddSpecialEffectTarget(HeroModelData(i).path, .charModel.picture, "origin")

            set .title[this.user.id] = UIText.createEx(this.user.toPlayer(), X/1.4, 0.1, 1)

            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this

            set this.prev = 0

            return this
        endmethod

        method equip takes InvItem itm, integer equipSlot returns boolean
            local integer slot = itm.slot - 1
         
            if (slot < 0) then
                return false
            endif
         
            if (this.item[equipSlot] != 0) then
                return false
            endif
         
            if (itm.slotAlt - 1 > 0) then
                if (equipSlot != slot and equipSlot != itm.slotAlt -1) then
                    call Inventory.err(this.player, "The item doesn't belong in that slot.")
                    return false
                endif
            else
                if (equipSlot != slot) then
                    call Inventory.err(this.player, "The item doesn't belong in that slot.")
                    return false
                endif
            endif
         
            if (itm.reqUnitType > 0 and GetUnitTypeId(this.unit) != itm.reqUnitType) then
                call Inventory.err(this.player, "Your unit type cannot equip this item.")
                return false
            endif
         
            if (itm.reqAbility > 0 and GetUnitAbilityLevel(this.unit, itm.reqAbility) < 0) then
                call Inventory.err(this.player, "Failed requirements.")
                return false
            endif
         
            if (GetHeroLevel(this.unit) < itm.reqLevel and GetUnitLevel(this.unit) < itm.reqLevel) then
                call Inventory.err(this.player, "Your level is too low to equip that item.")
                return false
            endif
         
            if (itm.equipCondition != null) then
                call TriggerClearConditions(InvItem.equipEval)
                call TriggerAddCondition(InvItem.equipEval, itm.equipCondition)
                set InvItem.eventUnit = this.unit
                if (not TriggerEvaluate(InvItem.equipEval)) then
                    return false
                endif
            endif

            set slot = equipSlot
         
            if (this.item[slot] != 0) then
                return false
            endif
         
            if (itm.equipAbility > 0) then
                call UnitAddAbility(this.unit, itm.equipAbility)
            endif
         
            set this.item[slot] = itm
            set this.itemId[slot] = itm.tempCustomId
         
            call this.slotButton[(this.user.id * .MAX_SLOTS) + slot].setTexture(Inventory.localInt(this.user.id, itm.icon, Inventory.ICON_TRANSPARENT))
            call this.slotButton[(this.user.id * .MAX_SLOTS) + slot].show(true, this.camera)

            set InvEventPlayer = this.player
            set InvEventItem = itm
            set InvEventSlot = equipSlot
         
            if (InvItem.onEquip != null and TriggerEvaluate(InvItem.onEquip)) then
                call TriggerExecute(InvItem.onEquip)
            endif
                 
            // add bonuses
            call itm.applyBonuses(this.unit)
         
            set itm.tempCustomId = 0
         
            return true
        endmethod
     
        method unequip takes InvItem itm, integer slot returns boolean
            local InvItem i2
            local integer i = 0
            local integer cid = itm.tempCustomId
         
            if (slot != itm.slot -1 and slot != itm.slotAlt - 1) then
                return false
            endif
         
            if (itm.equipAbility > 0) then
                call UnitRemoveAbility(this.unit, itm.equipAbility)
            endif
         
            // run unequip event
            set InvEventPlayer = this.player
            set InvEventItem = this.item[slot]
            set InvEventSlot = slot
             
            if (InvItem.onUnequip != null and TriggerEvaluate(InvItem.onUnequip)) then
                call TriggerExecute(InvItem.onUnequip)
            endif
         
            set this.item[slot] = 0

            call this.slotButton[(this.user.id * .MAX_SLOTS) + slot].setTexture(Inventory.ICON_EMPTY)
            call this.slotButton[(this.user.id * .MAX_SLOTS) + slot].show(false, this.camera)
         
            // remove bonuses
            call itm.removeBonuses(this.unit)

            return true
        endmethod
     
        method destroy takes nothing returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next
         
            call this.charModel.destroy()
         
            call this.deallocate()
        endmethod

        private static method onDisplay takes nothing returns nothing
            local User user = User(User.LocalId)
            local thistype equipment = Equipment[Equipment.PlayerCurrentUnit[user.id]]
            local real x
            local real y
            local real z
         
            if (equipment == 0 or not equipment.displayed or Inventory.PlayerCurrent[equipment.user.id] > 0 or User.Local != user.handle) then
                return
            endif
         
            set x = GetUnitX(equipment.unit)
            set y = GetUnitY(equipment.unit)
            set z = GetTerrainZ(x, y) + GetUnitDefaultFlyHeight(equipment.unit)
         
            call equipment.camera.setPosition(x, y, z)
         
            if equipment.camera.applyCameraForPlayer(user.handle, false) then
                call Interface.updateAll(true, true, true)
            endif
        endmethod
     
        method show takes boolean flag, Camera cam returns nothing
            local integer i = 0
            local thistype equip = 0
            local integer len
            local real timeout
         
            set this.displayed = flag
            set this.camera = cam
         
            if (flag) then
                set .DisplayCount = .DisplayCount + 1

                if (DisplayCount >= 1) then
                    call PauseTimer(.UpdateTimer)
                 
                    call TimerStart(.UpdateTimer, Inventory.UI_REFRESH_RATE, true, function thistype.onDisplay)
                endif
             
                if (.PlayerCurrentUnit[this.user.id] != null) then
                    set equip = Equipment[.PlayerCurrentUnit[this.user.id]]
                endif
             
                if (.PlayerCurrentUnit[this.user.id] != null and .PlayerCurrentUnit[this.user.id] != this.unit and equip != this) then
                    call equip.show(false, this.camera)
                endif
             
                set .PlayerCurrentUnit[this.user.id] = this.unit
             
                if (Inventory.PlayerCurrent[this.user.id] > 0 and Inventory.PlayerCurrent[this.user.id].owner != this.unit) then
                    call Inventory.PlayerCurrent[this.user.id].show(false, this.camera)
                endif

            else
                set .DisplayCount = .DisplayCount - 1
                set .PlayerCurrentUnit[this.user.id] = null
             
                if (DisplayCount == 0) then
                    call PauseTimer(.UpdateTimer)
                else
                    call PauseTimer(.UpdateTimer)
                    call TimerStart(.UpdateTimer, Inventory.UI_REFRESH_RATE, true, function thistype.onDisplay)
                endif
                if (User.Local == this.player) then
                    call ResetToGameCamera(0)
            set udg_CCSS_CamUpdateInterval[GetPlayerId(this.player)] = 0.1
                endif
            endif
         
            call this.charModel.showPlayer(this.user.toPlayer(), flag, this.camera)
            if (not flag) then
                call this.selector[this.user.id].show(flag, cam)
            endif
         
            call SetUnitColor(.charModel.picture, this.user.color)

            // todo: make one line / recode
            set len = StringLength(this.user.name)
         
            call .title[this.user.id].setPosition(X + 0.10, HERO_NAME_Y)

            call SetTextTagText(.title[this.user.id].text, HERO_WINDOW_NAME(this.unit), 8 * 0.0023)
         
            call this.title[this.user.id].show(flag, this.camera)
         
            set i = 0
         
            loop
                exitwhen i == thistype.MAX_SLOTS
             
                if (.getButton(i) != 0) then
                    call .getButton(i).showPlayer(this.user.handle, flag, this.camera)
                endif
             
                if (this.pictures[(this.user.id * .MAX_SLOTS) + i] != 0) then
                    call .pictures[(this.user.id * .MAX_SLOTS) + i].showPlayer(this.user.handle, flag, this.camera)
                endif
             
                if (this.item[i] > 0) then
                    call slotButton[(this.user.id * .MAX_SLOTS) + i].showPlayer(this.user.handle, flag, this.camera)
                endif
             
                set i = i + 1
            endloop
        endmethod
     
        private static method onInit takes nothing returns nothing
            set thistype.Hashtable = InitHashtable()
            set thistype.UpdateTimer = CreateTimer()
            set thistype.Initialized = true
        endmethod
     
    endstruct
 
    private function OnInventoryItemClick takes nothing returns nothing
        local UIButton but = GetTriggerButton()
        local player p = GetClickingPlayer()
        local integer pid = GetPlayerId(p)
        local Inventory inv =  Inventory.PlayerCurrent[pid]
        local integer slot =  but.customValue
        local integer itemId = 0
        local integer last = InvPlayerLastSlot[inv.pid] - 1
        local integer lastItemId
        local UIButton lastButton = InvPlayerLastButton[inv.pid]
        local Equipment gear = Equipment[Equipment.PlayerCurrentUnit[inv.pid]]
        local integer gearSlot
        local InvItem itm
     
        call Equipment.selector[inv.pid].show(false, inv.camera)
    
        if (gear <= 0) then
            return
        endif
     
        if (PlayerLastSlot[inv.pid] > 0) then
         
            set gearSlot = PlayerLastSlot[inv.pid] - 1

            if (inv.getItem(slot) == 0) then
                set gear.item[gearSlot].tempCustomId = gear.itemId[gearSlot]
                call inv.setItem(slot, gear.item[gearSlot])
             
                set gear.item[gearSlot].tempCustomId = gear.itemId[gearSlot]
                call gear.unequip(gear.item[gearSlot], gearSlot)
            endif
         
            set PlayerLastSlot[inv.pid] = 0
        else
            set itm = inv.getItem(slot)
         
            if (itm > 0 and not itm.isSocket and itm.slot > 0) then
                set but = gear.getButton(itm.slot - 1)
             
                call Equipment.selector[inv.pid].show(true, inv.camera)
                call Equipment.selector[inv.pid].setPosition(but.centerx - 0.007, but.centery + 0.0272)
                call Equipment.selector[inv.pid].showPlayer(Player(inv.pid), true, inv.camera)
            endif
        endif
    endfunction
 
    private function OnInventoryItemRightClick takes nothing returns nothing
        local UIButton but = GetTriggerButton()
        local player p = GetClickingPlayer()
        local integer pid = GetPlayerId(p)
        local Inventory inv =  Inventory.PlayerCurrent[pid]
        local integer slot =  but.customValue
        local Equipment gear = Equipment[Equipment.PlayerCurrentUnit[inv.pid]]
        local InvItem itm = inv.getItem(slot)
        local integer gearSlot = itm.slot - 1
        local integer unequippedSlot = 0
        local InvItem unequipItem = 0
        local integer unequipId = 0
     
        if (gear <= 0 or itm <= 0) then
            return
        endif
     
        set itm.tempCustomId = inv.getItemId(slot)
     
        if (gear.item[gearSlot] > 0) then
            set unequipId = gear.itemId[gearSlot]
            set gear.item[gearSlot].tempCustomId = unequipId
            call gear.unequip(gear.item[gearSlot], gearSlot)
            set unequippedSlot = InvEventSlot
            set unequipItem = InvEventItem
        endif
     
        set itm.tempCustomId = inv.getItemId(slot)
     
        if (gear.equip(itm, itm.slot - 1)) then
            call inv.setItem(slot, 0)
            set unequipItem.tempCustomId = unequipId
            call inv.addItem(unequipItem)
        elseif (unequipItem > 0) then
            call gear.equip(unequipItem, unequippedSlot)
        endif
    endfunction

    private function RClickItemSlot takes nothing returns boolean
        local UIButton but   = GetTriggerButton()
        local unit u         = Equipment.PlayerCurrentUnit[GetPlayerId(GetClickingPlayer())]
        local Equipment gear = Equipment[u]
        local Inventory inv  = Inventory[u]
        local integer slot   = but.customValue
        local integer itemId
        local InvItem itm
        local integer cid
     
        if (GetClickingPlayer() != User(inv.pid).handle or gear.item[slot] <= 0) then
            return false
        endif

        // clear tooltip
        call inv.showLines(inv.pid, false, inv.camera)
     
        set itm = gear.item[slot]
        set itemId = itm.id
        set cid = gear.itemId[slot]
     
        set gear.item[slot].tempCustomId = cid
     
        if (gear.unequip(itm, slot)) then
            set itm.tempCustomId = cid
            if (not inv.addItem(itm)) then
                set gear.item[slot].tempCustomId = cid
                call gear.equip(itm, slot)
                return false
            endif
        else
            return false
        endif
 
        call Inventory.showTooltip(inv, false)
     
        return false
    endfunction
 
    private function LClickItemSlot takes nothing returns boolean
        local UIButton but = GetTriggerButton()
        local player p = GetClickingPlayer()
        local Equipment equip = Equipment[Equipment.PlayerCurrentUnit[GetPlayerId(p)]]
        local Inventory inv = Inventory.PlayerCurrent[equip.user.id]
        local integer slot =  but.customValue
        local integer last = InvPlayerLastSlot[inv.pid] - 1
        local integer itemId = 0
        local UIButton lastButton = InvPlayerLastButton[equip.user.id]
        local InvItem itm
     
        if (User.Local == p) then
            call SelectUnit(but.picture, false)
            call SelectUnit(equip.unit, true)
        endif
     
        if (p != equip.user.handle) then
            return false
        endif
     
        if (InvButtonDisabled[but]) then
            return false
        endif

        call inv.showLines(inv.pid, false, inv.camera)
     
        set PlayerLastSlot[equip.user.id] = slot + 1
     
        // equip item
        if (inv > 0 and last+1 > 0) then
     
            set last = (Inventory.MAX_SLOTS * inv.currentPage) + last
         
            set itm = inv.getItem(last)
         
            if (itm.slot - 1 == slot or itm.slotAlt - 1 == slot) then
                set itm.tempCustomId = inv.getItemId(last)
             
                if (equip.equip(itm, slot)) then
                    set PlayerLastSlot[inv.pid] = 0
                    call inv.setItem(last, 0)
         
                    call TimerStart(NewTimerEx(but), 2, false, function InventoryCore_GracePeriod)
                    set InvButtonDisabled[but] = true
                endif
            endif
        endif
     
        set InvPlayerLastButton[equip.user.id] = but
        set InvPlayerLastSlot[equip.user.id] = 0
     
        if (but != lastButton) then
            call SetUnitVertexColor(lastButton.picture, 255, 255, 255, 255)
            call SetUnitVertexColor(but.picture, 175, 175, 175, 255)
        endif
     
        set itemId = equip.item[slot].id
     
        call Inventory.selector[inv.pid].show(false, inv.camera)
        call Equipment.selector[inv.pid].setPosition(but.centerx - 0.007, but.centery + 0.0272)
        call Equipment.selector[inv.pid].showPlayer(Player(inv.pid), true, inv.camera)
     
        if (itemId > 0) then
            set equip.item[slot].tempCustomId = equip.itemId[slot]
         
            call inv.setTooltipTitle(GetObjectName(itemId))
            call GetInvItem(itemId).buildDescription(inv.owner)
            call inv.setTooltipCost("|cffffcc00" + I2S(GetInvItem(itemId).cost) + "|r")
            call inv.setTooltipIcon(inv.localInt(inv.pid, GetItemIcon(itemId), Inventory.ICON_TRANSPARENT))
         
            set equip.item[slot].tempCustomId = 0
        endif
     
        // display tooltip
        set Inventory.TOOLTIP_X = but.minx - 0.4
        set Inventory.TOOLTIP_Y = but.miny - 0.08
     
        call Inventory.showTooltip(inv, itemId > 0)

        return false
    endfunction
 
    private function Init takes nothing returns nothing
        set FuncLClickSlot = Filter(function LClickItemSlot)
        set FuncRClickSlot = Filter(function RClickItemSlot)
        call Inventory.addLeftClickHook(function OnInventoryItemClick)
        call Inventory.addRightClickHook(function OnInventoryItemRightClick)
    endfunction

endlibrary
 
Last edited:

Uncle

Warcraft Moderator
Level 65
Joined
Aug 10, 2018
Messages
6,687
Unfortunately, I don't fully understand the issue and how the system works exactly so I can't help you out just yet.

I just wanted to note that in Wc3 version 1.31+ we have access to custom UI which makes all of this stuff rather obsolete. That being said, it's not accessible in GUI and will take some time to learn. I'm surprised to find nobody has made a custom Inventory/Equipment system revolving around this stuff just yet, but I guess it'd be a decent bit of work.

Tasyen is the expert on this stuff, I recommend checking out his custom UI resources:
 
Level 6
Joined
Jun 18, 2011
Messages
147
Unfortunately, I don't fully understand the issue and how the system works exactly so I can't help you out just yet.

I tried my best to explain precisely what the problem is. Another thing I noticed is that if I use the teleport spell on myself as Merlin or any other hero that I own, as long as I also own Merlin, then this problem does not occur. It is only when Merlin belongs to an ally that he erases my equipment and inventory, but the equipment stats remain. Again the items are not real. The unit gets bonuses based on what the items are meant to provide. The things that are disappearing are just the icons of the items which are made using the texture property of a destructible.

item_texture_example.JPG


Tasyen is the expert on this stuff, I recommend checking out his custom UI resources:

I planned on using his Talent JUI. I just imported it, so I will be working on configuring that and the rest of the items for the Equip/Inv system. Making my own system even with the UI capabilities now would still take a lot longer than using this old one. Eventually, I will get around to that, but I just wanted to have TriggerHappy's implemented for now because it is really beautiful looking imo. He advises not to use it because of how outdated it is, but it isn't giving me any major problems so far.
 
Last edited:
Level 6
Joined
Jun 18, 2011
Messages
147
Perhaps I could edit this method to fix it somehow since it is checking what I believe is if the units being teleported are allies of the caster.

JASS:
private method scrollTeleport takes nothing returns nothing
        local unit U2
        local real X1
        local real Y1
        local real Dist
        local real A

        call GroupEnumUnitsInRange(G, .x, .y, RADIUS, Condition(function nullFilter))

        loop
            set U2 = FirstOfGroup(G)
            exitwhen U2 == null
            call GroupRemoveUnit(G, U2)

            if IsUnitAlly(U2, .p) then
                set X1 = GetUnitX(U2)
                set Y1 = GetUnitY(U2)
                set Dist = distancePoints(.x, .y, X1, Y1)
                set A = Atan2(Y1-.y, X1-.x)

                call effectPoint(EFFECT_TELEPORT_START_PATH, X1, Y1)
      
                call SetUnitPosition(U2, (pt.x + (Cos(A) * Dist)), (pt.y + (Sin(A) * Dist)))

                call effectPoint(EFFECT_TELEPORT_ARRIVE_PATH, GetUnitX(U2), GetUnitY(U2))
            endif
        endloop

    endmethod

I don't understand how it is even teleporting the caster, if it is just teleporting allies of the caster. Unless a check of a player being an ally with itself returns true. I think it does, then maybe the problem lies elsewhere because the destructible icons for the Equip/Inv UI are created for the owner of the hero for it I'm pretty certain. I know this because I remember seeing it in the Equipment trigger and also because I had to move them on their creation from the center of the map {because it created visibility there} to somewhere I already had visibility enabled for the entire game). However, the items' destructible (for its icon that dissapears) I believe are likely not being created for the owner of the items. Hence, the reason why they would not be teleported, perhaps? That doesn't make sense though. They only disappear the very first time from teleporting and also the Equip/Inv system is constantly updating their position if you move around with the hero, so why wouldn't it update them to where the hero is teleported just the first time it is teleported and not cause this bug every time.
 
Last edited:
Level 14
Joined
Feb 7, 2020
Messages
387
Perhaps I could edit this method to fix it somehow since it is checking what I believe is if the units being teleported are allies of the caster.

JASS:
private method scrollTeleport takes nothing returns nothing
        local unit U2
        local real X1
        local real Y1
        local real Dist
        local real A

        call GroupEnumUnitsInRange(G, .x, .y, RADIUS, Condition(function nullFilter))

        loop
            set U2 = FirstOfGroup(G)
            exitwhen U2 == null
            call GroupRemoveUnit(G, U2)

            if IsUnitAlly(U2, .p) then
                set X1 = GetUnitX(U2)
                set Y1 = GetUnitY(U2)
                set Dist = distancePoints(.x, .y, X1, Y1)
                set A = Atan2(Y1-.y, X1-.x)

                call effectPoint(EFFECT_TELEPORT_START_PATH, X1, Y1)
     
                call SetUnitPosition(U2, (pt.x + (Cos(A) * Dist)), (pt.y + (Sin(A) * Dist)))

                call effectPoint(EFFECT_TELEPORT_ARRIVE_PATH, GetUnitX(U2), GetUnitY(U2))
            endif
        endloop

    endmethod

I don't understand how it is even teleporting the caster, if it is just teleporting allies of the caster. Unless a check of a player being an ally with itself returns true. I think it does, then maybe the problem lies elsewhere because the destructible icons for the Equip/Inv UI are created for the owner of the hero for it I'm pretty certain. I know this because I remember seeing it in the Equipment trigger and also because I had to move them on their creation from the center of the map {because it created visibility there} to somewhere I already had visibility enabled for the entire game). However, the items' destructible (for its icon that dissapears) I believe are likely not being created for the owner of the items. Hence, the reason why they would not be teleported, perhaps? That doesn't make sense though. They only disappear the very first time from teleporting and also the Equip/Inv system is constantly updating their position if you move around with the hero, so why wouldn't it update them to where the hero is teleported just the first time it is teleported and not cause this bug every time.
Where does .p in IsUnitAlly(U2, .p) come from in the rest of the script?
 
Level 6
Joined
Jun 18, 2011
Messages
147
Where does .p in IsUnitAlly(U2, .p) come from in the rest of the script?
JASS:
static method addPortal takes unit U, real X, real Y, real lvl returns nothing
        local Portal p
        local boolean found = false
        local integer I = 1

        loop
            exitwhen I > .PT
            set p = .P[I]

            if p.caster == U then
                set I = .PT
                set found = true

                set p.duration = 0
                set p.x = X
                set p.y = Y

                call BlzSetSpecialEffectX(p.sfx, X)
                call BlzSetSpecialEffectY(p.sfx, Y)

                if p.lvl != lvl then
                    set p.lvl = lvl
                    set p.durationMax = getDurationMax(lvl)
                endif
            endif

            set I = I + 1
        endloop

        if not(found) then
            set p = Portal.allocate()

            set p.caster = U
            set p.x = X
            set p.y = Y
            set p.p = GetOwningPlayer(U)
            set p.duration = 0
            set p.durationMax = getDurationMax(lvl)
            set p.lvl = lvl
            set p.sfx = createSfx(EFFECT_PORTAL_PATH, X, Y, randomRadian(), EFFECT_PORTAL_SIZE, EFFECT_SCROLL_HEIGHT)

            call p.swapSpells(true)

            set .PT = .PT + 1
            set .P[.PT] = p

            if .PT == 1 then
                call TimerStart(.T, INTERVAL_PORTAL, true, function Portal.update)
            endif
        endif

    endmethod
endstruct

function Trig_MythicalScrollTeleport_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_TELEPORT
endfunction
 
Level 6
Joined
Jun 18, 2011
Messages
147
TriggerHappy just explained to me on Discord how badly this equip/inv system can cause desyncs.

"the UI elements are units which need to have some vision so you can actually see the Ui
and the units are moved locally for performance reasons (moving 30x units every 0.01 seconds per player is really laggy)
so if the UI elements cause you to see a unit you're not supposed to see and you aggro them, you will desync"

Will update tomorrow after some testing to see if it is actually worth using. Still, I learned quite a bit by using it. I am hoping to study it more to figure out how to use the NewBonus Library (that Chopinski always uses in his hero concepts) to add cooldown reduction bonuses to items. The Equip/Inv system comes with what I believe is the older version of the NewBonus library called Bonus Mod which is missing a lot of the bonuses NewBonus has.
 
Last edited:
Level 6
Joined
Jun 18, 2011
Messages
147
If I'm feeling up to the challenge I may throw together a simple inventory/equipment system using Custom UI in Jass. It won't be pretty but maybe it could help you get the right idea. Also, if I do end up creating something then I'll make sure it's compatible with the latest NewBonus system.
That would be exactly what I need right now. If you do I'll be sure to credit you.
 

Uncle

Warcraft Moderator
Level 65
Joined
Aug 10, 2018
Messages
6,687
Hopefully you're on the latest version (I forgot to ask). So when I said it wouldn't be pretty I wasn't joking, I don't really know how to use Jass to it's full potential so I went with the tried and true method of creating a REALLY convoluted Hashtable to keep track of all of the information.

Things it does:
  • Creates a 30 slot inventory.
  • Creates 3 equipment slots (weapon, chest, helm).
  • Allows the player to pick-up items which then get transferred from the standard wc3 inventory to the custom 30 slot inventory.
  • Allows the player to left-click items in their inventory to equip them IF the item matches an appropriate unused equipment slot.
  • Allows the player to right-click items in their inventory to drop them at their feet.
  • Allows the player to left-click equipment to transfer them to their inventory (un-equip).
  • Syncs up with the latest version of Chopinski's NewBonus system, applying any number of stats to the hero upon equipping an item and removing the stats upon unequipping.
  • Fully MPI* allowing support for up to as many players as you'd like. Note that it can only work for 1 Hero at a time.

Things it doesn't do (yet):
  • You can't open or close the Inventory or the Equipment interface. I'm getting to that.
  • No page support for the Inventory. You can still go beyond the 30 slots it has now but at some point the UI will become too cumbersome.
  • Equipment only supports NewBonus mods so you can't attach an Ability like an Aura to the Item.
  • No fancy stuff like Sockets/Gems in the Equipment.
  • No Tooltip/Name/Text of any kind to describe what you're looking at.

The Hashtable is... well it's not difficult to understand but it's rather difficult to read in the code.

So the Hashtable uses the Player's Number as the Parent key for most things, particularly all of the frames aka framehandles (custom ui).
Since by default I gave the system a 30 slot inventory it goes as follows:

Inventory icon framehandles go from child index 1 to 30.
Inventory button framehandles go from child index 31 to 60.
Inventory slot booleans (whether the slot is in use or not) go from child index 61 to 90.

You can see that they're all 30 apart, this is because the inventory size is 30. It's a pattern that must not be broken. For example, if you click Inventory button 31 with your mouse then based on the pattern you know that it's associated Icon is saved at 1 and it's Boolean is saved at 61 since all you have to do is either subtract 30 or add 30 to to get either of these things. 1 = icon, 31 = button, 61 = is used boolean.

For Equipment I used the same pattern style but took advantage of the negative Indexes in the Hashtable:
-1 to -3 = Equipment buttons
-4 to -6 = Equpment icons
-7 to -9 = Equipment isUsed boolean

Since there are 3 Equipment types by default (weapon, chest, helm) the pattern I used with the Inventory comes up again, this time separating them by 3.

I also use the Hashtable to save information directly to the Buttons such as the Item-Type Id of the item being stored there. This way when you press a Button this information can be loaded. This links everything together. You press a button, the code loads the item that's stored there, and then loads all of the necessary information from the now loaded item. For the NewBonus mods I use the Hashtable again to link the mods to the Item-Types.

Hopefully it makes sense. At the very least you can see how I'm creating the custom ui frames. This stuff is not as difficult as it seems, it just takes time.
 

Attachments

  • Unce Item UI.w3m
    268 KB · Views: 6
Last edited:
Level 6
Joined
Jun 18, 2011
Messages
147
This is unbelievable! This is so close to Trigger Happy's beautiful version! I definitely see myself using this with a little bit more work to get it situated.

I think I am unequipping an item when I click a new one based on the sound and no error message, but all the items look exactly the same so I have no idea if I am or not, haha. I'll figure it out.

One day I will switch to lua, C# or ts hopefully.

Thank you so much, if you decide to do anymore let me know or let the whole community know and put it in Spells. I will do my best to get it in working order. I'm unsure how long it will take me to finish it but if I don't run into too many issues or get too much writer's block I would guess a couple weeks to a month. I'm also working with Spellweaver aka Mayday in the Morning on a talent tree system right now, so I'm gonna split my time between both of these systems.

edit: I meant to ask, did you programmatically make the fdf and toc files just using a text editor or do you have any tool that can help with that. I'm wondering because my UI from RUID for the talent system seems kind of cumbersome and I have never made a UI from scratch other than through RUID. I've only ever made minor edits to them like getting Trigger Happy's Equip system to appear actually on screen and not hanging off the top corner of the screen.

edit 2: ah I see you added plenty of comments in them for me, thank you!:thumbs_up:
 
Last edited:

Uncle

Warcraft Moderator
Level 65
Joined
Aug 10, 2018
Messages
6,687
I snagged all of the fdf/toc files from Tasyen's work so I can't take credit there. That's why their names are sort of random. To be honest I'm not that great at custom UI yet, I'm definitely doing things the wrong way, but if it works then whatever I suppose...

I actually work on my maps in C# now and I highly recommend it. I even made a custom Frames class which is just a lifesaver when it comes to this UI stuff. I'm sure others have made better systems, but I like to use my own stuff since I'm more comfortable/familiar with it and it's a good learning experiencing. But the point is that coding in Lua/TS/C#/etc is just soooo much better than coding in Jass.

Also, I would never dream of submitting this to the Spells section as the Jass is really just that awful. I imagine whoever had to review it would lose their mind, lol. That being said, it works! Once I'm feeling motivated again to touch the dreaded Jass I'll tweak some things and try to add some more features.
 
Last edited:
Level 6
Joined
Jun 18, 2011
Messages
147
That would be great, TriggerHappy's still has one noteworthy additional features that is socketing items. Only problem is I haven't tried figuring out a way to save sockets on items nor have I figured out how to either equip the items after I give them to a hero on load or have them created directly into the hero's equipment slots. I have a feeling that would be easier to figure out with your code, though. I can't wait to start messing with it.

I was busy implementing a Spell Book, SB, system today to clean up the extra passive abilities I am going to add from items. Today was my second attempt at this. I tried a long time ago to no avail, but I finally figured out how to correctly use them and honestly I think we need a much better tutorial on them than we currently have here on the hive. It was made in 2007 and is in the graveyard now. The demo map's only trigger command related to Spell Books is disabling the dummy spell and some unfinished trigger telling you to type add to add an ability to a footman, but the footman has no mana or inventory. So you can't click on the spell book icon ability to see if he has new spells. That guide is pretty good at explaining the spell book ability in minor detail, but the guide doesn't really explain what they mean by dummy ability nor does it talk about the spell book item. I want to emphasize that they do not specify between the dummy ability and the real ability you want hide inside the dummy spell book ability here. That is because the author only briefly touches on adding spells to a spell book ability and only adding a spell to the SB ability (not removing it and not adding it to SB item). This isn't a good guide imo for that reason.

The only other guide I found was two of the same tutorial on wc3campaigns/thehelpernet. That got a bad review from the author of this "in depth" one. That guide is more of just everything about spell books (bugs and detailed facts about them), but it doesn't have much anything in detail about how to use them.

I tend to overcomplicate things, so as I am looking back I keep wondering if its a problem with me or the guides. I've thought about it enough and decided to continue writing this post and why I think those tutorials are so awful. I first thought I needed to add the dummy spellbook itself to the triggering unit for the longest time. I eventually realized that they mean the ability inside the dummy spell book ability. Likewise, they say to disable the dummy ability and I thought it meant the dummy spellbook ability again because they always use the word dummy, but the spell inside these dummy spell books is not a dummy. It is the spell you want. Then, because of the wording on those earliest tutorials I thought I needed a different spell book for every combination of the hero abilities I wanted, ie (crit strike + evasion for one, crit strike + spell immune for another, and finally crit strike + spell imm + evasion etc as you add more abilities). You can see how quickly a mistake in my thinking created such a major time waste. Now I have all these spell books with differing abilities and am thinking of my next steps. Well, why would I have tried to add/remove the actual ability inside the dummy spell book if my thought was to create separate books. I was easily confused because all the guides keep calling this spell book ability the dummy ability. It literally says <spellbook> in this code:
  • Player - Disable <spellbook> for Player 1 (Red)
I finally realized it was the actual ability inside the dummy spell book that needed to be disabled in order for it to not show up on my hero because I was also giving my hero an actual spell book item to store the skills in. So in my case things were a little different. For me it should look like this:

  • Player - Disable Critical Strike 25 for Lancelot
25 is just for 25%
and for every other ability. Please note, the following limitation is the same in both methods: I must delete the item (Spell Book Main ability for the old tutorial) and replace it with a new version of itself in order to get rid of the abilities stored in it. So you can see a problem arises where you have to store every ability the hero had and check which he has and then only restore those certain ones if you want to be able to remove these abilities. Therefore, my idea to create multiple spell books wasn't totally wrong, but it is (I wanna say arguably) easier to perform that logic strictly through code and add back each ability rather than adding back the abilities as a set item (or as one spellbook ability with the correct abilities. Its just clearly a better choice not to clutter the object editor). I actually don't care about this that much and for my map I am just going to let the heroes keep any skills they learn from items permanently even if the items they were on are unequipped and just put a description on items saying "Permanent Learned Skill:" or something shorter in the tooltip.

but there were other problems...

One important part of the SB system that is overlooked by all the tutorials is setting the real abilities hero/item checks. Some mention that the spell books and dummy spell books need to be a hero ability while others say item ability. This is wrong at least for the way I did it. It didn't matter for me, but I made them hero/item abilities just to keep them separate from my cluttered item abilities. What /really/ matters is making the real ability (ie critical strike 25 that is going to be given back to the hero by adding the dummy SB spell that contains crit strike to the unit's Spell book item) both a hero and an item ability. Again, the ability is removed, then the spell is added back via the mystery of spell books. For this to be seemless you must do two things.

1. Disable an ability for a player in GUI. (The real ability so it doesn't show up). To do so you must make it a hero ability.
2. Add said real ability to a spell book. To do so it must be either a unit ability or an item ability. (Since you needed it to be a hero ability, it must then also be an item ability)

If you don't set set the hero check box than it is very easy to think you didn't make a mistake so far and then accidentally select the SB abilities (well intentionally according to the old tutorials) rather than the real abilities you want to hide.

In 2007 they did things a different way. That demo map had all the abilities set already in WE on a spell book ability for a Knight. No abilities showed up on them because they disabled the spell book ability itself, but again that wasn't an option for me and I wasted hours trying to follow a guide that was incomplete. I think the amount of people who want to add individual abilities to the character over time probably outweigh those that want to start units off with 5 hidden abilities. So, the tutorial could have also made this clear. Sorry, for the rant, but yea I was kinda upset at myself for allowing something super simple to turn into something extremely complicated. I think if people still care about the mystery of spell books, maybe I should write a tutorial, but idk it is kinda simple stuff.

Lua/TS/C#/etc is just soooo much better than coding in Jass.

Ah the possibilities and leaving headaches like having to use spell book in the past where it belongs. One day maybe I will, but my map is just too large for me to start on that right now. Once I finish these two last systems I plan to re-terrain, fix bugs, and just relax and slowly improve on what I have for awhile. Then, I'll take my time to learn some new stuff and attempt to recreate it or make a different version. I'm curious about being able to use the code I would be creating it in not only in WC3 but other engines as well. I talked with Spell Weaver quite a bit about how to start in all those languages. He of course recommends C# or ts over lua.
 
Last edited:

Uncle

Warcraft Moderator
Level 65
Joined
Aug 10, 2018
Messages
6,687
Yeah, custom UI replaces the need for Spellbooks as you can display anything you'd like wherever/however you'd like. But I understand why you're reluctant to make a big change like that.

I was fortunate enough to not have a massive project that was filled with Jass systems when Lua came out. At that moment I threw away GUI and began learning how to code. It was well worth the effort though and now I can create whatever I'd like. Maybe not optimally or with the best coding practices but good enough for me. You should see the Save/Load system that WCSharp (c#) offers, there's basically no limit to how much you can Save/Load and the process is beyond simple.
 
Status
Not open for further replies.
Top