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

Starfall v1.0

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
This is an attempt to replicate the Nightelf's Priestes of the Moon ability Starfall (AEsf) as close as possible. Unfortunately I could not find the path to the mdl file for the stars for AEsf (only the Starfall<Effect>) which wasn't useful because it destroies itself automatically.

Requires:
* xefx (which requires xebasic and xedummy)
* TimerUtils
* GroupUtils
* UnitZ

JASS:
library StarfallSpell initializer init uses /*
*/  xebasic    /* http://www.wc3c.net/showthread.php?t=101150
*/, xedummy    /* -//-
*/, xefx       /* -//-
*/, TimerUtils /* http://www.wc3c.net/showthread.php?t=101322
*/, GroupUtils /* http://www.wc3c.net/showthread.php?t=104464
*/, UnitZ      /* http://www.hiveworkshop.com/forums/jass-resources-412/snippet-getterrainz-unitz-236942/
*/

globals
private constant integer SPELL_ID       = 'A000'
private constant real    INITIAL_BEHIND = 200
private constant real    INITIAL_Z      = 1200
private constant real    FALLING_SPEED  = 1000
private constant string  MODEL          = "Abilities\\Spells\\Undead\\DevourMagic\\DevourMagicBirthMissile.mdl"
private constant real    SCALE          = 3.0
// "Abilities\\Spells\\NightElf\\Starfall\\StarfallTarget.mdl"
//"Abilities\\Weapons\\Mortar\\MortarMissile.mdl"
endglobals

private function IsUnitInvulnerable takes unit u returns boolean
    return GetUnitAbilityLevel(u, 'Avul') > 0 or IsUnitLoaded(u)
endfunction

private function targets_allowed takes unit caster, unit target returns boolean
    return not IsUnitType(target, UNIT_TYPE_DEAD)           /*
*/     and     IsUnitEnemy(target, GetOwningPlayer(caster)) /*
*/     and not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)   /*
*/     and not IsUnitInvulnerable(target)
endfunction



private function distance_between takes unit u1, unit u2 returns real
    local real x1 = GetUnitX(u1)
    local real y1 = GetUnitY(u1)
    local real x2 = GetUnitX(u2)
    local real y2 = GetUnitY(u2)
    local real dx = x2 - x1
    local real dy = y2 - y1
    return SquareRoot(dx * dx + dy * dy)
endfunction

private function say takes string s returns nothing
    call BJDebugMsg(s)
    // call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, s)
endfunction

globals
private          hashtable       HT = InitHashtable()
private constant real            DT = 0.03125

private real tx
private real ty
private real tz
private real dx
private real dy
private real dz
private real dist_behind
private real vlen
private real mvlen

private          real      array random_delays
private constant integer         random_delays_count  = 65
endglobals
private function init_random_delays takes nothing returns nothing
    set random_delays[0] = 0
    set random_delays[1] = 16
    set random_delays[2] = 31
    set random_delays[3] = 47
    set random_delays[4] = 63
    set random_delays[5] = 78
    set random_delays[6] = 94
    set random_delays[7] = 109
    set random_delays[8] = 125
    set random_delays[9] = 141
    set random_delays[10] = 156
    set random_delays[11] = 172
    set random_delays[12] = 188
    set random_delays[13] = 203
    set random_delays[14] = 219
    set random_delays[15] = 234
    set random_delays[16] = 250
    set random_delays[17] = 266
    set random_delays[18] = 281
    set random_delays[19] = 297
    set random_delays[20] = 313
    set random_delays[21] = 328
    set random_delays[22] = 344
    set random_delays[23] = 359
    set random_delays[24] = 375
    set random_delays[25] = 391
    set random_delays[26] = 406
    set random_delays[27] = 422
    set random_delays[28] = 438
    set random_delays[29] = 453
    set random_delays[30] = 469
    set random_delays[31] = 485
    set random_delays[32] = 500
    set random_delays[33] = 516
    set random_delays[34] = 531
    set random_delays[35] = 547
    set random_delays[36] = 563
    set random_delays[37] = 578
    set random_delays[38] = 594
    set random_delays[39] = 609
    set random_delays[40] = 625
    set random_delays[41] = 641
    set random_delays[42] = 656
    set random_delays[43] = 672
    set random_delays[44] = 688
    set random_delays[45] = 703
    set random_delays[46] = 719
    set random_delays[47] = 735
    set random_delays[48] = 750
    set random_delays[49] = 766
    set random_delays[50] = 782
    set random_delays[51] = 797
    set random_delays[52] = 813
    set random_delays[53] = 828
    set random_delays[54] = 844
    set random_delays[55] = 859
    set random_delays[56] = 875
    set random_delays[57] = 891
    set random_delays[58] = 906
    set random_delays[59] = 922
    set random_delays[60] = 938
    set random_delays[61] = 953
    set random_delays[62] = 969
    set random_delays[63] = 984
    set random_delays[64] = 1000
