1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Find your way through the deepest dungeon in the 18th Mini Mapping Contest Poll.
    Dismiss Notice
  3. A brave new world lies beyond the seven seas. Join the 34th Modeling Contest today!
    Dismiss Notice
  4. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
Hive 3 Remoosed BETA - NOW LIVE. Go check it out at BETA Hive Workshop! Post your feedback in this new forum BETA Feedback.
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Spiraling Trails

Submitted by Aniki
This bundle is marked as substandard. It may contain bugs, not perform optimally or otherwise be in violation of the submission rules.
Spiraling Trails - casts magical stars that leave spiraling trails

Code (vJASS):

scope SpiralingTrails initializer init

globals
    private constant integer ABILITY_ID = 'A000'

    private constant integer DUMMY_ID = 'e000'
    private constant string DUMMY_MODEL = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdx"

    private constant real DT = 1.0 / 32.0

    private constant real Z_OFFSET = 80.0

    // the target point is considered reached if we are this many units away from it
    private constant real TARGET_DISTANCE_THRESHOLD = 16.0

    // if we are this many or less units from the terrain we stop
    private constant real TERRAIN_DISTANCE_THRESHOLD = 16.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
endglobals

private function unit_filter takes unit u returns boolean
    // e.g: return false if the unit is invulnerable
    return true
endfunction

private function linear_speed takes integer level returns real
    return 500.0
endfunction
private function distance takes integer level returns real
    return 900.0
endfunction
private function dummy_scale takes integer level returns real
    return 0.5 + 0.5 * level
endfunction
private function spiral_radius takes integer level returns real
    return 64.0
endfunction
private function damage takes integer level returns real
    return 100.0 * level
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 returns unit
    set created_dummy = CreateUnit(p, DUMMY_ID, 0.0, 0.0, 270.0)

    call UnitAddAbility(created_dummy, 'Amrf')
    call UnitRemoveAbility(created_dummy, 'Amrf')
    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


globals
    private location loc = Location(0.0, 0.0)
endglobals
private function GetTerrainZ takes real x, real y returns real
    call MoveLocation(loc, x, y)
    return GetLocationZ(loc)
endfunction
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

native UnitAlive takes unit u returns boolean

globals
    private constant real TADT_POW_2 = TARGET_DISTANCE_THRESHOLD * TARGET_DISTANCE_THRESHOLD
    private constant real TEDT = TERRAIN_DISTANCE_THRESHOLD

    private real dx
    private real dy
    private real dz
    private real v_len

    private real ax
    private real ay
    private real az

    private group G = CreateGroup()
    private unit U
endglobals

