1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  5. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  6. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  7. The results are out! Check them out.
    Dismiss Notice
  8. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  9. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  10. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Realistic Airplane System

Submitted by Fingolfin
This bundle is marked as pending. It has not been reviewed by a staff member yet.
Requires:
ListModule

Optional:
AutoIndex

This is a lightweight system for adding realistic airplane movement to your map. It also features inertia, which means the airplanes will "slide" more or less depending on how low you set the air resistance to be. Please note that setting the air resistance to values above or equal to 1 will result in your airplanes accelerating out of control, and values below 0.95 might also cause your airplanes to spin out when turning.

To give airplane motion to a unit type, all you need to do is use the following line:

Code (vJASS):

call registerPlaneType('xxxx')
 


Where 'xxxx' is the unit ID of your airplane (you can find unit IDs by pressing CTRL+D in the object editor). This line must be put inside the function named initPlaneTypes, which you can easily find inside the library. The airplanes will use the movement and turning speed that you have specified in the object editor, but the max speed will be scaled by a variable called "SPEED_FACTOR", which defaults to 160%. This variable is used to bypass the hardcoded limit for unit speed that warcraft has.

Some things to keep in mind when creating units to work with this system:
  • It works best with low turning speeds - you may have to shift-click the turning speed field to enter values lower than 0.1. I've found that somewhere around 0.05 is good.
  • You will need to tweak the turning speed together with the movement speed so that the unit has a turning circle that works for doing attack runs. If the turning circle is too large, the unit won't be able to attack, and if it is too small, it will no longer look like a plane. Slower speeds need lower turn rates.
  • It is probably best to put "Animation - damage point" to 0 for all attacks, otherwise there is a risk that the unit doesn't have time to attack.

Code
Code (vJASS):

