1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  5. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  6. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  7. The results are out! Check them out.
    Dismiss Notice
  8. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  9. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  10. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[System] Melee/Ranged Attack Switch v1.1.1.1

Submitted by Maker
This bundle is marked as approved. It works and satisfies the submission rules.
Code (vJASS):

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
 *      Melee/Ranged system v1.1.1.1 by Maker                           *
 *                                                                      *
 *      Melee/ranged switching system allows a unit to switch between   *
 *      ranged and melee attacks.                                       *
 *                                                                      *
 *      Has two modes, automatic and manual.                            *
 *                                                                      *
 *      In auto mode, the unit switches automatically based on          *
 *      distance to target. In manual mode, player can switch           *
 *      the mode with an ability.                                       *
 *                                                                      *
 *      Supports Last Order, the unit doesn't stop when switching.      *
 *                                                                      *
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/


library MeleeRanged initializer Init requires UnitIndexer optional LastOrder

    globals
        // Configure unit types at Setup function
        // at the bottom of the library
   
        // Do units change tome melee/ranged automatically
        private constant    boolean     AUTO        = true
        // Ability with which unit can toggle attack
        private constant    integer     TOGGLEABI   = 'A005'
        // Order id of the transform, default = metamorphosis
        private constant    integer     TOGGLEOID   = 852180
        // Order id of toggle activation, default = magicdefence
        private constant    integer     TOGGLEON    = 852478
       
       
        private             hashtable   HT          = InitHashtable()
        private             trigger     TRG1
        private             trigger     TRG2         = CreateTrigger()
        private             timer       T1
        private             timer       T2
        private             group       G1
        private             group       G2
        private             group       U
        private             group       TEMP        
       
        private             integer     LEAKS       = 0
        private             integer     LEAKS_MAX   = 10
    endglobals
   
    // Disables given ability for players, won't show up in UI
    private function DisableAbil takes integer a returns nothing
        local integer i = 0
        loop
            call SetPlayerAbilityAvailable(Player(i), a, false)
            exitwhen i == 15
            set i = i + 1
        endloop
    endfunction
   
    // Adds a delay to order, allows to akncowledge the
    // change in unit's attack range
    private function Delay3 takes nothing returns nothing
        static if  LIBRARY_LastOrder then
            local unit u
            call DisableTrigger(TRG2)
            loop
                set u = FirstOfGroup(G1)
                exitwhen u == null
                if GetPastOrder(u, 3) != null then
                    call IssuePastOrder(u, 3)
                endif
                call GroupRemoveUnit(G1, u)
            endloop
            call EnableTrigger(TRG2)
        endif
    endfunction
   
    // Adds a delay to order, allows to akncowledge the
    // change in unit's attack range
    private function Delay1 takes nothing returns nothing
        local unit u
        call DisableTrigger(TRG2)
        loop
            set u = FirstOfGroup(G1)
            exitwhen u == null
            call IssueTargetOrderById(u, 851983, LoadUnitHandle(HT, GetHandleId(u), 2))
            call FlushChildHashtable(HT, GetHandleId(u))
            call GroupRemoveUnit(G1, u)
        endloop
        call EnableTrigger(TRG2)
    endfunction
   
    function ToggleMeleeRanged takes unit u, unit target, boolean toRanged returns nothing
        // Load correct ability for the unit type
        local integer a = LoadInteger(HT, GetUnitTypeId(u), 1)
        // Enable ability to be able to use it, then disable it to hide it
        //call GroupRemoveUnit(U, u)
        call SetPlayerAbilityAvailable(GetOwningPlayer(u), a, true)
        call IssueImmediateOrderById(u, TOGGLEOID)
        call SetPlayerAbilityAvailable(GetOwningPlayer(u), a, false)
        //call GroupAddUnit(U, u)
        // Save whether unit is melee or ranged
        static if AUTO then
            // Makes the unit attack the target
            // Use delay so the new range is used
            call SaveUnitHandle(HT, GetHandleId(u), 2, target)
            call GroupAddUnit(G1, u)
            call TimerStart(T1, 0.00, false, function Delay1)
        elseif LIBRARY_LastOrder then
            // Makes the unit stop when it transforms
            call GroupAddUnit(G1, u)
            call TimerStart(T1, 0.00, false, function Delay3)
        endif
    endfunction
   
    // Detects which event was triggered and whether the unit
    // should switch to melee/ranged or not
    private function Actions takes nothing returns boolean
        local unit u1
        local unit u2
        local real r1
        local real r2
        local integer uid
        local integer id = GetHandleId(GetTriggerEventId())
        if id == 18 then // A Unit is Attacked
            set u1 = GetAttacker()
            set uid = GetUnitTypeId(u1)
            if HaveSavedInteger(HT, uid, 1) then
                set u2 = GetTriggerUnit()
            else
                set u1 = null
                return false
            endif
        else
            set u1 = GetTriggerUnit()
            set uid = GetUnitTypeId(u1)
            if HaveSavedInteger(HT, uid, 1) then
                if id == 60 then // A Unit Acquires a Target
                    set u2 = GetEventTargetUnit()
                else // A Unit Is Issued an Order Targeting an Object
                    set u2 = GetOrderTargetUnit()
                    if GetIssuedOrderId() == 851971  then // Order is smart
                        if not IsUnitEnemy(u2, GetOwningPlayer(u1)) or u2 == null then
                            set u1 = null
                            set u2 = null
                            return false
                        endif
                    elseif GetIssuedOrderId() != 851983 then // Order is not attack
                        set u1 = null
                        set u2 = null
                        return false // Spells won't trigger the system
                    endif
                endif
            else
                set u1 = null
                return false
            endif
        endif
       
        set r1 = GetUnitX(u1)-GetUnitX(u2)
        set r2 = GetUnitY(u1)-GetUnitY(u2)
       
        // Checks melee distance
        if r1*r1+r2*r2 > LoadReal(HT, GetUnitTypeId(u1), 0) then
            if LoadInteger(HT, uid, 2) == 0 then
                call ToggleMeleeRanged(u1, u2, true)
            endif
        elseif LoadInteger(HT, uid, 2) == 1 then
            call ToggleMeleeRanged(u1, u2, false)
        endif
       
        set u1 = null
        set u2 = null
        return false
    endfunction
   
    // Recreates the trigger
    private function Recreate takes nothing returns nothing
        static if AUTO then
            local unit u
            call DestroyTrigger(TRG1)
            set TRG1 = CreateTrigger()
            call TriggerAddCondition(TRG1, Condition(function Actions))
            loop
                set u = FirstOfGroup(U)
                exitwhen u == null
                call GroupAddUnit(TEMP, u)
                call TriggerRegisterUnitEvent(TRG1, u, EVENT_UNIT_ACQUIRED_TARGET)
                call GroupRemoveUnit(U, u)
            endloop
            loop
                set u = FirstOfGroup(TEMP)
                exitwhen u == null
                call GroupAddUnit(U, u)
                call GroupRemoveUnit(TEMP, u)
            endloop
        endif
    endfunction

    // Adds delay to transform order to prevent the toggle
    // order firing twice, due to the instant transform
    private function Delay2 takes nothing returns nothing
        static if not AUTO then
            local unit u
            loop
                set u = FirstOfGroup(G2)
                exitwhen u == null
                call ToggleMeleeRanged(u, null, not LoadBoolean(HT, GetHandleId(u), 0))
                call GroupRemoveUnit(G2, u)
            endloop
        endif
    endfunction
   
    // Detects the toggle order
    private function Toggle takes nothing returns boolean
        static if not AUTO then
            if GetIssuedOrderId() == TOGGLEON then
                call GroupAddUnit(G2, GetTriggerUnit())
                call TimerStart(T2, 0.0, false, function Delay2)
            endif
        endif
        return false
    endfunction
   
    // Initializes a spesific unit for the system
    private function AddUnit takes unit u returns nothing
        local integer ut = GetUnitTypeId(u)
        local integer ab = LoadInteger(HT, ut, 1)
       
        call UnitAddAbility(u, ab)
        call UnitMakeAbilityPermanent(u, true, ab)
        if LoadInteger(HT, ut, 2) == 1 then // Is it the ranged version
            call SetPlayerAbilityAvailable(GetOwningPlayer(u), ab, true)
            call IssueImmediateOrderById(u, TOGGLEOID)
            call SetPlayerAbilityAvailable(GetOwningPlayer(u), ab, false)
        endif
       
        static if AUTO then
            call GroupAddUnit(U, u)
            call TriggerRegisterUnitEvent(TRG1, u, EVENT_UNIT_ACQUIRED_TARGET)
        else
            call UnitAddAbility(u, TOGGLEABI)
            call UnitMakeAbilityPermanent(u, true, TOGGLEABI)
        endif
    endfunction
   
    // Detects possible even leaks
    private function Die takes nothing returns boolean
        static if AUTO then
            if HaveSavedInteger(HT, GetUnitTypeId(GetIndexedUnit()), 1) then
                call GroupRemoveUnit(U, GetIndexedUnit())
                call GroupRemoveUnit(G1, GetIndexedUnit())
                if LEAKS == LEAKS_MAX then
                    set LEAKS = 0
                    call Recreate()
                else
                    set LEAKS = LEAKS + 1
                endif
            endif
        else
            call GroupRemoveUnit(G2, GetIndexedUnit())
            static if LIBRARY_LastOrder then
                call GroupRemoveUnit(G1, GetIndexedUnit())
            endif
        endif
        return false
    endfunction
   
    // Is unit type valid
    private function UnitFilt takes nothing returns boolean
        if HaveSavedInteger(HT, GetUnitTypeId(GetFilterUnit()), 1) then
            call AddUnit(GetFilterUnit())
        endif
        return false
    endfunction
   
    // Initializes unit types
    private function AddUT takes integer i1, integer i2, integer a, real r returns nothing
        call SaveReal(HT, i1, 0, r*r)
        call SaveReal(HT, i2, 0, r*r)
        call SaveInteger(HT, i1 ,1, a)
        call SaveInteger(HT, i2 ,1, a)
        call SaveInteger(HT, i1, 2, 0)  // Is melee type
        call SaveInteger(HT, i2, 2, 1)  // Is ranged type
        call DisableAbil(a)            
    endfunction
   
     private function Setup takes nothing returns nothing
        // Parameters are:
        // (Melee unit type, ranged unit type, transform ability, melee range)
        call AddUT('Hpal', 'H000', 'A000', 200)
        call AddUT('H001', 'Hamg', 'A001', 220)
        call AddUT('e000', 'earc', 'A002', 180)
        call AddUT('n000', 'n001', 'A003', 200)
        call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, function UnitFilt)
    endfunction
   
    private function Init takes nothing returns nothing
        local region r = CreateRegion()
       
        call RegionAddRect(r, bj_mapInitialPlayableArea)
        call TriggerRegisterEnterRegion(CreateTrigger(), r, function UnitFilt)
        call RegisterUnitIndexEvent(Condition(function Die), UnitIndexer.DEINDEX)
       
        static if AUTO then
            set T1 = CreateTimer()
            set G1 = CreateGroup()
            set U = CreateGroup()
            set TEMP = CreateGroup()
            set TRG1 = CreateTrigger()
       
            call TriggerAddCondition(TRG1, function Actions)
            call TriggerAddCondition(TRG2, function Actions)
           
            call TriggerRegisterAnyUnitEventBJ(TRG2, EVENT_PLAYER_UNIT_ATTACKED)
            call TriggerRegisterAnyUnitEventBJ(TRG2, EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER)
        else
            static if LIBRARY_LastOrder then
                set T1 = CreateTimer()
                set G1 = CreateGroup()
            endif
            set T2 = CreateTimer()
            set G2 = CreateGroup()
            call TriggerAddCondition(TRG2, function Toggle)
            call TriggerRegisterAnyUnitEventBJ(TRG2, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        endif
        call Setup()
        set r = null
    endfunction

endlibrary
 


Changeg Log

v1.0.0.0 Uploaded 21.10.2011
v1.0.0.1 Uploaded 21.10.2011
-Added a static if to prevent syntax error if Last Order is missing
v1.0.0.2 Uploaded 21.10.2011
-Removed a boolean
-Added optional library requirement
v1.0.0.3 Uploaded 21.10.2011
-Removed a possible unit variable leak
v1.0.0.4 Uploaded 21.10.2011
-Optimized code
-Disable/enable action detection trigger to avoid useless action checks
v1.0.0.5 Uploaded 21.10.2011
-Got rid of SquareRoot in the distance check
-Added local reals for distance calculation
v1.0.1.6 Uploaded 24.10.2011
-Added trigger recycling, events for removed units are cleared
-Uses u2 == null instead of GetManipulatedItem != null
-Static if now covers all actions in Delay3 function
v1.1.0.0 Uploaded 02.11.2011
-Combined Enter and UnitFilt functions
-Removed two integer globals
-Uses unit indexer to correctly handle revived units
-Removed unneeded variables from setup function
-Small static if change
-Two more static ifs, in Delay2 and Toggle functions
-Added a trigger to which unit acquires target events are added
-Uses filter function instead of condition for units that enter the map
v1.1.1.0 Uploaded 10.11.2011
-Changed the order of u = FirstOfGroup and exitwhen u = null
-When units die, they are removed from groups that cause them to perform actions
-Instead of creating a trigger, registers dying with RegisterUnitIndexEvent
v1.1.1.1 Uploaded 10.11.2011
-Removed leftovers from previous versions, i and j variables from Recreate function


Last Order is an optional library by Rising Dusk
Unit indexer is a requires library by Nestharus
Both are included in the map

Keywords:
melee, ranged, attack, switch, toggle
Contents

Map (Map)

Reviews
Moderator
10th Nov 2011 Bribe: The system is cool, useful and Highly Recommended. Thanks for making this resource.
  1. zv27

    zv27

    Joined:
    Aug 21, 2010
    Messages:
    295
    Resources:
    0
    Resources:
    0
    private function DisableAbil takes integer a returns nothing
    local integer i = 0
    loop
    call SetPlayerAbilityAvailable(Player(0), a, false)

    should be
    "call SetPlayerAbilityAvailable(Player(i), a, false)"
    exitwhen i == 15
    set i = i + 1
    endloop
    endfunction
     
  2. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,181
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    No need to guess if you try it ;)

    Fixed :)
     
  3. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,053
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    I don't see any instances of "DestroyTrigger", we could be looking at lots of event leaks as the game progresses.

    I recommend using the approach I did in DamageEvent (Nestharus used it in his vJass damage event library). Basically every 15 or so removed units, you destroy the trigger and re-add all the events of the still-existing units.
     
  4. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,181
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    v1.0.1.6
    -Added trigger recycling, events for removed units are cleared
    -Uses u2 == null instead of GetManipulatedItem != null
    -Static if now covers all actions in Delay3 function
     
  5. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Code (vJASS):
            local trigger t1 = CreateTrigger()
            local trigger t2 = CreateTrigger()
            local region r = CreateRegion()
           
            call RegionAddRect(r, bj_mapInitialPlayableArea)
            call TriggerAddCondition(t1, function Enter)
            call TriggerRegisterEnterRegion(t1, r, null)
            call TriggerAddCondition(t2, function Die)
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
            set t1 = null
            set t2 = null
     

    ->>
    Code (vJASS):
            local trigger t = CreateTrigger()
            local region r = CreateRegion()
           
            call RegionAddRect(r, bj_mapInitialPlayableArea)
            call TriggerAddCondition(t, function Enter)
            call TriggerRegisterEnterRegion(t, r, null)
            set t = CreateTrigger()
            call TriggerAddCondition(t, function Die)
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
            set t = null

    Marginal issue: 0.0 can be set to just 0.
     
  6. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,053
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Instead of using a condition it could use the filter function, and instead of
    GetTriggerUnit()
    it would then use
    GetFilterUnit()
    .

    The only thing you can't use that filter for is for
    GetTriggeringTrigger()
    or
    GetTriggeringRegion()
    .
     
  7. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,053
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    I almost thought this library could make use of the GroupTargetOrderById function but sadly not :(

    Would have been cool though.

    You can use "elseif" in static if's so you don't need a seperate "static if" block for that.

    The dynamic trigger "TRG" should not have the TriggerRegisterAnyUnitEventBJ events attached to it. Those should be in a seperate trigger.

    You must use trigger conditions for dynamic triggers, because trigger actions are not recycled properly when the trigger is destroyed (conditions are).
     
  8. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,181
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    I'll update it. I have made it use a unit indexer, so it handles units that are revived correctly among other things.
     
  9. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,181
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    Updated to v1.1.0.0
    -Combined Enter and UnitFilt functions
    -Removed two integer globals
    -Uses unit indexer to correctly handle revived units
    -Removed unneeded variables from setup function
    -Small static if change
    -Two more static ifs, in Delay2 and Toggle functions
    -Added a trigger to which unit acquires target events are added
    -Uses filter function instead of condition for units that enter the map
     
  10. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,181
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    Updated to v1.1.1.1, check change log.
     
    Last edited: Nov 10, 2011
  11. Atideva

    Atideva

    Joined:
    May 9, 2010
    Messages:
    47
    Resources:
    2
    Spells:
    2
    Resources:
    2
    Can you add "Animation tags" change.
    Some models have different anim tags for melee and range attacks.
    Need to change them, using your system, please.
     
  12. Mr.Foxy

    Mr.Foxy

    Joined:
    Dec 21, 2011
    Messages:
    234
    Resources:
    0
    Resources:
    0
    How to use it?
     
  13. -Kobas-

    -Kobas-

    Joined:
    Jan 17, 2010
    Messages:
    5,894
    Resources:
    28
    Icons:
    1
    Tools:
    2
    Maps:
    10
    Spells:
    4
    Template:
    5
    Tutorials:
    6
    Resources:
    28
    NOTE: Try to replace "metamorphosis" with "bear form", there are bugs with first one because of animations, if you use unit with few animation categories they can mix. I lost 4 hours on fixing simple system, I hope that this will help you guys so you don't bother.
    I managed to fix this shit, I'm still confused was this above real reason why it failed so many times.
     
    Last edited: Apr 20, 2012
  14. defskull

    defskull

    Joined:
    Mar 27, 2008
    Messages:
    7,978
    Resources:
    17
    Spells:
    17
    Resources:
    17
    Maker, can you do like change the unit from Melee to Range (vice-versa) which only requires 1 unit ?
    It would be more awesome :p
    I hate to copy each unit...
     
  15. Sunchips

    Sunchips

    Joined:
    Jul 23, 2009
    Messages:
    871
    Resources:
    123
    Models:
    112
    Icons:
    4
    Packs:
    7
    Resources:
    123
    I tried to implement this system in my map but for some reason it messed up my units' max health. Suddenly all units on the map has like 100879 max health. Why is this? I use another unit indexer for Zwiebels threat system if that has any relevance.
     
  16. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,181
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    The reason is most likely that I (and you) have used Metamorphosis as the base ability, it can cause such behaviour. It would be better to use Bear Form as the base ability.
     
  17. Duke

    Duke

    Joined:
    Nov 23, 2006
    Messages:
    544
    Resources:
    1
    Maps:
    1
    Resources:
    1
    Hey, so I tried to use this system in my campaign, but it didn't work (got black screen after selecting mission). So I created a new empty campaign, imported the system map and tested it again. This time the mission loads fine, but the system does not work.
    Any idea how to make this work in a campaign? Would highly appreciate it!