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

Frozen Orb

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: Kakerate and Jay-B
Frozen Orb

An intimidating sight that strikes fear into the hearts of her opponents, the Frozen Orb is an awesome spectacle to behold. The Orb coalesces from the air, unleashing freezing bolts at all nearby, and wreaking havoc, seemingly at random, before bursting into a brilliant explosion of frigid destruction. --Blizzard North

This is an attempt to recreate the Diablo 2 sorceress' cold skill Frozen Orb in Warcraft 3.

JASS:
library FrozenOrb

globals
    private constant integer ABILITY_ID = 'A000'
    private constant integer DUMMY_ID = 'e000'

    private constant boolean APPLY_BUFF = true
    private constant integer BUFF_ID = 'A001' // must be based on Slow Aura('Aasl')

    private constant real DT = 1.0 / 32.0

    // We divide the unit circle into this many slices so that we can pick random directions
    // for ice bolts to travel in.
    private constant integer MAX_DIRECTIONS = 16

    // The offset height for the orb and the frost bolts.
    private constant real Z_OFFSET = 80.0

    // Every this amount of seconds the orb spawns <frost_bolt_count_on_orb_spawn> many frost bolts.
    private constant real ORB_FROST_BOLT_SPAWN_DELAY = 0.75

    // When the orb explodes it spawns ice bolts that follow a curved path.
    private constant real FROST_BOLT_CURVE_ANGLE = 90.0 // in degrees
    private constant real FROST_BOLT_CURVE_SPEED = 180.0

    // UnitDamageTarget arguments
    private constant boolean MELEE_ATTACK = false
    private constant boolean RANGE_ATTACK = true
    private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL // spell
    private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL // ignore armor value
    private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS

    private constant boolean ENABLE_UNIT_FREEZING = true
    private constant boolean REDUCE_MOVE_SPEED = true
    private constant real MOVE_SPEED_REDUCTION = 0.50 // 50%
    private constant boolean CHANGE_VERTEX_COLOR_ON_FREEZE = true
endglobals

globals
    private real MIN_X
    private real MAX_X
    private real MIN_Y
    private real MAX_Y
endglobals
private function set_bounds takes nothing returns nothing
    local rect r = GetPlayableMapRect()
    set MIN_X = GetRectMinX(r)
    set MAX_X = GetRectMaxX(r)
    set MIN_Y = GetRectMinY(r)
    set MAX_Y = GetRectMaxY(r)
endfunction

native UnitAlive takes unit u returns boolean

private function targets_allowed takes FrozenOrb instance, unit u returns boolean
    local integer ut = GetUnitTypeId(u)

    if ut == 'nwen' then
        // wendigos are immune to cold =)
        return false
    endif

    return UnitAlive(u) and IsUnitEnemy(u, instance.caster_owner)
endfunction

private function frost_bolt_damage takes FrozenOrb frost_bolt returns real
    return 25.0 + 5.0 * frost_bolt.ability_level // 30.0, 35.0, 40.0
endfunction

private function orb_speed takes FrozenOrb orb returns real
    // can use orb.ability_level for level specific speed
    return 275.0
endfunction
private function frost_bolt_speed takes FrozenOrb frost_bolt returns real
    // can use frost_bolt.ability_level for level specific speed
    return 600.0
endfunction

private function orb_max_distance takes FrozenOrb orb returns real
    return 900.0
endfunction
private function frost_bolt_max_distance takes FrozenOrb frost_bolt returns real
    return 1000.0
endfunction

private function frost_bolt_count_on_orb_spawn takes FrozenOrb orb returns integer
    return 3
endfunction
private function frost_bolt_count_on_orb_explode takes FrozenOrb orb returns integer
    return MAX_DIRECTIONS // must be <= MAX_DIRECTIONS
endfunction

private function freeze_duration takes FrozenOrb instance returns real
    return 5.0
endfunction

globals
    private location loc = Location(0.0, 0.0)
endglobals
private function GetUnitZ takes unit u returns real
    call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
    return GetLocationZ(loc) + GetUnitFlyHeight(u)
endfunction
private function SetUnitZ takes unit u, real z returns nothing
    call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
    call SetUnitFlyHeight(u, z - GetLocationZ(loc), 0.0)
