• 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.

[Spell] [vJass] Vacuum Ability

Level 14
Joined
Jan 24, 2017
Messages
280
I am trying to create an ability. It should shoot a missile towards the middle of the target location and when it arrives at the location it should start an aoe pull effect that pulls all enemies towards the middle.

I was trying to do it myself and it was almost working. But I kept trying to mess around with it and got new and new problems. So I am here and hope for someone that can give it a fix.
I would appreciate it a lot, I am sure right now I am just making it worse :(

This is what I have right now.
JASS:
library Vacuum initializer Init requires TimerUtils

globals
    private constant integer ABILITY_ID = 'A0LD'
    private constant integer PROJECTILE_UNIT_ID = 'h031'
    private constant real VACUUM_RADIUS = 400.0
    private constant real PULL_INTERVAL = 0.03
    private constant real PULL_DISTANCE = 15.0
    private constant real PROJECTILE_SPEED = 1000.0
    private constant real VACUUM_DURATION = 1.0
endglobals

struct VacuumData
    unit caster
    unit projectile
    real targetX
    real targetY
    real duration
    group units
    timer pullTimer

    static method create takes unit caster, real x, real y returns VacuumData
        local VacuumData data = VacuumData.allocate()
        set data.caster = caster
        set data.targetX = x
        set data.targetY = y
        set data.duration = VACUUM_DURATION
        set data.units = null
        set data.projectile = CreateUnit(GetOwningPlayer(caster), PROJECTILE_UNIT_ID, GetUnitX(caster), GetUnitY(caster), 0)
        set data.pullTimer = null

        return data
    endmethod

    method onDestroy takes nothing returns nothing
        call DestroyGroup(this.units)
        call RemoveUnit(this.projectile)
        if this.pullTimer != null then
            call ReleaseTimer(this.pullTimer)
        endif
        call this.deallocate()
    endmethod
endstruct

private function Vacuum_PullEffect takes nothing returns nothing
    local VacuumData data = GetTimerData(GetExpiredTimer())
    local unit u
    local real x
    local real y
    local real angle

    if data.units == null then
        set data.units = CreateGroup()
        call GroupEnumUnitsInRange(data.units, data.targetX, data.targetY, VACUUM_RADIUS, null)
    endif

    set data.duration = data.duration - PULL_INTERVAL
    if data.duration <= 0 then
        call data.destroy()
        return
    endif

    loop
        set u = FirstOfGroup(data.units)
        exitwhen u == null
        call GroupRemoveUnit(data.units, u)

        if not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
            set x = GetUnitX(u)
            set y = GetUnitY(u)
            set angle = Atan2(data.targetY - y, data.targetX - x)

            // Ensure unit can be positioned safely
            if IsTerrainPathable(x + PULL_DISTANCE * Cos(angle), y + PULL_DISTANCE * Sin(angle), PATHING_TYPE_WALKABILITY) then
                call SetUnitPosition(u, x + PULL_DISTANCE * Cos(angle), y + PULL_DISTANCE * Sin(angle))
            endif
        endif
    endloop
endfunction

private function OnProjectileMove takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local VacuumData data = GetTimerData(t)
    local real projX = GetUnitX(data.projectile)
    local real projY = GetUnitY(data.projectile)
    local real angle = Atan2(data.targetY - projY, data.targetX - projX)
    local real newX = projX + PROJECTILE_SPEED * PULL_INTERVAL * Cos(angle)
    local real newY = projY + PROJECTILE_SPEED * PULL_INTERVAL * Sin(angle)

    // Move projectile towards the target
    call SetUnitX(data.projectile, newX)
    call SetUnitY(data.projectile, newY)

    if (newX - data.targetX) * (newX - data.targetX) + (newY - data.targetY) * (newY - data.targetY) <= 100.0 then
        set data.pullTimer = NewTimer()
        call SetTimerData(data.pullTimer, data)
        call TimerStart(data.pullTimer, PULL_INTERVAL, true, function Vacuum_PullEffect)

        call ReleaseTimer(t)
    endif
endfunction

private function LaunchProjectile takes unit caster, real x, real y returns nothing
    local VacuumData data = VacuumData.create(caster, x, y)
    local timer t = NewTimer()

    call SetTimerData(t, data)
    call TimerStart(t, PULL_INTERVAL, true, function OnProjectileMove)
endfunction

private function OnCast takes nothing returns nothing
    local unit caster = GetTriggerUnit()
    local real x = GetSpellTargetX()
    local real y = GetSpellTargetY()

    if GetSpellAbilityId() == ABILITY_ID then
        call LaunchProjectile(caster, x, y)
    endif
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddAction(t, function OnCast)
endfunction

endlibrary
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
How about using a Missile system like this:
We don't need to rely on Dummy units anymore for Missiles if you're on patch 1.31+.

Then when the Missile reaches it's destination you would run your own vacuum code, which should be rather simple:

Create a Point at the position of the Missile -> Start a unique timer -> Periodically pick nearby units around the Point -> Get angle between unit and the Point -> Move unit towards the angle or -angle.
 
Top