• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Fire Escort v1.2


DESCRIPTION:
167625-albums4356-picture92285.jpg


PREVIEW 1:
FireEscort%20-%20Anim_zps129sapn2.gif


PREVIEW 2:
FireEscort%20-%20Anim2_zpstaoigpe0.gif


JASS:
/*
=====Spell Name: Fire Escort v1.2
=====Created by: Mckill2009

REQUIRES:
- JassNewGenPack by Vexorian
- SpellEffectEvent by Bribe

OPTIONAL REQUIREMENTS:
- BoundSentinel by Vexorian

HOW TO INSTALL:
- Copy ALL the custom units/abilities/buff from the object editor to your map.
- This has custom buff to remove the stun buff from Firebolt spell.
- Copy ALL that is inside the folder 'FireEscort' to your trigger editor.
- If you want to make your own units/abilities/buff, make sure you change the rawID indicated in the code.
- If you change the DUMMY_SPELL_ID rawID, make sure it will be casted by ORDER_ID.
- To view rawID, press CTRL+D in the object editor.
- Save and Done!
*/

library FireEscort uses SpellEffectEvent optional BoundSentinel

globals
    /**************************************************************
    *   RAW IDs: change the IDs according to your object editor
    *   The DUMMY_SPELL_ID and ORDER_ID must match!
    ***************************************************************/ 
    private constant integer              SPELL_ID = 'A002' //Howl of Terror custom spell (rawID)
    private constant integer        DUMMY_SPELL_ID = 'A000' //Firebolt custom spell (rawID)
    private constant integer              ORDER_ID = 852231 //Firebolt (orderID)
    private constant integer              DUMMY_ID = 'h000' //rawID
    private constant integer               BOMB_ID = 'h001' //rawID
    /**************************************************************
    *   CONFIGURABLE GLOBALS
    ***************************************************************/   
    //It is recommended not to exceed 2, you may use negative values
    private constant real           ROTATION_SPEED = 0.05
    
    //Fireball move speed towards the caster or target
    private constant real             FOLLOW_SPEED = 7    
    
    //Fireballs MAX offset from dummy's center
    private constant real             EXPAND_RANGE = 300
    
    //Fireballs move speed away from hero to EXPAND_RANGE
    private constant real             EXPAND_SPEED = 1
    
    //Sets how many seconds the dummy will search for new target
    private constant real   TARGET_SEARCH_INTERVAL = 5
    
    //Cast interval of the Fireballs
    private constant real    MISSILE_CAST_INTERVAL = 1
    
    //The FireballLifeDuration will start here, when fireballs at MAX height
    private constant real           MAX_FLY_HEIGHT = 400
    
    //Fly speed from ground to MAX_FLY_HEIGHT
    private constant real                FLY_SPEED = 2
    /**************************************************************
    *   NON-CONFIGURABLE GLOBALS
    ***************************************************************/     
    private constant real                 INTERVAL = 0.03125
    private group g = CreateGroup()
    private integer count = 0
    private unit TempU = null
endglobals

/**************************************************************
    CONFIGURABLES
***************************************************************/
private function FilterEnemies takes unit u returns boolean
    return not (IsUnitType(u, UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MECHANICAL) or IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE))
endfunction

private function FireballLifeDuration takes integer level returns real
    return 5. * level + 5 //10/15/20/25/30
endfunction

//Range of Fireball casting Firebolt
private function FireballCastRange takes integer level returns real
    return 150. * level + 250 //400/550/700/850/1000
endfunction

//How many fireballs created
private function FireballCount takes integer level returns integer
    return 2 * level + 4 //6/8/10/12/14
endfunction

//Fireballs search for nearby enemies
private function SearchEnemyRange takes integer level returns real
    return 200. * level + 600   //800/1000/1200/1400/1600
endfunction

/**************************************************************
    NON-CONFIGURABLES
***************************************************************/
private function UnitAlive takes unit u returns boolean
    return not IsUnitType(u, UNIT_TYPE_DEAD) and u!=null
endfunction

private function GetDistance takes real x1, real y1, real x2, real y2 returns real
    return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)
endfunction
    
private function FilterThem takes unit caster, unit target returns boolean
    return UnitAlive(target) and IsUnitEnemy(caster, GetOwningPlayer(target)) and FilterEnemies(target)
endfunction

private function FilterTarget takes nothing returns boolean
    return FilterThem(TempU, GetFilterUnit())
endfunction

private function EnumUnits takes nothing returns nothing
    set count = count+1
endfunction

private function SetUnitXY takes unit u, real x, real y, real angle returns nothing
    call SetUnitX(u, x + FOLLOW_SPEED * Cos(angle))
    call SetUnitY(u, y + FOLLOW_SPEED * Sin(angle))
