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

SummonedEscort v1.5

Summoned Escort v1.5
Information

Allows your summoned units to follow and guards the summoner, if summoner dies, the summoned unit searches for a new master or returns to it's original location.
JASS:
library SummonedEscort /* v1.5 
***************************************************************************
*   by mckill2009
***************************************************************************
*
*   */ uses /*
*       */ GetClosestWidget /* www.hiveworkshop.com/forums/jass-resources-412/snippet-getclosestwidget-204217
*       */ Table            /* www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084
*
***************************************************************************
*
*   Features:
*       - Allows your summoned units to follow and guards the summoner.
*       - If the summoner dies, the summoned unit searches for a new ally or
*         returns to it's original location.
*       - Used also to focus attack an enemy hero or unit.
*
***************************************************************************
*   
*   Installation:
*       Copy and paste the required libraries and this code to your map     
*
***************************************************************************
*
*   API: 
*       static method summoned takes unit summoningUnit, unit summonedUnit returns nothing
*           - call SE.summoned(GetSummoningUnit(),GetTriggerUnit())
*           - If AUTO is true, it will do automatically for you.
*           - AUTO only works for summoned unit           
*
*       static method remove takes unit escortUnit returns nothing
*           - Removes the escort from the system
*
***************************************************************************/

globals
/**************************************************************************
*   Auto registers ALL summoned units in map, it is recommended to set this
*   to false if you want a unit to escort an ally or attack an enemy hero/unit
***************************************************************************/
    private constant boolean AUTO = true 
    
    
/**************************************************************************
*   Searches for closest ally hero if main hero is dead, if FOLLOW_ONLY_HEROES
*   is false, the escort will follow a closest ally
***************************************************************************/
    private constant boolean ALLY_IN_RANGE = true
    
/**************************************************************************
*   The unit only follows heroes when the main unit dies
***************************************************************************/
    private constant boolean FOLLOW_ONLY_HEROES = true

/**************************************************************************
*   This is the offset distance from the unit to his master   
***************************************************************************/
    private constant real OFFSET = 200

/**************************************************************************
*   Searches for closest ally if main ally unit is dead
*   ALLY_IN_RANGE must be true
***************************************************************************/
    private constant real CLOSEST_ALLY = 600 

/**************************************************************************
*   Targets/attacks closest enemy in range of master
***************************************************************************/
    private constant real CLOSEST_ENEMY = 400 
endglobals

