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

[System][CC]Airborne 1.5

  • Like
Reactions: deepstrasz
Was bored, so I made this.

JASS:
//******************************************************************************************
//*
//*     Airborne by Almia
//*
//******************************************************************************************
//*
//*     What is Airborne?
//*
//*     Airborne is a kind of Jump System which applies the Crowd Control(obviously Airborne)
//*     to units. This also uses Bezier Curves for a better overwriteable airborne.
//*
//*     Notes:
//*     - Target heigts may not be accurate.
//*     - works much better when used with Stun Systems
//*
//******************************************************************************************
//*
//*     Requires:
//*
//*     Unit Indexer by Bribe
//*     - http://www.hiveworkshop.com/forums/spells-569/gui-unit-indexer-1-2-0-2-a-197329/
//*
//******************************************************************************************
//*
//*      API
//*
//*      constant function AB_STRICTLY_DISABLE_MOVEMENT takes nothing returns boolean
//*         - This value dictates whether or not a unit shall be locked on it's position,
//*           so that it won't be affected of the function SetUnitX/Y
//*
//*      constant function AB_DISABLE_FACING takes nothing returns boolean
//*         - Disables the unit's ability to change facing
//*
//*      constant function AB_CANCEL_ORDERS takes nothing returns boolean
//*         - Cancels orders after applying Airborne(if false, resumes it's previous order "naturally")
//*
//*      function IsUnitAirborne takes unit u returns boolean
//*         - Checks whether the unit is airborne or not.
//*
//*      function UnitAddAirborne takes unit u, real z, real duration returns nothing
//*         - Adds airborne to unit, lasts till "duration" ends.
//*         - "z" is the target height of the airborne, a.k.a the highest point.
//*         - this is overwriteable.
//*
//*      function UnitSuspendAirborne takes unit u, boolean flag returns nothing
//*         - Suspends the unit, stop it's airborne progress.
//*         - "flag" tells whether or not to unsuspend the unit(true = suspend)
//*
//*      function IsAirborneUnitSuspended takes unit u returns boolean
//*         - Checks whether or not the airborne unit is suspended.
//*
//*      function UnitRemoveAirborne takes unit u, boolean resetHeight returns nothing
//*         - Removes the unit's airborne status
//*         - "resetHeight" set the unit to it's original height
//*            setting it to false will leave the unit to it's current height.
//*
//*     Events
//*
//*     constant function AIRBORNE_ON_LIFT takes nothing returns real
//*         - Executes if a unit is lifted
//*     constant function AIRBORNE_ON_DROP takes nothing returns real
//*         - Executes if a unit is dropped
//*     constant function AIRBORNE_ON_AIR takes nothing returns real
//*         - Executes if a unit is on air
//*
//*     function TriggerRegisterAirborneEvent takes trigger t, real ev returns nothing
//*     function RegisterAirborneEvent takes code c, real ev returns nothing
//*
//******************************************************************************************
//*
//*      Credits 
//*          Bribe - Unit Indexer
//*          BlinkBoy - Bezier Function
//*          <some unknown guy in the internet> - finding the Bezier's control points
//*
//******************************************************************************************
//*
//*    Constants
//*
//******************************************************************************************
constant function AB_TIMEOUT takes nothing returns real
    return 0.031250000
endfunction

constant function AB_STRICTLY_DISABLE_MOVEMENT takes nothing returns boolean
    return true
endfunction

constant function AB_DISABLE_FACING takes nothing returns boolean
    return true
endfunction

constant function AB_CANCEL_ORDERS takes nothing returns boolean
    return true
endfunction
//******************************************************************************************
//*     Event
//******************************************************************************************
constant function AIRBORNE_ON_LIFT takes nothing returns real
    return 1.0
endfunction

constant function AIRBORNE_ON_DROP takes nothing returns real
    return 2.0
endfunction

constant function AIRBORNE_ON_AIR takes nothing returns real
    return 3.0
endfunction
//******************************************************************************************
function Bezier takes real a, real b, real c, real t returns real
    return a + 2.*(b-a)*t + (c -2.*b + a)*t*t
endfunction

function GetBezierControlPoint takes real a, real b, real t returns real
    return 2*t - a/2 - b/2
endfunction

function AB_UnitCheck takes unit u returns boolean
    return u != null
endfunction
//******************************************************************************************

