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

[JASS] Whirlwind spell

Status
Not open for further replies.
Level 13
Joined
Jul 26, 2008
Messages
1,009
Alright this spell is pretty simple.

Using XEcolliders I've made it so when the unit casts Whirlwind, it makes a collider dummy at the targeted point. Any unit that gets within the collision radius will be added to the struct group.

Units in the group are sucked up by the Tornado and whirred around in a circle. If the channeling ends, the units will drop and take damage depending on their fly height.

My problem is I'm not too great at math and I want the units to be picked up and whirred around where they're standing, not a preset point. Also I the distance part of it isn't going as well, and I could use some advice on that.

JASS:
scope Whirlwind initializer init

globals
    private constant integer SPELLID    = 'WhWi'
    private real ANGLE
    private real  X
    private real Y
endglobals

private struct Data extends xecollider

    unit caster
    group g
    real ang
    integer i

    static method GroupMove takes nothing returns nothing
        local real dx = GetUnitX(GetEnumUnit()) - X
        local real dy = GetUnitY(GetEnumUnit()) - Y
        local real dist = SquareRoot(dx*dx + dy*dy)
        set X = X + dist*Cos(ANGLE)
        set Y = Y + dist*Sin(ANGLE)
        call SetUnitPosition(GetEnumUnit(), X, Y)
        call SetUnitFlyHeight(GetEnumUnit(), GetUnitFlyHeight(GetEnumUnit()) + 1.75, 0)
    endmethod

    method loopControl takes nothing returns nothing
     local unit u
     local real lvl = GetUnitAbilityLevel(.caster, SPELLID)
     local real dam

        set .ang = .ang + 0.07
        set .i = .i + 1
        set .scale = 0.4+0.005*.i
        set .collisionSize = 100+2*.i

        set ANGLE = .ang
        set X = .x
        set Y = .y

        call ForGroup(.g, function Data.GroupMove)

        if GetUnitCurrentOrder(.caster) != OrderId("tornado") then
            loop
                set u = FirstOfGroup(.g)
            exitwhen u == null
                set dam = GetUnitFlyHeight(u)
                call UnitDamageTarget(.caster, u, 0.6*dam, true, false, ATTACK_TYPE_MELEE, DAMAGE_TYPE_UNIVERSAL, wtype)
                call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 25*.i)
                call SetUnitPathing(u,true)
                call GroupRemoveUnit(.g, u)
            endloop
            call .terminate()
            call ReleaseGroup(.g)
        endif

    endmethod

    method onUnitHit takes unit target returns nothing

        if IsUnitEnemy(target, GetOwningPlayer(.caster)) and not(IsUnitType(target,UNIT_TYPE_STRUCTURE) or IsUnitType(target,UNIT_TYPE_FLYING)) then
            call GroupAddUnit(.g, target)
            call UnitAddAbility(target,XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(target,XE_HEIGHT_ENABLER)
            call SetUnitPathing(target,false)
        endif

    endmethod

    static method Actions takes unit c, real x, real y returns nothing
     local real angle = Atan2(GetUnitFacing(c) - GetUnitY(c), GetUnitFacing(c) - GetUnitX(c))
     local integer lvl = GetUnitAbilityLevel(c, SPELLID)
     local timer tim = NewTimer()
     local Data xc = Data.create(x, y, 0)

        set xc.g                = NewGroup()
        set xc.caster           = c
        set xc.expirationTime   = 999999
        set xc.collisionSize    = 100
        set xc.fxpath           = GetAbilityEffectById(SPELLID, EFFECT_TYPE_MISSILE, 0)
        set xc.scale            = 0.4
        set xc.ang              = angle
    endmethod

    static method Conditions takes nothing returns boolean
        if GetSpellAbilityId() == SPELLID then
            call Data.Actions(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
        endif
     return false
    endmethod

endstruct

function Actions takes nothing returns nothing
endfunction

//===========================================================================
public function init takes nothing returns nothing
 local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CHANNEL )
    call TriggerAddCondition( t, function Data.Conditions )
    call XE_PreloadAbility(SPELLID)
 set t = null
endfunction

endscope
 
Level 7
Joined
Jun 6, 2010
Messages
224
theres not much to do
i think all you need is declaring some reals like

JASS:
struct UnitData
    real height
    real f
endstruct

in your perodic whirlwind timer all you do is slightly increase the height and the f is supposed to start as an angle from the center dummy to the unit caught. You increase that angle periodically and so adjust it's x and y like this

JASS:
method Adjust takes nothing returns nothing
    if .height < 600 then
        set .height = .height + .5
    endif
    set .f = .f + 1
    call SetUnitX(.u, (GetUnitX(.tornado) + (Distance) * Cos(.f * bj_DEGTORAD)))
    call SetUnitY(.u, (GetUnitY(.tornado) + (Distance) * Sin(.f * bj_DEGTORAD)))
endmethod

that will rotate the unit around the tornado
hope i provided some valid help
 
Level 13
Joined
Jul 26, 2008
Messages
1,009
@Bribe: Finding the distance between the Unit in the Group and the Dummy Collision Unit when I start rotating them seems to be causing problems.

By "around where they're standing and not a preset point" I mean when it starts twisting them around the tornado if they get too close I want their movement to start from where they're standing and not from the northern, western, eastern, or southern part of the tornado.

@Prince.Zero: I had that set up already before you mentioned setting it up. It's in the original code up there, only in a more complex form to allow multiple units in a group to be moved.

Alright a description of how the spell looks with that code. The whirlwind grabs all the units then it rotates them around. they're all at a heavily spaced out distance from each other in a line, like a needle rotating on a clock. As it goes on they get farther and farther away from the whirlwind. It seems like everything is being saved to the same value. Maybe I should do arrays? Also it seems like this.i doesn't get reset and so the tornado is huge on the second cast.

How I want it: If a unit enters the tornado it will slowly start lifting up as it rotates around it. The distance the unit is from the tornado is taken into account when they're picked up as well as where they're standing for optimal eye-candy.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
I think I might know why the units are all being rotated "in a line", as you've described.

JASS:
        set ANGLE = .ang
        set X = .x
        set Y = .y

This is where you setup your members to be used in the enumeration ForGroup. Notice how you've got an angle, and two coordinates x and y.

local real dx = GetUnitX(GetEnumUnit()) - X
local real dy = GetUnitY(GetEnumUnit()) - Y
local real dist = SquareRoot(dx*dx + dy*dy)
set X = X + dist*Cos(ANGLE)
set Y = Y + dist*Sin(ANGLE)

Okay so for each unit in the group you assign a unique dist value which is used to calculate it's position. This is probably why the units end up in a "needle" formation as you've described. You correctly adjust each individual unit's distance to the tornado but you do not do the same for the angle that it should be displaced by.

I think that it should be more like this, with a specialized "angle" value for each unit that is enumerated.

JASS:
        local real dx = GetUnitX(GetEnumUnit()) - X
        local real dy = GetUnitY(GetEnumUnit()) - Y
        local real dist = SquareRoot(dx*dx + dy*dy)
        local real a = Atan2(dy, dx) // added local value
        set X = X + dist*Cos(a) // replaced "ANGLE" with "a"
        set Y = Y + dist*Sin(a) // same as above
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
You'll want to follow Berb's suggestion, but I think you should add 0.07 to the a or else the unit won't be rotated correctly.
Also, I don't think you should be changing the global variables or else that would affect other units, right?

On an unrelated note to your problem:
Did you know that units that are already in the group g will repeatedly get added in your onUnitHit method?

You could have a struct member like this:
JASS:
static thistype temp
which is basically a global variable of your struct. You can do this to get rid of using the globals, X and Y, along with that FirstOfGroup loop.

Ex: Somewhere in loopControl but before the first ForGroup is called:
JASS:
set temp = this
Now make X and Y become temp.x and temp.y in GroupMove.
To get rid of the FirstOfGroup loop, make a separate method which would be called by ForGroup when the caster is no longer channeling the spell. The method would look something like this:
JASS:
     local real dam = GetUnitFlyHeight(GetEnumUnit())
                call UnitDamageTarget(temp.caster, GetEnumUnit(), 0.6*dam, true, false, ATTACK_TYPE_MELEE, DAMAGE_TYPE_UNIVERSAL, wtype)
                call SetUnitFlyHeight(GetEnumUnit(), GetUnitDefaultFlyHeight(GetEnumUnit()), 25*.i)
                call SetUnitPathing(GetEnumUnit(),true)
You'll probably want to use local variable for GetEnumUnit both in GroupMove and if you use the method I gave you.
 
Level 13
Joined
Jul 26, 2008
Messages
1,009
It works better, but there's still a huge bug with the mathmatics. The units fly outward from the tornado, and not just a little bit but rather they shoot like a bullet.

JASS:
scope Whirlwind initializer init

globals
    private constant integer SPELLID    = 'WhWi'
    private constant attacktype atype   = ATTACK_TYPE_MELEE
    private constant damagetype dtype   = DAMAGE_TYPE_UNIVERSAL
endglobals

private struct Data extends xecollider

    static thistype temp
    unit caster
    group g
    real ang
    integer i

    static method EndIt takes nothing returns nothing
     local real dam = GetUnitFlyHeight(GetEnumUnit())*0.6
     local unit u = GetEnumUnit()
        call UnitDamageTarget(temp.caster, u, dam, true, false, atype, dtype, wtype)
        call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 25*temp.i)
        call SetUnitPathing(u,true)
    endmethod

    static method GroupMove takes nothing returns nothing
        local real dx = GetUnitX(GetEnumUnit()) - temp.x
        local real dy = GetUnitY(GetEnumUnit()) - temp.y
        local real dist = SquareRoot(dx*dx + dy*dy)
        local real a = Atan2(dy,dx)+0.07
        set temp.x = temp.x + dist*Cos(a)
        set temp.y = temp.y + dist*Sin(a)
        call SetUnitPosition(GetEnumUnit(), temp.x, temp.y)
        call SetUnitFlyHeight(GetEnumUnit(), GetUnitFlyHeight(GetEnumUnit()) + 1.75, 0)
    endmethod

    method loopControl takes nothing returns nothing
     local unit u
     local real lvl = GetUnitAbilityLevel(this.caster, SPELLID)
     local real dam

        set this.ang = this.ang + 0.07
        set this.i = this.i + 1
        set this.scale = 0.4+0.005*this.i
        set this.collisionSize = 70+1*this.i
        set temp = this