endfunction


public  keyword Starfall
private keyword FallingStars


private struct FallingStar extends array

FallingStars fs
xefx         star

static method move_star takes nothing returns nothing
    local timer        t     = GetExpiredTimer()
    local thistype     this  = GetTimerData(t)
    local FallingStars fsx   = this.fs
    local real         speed = fsx.sf.speed
    local real angle_behind  = (GetUnitFacing(fsx.target) - 180) * bj_DEGTORAD

    set tx = GetUnitX(fsx.target)
    set ty = GetUnitY(fsx.target)
    set tz = GetUnitZ(fsx.target)

    set dx = tx - star.x
    set dy = ty - star.y
    set dz = tz - star.z

    set vlen = SquareRoot(dx * dx  + dy * dy + dz * dz)
    set mvlen = 1 / vlen
    set dx = dx * mvlen
    set dy = dy * mvlen
    set dz = dz * mvlen

    set star.x = star.x + dx * speed * DT
    set star.y = star.y + dy * speed * DT
    set star.z = star.z + dz * speed * DT

    set dx = tx - star.x
    set dy = ty - star.y
    set dz = tz - star.z
    set dist_behind = SquareRoot(dx * dx + dy * dy)

    set star.x = tx + Cos(angle_behind) * (dist_behind)
    set star.y = ty + Sin(angle_behind) * (dist_behind)

    set star.xyangle =  Atan2(ty - star.y, tx - star.x)
    set star.zangle  = -Atan((star.z - tz) / dist_behind)

    if vlen <= 32 then
        if not IsUnitInvulnerable(fsx.target) then
            call UnitDamageTarget(fsx.sf.caster, fsx.target, fsx.sf.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
        endif

        call ReleaseTimer(t)
        call star.destroy()
    endif

    set t = null
endmethod

private static thistype that = 0

static method fall takes FallingStars fs returns thistype
    local real     angle_behind
    local thistype this = that - (that / 8190) * 8190 + 1
    set that = this

    set this.fs = fs

    set angle_behind  = GetUnitFacing(fs.target) - 180
    set tx            = GetUnitX(fs.target) + Cos(angle_behind * bj_DEGTORAD) * fs.sf.initial_behind
    set ty            = GetUnitY(fs.target) + Sin(angle_behind * bj_DEGTORAD) * fs.sf.initial_behind

    set star          = xefx.create(tx, ty, (angle_behind + 180) * bj_DEGTORAD)
    set star.fxpath   = fs.sf.model
    set star.scale    = fs.sf.scale
    set star.z        = fs.sf.initial_z + GetUnitZ(fs.target)
    set star.zangle   = -Atan(star.z / fs.sf.initial_behind)

    call TimerStart(NewTimerEx(this), DT, true, function thistype.move_star)

    return this
endmethod

endstruct


private struct FallingStars extends array

Starfall sf
unit     target

private static thistype self
private static trigger  call_starfall


private static method star_fall takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local thistype this = GetTimerData(t)

    if sf.duration > 0 then
        if distance_between(sf.caster, target) <= sf.AOE and targets_allowed(sf.caster, target) then
            call FallingStar.fall(this)
        endif
        call ReleaseTimer(t)
        set self = this
        call TriggerExecute(call_starfall) // self.starfall(), i.e drop another star
    else
        call ReleaseTimer(t)
    endif

    set t = null
endmethod

private static method starfall takes nothing returns nothing
    local thistype this         = self
    local real     random_delay = random_delays[GetRandomInt(0, random_delays_count - 1)]
    local real     delay

    set delay = random_delay / 1000.0
    if GetRandomInt(1, 100) <= 50 then
        set delay = -delay
    endif
    set delay = sf.initial_delay + delay

    call TimerStart(NewTimerEx(this), delay, false, function thistype.star_fall)
endmethod

private static thistype that = 0

static method fall_over takes Starfall sf, unit target returns thistype
    local thistype this = that - (that / 8190) * 8190 + 1
    set that = this

    set this.sf     = sf
    set this.target = target

    set self = this
    call thistype.starfall()

    return this
endmethod

private static method onInit takes nothing returns nothing
    call ExecuteFunc(SCOPE_PRIVATE + "init_random_delays")

    set call_starfall = CreateTrigger()
    call TriggerAddAction(call_starfall, function thistype.starfall)
endmethod

endstruct


struct Starfall extends array

unit    caster
real    AOE
real    duration
real    damage
real    initial_delay
real    initial_behind
real    initial_z
real    speed
string  model
real    scale

private group affected_units
private boolean stopped

private static thistype self
private static trigger  call_starfall

private static method starfall_recur takes nothing returns nothing
    local timer    t    = GetExpiredTimer()
    local Starfall this = GetTimerData(t)

    set duration = duration - initial_delay
    if duration > 0 then
        call ReleaseTimer(t)
        set t = null

        set self = this
        call TriggerExecute(call_starfall) // call self.starfall()
        return
    else
        if not stopped then
            call IssueImmediateOrder(caster, "stop")
        endif
        call stop()
        call ReleaseTimer(t)
        set t = null
    endif
endmethod

private static method starfall takes nothing returns nothing
    local thistype this = self
    local unit  u

    call GroupUnitsInArea(ENUM_GROUP, GetUnitX(caster), GetUnitY(caster), AOE)
    loop
        set u = FirstOfGroup(ENUM_GROUP)
        exitwhen u == null
        call GroupRemoveUnit(ENUM_GROUP, u)

        if targets_allowed(caster, u) and not IsUnitInGroup(u, affected_units) then
            call GroupAddUnit(affected_units, u)
            call FallingStars.fall_over(this, u)
        endif
    endloop

    call TimerStart(NewTimerEx(this), initial_delay, false, function thistype.starfall_recur)
endmethod

private static thistype that = 0

static method cast takes unit caster, real AOE, real duration, real damage, real initial_delay, real initial_behind, real initial_z, real falling_speed, string model, real scale returns thistype
    local thistype this = that - (that / 8190) * 8190 + 1
    set that = this

    set this.caster         = caster
    set this.AOE            = AOE
    set this.duration       = duration
    set this.damage         = damage
    set this.initial_delay  = initial_delay
    set this.initial_behind = initial_behind
    set this.initial_z      = initial_z
    set this.speed          = falling_speed
    set this.model          = model
    set this.scale          = scale

    set affected_units = NewGroup()
    set stopped        = false

    call SetUnitAnimation(caster, "channel")
    set self = this
    call thistype.starfall() // call self.starfall()

    return this
endmethod

method stop takes nothing returns nothing
    if stopped then
        return
    endif
    set stopped = true
    set duration = 0
    call ReleaseGroup(affected_units)
endmethod

private static method onInit takes nothing returns nothing
    set call_starfall = CreateTrigger()
    call TriggerAddAction(call_starfall, function thistype.starfall)
endmethod

endstruct


private function on_spell_effect takes nothing returns boolean
    local Starfall sf
    local unit     caster
    local integer  spell_level
    local real     AOE
    local real     damage
    local real     duration
    local real     initial_delay
    local real     initial_behind
    local real     initial_z
    local real     falling_speed
    local string   model
    local real     scale

    if GetSpellAbilityId() != SPELL_ID then
        return false
    endif

    set caster         = GetTriggerUnit()
    set spell_level    = GetUnitAbilityLevel(caster, SPELL_ID)

    set AOE            = 1000
    set duration       = 30    // seconds
    set damage         = 50
    set initial_delay  = 1.5
    set initial_behind = INITIAL_BEHIND
    set initial_z      = INITIAL_Z
    set falling_speed  = FALLING_SPEED
    set model          = MODEL
    set scale          = SCALE

    set sf                    /*
*/      = Starfall.cast(      /*
*/          caster            /*
*/        , AOE               /*
*/        , duration          /*
*/        , damage            /*
*/        , initial_delay     /*
*/        , initial_behind    /*
*/        , initial_z         /*
*/        , falling_speed     /*
*/        , model             /*
*/        , scale             /*
*/      )
    call SaveInteger(HT, GetHandleId(GetTriggerUnit()), SPELL_ID, sf)

    set caster = null
    return false
endfunction

private function on_end_cast takes nothing returns boolean
    local integer sf
    if GetSpellAbilityId() == SPELL_ID then
        set sf = LoadInteger(HT, GetHandleId(GetTriggerUnit()), SPELL_ID)
        call Starfall(sf).stop()
        call FlushChildHashtable(HT, GetHandleId(GetTriggerUnit()))
    endif
    return false
endfunction


private function init takes nothing returns nothing
    local trigger t

    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function on_spell_effect))

    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
    call TriggerAddCondition(t, Condition(function on_end_cast))