endfunction


// NOTE: you can rewrite these functions to use your dummy recycling scripts!
//
globals
    private unit created_dummy
endglobals
private function dummy_create takes player p, real x, real y, real z, real facing returns unit
    set created_dummy = CreateUnit(p, DUMMY_ID, x, y, facing)
    call SetUnitX(created_dummy, x)
    call SetUnitY(created_dummy, y)
    call SetUnitFacing(created_dummy, facing)

    call UnitAddAbility(created_dummy, 'Amrf')
    call UnitRemoveAbility(created_dummy, 'Amrf')
    call SetUnitZ(created_dummy, z)

    call UnitAddAbility(created_dummy, 'Aloc')
    call UnitRemoveAbility(created_dummy, 'Aloc')

    return created_dummy
endfunction
private function dummy_destroy takes unit u returns nothing
    call RemoveUnit(u)
endfunction


private struct Timer extends array
    readonly static integer max_count = 0

    private static Timer head = 0
    private Timer next

    private timer t
    integer data
    real timeout

    static method start takes integer user_data, real timeout, code callback returns Timer
        local Timer this

        if head != 0 then
            set this = head
            set head = head.next

        else
            set max_count = max_count + 1
            set this = max_count
            set this.t = CreateTimer()
        endif

        set this.next = 0
        set this.data = user_data
        set this.timeout = timeout

        call TimerStart(this.t, this, false, null)
        call PauseTimer(this.t)
        call TimerStart(this.t, timeout, false, callback)

        return this
    endmethod

    method stop takes nothing returns nothing
        call TimerStart(this.t, 0.0, false, null)
        set this.next = head
        set head = this
    endmethod

    static method get_expired_data takes nothing returns integer
        local Timer t = Timer( R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) )
        local integer data = t.data
        call t.stop()
        return data
    endmethod
endstruct