function InitABEvents takes nothing returns nothing
    if udg_AB_DropEvent == null then
        set udg_AB_DropEvent = CreateTrigger()
        call TriggerRegisterVariableEvent(udg_AB_DropEvent, "udg_AB_Event", EQUAL, AIRBORNE_ON_DROP())
    endif
    if udg_AB_LiftEvent == null then
        set udg_AB_LiftEvent = CreateTrigger()
        call TriggerRegisterVariableEvent(udg_AB_LiftEvent, "udg_AB_Event", EQUAL, AIRBORNE_ON_LIFT())
    endif
    if udg_AB_AirEvent == null then
        set udg_AB_AirEvent = CreateTrigger()
        call TriggerRegisterVariableEvent(udg_AB_AirEvent, "udg_AB_Event", EQUAL, AIRBORNE_ON_AIR())
    endif
endfunction

function TriggerRegisterAirborneEvent takes trigger t, real ev returns nothing
    call InitABEvents()
    call TriggerRegisterVariableEvent(t, "udg_AB_Event", EQUAL, ev)
endfunction

function RegisterAirborneEvent takes code c, real ev returns nothing
    call InitABEvents()
    if ev == AIRBORNE_ON_DROP() then
        call TriggerAddCondition(udg_AB_DropEvent, Condition(c))
    elseif ev == AIRBORNE_ON_LIFT() then
        call TriggerAddCondition(udg_AB_LiftEvent, Condition(c))
    elseif ev == AIRBORNE_ON_AIR() then
        call TriggerAddCondition(udg_AB_AirEvent, Condition(c))
    endif
endfunction

function IsUnitAirborne takes unit u returns boolean
    return udg_AB_Airborned[GetUnitUserData(u)]
endfunction

function UnitRemoveAirborne takes unit u, boolean resetHeight returns nothing
    local integer i
    if AB_UnitCheck(u) then
        set i = GetUnitUserData(u)
        if udg_AB_Airborned[i] then
            set udg_AB_DroppedUnit = u
            set udg_AB_Event = AIRBORNE_ON_DROP()
            set udg_AB_Event = 0.0
            if resetHeight then
                call SetUnitFlyHeight(u, udg_AB_OriginalZ[i], 0)
            endif
            if AB_STRICTLY_DISABLE_MOVEMENT() then
                set udg_AB_X[i] = 0
                set udg_AB_Y[i] = 0
            endif
            if AB_DISABLE_FACING() then
                set udg_AB_Facing[i] = 0
            endif
            set udg_AB_Airborned[i] = false
            set udg_AB_OriginalZ[i] = 0
            set udg_AB_TargetZ[i] = 0
            set udg_AB_SourceZ[i] = 0
            set udg_AB_Duration[i] = 0
            set udg_AB_MaxDuration[i] = 0
            set udg_AB_Paused[i] = false
            set udg_AB_N[udg_AB_P[i]] = udg_AB_N[i]
            set udg_AB_P[udg_AB_N[i]] = udg_AB_P[i]
            call SetUnitPropWindow(u, GetUnitDefaultPropWindow(u)*bj_DEGTORAD)
    
            if udg_AB_N[0] == 0 then
                call PauseTimer(udg_AB_Timer)
            endif
        endif
    endif
endfunction

function Airborne_Periodic takes nothing returns nothing
    local integer i = udg_AB_N[0]
    local unit u
    loop
        exitwhen i == 0
        set u = udg_UDexUnits[i]
        if AB_UnitCheck(u) and udg_AB_Duration[i] < udg_AB_MaxDuration[i] then
            set udg_AB_AirborneUnit = u
            set udg_AB_Event = AIRBORNE_ON_AIR()
            set udg_AB_Event = 0.0
            if not udg_AB_Paused[i] then
                set udg_AB_Duration[i] = udg_AB_Duration[i] + AB_TIMEOUT()
            endif
            if AB_STRICTLY_DISABLE_MOVEMENT() then
                call SetUnitX(u, udg_AB_X[i])
                call SetUnitY(u, udg_AB_Y[i])
            endif
            if AB_DISABLE_FACING() then
                call SetUnitFacing(u, udg_AB_Facing[i])
            endif
            call SetUnitFlyHeight(u, Bezier(udg_AB_SourceZ[i], udg_AB_TargetZ[i], udg_AB_OriginalZ[i], udg_AB_Duration[i]/udg_AB_MaxDuration[i]), 0)
        else
            call UnitRemoveAirborne(u, true)
        endif
        set i = udg_AB_N[i]
    endloop
    set u = null