endfunction

private function GetRandomUnitInArea takes real x, real y, real aoe, boolexpr b returns unit
    local integer ran
    call GroupEnumUnitsInRange(g, x, y, aoe, b)
    set count = 0
    call ForGroup(g, function EnumUnits)   
    set ran = GetRandomInt(1, count)
    set count = 0
    loop
        set TempU = FirstOfGroup(g)
        exitwhen TempU==null
        set count = count + 1
        if count==ran then
            exitwhen true
        endif        
        call GroupRemoveUnit(g, TempU)
    endloop
    call GroupClear(g)
    return TempU
endfunction

private struct EscortRotation
    unit leader
    unit escort
    real angle
    real aoe
    real expand
    real height
    real duration
    real castInterval
    
    static timer t = CreateTimer()
    static integer index = 0
    static integer array indexAR
    
    static method periodic takes nothing returns nothing
        local thistype this
        local integer i = 0
        local unit tar
        local real xEscort
        local real yEscort
        local real xLeader
        local real yLeader
        loop
            set i = i + 1
            set this = indexAR[i]
            if .duration > 0 then
                set .angle = .angle + ROTATION_SPEED
                set xLeader = GetUnitX(.leader)
                set yLeader = GetUnitY(.leader)
                if EXPAND_RANGE > .expand then
                    set .expand = .expand + EXPAND_SPEED
                endif
                if MAX_FLY_HEIGHT > .height then
                    set .height = .height + FLY_SPEED
                    call SetUnitFlyHeight(.escort, .height, 0)
                elseif .height >= MAX_FLY_HEIGHT then
                    set .duration = .duration - INTERVAL
                    set .castInterval = .castInterval + INTERVAL
                    if .castInterval > MISSILE_CAST_INTERVAL then
                        set .castInterval = 0
                        set xEscort = GetUnitX(.escort)
                        set yEscort = GetUnitY(.escort)
                        set TempU = .escort
                        set tar = GetRandomUnitInArea(xEscort, yEscort, .aoe, Filter(function FilterTarget))
                        if tar!=null then
                            call IssueTargetOrderById(.escort, ORDER_ID, tar)
                            set tar = null
                        endif                 
                    endif
                endif
                call SetUnitX(.escort, xLeader + .expand * Cos(.angle))
                call SetUnitY(.escort, yLeader + .expand * Sin(.angle))                
            else
                if UnitAlive(.leader) then
                    call KillUnit(.leader)
                endif
                call KillUnit(.escort)
                set .leader = null
                set .escort = null
                call .destroy()
                set indexAR[i] = indexAR[index]
                set indexAR[index] = this
                set i = i - 1
                set index = index - 1    
                if index==0 then
                    call PauseTimer(t)
                endif
            endif
            exitwhen i==index
        endloop
    endmethod
    
    static method register takes unit leader, unit escort, real angle, real duration, real aoe returns nothing
        local thistype this = allocate()
        set .leader = leader
        set .escort = escort
        set .angle = angle*bj_DEGTORAD  
        set .expand = 0
        set .castInterval = 0
        set .height = 0
        set .duration = duration
        set .aoe = aoe
        if index==0 then
            call TimerStart(t, INTERVAL, true, function thistype.periodic)
        endif
        set index = index + 1
        set indexAR[index] = this
    endmethod
endstruct