library AirplaneSystem requires ListModule, optional AutoIndex

    /*
       .~@ FINGOLFINS AIRPLANE MOVEMENT SYSTEM @~.
   
        INSTRUCTIONS:
            - Implement AutoIndex as specified in its description
            - Create your airplane units as you would with any normal unit
            - Set the turn speed of your airplanes to something below 0.1 (use shift-click)
            - Register plane types by adding this line to the initPlaneTypes function:
           
              call registerPlaneType('xxxx')
             
              where 'xxxx' is the id of your airplane.
    */

   
    //=======================//
    //======= GLOBALS =======//
    //=======================//

    globals
        private constant real UPDATE_INTERVAL   = 0.03125 //I SUBMIT TO THIS FOLLY
        private constant real ORDER_INTERVAL    = 1 // HOW OFTEN ORDERS ARE REFRESHED
        private constant real DEFAULT_FACING    = 270
       
        private constant integer MAX_PLAYERS    = 24
       
        //LOWER NUMBERS = LESS INERTIA. SET TO ZERO TO REMOVE INERTIA COMPLETELY. THIS VALUE MUST BE LOWER THAN 1!
        private constant real AIR_FRICTION      = 0.945
       
        //A CONSTANT MULTIPILER TO THE UNIT SPEED, TO CIRCUMVENT THE HARDCODED SPEED LIMIT OF WC3.
        private constant real SPEED_FACTOR      = 1.6
       
        //MAX DISTANCE TO MAP BORDER WHERE PLANES CAN TRAVEL
        private constant real BORDER_WIDTH      = 200
        //MAP BOUNDS
        private real MAP_MAX_X
        private real MAP_MAX_Y
        private real MAP_MIN_X
        private real MAP_MIN_Y
       
        private constant integer ORDER_SMART    = OrderId("smart")
        private constant integer ORDER_MOVE     = OrderId("move")
        private constant integer ORDER_ATTACK   = OrderId("attack")
       
        private constant integer TYPES_KEY      = StringHash("Airplane Types")
        private constant integer INSTANCES_KEY  = StringHash("Airplane Instances")
       
        private hashtable Hash                  = InitHashtable()
    endglobals
   
    native UnitAlive takes unit id returns boolean
   
    //=======================//
    //===== SYSTEM CODE =====//
    //=======================//
   
    function IsUnitPlane takes unit whichunit returns boolean
        return HaveSavedInteger(Hash, TYPES_KEY, GetUnitTypeId(whichunit))
    endfunction
   
    private function RegisterPlaneType takes integer unitid returns nothing
        call SaveInteger(Hash, TYPES_KEY, unitid, 1)
    endfunction
   
    private function InitPlaneTypes takes nothing returns nothing
        //REGISTER YOUR PLANE TYPES LIKE THIS
        call RegisterPlaneType('jetf')
        call RegisterPlaneType('bomb')
        call RegisterPlaneType('rdra')
    endfunction
   
    struct Plane
   
        implement List
   
        unit plane      = null
        unit target     = null
       
        real vx         = 0
        real vy         = 0
       
        real tx         = 0
        real ty         = 0
       
        real speed      = 0
       
        integer order   = 0
       
        private static timer t1
        private static timer t2
       
        private static method get takes unit whichunit returns thistype
            return LoadInteger(Hash, INSTANCES_KEY, GetHandleId(whichunit))
        endmethod
       
        method onDestroy takes nothing returns nothing
            //DON'T GIVE ME CRAP ABOUT USING onDestroy(), IT POSES NO PERFORMANCE ISSUE IN THIS CASE.
            call .listRemove()
            call RemoveSavedInteger(Hash, INSTANCES_KEY, GetHandleId(.plane))
           
            if .count == 0 then
                call PauseTimer(.t1)
                call PauseTimer(.t2)
            endif
        endmethod
       
        private static method updateOrder takes nothing returns nothing
            local thistype this = .first
            local thistype temp
            local real x
            local real y
           
            if .count == 0 then
                call PauseTimer(.t1)
                call PauseTimer(.t2)
            endif
           
            loop
                exitwhen this == 0
                set temp = .next
               
                if GetUnitTypeId(.plane) == 0 then
                    //DESTROY THE STRUCT WHEN THE PLANE NO LONGER EXISTS
                    call .destroy()
                else
                    if .target != null then
                        if UnitAlive(.target) == false or GetUnitTypeId(.target) == 0 then
                            set .target = null
                            set .tx = GetUnitX(.plane)
                            set .ty = GetUnitY(.plane)
                            set .order = ORDER_ATTACK
                        else
                            call IssueTargetOrderById(.plane, .order, .target)
                        endif
                    else
                        if .order == ORDER_MOVE or .order == ORDER_SMART then
                            set x = GetUnitX(.plane)
                            set y = GetUnitY(.plane)
                            if (x - .tx)*(x - .tx) + (y - .ty)*(y - .ty) < 10000 then
                                //ALLOW THE UNIT TO ATTACK ONCE IT HAS REACHED ITS DESTINATION
                                set .order = ORDER_ATTACK
                            endif
                        endif
                       
                        call IssuePointOrderById(.plane, .order, .tx, .ty)
                    endif
                endif
               
                set this = temp
            endloop
        endmethod
       
        private static method updatePosition takes nothing returns nothing
            local thistype this = .first
            local real x         = 0
            local real y         = 0
            local real f         = 0
           
            loop
                exitwhen this == 0  
               
                set f = GetUnitFacing(.plane)*bj_DEGTORAD
               
                //ADD FRICTION FIRST INCASE IT IS ZEROED
                set .vx = (.vx * AIR_FRICTION) + .speed * Cos(f)
                set .vy = (.vy * AIR_FRICTION) + .speed * Sin(f)
               
                set x = GetUnitX(.plane)+.vx
                set y = GetUnitY(.plane)+.vy
               
                //ENFORCE BOUNDS
               
                if x > MAP_MAX_X then
                    set x = MAP_MAX_X
                elseif x < MAP_MIN_X then
                    set x = MAP_MIN_X
                endif
               
                if y > MAP_MAX_Y then
                    set y = MAP_MAX_Y
                elseif y < MAP_MIN_Y then
                    set y = MAP_MIN_Y
                endif
               
                call SetUnitX(.plane, x)
                call SetUnitY(.plane, y)
               
                set this = .next
            endloop
        endmethod
       
        private static method onOrderTarget takes nothing returns boolean
            local thistype this = thistype.get(GetTriggerUnit())
           
            if this == 0 then
                return false
            endif
           
            set .target = GetOrderTargetUnit()
            set .tx = GetUnitX(.target)
            set .ty = GetUnitY(.target)
            set .order = GetIssuedOrderId()
           
            return false
        endmethod
       
        private static method onOrderPoint takes nothing returns boolean
            local real x = GetOrderPointX()
            local real y = GetOrderPointY()
            local thistype this = thistype.get(GetTriggerUnit())
           
            if this == 0 then
                return false
            endif
           
            set .tx = x
            set .ty = y
            set .target = null
            set .order = GetIssuedOrderId()
           
            return false
        endmethod
       
        static method create takes unit whichunit returns thistype
            local thistype this = thistype.allocate()
           
            call .listAdd()
           
            call SaveInteger(Hash, INSTANCES_KEY, GetHandleId(whichunit), this)
           
            //=========SOME MATH TRIVIA!=========//
            //The acceleration is given by the following differential equation:
            // v(t)' = a = v(t) - v(t)*(1-R), where R = AIR_FRICTION
            //And it's solution:
            //v(t) = (a/(1-R)*(1 + e^-(1-R)t)
            //Calculating the limit when (t -> infinity) gives:
            //vmax = a/(1-R)
            //vmax*(1-R) = a (in this case 'vmax' is known and 'a' is unknown)
            //===========END OF TRIVIA===========//
           
            set .plane = whichunit
            set .speed = GetUnitDefaultMoveSpeed(.plane)*(1-AIR_FRICTION)*UPDATE_INTERVAL*SPEED_FACTOR
           
           
            set .tx = GetUnitX(.plane) + 500 * Cos(DEFAULT_FACING*bj_DEGTORAD)
            set .ty = GetUnitY(.plane) + 500 * Sin(DEFAULT_FACING*bj_DEGTORAD)
            set .order = ORDER_SMART
           
            call SetUnitMoveSpeed(.plane, 0.01)
            call SetUnitFacing(.plane, DEFAULT_FACING)
            call SetUnitFlyHeight(.plane, 0, 0)
            call SetUnitFlyHeight(.plane, GetUnitDefaultFlyHeight(.plane), GetUnitDefaultFlyHeight(.plane)/3)
           
            if .count == 1 then
                call TimerStart(.t1, UPDATE_INTERVAL, true, function thistype.updatePosition)
                call TimerStart(.t2, ORDER_INTERVAL, true, function thistype.updateOrder)
            endif
            return this
        endmethod
       
        private static method onUnitEntersMap takes nothing returns boolean
            call thistype.onIndex(GetFilterUnit())
            return false
        endmethod
       
        private static method onIndex takes unit u returns nothing
            if IsUnitPlane(u) then
                call .create(u)
            endif
        endmethod
       
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local integer i = 0
            local rect r = GetWorldBounds()
            local region map = CreateRegion()
            local group g = CreateGroup()
           
            set MAP_MAX_X = GetRectMaxX(r)-BORDER_WIDTH
            set MAP_MAX_Y = GetRectMaxY(r)-BORDER_WIDTH
            set MAP_MIN_X = GetRectMinX(r)+BORDER_WIDTH
            set MAP_MIN_Y = GetRectMinY(r)+BORDER_WIDTH
           
            set Plane.t1 = CreateTimer()
            set Plane.t2 = CreateTimer()
           
            call InitPlaneTypes()
           
            loop
                exitwhen i > MAX_PLAYERS
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
                set i = i+1
            endloop
            call TriggerAddCondition(t, Condition(function Plane.onOrderTarget))
           
            set t = CreateTrigger()
            set i = 0
           
            loop
                exitwhen i > MAX_PLAYERS
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
                set i = i+1
            endloop
            call TriggerAddCondition(t, Condition(function Plane.onOrderPoint))
           
            static if (LIBRARY_AutoIndex) then
                call OnUnitIndexed(Plane.onIndex)
            else
                set t = CreateTrigger()
                call RegionAddRect(map, r)
                call TriggerRegisterEnterRegion(t, map, function Plane.onUnitEntersMap)
               
                set i = 0
                loop
                    exitwhen i > MAX_PLAYERS
                    call GroupEnumUnitsOfPlayer(g, Player(i), function Plane.onUnitEntersMap)
                    set i = i+1
                endloop
            endif
           
            call DestroyGroup(g)
            call RemoveRect(r)
            set g = null
            set r = null
            set t = null
            set map = null
        endmethod
   
    endstruct