endfunction

function UnitAddAirborne takes unit u, real z, real duration returns nothing
    local integer i
    call InitABEvents()
    if AB_UnitCheck(u) then
        set i = GetUnitUserData(u)
        set udg_AB_LiftedUnit = u
        set udg_AB_Event = AIRBORNE_ON_LIFT()
        set udg_AB_Event = 0.0
        if not udg_AB_Airborned[i] then
            set udg_AB_Airborned[i] = true
            call SetUnitPropWindow(u, 0)
            set udg_AB_N[i] = 0
            set udg_AB_P[i] = udg_AB_P[0]
            set udg_AB_N[udg_AB_P[0]] = i
            set udg_AB_P[0] = i
        
            set udg_AB_OriginalZ[i] = GetUnitFlyHeight(u)
            if AB_STRICTLY_DISABLE_MOVEMENT() then
                set udg_AB_X[i] = GetUnitX(u)
                set udg_AB_Y[i] = GetUnitY(u)
            endif
            if AB_DISABLE_FACING() then
                set udg_AB_Facing[i] = GetUnitFacing(u)
            endif
        
            if UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf') then
            endif
            if udg_AB_N[0] == udg_AB_P[0] then
                call TimerStart(udg_AB_Timer, AB_TIMEOUT(), true, function Airborne_Periodic)
            endif
        endif
        set udg_AB_Duration[i] = 0
        set udg_AB_MaxDuration[i] = duration
        set udg_AB_SourceZ[i] = GetUnitFlyHeight(u)
        set udg_AB_TargetZ[i] = GetBezierControlPoint(udg_AB_SourceZ[i], udg_AB_OriginalZ[i], udg_AB_SourceZ[i] + z)
        if AB_CANCEL_ORDERS() then
            call IssueImmediateOrderById(u, 851972) 
        endif
    endif
endfunction

function IsAirborneUnitSuspended takes unit u returns boolean
    local integer i
    if AB_UnitCheck(u) then
        set i = GetUnitUserData(u)
        return udg_AB_Airborned[i] and udg_AB_Paused[i]
    endif
    return false
endfunction

function UnitSuspendAirborne takes unit u, boolean flag returns nothing
    local integer i
    if AB_UnitCheck(u) then
        set i = GetUnitUserData(u)    
        if udg_AB_Airborned[i] then
            set udg_AB_Paused[i] = flag
        endif
    endif
endfunction
Example:
  • Test
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • Set TestLoc = (Position of (Triggering unit))
      • Set TestGroup = (Units within 500.00 of TestLoc)
      • Unit Group - Pick every unit in TestGroup and do (Actions)
        • Loop - Actions
          • Custom script: call UnitAddAirborne(GetEnumUnit(), 500, 1)
      • Custom script: call RemoveLocation(udg_TestLoc)
      • Custom script: call DestroyGroup(udg_TestGroup)
  • Init Test
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set t = Event Drop Test <gen>
      • Custom script: call TriggerRegisterAirborneEvent(udg_t, AIRBORNE_ON_DROP())
      • Set t = Event Lift Test <gen>
      • Custom script: call TriggerRegisterAirborneEvent(udg_t, AIRBORNE_ON_LIFT())
      • Set t = Event Air Test <gen>
      • Custom script: call TriggerRegisterAirborneEvent(udg_t, AIRBORNE_ON_AIR())
  • Event Drop Test
    • Events
    • Conditions
    • Actions
      • Set TestLoc = (Position of AB_DroppedUnit)
      • Special Effect - Create a special effect at TestLoc using Abilities\Spells\Other\Volcano\VolcanoDeath.mdl
      • Special Effect - Destroy (Last created special effect)
      • Custom script: call RemoveLocation(udg_TestLoc)
  • Event Lift Test
    • Events
    • Conditions
    • Actions
      • Set TestLoc = (Position of AB_LiftedUnit)
      • Special Effect - Create a special effect at TestLoc using Abilities\Spells\Human\Polymorph\PolyMorphTarget.mdl
      • Special Effect - Destroy (Last created special effect)
      • Custom script: call RemoveLocation(udg_TestLoc)
  • Event Air Test
    • Events
    • Conditions
    • Actions
      • Special Effect - Create a special effect attached to the chest of AB_AirborneUnit using Abilities\Weapons\BloodElfMissile\BloodElfMissile.mdl
      • Special Effect - Destroy (Last created special effect)