private struct Cast
    unit caster
    unit target
    unit dummy
    real search
    real aoe
    real goBack
    real height
    
    static timer t = CreateTimer()
    static integer index = 0
    static integer array indexAR
    
    static method periodic takes nothing returns nothing
        local thistype this
        local integer i = 0
        local unit first
        local real xCaster
        local real yCaster
        local real xDum
        local real yDum
        loop
            set i = i+1
            set this = indexAR[i]
            if UnitAlive(.caster) and UnitAlive(.dummy) then
                set xCaster = GetUnitX(.caster)
                set yCaster = GetUnitY(.caster)
                set xDum = GetUnitX(.dummy)
                set yDum = GetUnitY(.dummy)
                if .target==null then
                    //search for enemies from dummy's aoe
                    set .target = GetRandomUnitInArea(xCaster, yCaster, .aoe, Filter(function FilterTarget))
                    if .target==null then //back to caster
                        call SetUnitXY(.dummy, xDum, yDum, Atan2(yCaster - yDum, xCaster - xDum))
                    endif
                else
                    //this is not the height of the fireballs
                    set .height = .height + FLY_SPEED
                    if .height >= MAX_FLY_HEIGHT then
                        if GetDistance(xCaster, yCaster, xDum, yDum) > .goBack then
                            call SetUnitXY(.dummy, xDum, yDum, Atan2(yCaster - yDum, xCaster - xDum))
                        else
                            //goes targets if caster is also near the target's aoe
                            call SetUnitXY(.dummy, xDum, yDum, Atan2(GetUnitY(.target) - yDum, GetUnitX(.target) - xDum))
                        endif
                    else
                        call SetUnitXY(.dummy, xDum, yDum, Atan2(yCaster - yDum, xCaster - xDum))
                    endif
                    
                    //This refreshes/nulls the targets to search for a new one
                    set .search = .search + INTERVAL
                    if .search > TARGET_SEARCH_INTERVAL then
                        set .search = 0
                        set .target = null
                    endif
                endif
            else //End the Spell
                if UnitAlive(.dummy) then
                    call KillUnit(.dummy)
                endif
                set .caster = null
                set .target = null
                set .dummy = null
                call .destroy()
                set indexAR[i] = indexAR[index]
                set indexAR[index] = this
                set i = i - 1
                set index = index - 1    
                if index==0 then
                    call PauseTimer(t)
                endif                
            endif
            exitwhen i==index
        endloop
    endmethod
    
    static method onCast takes nothing returns nothing
        local thistype this = allocate()
        local integer level
        local integer i = 0
        local integer countEscorts
        local real g = 0
        local real gap
        local real fireRange
        local unit bomb
        local real duration
        set .caster = GetTriggerUnit()
        set level = GetUnitAbilityLevel(.caster, SPELL_ID)
        set .target = null
        set .dummy = CreateUnit(GetTriggerPlayer(), DUMMY_ID, GetUnitX(.caster), GetUnitY(.caster), 0)
        set .search = 0
        set .aoe = SearchEnemyRange(level)
        set .goBack = .aoe*.aoe
        set .height = 0
        set countEscorts = FireballCount(level)
        set duration = 120. //FireballLifeDuration(level)
        set fireRange = FireballCastRange(level)        
        set gap = (360/countEscorts)
        loop
            exitwhen i==countEscorts
            set bomb = CreateUnit(GetTriggerPlayer(), BOMB_ID, 0, 0, 0)
            call SetUnitFlyHeight(bomb, 0, 0)
            call UnitAddAbility(bomb, DUMMY_SPELL_ID)
            call SetUnitAbilityLevel(bomb, DUMMY_SPELL_ID, level)
            call EscortRotation.register(.dummy, bomb, g, duration, fireRange)
            set g = g + gap
            set i = i + 1
        endloop  
        if index==0 then
            call TimerStart(t, INTERVAL, true, function thistype.periodic)
        endif
        set index = index + 1
        set indexAR[index] = this
        set bomb = null
    endmethod 
    
    static method onInit takes nothing returns nothing
        local unit u = CreateUnit(Player(15), DUMMY_ID,0,0,0)
        call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)        
        call UnitAddAbility(u, DUMMY_SPELL_ID)
        call KillUnit(u)
        set u = null
    endmethod   
endstruct

endlibrary


v1.2
- Improved random units

v1.1a
- Fixed Documentation and big bug

v1.1
- Description changed to random units
- Added BoundSentinel
- Stun buff removed
- More configurables and explained



- JassNewGenPack by Vexorian
- SpellEffectEvent by Bribe
- BoundSentinel by Vexorian


Keywords:
pet, escort, fire, bolt, mage, diablo, dragonball, ai, vortex, stun, mckill2009
Contents

FireEscort (Map)

Reviews
21st May 2012 Bribe: I recommend that you prefix the "SE" textmacro for the sake of relevance. Spell and code look good, approved.

Moderator

M

Moderator

21st May 2012
Bribe: I recommend that you prefix the "SE" textmacro for the sake of relevance.

Spell and code look good, approved.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
Just like Wisp's Spirits,is it??
Hey,do you want to join me to make Wisp's(dota)Spell Pack???
i know how to make Relocate and Tether...i made them Months ago(its mui i think cause it doesnt have any waits)
would you like too?
for that ill give it 4/5 because its like you got it from other Spells
After 10 days actually ^^
 
Level 9
Joined
Aug 7, 2009
Messages
380
The Fireballs move quite "un-smoothy"
And i think i would prefer to use the Locust ability if it doesn't have some more advances :D Like:
The caster has a sub-ability to call the balls to target a unit
Or
Switching them between defensive and offensive. In defensive, they heal allies. Offensive, they attack
etc...
Just my opinion to improve the spell. Hope you welcome it :D
PM me when the next update comes, cause i don't want to vote for a bad result :p

=======Edit=======
Oh... and i think i found a bug. When the balls goes out the borders of the map (playable area), my warcraft III crashes. (it happens twice so... check out for it :D) (but maybe it because my comp is sucks :( )
 
Top