endfunction

endlibrary



library Rockets initializer init uses StarfallSpell

globals
private constant integer SPELL_ID       = 'A001'
private constant real    INITIAL_BEHIND = 500
private constant real    INITIAL_Z      = 800
private constant real    FALLING_SPEED  = 700
private constant string  MODEL          = "Abilities\\Weapons\\Mortar\\MortarMissile.mdl"
private constant real    SCALE          = 1.0
endglobals

globals
private hashtable HT = InitHashtable() // might be a good idea to use a SPELL_HT for the whole map
endglobals


private function on_spell_effect takes nothing returns boolean
    local StarfallSpell_Starfall sf
    local unit     caster
    local integer  spell_level
    local real     AOE
    local real     damage
    local real     duration
    local real     initial_delay
    local real     initial_behind
    local real     initial_z
    local real     falling_speed
    local string   model
    local real     scale

    if GetSpellAbilityId() != SPELL_ID then
        return false
    endif

    set caster         = GetTriggerUnit()
    set spell_level    = GetUnitAbilityLevel(caster, SPELL_ID)

    set AOE            = 1000
    set duration       = spell_level * 5
    set damage         = 50
    set initial_delay  = 0.5
    set initial_behind = INITIAL_BEHIND
    set initial_z      = INITIAL_Z
    set falling_speed  = FALLING_SPEED
    set model          = MODEL
    set scale          = SCALE

    set sf                    /*
*/      = StarfallSpell_Starfall.cast(      /*
*/          caster            /*
*/        , AOE               /*
*/        , duration          /*
*/        , damage            /*
*/        , initial_delay     /*
*/        , initial_behind    /*
*/        , initial_z         /*
*/        , falling_speed     /*
*/        , model             /*
*/        , scale             /*
*/      )
    call SaveInteger(HT, GetHandleId(GetTriggerUnit()), SPELL_ID, sf)

    set caster = null
    return false