/*        set temp.x = this.x
        set temp.y = this.y
        set temp.caster = this.caster
*/
        call ForGroup(this.g, function Data.GroupMove)

        if GetUnitCurrentOrder(this.caster) != OrderId("tornado") then
            call ForGroup(.g, function Data.EndIt)
/*            loop
                set u = FirstOfGroup(this.g)
            exitwhen u == null
                set dam = GetUnitFlyHeight(u)
                call UnitDamageTarget(this.caster, u, 0.6*dam, true, false, atype, dtype, wtype)
                call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 15*this.i)
                call SetUnitPathing(u,true)
            endloop
            call this.terminate()
            call ReleaseGroup(this.g)
*/        endif

    endmethod

    method onUnitHit takes unit target returns nothing

        if IsUnitEnemy(target, GetOwningPlayer(.caster)) and not(IsUnitType(target,UNIT_TYPE_STRUCTURE) or IsUnitType(target,UNIT_TYPE_FLYING) or IsUnitInGroup(target,.g)) then
            call GroupAddUnit(.g, target)
            call UnitAddAbility(target,XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(target,XE_HEIGHT_ENABLER)
            call SetUnitPathing(target,false)
        endif

    endmethod

    static method Actions takes unit c, real x, real y returns nothing
     local real angle = Atan2(GetUnitFacing(c) - GetUnitY(c), GetUnitFacing(c) - GetUnitX(c))
     local integer lvl = GetUnitAbilityLevel(c, SPELLID)
     local timer tim = NewTimer()
     local Data xc = Data.create(x, y, 0)

        set xc.g                = NewGroup()
        set xc.caster           = c
        set xc.expirationTime   = 999999
        set xc.collisionSize    = 70
        set xc.fxpath           = GetAbilityEffectById(SPELLID, EFFECT_TYPE_MISSILE, 0)
        set xc.scale            = 0.4
        set xc.ang              = angle
    endmethod

    static method Conditions takes nothing returns boolean
        if GetSpellAbilityId() == SPELLID then
            call Data.Actions(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
        endif
     return false
    endmethod

endstruct

//===========================================================================
public function init takes nothing returns nothing
 local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CHANNEL )
    call TriggerAddCondition( t, function Data.Conditions )
    call XE_PreloadAbility(SPELLID)
 set t = null
endfunction

endscope
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Is the tornado itself supposed to be moving anywhere? That's what you're doing with
JASS:
         set temp.x = temp.x + dist*Cos(a)
        set temp.y = temp.y + dist*Sin(a)
If you don't want to change the tornado's coordinates, you should just do
JASS:
        call SetUnitPosition(GetEnumUnit(),temp.x + dist*Cos(a), temp.y + dist*Sin(a))
Other things:

  • The struct member ang is no longer needed.
  • Struct member i should be initially set to 0.
    Alternatively get rid of that member and do this:
    JASS:
    set this.collisionSize = this.collisionSize + 1
  • In EndIt, declare u before dam so you can use it. Also remember to null it afterwards.
  • u is not needed in loopControl.
  • Uncomment this.terminate or else the struct won't be destroyed properly. Move the ReleaseGroup before that.
 
Status
Not open for further replies.
Top