Changelogs:
1.0
- First release
1.1
- Added new Function : IsAirborneUnitSuspended
- Fixed some deindexing errors.
1.2
- Fixed Suspension Effect
1.3
- Now checks unit if null
- Added Events
- Removed AB_Count for timer purposes
1.4
- minor fix
1.5
- fixed suspension bug

Keywords:
Jump, Air, Knock, Fly
Contents

Airborne (Map)

Reviews
17:58, 1st Mar 2016 BPower: Works as intended. Can be useful for unit air controll. Approved!

Moderator

M

Moderator

17:58, 1st Mar 2016
BPower: Works as intended. Can be useful for unit air controll. Approved!
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Good for jump in place or additional air controll for other systems.

//* - this is stackable.
Yes and no. It's a bit confusing, because actually there is no stacking.
It keeps the first original Z, while adjusting the controll point.
This results in a stack like effect, however two airbone function calls
without a given time interval in between will not result in stacking.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
Good for jump in place or additional air controll for other systems.


Yes and no. It's a bit confusing, because actually there is no stacking.
It keeps the first original Z, while adjusting the controll point.
This results in a stack like effect, however two airbone function calls
without a given time interval in between will not result in stacking.

oh you are right. Maybe I should rephrase that with Overwriting or somesort :D
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
Would be nice if you had a dedicated ability that did certain aspects of your system (ex: one ability does a simple Z height change, another uses an X/Y bezier curve). I was only able to user thunderclap for the demo. Nonetheless, the API presentation is well done and easy to read.

Here are some things I'm concerned about with your code:
  • I think you should have a dedicated udg for the most recent index instead of using AB_P[0] :p I was a little confused at first as to why you weren't storing the most recent index
  • I think you should add a clear seperation between the constant functions and regular functions :3
  • Sorry I'm new to JASS, but wouldn't Adding/Removing crow be safer like this? Did you do it your way for readibility or is there an actual benefit to having it the way you did it?
    JASS:
    //current setup
    if UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf') then
    endif
    
    // >
    
    if UnitAddAbility(u, 'Amrf') then
        call UnitRemoveAbility (u, 'Amrf') 
    endif
  • Shouldn't you be checking to see if the current iteration is the last iteration so you can set udg_AB_P[0] to 0 instead of the most recent GetUnitUserData() ? It looks like the chain will look f*cked if the previous node is some unit that is probably not even airborne.
  • I'm sure this is something you missed, but on deindex, you increment AB_Count instead of decrementing it. AB_Timer will never be paused in this case
    JASS:
    set udg_AB_Count = udg_AB_Count + 1
    
    if udg_AB_Count == 0 then
        call PauseTimer(udg_AB_Timer)
    endif
 
Level 18
Joined
Oct 17, 2012
Messages
818
Sorry I'm new to JASS, but wouldn't Adding/Removing crow be safer like this? Did you do it your way for readibility or is there an actual benefit to having it the way you did it?
JASS:
//current setup
if UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf') then
endif

// >

if UnitAddAbility(u, 'Amrf') then
    call UnitRemoveAbility (u, 'Amrf') 
endif

JASS has a short-circuiting feature. If one of the expressions in an "and" expression returns false, then it stops.
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
JASS has a short-circuiting feature. If one of the expressions in an "and" expression returns false, then it stops.

I'm aware of the feature. However, I always figured readability was more important. If there is no actual benefit, then I think it would be better off not having the two natives in the same statement.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
I'm sure this is something you missed, but on deindex, you increment AB_Count instead of decrementing it. AB_Timer will never be paused in this case
JASS:
set udg_AB_Count = udg_AB_Count + 1

if udg_AB_Count == 0 then
    call PauseTimer(udg_AB_Timer)
endif

Dayum, i'm bad at copy-pastes :v
Thank you for spotting that

[edit]
Updated!
Thanks KILLCIDE for some suggestions.

If this gets approved I will make a demo-slash-Spell Pack using this system (hint: Yasuo)

[edit]
Found out that Suspension doesn't work as expected
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
You'll not need AB_Count to check when the timer must be started.
You can use the variables of the static unique list instead.
For start NodePrev[this] == 0.
For stop NodeNext[0] == 0