endlibrary
 


UPDATE 1:
- Made most struct methods private.
- The system now uses GetUnitTypeId(whichunit) == 0 to check if a unit no longer exists.

UPDATE 2:
- The system now uses AutoIndex instead of AutoDex. Various other fixes to satisfy the moderators demands.

UPDATE 3:
- AutoIndex is now optional.
- Now supports 24 players.

UPDATE 4:
- Removed all custom models from the map, so that people won't be confused as to what to import.
- Map border width is now customizeable.

UPDATE 5:
- Various small changes to conform with moderator's requests


Keywords:
Airplane, inertia, flying, air, fighter, ship
Contents

Realistic Airplane System (Map)

Reviews
Moderator
Realistic Airplane System | Reviewed by BPower | 15.06.2015 Concept[/COLOR]] [IMG]Partly takes over movement controll of units. Is capable of creating a smooth movement for airplanes. [IMG] The target group of map makers is probably very...
  1. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,191
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    Thanks! Nice to hear and looking forward to seeing it in action!
     
  2. Rockstar356

    Rockstar356

    Joined:
    Nov 15, 2017
    Messages:
    60
    Resources:
    5
    Maps:
    5
    Resources:
    5
    It's really amazing, I love the inertia of movement, it gives it to very realistic style.
    I could not use it, I do not understand a single word of JASS.
    I do not understand what kind of people this tutorial is for, for people who already know how to create and configure a flight system?
    Because people like me, who know something about gui but nothing about jass, we can not use it.
    But I do not want to be bad. Great work, I just leave my opinion. I will use it after learning JASS.
     
  3. Hazop

    Hazop

    Joined:
    Jul 2, 2015
    Messages:
    766
    Resources:
    2
    Maps:
    2
    Resources:
    2
    Unfortunate that its broken :(
     
  4. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,191
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    Uhm, no, it's not broken?! I just tried it and it works perfectly. It's also used in my World Domination map.


    You shouldn't need JASS knowledge to use it... just copy and paste. The only thing you need is the unit ID of your plane. You can get it from presing CTRL+D in the object editor (there is also an option from a drop down menu to "show raw unit data" or something like that). Unit IDs are 4 letters and numbers - for instance, Footman is 'hfoo'. Custom units usually have IDs like 'h000', 'h001', and so on. Just paste this value into the "()" symbols the way i wrote it in the description.
     
  5. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,191
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    Updated to support 24 players. AutoIndex is now optional.

    Also, can someone PLEASE move this out of substandard, or at least tell me what i need to change to have it moved? @IcemanBo
     
  6. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,181
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    @MyPad fyi. (I'm not a reviewer anymore)
     
  7. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,191
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    Sorry, you're right, i keep forgetting!
     
  8. Hazop

    Hazop

    Joined:
    Jul 2, 2015
    Messages:
    766
    Resources:
    2
    Maps:
    2
    Resources:
    2
    Thanks for updating it! I can finally use it for my map. :D
     
  9. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,306
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    Moved to Pending.

     
    Last edited: Oct 10, 2019
  10. purparisien

    purparisien

    Joined:
    Jul 17, 2011
    Messages:
    529
    Resources:
    17
    Models:
    2
    Icons:
    4
    Maps:
    11
    Resources:
    17
    Fingfolfin, I have an issue with the system. It can works if I use premade units in the map, but if I create them, it no longer works.
    Empire Earth V.1.74
    Do you have any idea why ?
     
  11. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,191
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    Yes, it must be that the tirigger for registering new units is not working. I will fix it once i get home.
     
  12. purparisien

    purparisien

    Joined:
    Jul 17, 2011
    Messages:
    529
    Resources:
    17
    Models:
    2
    Icons:
    4
    Maps:
    11
    Resources:
    17
    Thanks you. By the way, I really dont understand why this system is in substandard
     
  13. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,306
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    That's an odd observation. I thought this was already moved back to Pending.
     
  14. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,191
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    I just tested it out, and it works for me. Can you give some more information?

    I updated the map, it now has a factory where you can produce new planes.
     
  15. purparisien

    purparisien

    Joined:
    Jul 17, 2011
    Messages:
    529
    Resources:
    17
    Models:
    2
    Icons:
    4
    Maps:
    11
    Resources:
    17
    I've made a pastebin to show you. The models at the start of the move properly. However, if you train one, he will stay idle
    Test | HIVE

    Edit: I found out why. Mixing your triggers with Is unit moving create confusion within units.
     
    Last edited: Oct 10, 2019
  16. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,306
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    In
    Plane.updateOrder
    , it looks like
    OrderId
    is often called for a constant input. This can be stored in a static instance, so as to optimize performance (even just a little bit).

    Code (vJASS):

    OrderId("smart")  // Could be transformed into a global
    OrderId("attack") // Same as above
     


    UnitAlive(.target) == false
    ->
    (not UnitAlive(.target))
    , again for performance.

    In
    Plane.get
    (a public static method for some reason), just return
    LoadInteger(Hash, INSTANCES_KEY, GetHandleId(whichunit))
    directly. No need for checking if an entry exists for that unit, since it will return a safe value of 0 anyway for units which aren't registered to the system.

    Following
    Plane.get
    , the functions
    IsUnitPlane
    and
    RegisterPlaneType
    appear to be public, even though the presence of the 'private' keyword would suggest otherwise. Are they meant to be private?

    That's about it. Right now, UnitDex is currently a functional library. It would be appreciated, though optional, if support for it is added as an optional requirement.
     
  17. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,191
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    This function only runs once every second. If it was once every frame, i could accept that this would save a handful of instructions (on what is likely a hashtable lookup), but seriously - this quest for performance which most JASS-users love to pursue has an almost cargo cult-like nature to it, Nestharus was especially guilty of this to the point of purposefully obfuscating his code in the hope of gaining a couple of nanoseconds of execution time. The rule that timers should run exactly once every 0.03125 second falls into the same category - there is so much going on during any given frame of game time that updating only every 0.03 seconds is not going to make things any less balanced, only when you go above that value are you actually wasting time running things faster than the game updates. These arbitrary practices serve no purpose except being a pain in the ass for anyone trying to upload JASS resources. Nevertheless, i can change this for neatness and your peace of mind more than anything else.

    End of rant.

    Fair enough, i will change this.

    What do you mean by this? If there is a private keyword before the function, then it is by definition private. There is no utility in registering plane types anywhere else than in this library, since you can't create unit types at run-time. Though I guess I can expose IsUnitPlane in case someone would want to know this. My idea was to have Plane.get(u) != 0 to check for this, but i can swap that around, again, for the sake of neatness.

    Thanks for taking your time moderating this, sorry if I'm being an ass.
     
  18. kellym0

    kellym0

    Joined:
    Nov 28, 2009
    Messages:
    841
    Resources:
    51
    Models:
    49
    Skins:
    1
    Tutorials:
    1
    Resources:
    51
    still updating it are you? mmmmmmm? hmm heh heheheh...

    I remember this being pivotal to the star wars mini game I had in my map, all those years ago... good times... my friends thought this system was incredible. they all got to try it them selves alone, I must of given away 6 copies of that version of your map.

    I remember taking this test map, and trying to host it on bnet, but I couldn't. lol
     
  19. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,306
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    Did not notice that they were running on different intervals. I had previously assumed that they were both running on the same interval, so the performance optimizations were suggested.

    The functions being described (IsUnitPlane and RegisterPlaneType) look like they could be public functions due to their name, despite their private status. Yes, it would be nonsensical to register plane types at runtime, but it can be done during the initialization phases of the map (barring zero timer initialization).