endfunction

private function on_end_cast takes nothing returns boolean
    local integer sf
    if GetSpellAbilityId() == SPELL_ID then
        set sf = LoadInteger(HT, GetHandleId(GetTriggerUnit()), SPELL_ID)
        call StarfallSpell_Starfall(sf).stop()
        call FlushChildHashtable(HT, GetHandleId(GetTriggerUnit()))
    endif
    return false
endfunction


private function init takes nothing returns nothing
    local trigger t

    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function on_spell_effect))

    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
    call TriggerAddCondition(t, Condition(function on_end_cast))
endfunction

endlibrary

Keywords:
Starfall, Nieghtelf, Spell, Ability
Contents

Just another Warcraft III map (Map)

Reviews
12th Dec 2015 IcemanBo: For long time as NeedsFix. Rejected. 15:47, 7th Jan 2015 IcemanBo: http://www.hiveworkshop.com/forums/spells-569/starfall-v1-0-a-259186/#post2637089
  • Please follow http://www.hiveworkshop.com/forums/...80/jpag-jass-proper-application-guide-204383/.
  • Add useful documentation.
  • Scopes are used for spells. Library usually for systems, that it can be used by seen by other functions.
  • You actullly provide a library and two example of how to use it. Sperate the Starfall example from your system.
  • Remove not needed stuff from your system like private function say.
  • The "random" delay is actually not random. It's pre defined.
  • Add more configuration. Like attack- damage type.
  • Take in consideration of using Table instead of hashtable.
  • Take in consideration of using one loop for all instances in a callback function of one global timer.
  • WEAPON_TYPE_WHOKNOWS -> null.
  • Released timers don't need to be nulled. They are recycled.
  • There must be a decent tooltip with spell description.
Needs Fix
 
Top