struct SE
    private unit master
    private unit sum   
    private real xUnit
    private real yUnit
    
    private static integer DATA
    private static constant integer ATTACK = 851983
    private static timer t = CreateTimer()
    private static integer instance = 0
    private static integer array insAR
    private static unit TempUnit = null
    private static Table tb
    
    private static method UnitAlive takes unit u returns boolean
        return not IsUnitType(u,UNIT_TYPE_DEAD) and u!=null
    endmethod
    
    private static method closestEnemy takes nothing returns boolean
        local thistype this = DATA
        set TempUnit = GetFilterUnit()   
        return UnitAlive(TempUnit) and IsUnitEnemy(TempUnit, GetOwningPlayer(.sum))
    endmethod
    
    private static method closestAlly takes nothing returns boolean
        local thistype this = DATA
        set TempUnit = GetFilterUnit()        
        if UnitAlive(TempUnit) and GetOwningPlayer(TempUnit)==GetOwningPlayer(.sum) and TempUnit!=.sum /*
        */ and not IsUnitType(TempUnit,UNIT_TYPE_STRUCTURE) and GetUnitMoveSpeed(TempUnit)>0 then
            static if FOLLOW_ONLY_HEROES then
                return IsUnitType(TempUnit, UNIT_TYPE_HERO)
            endif
            return true
        endif
        return false
    endmethod
    
    private method destroy takes nothing returns nothing
        set .master = null
        set .sum = null
        call .deallocate()        
    endmethod        
    
    private static method looper takes nothing returns nothing
        local thistype this
        local unit target
        local integer orderSum
        local integer index = 0
        local real angle
        local real xMaster
        local real yMaster
        local real xSummoned
        local real ySummoned
        loop
            set index = index+1
            set this = insAR[index]
            if UnitAlive(.sum) and tb.has(GetHandleId(.sum)) then
                set angle = GetRandomReal(0,6.28)
                set orderSum = GetUnitCurrentOrder(.sum)
                if UnitAlive(.master) then   
                    if orderSum==0 then
                        set xMaster = GetUnitX(.master)+OFFSET*Cos(angle)
                        set yMaster = GetUnitY(.master)+OFFSET*Sin(angle)
                        call IssuePointOrderById(.sum,ATTACK,xMaster,yMaster)
                        set DATA = this
                        set target = GetClosestUnitInRange(xMaster,yMaster,CLOSEST_ENEMY,Filter(function thistype.closestEnemy))
                        if target!=null then
                            if IsUnitType(target,UNIT_TYPE_SLEEPING) then
                                call IssueTargetOrderById(.sum,ATTACK,target)
                            else
                                call IssuePointOrderById(.sum,ATTACK,GetUnitX(target),GetUnitY(target))
                            endif
                            set target = null
                        endif
                    endif  
                else
                    set DATA = this
                    set xSummoned = GetUnitX(.sum)
                    set ySummoned = GetUnitY(.sum)
                    static if ALLY_IN_RANGE then
                        set .master = GetClosestUnitInRange(xSummoned,ySummoned,CLOSEST_ALLY,Filter(function thistype.closestAlly))
                    else
                        set .master = GetClosestUnit(xSummoned,ySummoned,Filter(function thistype.closestAlly))   
                    endif
                    if .master==null and orderSum==0 then
                        call IssuePointOrderById(.sum,ATTACK,.xUnit+OFFSET*Cos(angle),.yUnit+OFFSET*Sin(angle))
                    endif
                endif            
            else
                call .destroy()
                set insAR[index] = insAR[instance]
                set insAR[instance] = this
                set index = index - 1
                set instance = instance - 1
                if instance==0 then
                    call PauseTimer(t)
                endif              
            endif      
            exitwhen index==instance
        endloop
    endmethod   
    
    private static method create takes unit summoningUnit, unit summonedUnit returns thistype
        local thistype this
        if instance==8190 then
            call BJDebugMsg("Library_SummonedEscort ERROR: Too many instances!")
        else
            set this = allocate()
            set .master = summoningUnit
            set .sum = summonedUnit
            set .xUnit = GetUnitX(summonedUnit)
            set .yUnit = GetUnitY(summonedUnit)
            set tb[GetHandleId(.sum)] = 0
            if instance==0 then
                call TimerStart(t,1.0,true,function thistype.looper)
            endif
            set instance = instance + 1
            set insAR[instance] = this
            call RemoveGuardPosition(summonedUnit)
        endif
        return this
    endmethod
    
    private static method fire takes nothing returns boolean
        call thistype.create(GetSummoningUnit(),GetTriggerUnit())
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing    
        static if AUTO then
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SUMMON)
            call TriggerAddCondition(t,function thistype.fire)
            set t = null
        endif
        set tb = Table.create()
    endmethod  
    
    //API:======================================
    static method summoned takes unit summoningUnit, unit summonedUnit returns nothing
        call thistype.create(summoningUnit,summonedUnit)
    endmethod
    
    static method remove takes unit escortUnit returns nothing
        call tb.remove(GetHandleId(escortUnit))
    endmethod    
endstruct

endlibrary

HOW TO USE IN GUI: If AUTO is false
  • GUI Sample Without Auto
    • Events
      • Unit - A unit Spawns a summoned unit
    • Conditions
    • Actions
      • Custom script: call SE.summoned(GetSummoningUnit(),GetTriggerUnit())
      • Wait 10.00 seconds
      • -------- New addition, removes the unit from the system --------
      • Custom script: call SE.remove(GetTriggerUnit())