private struct Sound
    static thistype array heads
    thistype next

    sound s
    integer head
    boolean m_is_looping
    boolean m_is_3d
    real m_duration

    static string sound_file
    static boolean is_looping
    static boolean is_3d
    static boolean stop_when_out_of_range
    static integer fade_in_rate
    static integer fade_out_rate
    static string eax_setting
    static real duration

    static method preload_sound takes nothing returns nothing
        local Sound snd = Timer.get_expired_data()

        if snd.m_is_3d then
            call SetSoundPosition(snd.s, 0, 0, 0)
        endif
        call StartSound(snd.s)
        call StopSound(snd.s, /*kill-when-done:*/ false, /*fade-out:*/ false)

        set snd.next = heads[snd.head]
        set heads[snd.head] = snd
    endmethod

    static integer /*Sound.*/FROST_BOLT_HIT
    static integer /*Sound.*/ORB_MOVING
    static integer /*Sound.*/ORB_EXPLOSION

    static method onInit takes nothing returns nothing
        local Sound snd
        local sound s
        local integer i
        local integer j

        set i = 1
        loop
            exitwhen i > 3

            if i == 1 then
                set thistype.FROST_BOLT_HIT = 1
                set sound_file = "Abilities\\Spells\\Other\\FrostBolt\\FrostBoltLaunch1.wav"
                set duration = 1.1
                set is_looping = false
                set is_3d = true
                set stop_when_out_of_range = true
                set fade_in_rate = 10
                set fade_out_rate = 10
                set eax_setting = "MissilesEAX"

            elseif i == 2 then
                set thistype.ORB_MOVING = 2
                set sound_file = "Abilities\\Spells\\Other\\Doom\\DoomTarget.wav"
                set duration = 2.261
                set is_looping = true
                set is_3d = true
                set stop_when_out_of_range = true
                set fade_in_rate = 10
                set fade_out_rate = 10
                set eax_setting = "SpellsEAX"

            elseif i == 3 then
                set thistype.ORB_EXPLOSION = 3
                set sound_file = "Abilities\\Spells\\Other\\FrostBolt\\FrostBoltHit1.wav"
                set duration = 1.347
                set is_looping = false
                set is_3d = true
                set stop_when_out_of_range = true
                set fade_in_rate = 10
                set fade_out_rate = 10
                set eax_setting = "MissilesEAX"
            endif

            set j = 1
            loop
                exitwhen j > 4 // can't have more than 4 of the same sound playing at the same time

                set s = CreateSound(sound_file, is_looping, is_3d, stop_when_out_of_range, fade_in_rate, fade_out_rate, eax_setting)
                if is_3d then
                    call SetSoundDistances(s, /*min-dist */ 600, /*max-dist*/ 100000)
                    call SetSoundConeAngles(s, 0, 0, 0)
                    call SetSoundConeOrientation(s, 0, 0, 0)
                    call SetSoundVelocity(s, 0, 0, 0)
                endif

                set snd = Sound.create()
                set snd.s = s
                set snd.head = i
                set snd.m_duration = duration
                set snd.m_is_looping = is_looping
                set snd.m_is_3d = is_3d
                call Timer.start(snd, 0.0, function thistype.preload_sound)

                set j = j + 1
            endloop

            set i = i + 1
        endloop
    endmethod

    method kill takes nothing returns nothing
        if this == 0 then
            return
        endif

        call StopSound(this.s, /*kill-when-done:*/ false, /*fade-out:*/ true)

        set this.next = heads[this.head]
        set heads[this.head] = this
    endmethod

    static method kill_when_done takes nothing returns nothing
        call thistype(Timer.get_expired_data()).kill()
    endmethod

    static method get takes integer sid returns thistype
        local Sound snd

        if heads[sid] == 0 then
            return 0
        endif

        set snd = heads[sid]
        set heads[sid] = heads[sid].next

        call SetSoundChannel(snd.s, GetRandomInt(0, 11))

        if not snd.m_is_looping then
            call Timer.start(snd, snd.duration, function thistype.kill_when_done)
        endif

        return snd
    endmethod

    method volume takes real v returns thistype
        if this != 0 then
            call SetSoundVolume(this.s, R2I(v / 100.0 * 127.0))
        endif
        return this
    endmethod
    method cutoff takes real c returns thistype
        if this != 0 and this.is_3d then
            call SetSoundDistanceCutoff(this.s, c)
        endif
        return this
    endmethod
    method attach_to_unit takes unit u returns thistype
        if this != 0 then
            call AttachSoundToUnit(this.s, u)
        endif
        return this
    endmethod
    method set_xyz takes real x, real y, real z returns thistype
        if this != 0 then
            call SetSoundPosition(this.s, x, y, z)
        endif
        return this
    endmethod
    method play takes nothing returns thistype
        if this != 0 then
            call StartSound(this.s)
        endif
        return this
    endmethod
endstruct


private struct FreezeUnit
    static group G = CreateGroup()
    unit u
    effect e
    real ms_delta

    static method unfreeze takes nothing returns nothing
        local thistype this = Timer.get_expired_data()

        call GroupRemoveUnit(G, this.u)
static if APPLY_BUFF then
        call UnitRemoveAbility(u, BUFF_ID)
endif

static if REDUCE_MOVE_SPEED then
        call SetUnitMoveSpeed(u, GetUnitMoveSpeed(u) + this.ms_delta)
endif

        call DestroyEffect(this.e)

static if CHANGE_VERTEX_COLOR_ON_FREEZE then
        call SetUnitVertexColor(u, 255, 255, 255, 255)
endif

        call this.deallocate()
    endmethod

    static method freeze takes unit u, real ms_reduce_perc, real duration returns nothing
        local thistype this

        if IsUnitInGroup(u, G) then
            return
        endif

        set this = allocate()

        call GroupAddUnit(G, u)
static if APPLY_BUFF then
        call UnitAddAbility(u, BUFF_ID)
endif

        set this.u = u

static if REDUCE_MOVE_SPEED then
        set this.ms_delta = GetUnitDefaultMoveSpeed(u) * ms_reduce_perc
        call SetUnitMoveSpeed(u, GetUnitMoveSpeed(u) - this.ms_delta)
endif

        set this.e = AddSpecialEffectTarget("Abilities\\Spells\\Other\\FrostDamage\\FrostDamage.mdl", u, "origin")

static if CHANGE_VERTEX_COLOR_ON_FREEZE then
        call SetUnitVertexColor(u, 127, 190, 190, 255)