Maybe you can add a custom event for landing units.
AirboneEvent == 1.00 or something like that. What do you think?
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
You'll not need AB_Count to check when the timer must be started.
You can use the variables of the static unique list instead.
For start NodePrev[this] == 0.
For stop NodeNext[0] == 0
Now that's clever! I will definitely need to remember this :) on a side note:
  • I still think you should add this before you relink the chain on deindex:
    JASS:
    if udg_AB_P[0] == i then
        set udg_AB_P[0] = udg_AB_P[i]
    endif
    If I'm reading your code correctly, a unit that is not airborne has the potential to be the a part of the list since udg_AB_P[0] will still store the most recent GetUnitUserData().

  • Wouldn't it be better to just keep UnitRemoveAirborne as a part of the loop? I see no need for making a completely seperate function for it other then readability.
  • You should add other deindex conditions for the airborne unit like if it is null or dead (this can be added as a config option as well. make it so that a user can decide if he wants dead units to still go through the full airborne duration)
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
my deindexing method is how everyone removes a node from a doubly linked list, i don't see any problems with that :V I have been using linked lists in my resources for 3 years now.
That's where the problem is. You aren't removing the node of the last unit.

Let's say at the start of the game, we make 6 units airborne (udg_AB_P[0] is currently 0):

Chain0<->1<->2<->3<->4<->5<->6<->0

Unit ID
02548930


The start of the chain at the start of the game is fine since udg_AB_P[0] is initialized to 0 when the game starts. Now, let's assume that the order they are deallocated is the same as they were created. That means the last instance to be created and destroyed is Unit ID 3.

Chain0<->0

Unit ID
00


Imagine the list is empty, and we make Unit ID 2 the only airborne with your current index (udg_AB_P[0] is currently 3):

JASS:
local integer i = GetUnitUserData(u) // i = 2

set udg_AB_N[i] = 0 // udg_AB_N[2] = 0
set udg_AB_P[i] = udg_AB_P[0] // udg_AB_P[2] = 3
set udg_AB_N[udg_AB_P[0]] = i // udg_AB_N[3] = 2
set udg_AB_P[0] = i // udg_AB_P[0] = 2

Instead of your list looking like this:

Chain0<->1<->0

Unit ID
020

It's going to look like this:

Chain0????<->1<->0

Unit ID
0320

That's how I'm reading your code right now.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
P[0] Can never be 0 except the list is empty:

For example:
JASS:
local integer i = GetUnitUserData(u) // i = 2

set udg_AB_N[i] = 0 // udg_AB_N[2] = 0
set udg_AB_P[i] = udg_AB_P[0] // udg_AB_P[2] = 0
set udg_AB_N[udg_AB_P[0]] = i // udg_AB_N[0] = 2
set udg_AB_P[0] = i // udg_AB_P[0] = 2

Btw, The list never starts on 0, except it is empty :V

When for example, the last deindexed ID is 3:
JASS:
set udg_AB_N[udg_AB_P[i]] = udg_AB_N[i]//set udg_AB_N[0] = 0
set udg_AB_P[udg_AB_N[i]] = udg_AB_P[i]//set udg_AB_P[0] = 0

There will never always be an index left out.

If you have still doubts, you should better ask either Magtheridon96, Bribe or Nestharus.
 
Level 37
Joined
Jul 22, 2015
Messages
3,485

That only works on the forums x)

The data structure looks good from the description. I'm just a little curious about how you start the timer:
JASS:
if udg_AB_N[0] == udg_AB_P[0] and udg_AB_N[0] != 0 then
    call TimerStart(udg_AB_Timer, AB_TIMEOUT(), true, function Airborne_Periodic)
endif

What's the point of checking if "AB_N[0] != 0"? This would never return false because the only time you check is when you create a new node, meaning that N[0] would never be 0.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
They only works on the forums x)

The data structure looks good from the description. I'm just a little curious about how you start the timer:
JASS:
if udg_AB_N[0] == udg_AB_P[0] and udg_AB_N[0] != 0 then
    call TimerStart(udg_AB_Timer, AB_TIMEOUT(), true, function Airborne_Periodic)
endif

What's the point of checking if "AB_N[0] != 0"? This would never return false because the only time you check is when you create a new node, meaning that N[0] would never be 0.

hmmm, idk why did add that. Updated.
 
Top