- You can also use this system to follow/guard any allied units in the map but this can only be done manually.
- Allows auto follow-attack an enemy unit.

Sample:
  • GUI Sample Spell
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • Custom script: call SE.summoned(GetTriggerUnit(), GetSpellTargetUnit())
      • Wait 10.00 seconds
      • -------- New addition, removes the unit from the system --------
      • Custom script: call SE.remove(GetSpellTargetUnit())



- TableTable by Bribe
- GetClosestWidget by Spinnaker



v1.5
- Added feature to remove the escort from system
- Improved documentation

v1.4
- Added 1 configuration to follow allied heroes or not

v1.3
- UnitAlive native removed
- Non configurables isolated

v1.2
- Suggested by the moderator applied

v1.1
- Added many configurations
- RegisterPlayerUnitEvent removed


Keywords:
mckill2009, summon, escort, AI, dota, melee, ally, enemy, target, ai
Contents

SummonedEscort (Map)

Reviews
23:58, 27th Aug 2012 Magtheridon96: Approved. This is a very useful system that a lot of people would use and benefit from. Good job.

Moderator

M

Moderator

23:58, 27th Aug 2012
Magtheridon96:

Approved.
This is a very useful system that a lot of people would use and benefit from.
Good job.
 
Level 6
Joined
Oct 23, 2011
Messages
182
Cool system..
1 question. Does this work if summoner moves through some kind of waygate?


* Suggestions

1. Remove unneeded local variables
(Like moving set orderSum = GetUnitCurrentOrder(.sum) to GetUnitCurrentOrder(.sum) == 0)

2. Make the interval configurable. Also make 'searching for new master' option configurable

3.

JASS:
    static if LIBRARY_RegisterPlayerUnitEvent and AUTO then
    
        private static method s takes nothing returns nothing
            call thistype.summoned(GetSummoningUnit(),GetTriggerUnit())
        endmethod
        
        private static method onInit takes nothing returns nothing
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SUMMON,function thistype.s)
        endmethod
    endif

4. set angle = GetRandomReal(0,6)
why not GetRandomReal(0, 2 * bj_PI)?
 
Last edited:
Default melee game initialization for all players

@kStiyl
1) Yes it will move through waygates.
2) I deliberately did the locals that way coz Ima update this from time to time, like when enemy is sleeping, the summoned will attack, or return to base if no more master is around.
3) Interval is already configurable via 'delay', but Ima take note of the option for searching a new master, maybe a configurable boolexpr.
4) Im planing to remove the optional anyway :)...
5) Coz it's faster than multiplying...

@Mags
Im doing the GUI to tell people how to use it that way, also if you noticed, the summoning event is already there :)...
 
23:58, 27th Aug 2012
Magtheridon96: if orderSum==0 then

A unit always has a current order.
"Stop" is a valid order you know :p
0 is never a valid order I believe.
OMG!, stop jerking with suggestions ONE BY ONE and analyze the code fully dude!, that's very bad and I'm very dissapointed!

EDIT:
Besides I did that coz what if the summoning unit is attacking or channeling?, then it will break it's current order...and for the record
STOP!=0, but null==0 or the unit is doing NOTHING!

EDIT2:
and for the record, your suggestion (STOP==851972) will NOT WORK!, so please stop that non-sense and reverse it!
 
Last edited:
Man, I don't look for things one by one, I sometimes miss things and find only 1 problem with the code, hence the short reviews. Other times, I can find a hundred problems, and so I try to list about 20 or so of them.

I never insinuated that OrderId("stop") is equal to 0, I was just wondering why you were comparing the order to a null one.
 
If this is a long code then I understand but this is just a short code, so I have faith in you to find the problem in 1 or 2 checks...

Bcoz null orders is 0 or doing nothing...

- The isolation Ima do...
- I know that, I just put the native so that I dont have to put not IsUnitType(u, UNIT_TYPE_DEAD) everytime I make a spell...
- I believe the prop window is a waste of time since it's rarely used in wc3 and the code doesnt execute if it's order!=0...
 
Top