private struct SpiralingTrail
    static timer tmr = CreateTimer()
    static thistype array update_list
    static integer update_count = 0

    player caster_owner
    unit caster
    unit dummy
    effect dummy_model
    real dummy_scale
    real damage

    // current position
    real px
    real py
    real pz

    // linear velocity
    real dpx
    real dpy
    real dpz

     // target point
    real tx
    real ty
    real tz

    real bvx_x
    real bvx_y
    real bvx_z

    real bvy_x
    real bvy_y
    real bvy_z

    real bvz_x
    real bvz_y
    real bvz_z

    real spiral_radius
    real ang_xy
    real ang_xz
    real ang_xz_inc

    real x
    real y
    real z

    static method create takes unit caster, string dummy_model, real px, real py, real pz, real tx, real ty, real tz, real ang_xz_initial, integer inc_sign, real speed, real scale, real p_spiral_radius, real p_damage returns thistype
        local thistype this = allocate()
        local real ang

        local real y_axis_deg
        local real bvz_ang_xy
        local real bvz_ang_xz

        set this.caster_owner = GetOwningPlayer(caster)
        set this.caster = caster
        set this.dummy = dummy_create(this.caster_owner)
        set this.dummy_model = AddSpecialEffectTarget(dummy_model, this.dummy, "origin")
        set this.dummy_scale = scale
        call SetUnitScale(this.dummy, scale, scale, scale)
        set this.damage = p_damage

        set this.px = px
        set this.py = py
        set this.pz = pz

        set this.tx = tx
        set this.ty = ty
        set this.tz = tz

        set dx = this.tx - this.px
        set dy = this.ty - this.py
        set dz = this.tz - this.pz
        set v_len = SquareRoot(dx * dx + dy * dy + dz * dz)
        set dx = dx / v_len
        set dy = dy / v_len
        set dz = dz / v_len

        set this.dpx = dx * speed * DT
        set this.dpy = dy * speed * DT
        set this.dpz = dz * speed * DT

        set ang = 2.0 * bj_PI - Atan2(dy, dx)
        set bvx_x = Cos(ang - bj_PI / 2.0)
        set bvx_y = Sin(ang - bj_PI / 2.0)
        set bvx_z = 0.0
        set bvy_x = Cos(ang)
        set bvy_y = Sin(ang)
        set bvy_z = 0.0
        set bvz_x = 0.0
        set bvz_y = 0.0
        set bvz_z = 1.0

        set this.ang_xy = 0.0
        set this.ang_xz = ang_xz_initial
        set this.spiral_radius = p_spiral_radius
        set this.ang_xz_inc = inc_sign * 2.0 * bj_PI / 15.0

        call SetUnitX(this.dummy, this.px)
        call SetUnitY(this.dummy, this.py)
        call SetUnitZ(this.dummy, this.pz)
        call SetUnitFacing(this.dummy, Atan2(dy, dx) * bj_RADTODEG)
        call SetUnitAnimationByIndex(this.dummy, R2I(Atan2(dz, SquareRoot(dx * dx  + dy * dy)) * bj_RADTODEG + 90.0))

        return this
    endmethod

    method destroy2 takes integer i returns nothing
        set update_list[i] = update_list[update_count]
        set update_count = update_count - 1

        call DestroyEffect(this.dummy_model)
        call dummy_destroy(this.dummy)
        set this.caster = null
        set this.dummy = null
        call this.destroy()
    endmethod

    static method move takes nothing returns nothing
        local integer i
        local thistype this
        local real terrain_z
        local boolean hit_unit

        set i = 1
        loop
            exitwhen i > update_count
            set this = update_list[i]

            set dx = this.tx - this.px
            set dy = this.ty - this.py
            set dz = this.tz - this.pz

            if dx * dx + dy * dy + dz * dz <= TADT_POW_2 then
                // we reached the target point
                //
                set this.px = this.tx
                set this.py = this.ty
                set this.pz = this.tz
                call SetUnitX(this.dummy, this.px)
                call SetUnitY(this.dummy, this.py)
                call SetUnitZ(this.dummy, this.pz)
                call this.destroy2(i)
                set i = i - 1

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

                set ax = this.spiral_radius * Sin(this.ang_xz) * Cos(this.ang_xy)
                set ay = this.spiral_radius * Sin(this.ang_xz) * Sin(this.ang_xy)
                set az = this.spiral_radius * Cos(this.ang_xz)

                set this.x = this.px + this.bvx_x * ax + this.bvx_y * ay + this.bvx_z * az
                set this.y = this.py + this.bvy_x * ax + this.bvy_y * ay + this.bvy_z * az
                set this.z = this.pz + this.bvz_x * ax + this.bvz_y * ay + this.bvz_z * az

                call SetUnitX(this.dummy, this.x)
                call SetUnitY(this.dummy, this.y)
                call SetUnitZ(this.dummy, this.z)

                set this.ang_xz = this.ang_xz + this.ang_xz_inc

                // check for enemy units
                set hit_unit = false
                call GroupEnumUnitsInRange(G, this.x, this.y, this.dummy_scale * 64.0, null)
                loop
                    set U = FirstOfGroup(G)
                    exitwhen U == null
                    call GroupRemoveUnit(G, U)

                    if UnitAlive(U) and IsUnitEnemy(U, this.caster_owner) and not hit_unit and unit_filter(U) then
                        set hit_unit = true

                        if GetWidgetLife(U) - this.damage <= 0.405 then
                            call SetUnitExploded(U, true)
                            call UnitDamageTarget(this.caster, U, this.damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                        else
                            call UnitDamageTarget(this.caster, U, this.damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                        endif

                        call this.destroy2(i)
                        set i = i - 1

                        exitwhen true
                    endif
                endloop

                if not hit_unit then
                    // check for terrain/cliffs
                    call MoveLocation(loc, this.x, this.y)
                    set terrain_z = GetLocationZ(loc)
                    if this.pz - terrain_z <= TEDT then
                        call this.destroy2(i)
                        set i = i - 1
                    endif
                endif

            endif

            set i = i + 1
        endloop
    endmethod

    method start_moving takes nothing returns nothing
        set update_count = update_count + 1
        set update_list[update_count] = this

        if update_count == 1 then
            call TimerStart(tmr, DT, true, function thistype.move)
        endif
    endmethod
endstruct

private struct SpiralingTrails extends array

    static method cast takes nothing returns nothing
        local SpiralingTrail st
        local unit caster = GetTriggerUnit()
        local integer level = GetUnitAbilityLevel(caster, ABILITY_ID)
        local real dist = distance(level)
        local real speed = linear_speed(level)
        local real scale = dummy_scale(level)
        local real p_spiral_radius = spiral_radius(level)
        local real p_damage = damage(level)
        local real cx = GetUnitX(caster)
        local real cy = GetUnitY(caster)
        local real cz = GetUnitZ(caster) + Z_OFFSET
        local real tx = GetSpellTargetX()
        local real ty = GetSpellTargetY()
        local real tz = cz // GetTerrainZ(tx, ty)
        // call MoveLocation(loc, tx, ty)
        // set tz = GetLocationZ(loc)

        set dx = tx - cx
        set dy = ty - cy
        set dz = tz - cz
        set v_len = SquareRoot(dx * dx + dy * dy + dz * dz)
        set dx = dx / v_len
        set dy = dy / v_len
        set dz = dz / v_len

        set tx = cx + dx * dist
        set ty = cy + dy * dist
        set tz = cz + dz * dist

        set st = SpiralingTrail.create(caster, DUMMY_MODEL, cx, cy, cz, tx, ty, tz, 0.0, 1, speed, scale, p_spiral_radius, p_damage)
        call st.start_moving()
        set st = SpiralingTrail.create(caster, DUMMY_MODEL, cx, cy, cz, tx, ty, tz, bj_PI / 4.0, -1, speed, scale, p_spiral_radius, p_damage)
        call st.start_moving()

        set caster = null
    endmethod

endstruct

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
    call TriggerAddAction(t, function SpiralingTrails.cast)
endfunction

endscope

 
Contents

Spiraling Trails (Map)

  1. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,505
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Please add a "How to Install" in your description + map. Could you also describe the spell a little more? I feel I'm missing some actual spell mechanics from "Casts magical stars that leave spiraling trails"
    • If you used static method onInit inside the struct, you can get rid of that ugly
      initializer init
    • EVENT_PLAYER_UNIT_SPELL_CAST
      >
      EVENT_PLAYER_UNIT_SPELL_EFFECT
    • It would be better if static method cast returned a boolean and you used
      TriggerAddCondition()
      instead of
      TriggerAddAction()
    • Those method create arguments... lord have mercy. Do you really need to seperate the structs? That looks horrendous. On a side note, I see no reason for you to have seperate structs if you aren't going to encapsulate anything
    • The number of spiral effects created should be configurable
    • private function unit_filter should have all the filters of the group enumeration
    I didn't really feel like looking too far into the code. At quick glance, I can see there are a lot of numbers that look arbitary to me. I'd love for you to add some comments that describe your reasoning behind them (ex:
    SquareRoot(dx * dx  + dy * dy)) * bj_RADTODEG + 90.0
    &
    bj_PI / 4.0
    )

    Awaiting Update