endif

        call Timer.start(this, duration, function thistype.unfreeze)
    endmethod
endstruct


globals
    private real dx
    private real dy
    private real dz
    private real v_len
endglobals
private function normalize_or_z takes real vx, real vy, real vz returns nothing
    set v_len = SquareRoot(vx * vx + vy * vy + vz * vz)
    if v_len > 0.0 then
        set dx = vx / v_len
        set dy = vy / v_len
        set dz = vz / v_len
    else
        set dx = 0.0
        set dy = 0.0
        set dz = 1.0
    endif
endfunction

struct FrozenOrb
    static thistype array ul // update list
    static integer ul_size = 0
    static timer ul_tmr = CreateTimer()
    static code update_func
    static group G = CreateGroup()

    static real array dirs
    static constant integer dirs_count = MAX_DIRECTIONS

    unit caster
    player caster_owner
    integer ability_level

    unit pu1
    effect pu1m

    // the orb is made of two parts, the rotating "sphere" and the bluish glowing light
    unit pu2
    effect pu2m

    // current position
    real px
    real py
    real pz

    // velocity
    real dpx
    real dpy
    real dpz
    real speed
    real speed_inc

     // target point
    real tx
    real ty
    real tz

    real dist = 0.0
    real max_dist

    real facing
    real end_facing
    real facing_inc

    Sound snd = 0

    real tick = 0.0

    real radius
    real damage
    real freeze_duration

    static method add_to_update_list takes thistype instance returns nothing
        set ul_size = ul_size + 1
        set ul[ul_size] = instance

        if ul_size == 1 then
            call TimerStart(ul_tmr, DT, true, update_func)
        endif
    endmethod

    static method remove_from_update_list takes integer i returns nothing
        set ul[i] = ul[ul_size]
        set ul_size = ul_size - 1
        if ul_size == 0 then
            call PauseTimer(ul_tmr)
        endif
    endmethod

    method orb_spawn_frost_bolts takes integer n, real curve_angle returns nothing
        local thistype orb = this
        local thistype fb // frost bolt
        local integer i
        local integer r
        local integer m
        local real dir
        local real a

        set i = 1
        set m = dirs_count
        loop
            exitwhen i > n // n must be <= m
            set r = GetRandomInt(1, m)
            set dir = dirs[r]
            set dirs[r] = dirs[m]
            set dirs[m] = dir
            set m = m - 1

            set fb = allocate()

            set fb.caster = orb.caster
            set fb.caster_owner = orb.caster_owner
            set fb.ability_level = orb.ability_level

            set fb.px = orb.px
            set fb.py = orb.py
            set fb.pz = orb.pz

            set fb.speed = frost_bolt_speed(fb.ability_level)
            set fb.speed_inc = fb.speed * DT
            set a = dir * bj_DEGTORAD
            set fb.dpx = Cos(a) * fb.speed_inc
            set fb.dpy = Sin(a) * fb.speed_inc
            set fb.dpz = 0.0

            set fb.max_dist = frost_bolt_max_distance(fb)
            // set fb.tx = fb.px + Cos(a) * fb.max_dist
            // set fb.ty = fb.py + Sin(a) * fb.max_dist
            // set fb.tz = fb.pz

            set fb.facing = dir
            set fb.end_facing = dir + curve_angle
            set fb.facing_inc = -FROST_BOLT_CURVE_SPEED * DT

            set fb.radius = 64.0
            set fb.damage = frost_bolt_damage(fb)
            set fb.freeze_duration = freeze_duration(fb)

            set fb.pu1 = dummy_create(fb.caster_owner, fb.px, fb.py, fb.pz, fb.facing)
            set fb.pu1m = AddSpecialEffectTarget("Abilities\\Weapons\\LichMissile\\LichMissile.mdl", fb.pu1, "origin")
            call SetUnitScale(fb.pu1, 0.70, 0.70, 0.70)
            set fb.pu2 = null
            set fb.pu2m = null

            call add_to_update_list(fb)

            set i = i + 1
        endloop
    endmethod

    // method destroy can't have arguments...
    method destroy2 takes real pos_backtrack returns nothing
        call DestroyEffect(this.pu1m)
        call dummy_destroy(this.pu1)

        if this.pu2 != null then // orb
            call DestroyEffect(this.pu2m)
            call dummy_destroy(this.pu2)

            call Sound.get(Sound.ORB_EXPLOSION).volume(100).cutoff(3000.0).set_xyz(this.px, this.py, this.pz).play()

            set this.px = this.px - Cos(this.facing * bj_DEGTORAD) * pos_backtrack
            set this.py = this.py - Sin(this.facing * bj_DEGTORAD) * pos_backtrack
            call this.orb_spawn_frost_bolts(frost_bolt_count_on_orb_explode(this), -FROST_BOLT_CURVE_ANGLE)
        endif

        if this.snd != 0 then
            call this.snd.kill()
        endif

        call this.deallocate()
    endmethod

    static method update takes nothing returns nothing
        local thistype this
        local integer i
        local boolean is_orb
        local real a
        local unit u
        local boolean has_hit
        local real z

        set i = 1
        loop
            exitwhen i > ul_size
            set this = ul[i]
            set is_orb = this.pu2 != null

            set this.dist = this.dist + this.speed_inc

            call MoveLocation(loc, this.px, this.py)

            if this.dist > this.max_dist /*
            */ or this.px < MIN_X or this.px > MAX_X or this.py < MIN_Y or this.py > MAX_Y /*
            */ or this.pz - GetLocationZ(loc) < 32.0 then
                call remove_from_update_list(i)
                set i = i - 1
                if is_orb then
                    call this.destroy2(64.0)
                else
                    call this.destroy2(0.0)
                endif
            else

                set this.px = this.px + this.dpx
                set this.py = this.py + this.dpy
                set this.pz = this.pz + this.dpz

                call SetUnitX(this.pu1, this.px)
                call SetUnitY(this.pu1, this.py)
                call MoveLocation(loc, this.px, this.py)
                set z = this.pz - GetLocationZ(loc)
                call SetUnitFlyHeight(this.pu1, z, 0.0)

                call GroupEnumUnitsInRange(G, this.px, this.py, this.radius, null)
                set has_hit = false
                loop
                    set u = FirstOfGroup(G)
                    exitwhen u == null
                    call GroupRemoveUnit(G, u)

                    if targets_allowed(this, u) then
                        if is_orb then
                            call UnitDamageTarget(this.caster, u, this.damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)

                        elseif not has_hit then // frost bolt
                            call Sound.get(Sound.FROST_BOLT_HIT).volume(100).cutoff(3000.0).attach_to_unit(u).play()
                            call UnitDamageTarget(this.caster, u, this.damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                            call remove_from_update_list(i)
                            set i = i - 1
                            call this.destroy2(0.0)
                            set has_hit = true
                        endif

static if ENABLE_UNIT_FREEZING then
                        call FreezeUnit.freeze(u, MOVE_SPEED_REDUCTION, this.freeze_duration)
endif

                    endif
                endloop

                if is_orb then
                    call SetUnitX(this.pu2, this.px)
                    call SetUnitY(this.pu2, this.py)
                    call SetUnitFlyHeight(this.pu2, z, 0.0)

                    set this.tick = this.tick + DT
                    if this.tick > ORB_FROST_BOLT_SPAWN_DELAY then
                        set this.tick = 0.0
                        call this.orb_spawn_frost_bolts(frost_bolt_count_on_orb_spawn(this), 0.0)
                    endif

                else // frost bolt

                    if this.facing > this.end_facing then
                        set this.facing = this.facing + this.facing_inc
                        call SetUnitFacing(this.pu1, this.facing)
                        set a = this.facing * bj_DEGTORAD
                        set this.dpx = Cos(a) * this.speed_inc
                        set this.dpy = Sin(a) * this.speed_inc
                    endif

                endif
            endif

            set i = i + 1
        endloop

        set u = null
    endmethod

    static method create_orb takes thistype caster_info returns thistype
        local thistype orb = allocate()

        set orb.caster = caster_info.caster
        set orb.caster_owner = GetOwningPlayer(orb.caster)
        set orb.ability_level = caster_info.ability_level

        set orb.px = GetUnitX(orb.caster)
        set orb.py = GetUnitY(orb.caster)
        set orb.pz = GetUnitZ(orb.caster) + Z_OFFSET

        set orb.tx = caster_info.tx
        set orb.ty = caster_info.ty
        set orb.tz = orb.pz

        call normalize_or_z(orb.tx - orb.px, orb.ty - orb.py, orb.tz - orb.pz)
        set orb.speed = orb_speed(orb.ability_level)
        set orb.speed_inc = orb.speed * DT
        set orb.dpx = dx * orb.speed * DT
        set orb.dpy = dy * orb.speed * DT
        set orb.dpz = 0.0

        set orb.max_dist = orb_max_distance(orb)

        set orb.facing = Atan2(orb.dpy, orb.dpx) * bj_RADTODEG
        set orb.end_facing = orb.facing

        set orb.pu1 = dummy_create(orb.caster_owner, orb.px, orb.py, orb.pz, orb.facing)
        set orb.pu1m = AddSpecialEffectTarget("Abilities\\Spells\\Human\\ManaFlare\\ManaFlareTarget.mdl", orb.pu1, "origin")
        call SetUnitScale(orb.pu1, 5.0, 5.0, 5.0)

        set orb.pu2 = dummy_create(orb.caster_owner, orb.px, orb.py, orb.pz, orb.facing)
        set orb.pu2m = AddSpecialEffectTarget("Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl", orb.pu2, "origin")
        call SetUnitScale(orb.pu2, 4.0, 4.0, 4.0)

        set orb.snd = Sound.get(Sound.ORB_MOVING).volume(100).cutoff(3000.0).attach_to_unit(orb.pu1).play()

        set orb.radius = 128.0
        set orb.damage = 0.333 * frost_bolt_damage(orb) * DT
        set orb.freeze_duration = freeze_duration(orb)

        call orb.orb_spawn_frost_bolts(frost_bolt_count_on_orb_spawn(orb), 0.0)
        call add_to_update_list(orb)

        return orb
    endmethod

    static method reset_time_scale takes nothing returns nothing
        local thistype caster_info = Timer.get_expired_data()
        call SetUnitTimeScale(caster_info.caster, 1.0)
        call caster_info.deallocate()
    endmethod

    static method on_spell_effect takes nothing returns nothing
        local thistype caster_info = allocate()
        local unit u = GetTriggerUnit()

        set caster_info.caster = u
        set caster_info.ability_level = GetUnitAbilityLevel(u, GetSpellAbilityId())
        set caster_info.tx = GetSpellTargetX()
        set caster_info.ty = GetSpellTargetY()

        // Jaina's spell animation is a bit slow, let's make it as if she had a "faster cast rate"
        call SetUnitTimeScale(u, 4.0)
        call Timer.start(caster_info, 0.4, function thistype.reset_time_scale)

        call create_orb(caster_info)

        set u = null
    endmethod

    static method cast takes nothing returns nothing
        if GetSpellAbilityId() != ABILITY_ID then
            return
        endif
        call on_spell_effect()
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t
        local real dir
        local real step
        local integer i

        call set_bounds()

        set dir = 0.0
        set step = 360.0 / dirs_count
        set i = 0
        loop
            exitwhen i > dirs_count
            set dirs[i] = dir
            set dir = dir + step
            set i = i + 1
        endloop

        set update_func = function thistype.update

        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction(t, function thistype.cast)
    endmethod
endstruct

endlibrary
Contents

Frozen Orb (Map)

Reviews
KILLCIDE
Needs Fixed Include import instructions and credits (if there are any) in both the description and ingame Have a proper ingame tooltip Suggestions For the sanity of the Code Moderators and Spell Reviewers, please use JPAG The demo map is definitely...
Things that are meant to be configured should be a bit seperated from code that isn't meant to be configured.

I personaly find it bad you have a sound struct and a timer structute included in one spell.
Work like this should be outsourced for modularity reasons. That's why we have libraries.. :)
(or even WorldBounds can be used ^^)

agents, effects/units, have to be nulled.

Encapsulation, like making things private is asked for.

Haven't really read all code yet.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Things that are meant to be configured should be a bit seperated from code that isn't meant to be configured.
I guess I could move the set_bouds function, its globals and the UnitAlive declaration after the last configuration function (freeze_duration) and leave 2 or 3 empty lines, maybe.

I personaly find it bad you have a sound struct and a timer structute included in one spell.
Work like this should be outsourced for modularity reasons. That's why we have libraries.. :)
(or even WorldBounds can be used ^^)
Don't you find it worse to have to copy a dozen libraries just to try out a spell in a map different from the demo?
I do, but if someone wants to patch the spell here are some libs they might use (shameless self promotion):
dummy lib: Dummy
timer lib: Timers
sound lib: Sound

agents, effects/units, have to be nulled.
If they are struct members there's no point because struct instances are reused (i.e these handle members will be reassigned on the next spell cast).
If not then could you point where a handle is not nulled?

Encapsulation, like making things private is asked for.
The only thing I can see poluting the user's global namespace is the FrozenOrb struct itself which is not private because it would make all its members even longer, i.e it would slow variable lookup =).
 
Yeh I personaly thought at first that you forgot config for damage or so. But then I saw it.

For libraries, yeh for a quick copy & paste test on one's own demo map, direct including is faster, though this benefit is pretty low in comparison if you ask me.
If you make a second spell, you would you also not want to privatly create it all again, just to use timers and so.

If not then could you point where a handle is not nulled?

It's likeley there are multiple casts, but it's still a potential leak.
Maybe at one time of the game there are like 10 instances running at same time, and then never ever again only one. So the instances will not be re-used in the example.
It's good habit to null agents when they fall out of scope.

not private because it would make all its members even longer, i.e it would slow variable lookup =).
Isn't encapsulation a reason next to readability that longer names sometimes don't matter in terms of efficiency?
Even it internaly generates a few more chars, it doesn't really matter if it's used for something good, I believe.
I also hated it always when users tried to use 1-letter, and 2-letter names for like evereything in the code, just to gain some magical microsecond.
People have lots of fun of reading and understanding it. ;D, .. a bit offtopic, sorry. But yeh, from my opinion it doesn't matter, and things in vjass should be only public if they have to be.
 
Level 38
Joined
Feb 27, 2007
Messages
4,951
JASS:
        // Jaina's spell animation is a bit slow, let's make it as if she had a "faster cast rate"
        call SetUnitTimeScale(u, 4.0)
        call Timer.start(caster_info, 0.4, function thistype.reset_time_scale)
There are other little nuggets of non-configurability like effect paths and the thing above kinda scattered throughout the spell. I again think this sort of comes down to a site policy question: is it okay to have an entirely self-contained spell instead of one that uses a '3rd-party' timer system, etc.?

@Aniki It's ironic that you're arguing it's better to have a spell without requirements for ease of import (which effectively adds 'unnecessary' code to your map!) when you previously refused to use .name (in another thread) because of all the "bonus stuff" JASSHelper creates to be able to do it.
 
Level 13
Joined
Nov 7, 2014
Messages
571
It's ironic that you're arguing it's better to have a spell without requirements for ease of import (which effectively adds 'unnecessary' code to your map!) when you previously refused to use .name (in another thread) because of all the "bonus stuff" JASSHelper creates to be able to do it.
Think of it this way, you are free to choose whatever dummy/timer/sound library you want and replace the ones that the spell comes with, if you really wanted to reuse some library code. I think this is a bit easier than learning Pascal/Delphi (or whatever jasshelper was written in), patching jasshelper and recompiling.
 
Level 13
Joined
Nov 7, 2014
Messages
571
For the sanity of the Code Moderators and Spell Reviewers, please use JPAG
What's not JPAG-ish about it?

The demo map is definitely one of the worse I've seen
How come (its a spell demo map you shoudn't expect to it to make the director's cut list)?

Have a proper ingame tooltip
I am not intrested in that.

Include import instructions and credits (if there are any) in both the description and ingame
There are no library dependencies and only blizzard models are used.
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
How come (its a spell demo map you shoudn't expect to it to make the director's cut list)?
I am not intrested in that.
The Spell Section is different from The Lab and JASS section. If you don't like the rules, then please don't post here.

There are no library dependencies and only blizzard models are used.
Still needs import instructions.
 
Top