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

Realistic Airplane System

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:

JASS:
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.

JASS:
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
MyPad
The realistic airplane system is a resource that few users expect. As a somewhat original system (this may be changed later), this deserves some recognition on that front. It forces the units registered to the system to move in an airplane-like...

Moderator

M

Moderator


Realistic Airplane System | Reviewed by BPower | 15.06.2015

[COLOR="gray"

[COLOR="gray"

[COLOR="gray"

[COLOR="gray"

[COLOR="gray"

Concept[/COLOR]]
126248-albums6177-picture66521.png
Partly takes over movement controll of units.
Is capable of creating a smooth movement for airplanes.
126248-albums6177-picture66523.png

The target group of map makers is probably very small.
However it's an interesting sytem. I like it.
Code[/COLOR]]
126248-albums6177-picture66521.png
  • The systems is MUI and leakless.
  • I still don't recommend to use it in its current state.
  • I think there is alot of room for improvements.
126248-albums6177-picture66523.png
  • Normally systems use a divisor of 1 for their timer timeout. (.03125, .0625, ... )
  • Update order timeout should also be configurable.
  • When deallocating an instance, you could null those two members: unit u and unit target.
  • Plane unit handles are public and therefore should use a reasonable name i.e "plane" and not "u"
  • Encapsulation is very important, so users do not accidently break the system code.
    Everything not ment to be manipulated should be private by default i.e your static timers.
  • OrderId("attack") could be stored into a variable or use "851983".
    Same applies for order move, which is 851986.
  • GetWidgetLife(.target) <= 0 --> GetUnitState(target) == UNIT_STATE_DEAD or GetUnitTypeId(target) == 0
    As alternative you could use native not UnitAlive(target)
  • Set map bounds -200/200 when you define them in the first place.
    This will prevent unessesary extra computation time.
  • According to the JPAG function names should be written IsUnitPlane and not isUnitPlane.
    We use camelCase for locals, non constant globals, struct methods and struct members.
  • struct plane --> struct Plane
  • According to user reports library UnitDex is currently not working properly.
    You might want to switch to another unit indexing tool.
    Anyway:
    I suggest you go with GetHandleId(unit) as you already initialize an hashtable.
Demo Map[/COLOR]]
126248-albums6177-picture66523.png
  • Your demo map is packed with a lot of imported files, which do not relate to the system.
    This can cause confusion for users, as they don't know what to import in the first place.
  • Apart from that it's a nice demo.
Rating[/COLOR]]
CONCEPTCODEDEMO MAPRATINGSTATUS
3.5/5
2/5
4/5
3/5
NEED FIX
Links[/COLOR]]

[COLOR="gray"

[/TD]
 
Level 4
Joined
Oct 22, 2013
Messages
73
I would have always said a system like that would take much more lines!
This deserves a 5/5.
It'd be too much to ask for the GUI version, I guess...
 
I feel uncomfortable with the name.

Why not Airplane Simulator?

I think the word "simulator" would imply that it is a realistic airplane experience (such as bob666's FlightSim), which is not the case here. The title may be a bit uninspired - but on the other hand, there is no other such system available in this section, so this is pretty much "THE airplane system".
 
I see you also cause an airplane to attack.

Could you describe what exactly an airplane is supposed to do?
For example, as mentioned it might be explained that it automatically attacks nearby enemies.
This influences unit behaviour, for example unit won't move to targeted point,
or it will stop patroling, once it mentiones an enemy in range and starts attacking.

In your struct you can make methods private if they should not be touched.
You can use onInit static method and make all private.

private constant real INTERVAL = 0.038
How you come to this value for interval?

onDestroy ->
set planes[GetUnitId(this.u)] = 0

Use UnitAlive native over check life > 0.405.

Ignore an instance in loop if plane (unit) is dead.

Destroy an instance if plane (unit) is not in game anymore.
 
Last edited:
Alright, i will change methods to private and use IsUnitAlive. However, i can't skip instances for dead planes, since i want dead planes to keep flying while their death animation is played. This way, users have more freedom in customizing the crash sequence, by manipulating the "art - death time" in conjugation with "can't raise - does not decay" death type. I could use a trigger fired on unit deindex to remove instances, but the performance gain would be debatable as the trigger would be fired everytime any unit exits the map (as opposed to only airplanes, the way the code works now).
 
By the way, IcemanBo, is it safe to run the UnitDex initialization stuff within a struct onInit method? Because i know that onInit methods and library initializers are run in different segements during the map load. I was afraid that this would prevent the index event to properly fire, in the event that the NeoDex initializer would be run after the airplane initializer.


EDIT: Updated. However, i didn't add any UnitAlive native, because it doesn't exist. There is only a BJ for this, which leads to another BJ, which uses the same method as i do.
 
Last edited:
Sorry man, for lateness.

I was afraid that this would prevent the index event to properly fire, in the event that the NeoDex initializer would be run after the airplane initializer.
If you make your library require an other library, it will be ok.

UnitDex should not be used, btw. I also tried using it, and mentioned an error onIndex event.
At this moment we should go with approved indexers, until UnitDex is not ensured to work correctly.

i didn't add any UnitAlive native, because it doesn't exist
Yes, you first need to declare it somewhere on top. It does exist in commin.ai.
Just write this line after the globals for example:

native UnitAlive takes unit id returns boolean

... then you should be able to use it.

Some members don't need to be public. (if no private is infront, they are public)

private constant real INTERVAL = 0.033
^In JASS you can be very precise. You can use 0.031250000,
it will result in exactly 32 ticks per second.

I think there should be some deindex event used. onDeindex the player[unitIndex] should be removed from list, as it's index can be recylcled.
Later a random unit can get the same index again, and it would be identified as a plane, even it would be an other unit type.

Very interesting system, btw.
 
Update order timeout should also be configurable.
Plane unit handles are public and therefore should use a reasonable name i.e "plane" and not "u"
Encapsulation is very important, so users do not accidently break the system code.
Everything not ment to be manipulated should be private by default i.e your static timers.
Set map bounds -200/200 when you define them in the first place.
This will prevent unessesary extra computation time.
According to the JPAG function names should be written IsUnitPlane and not isUnitPlane.
We use camelCase for locals, non constant globals, struct methods and struct members.
struct plane --> struct Plane

Fair enough, i will fix all this. Keep in mind though, if i am to "bake in" the offset into the world bounds, i won't benefit from using the WorldBounds library as you suggested.

GetWidgetLife(.target) <= 0 --> GetUnitState(target) == UNIT_STATE_DEAD or GetUnitTypeId(target) == 0

All three of these statements are integer comparisions, what is the difference?
Hell, comparing an integer to a global is even slower than comparing it to a value.

OrderId("attack") could be stored into a variable or use "851983".
Same applies for order move, which is 851986.

Uugghhh.... why in the world would you change a perfectly readable line of code to something completely unreadable, for a speed gain that is neglible to say the least??? This is so amazingly stupid, i just want to beat my head into the desk.

According to user reports library UnitDex is currently not working properly.
You might want to switch to another unit indexing tool.

Hmm, it seems to be working fine for me, i've used it for multiple different maps. But ok, maybe i will use the hashtable.

When deallocating an instance, you could null those two members: unit u and unit target.

I don't really see the point of this since these members are assigned in the constructor and thus will never hold old values.

Normally systems use a divisor of 1 for their timer timeout. (.03125, .0625, ... )

This is completely arbitrary as computers don't care about what kind of fractions you use. I hate it when people try to enforce these kind of useless rules, but i will conform to it just because people here seem to be so damn anal about it.
 
Last edited:
Just updated this, any chance it can be moved back into "submissions"? How does the new system work?
Tested the uploaded map and it seems to work flawlessly. I'll be using this bad boy.

Edit: I don't quite understand how the tag "euro" etc that are declared in the trigger match/connect to the units in the object editor? In terms of getting a unit's ID.
 
Last edited:
Level 7
Joined
Nov 15, 2017
Messages
60
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.
 
Uhm, no, it's not broken?! I just tried it and it works perfectly. It's also used in my World Domination map.


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.

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

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

JASS:
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.
 
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).

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

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

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.

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.

Fair enough, i will change this.

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.

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.
 
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
 
This function only runs once every second.
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.

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.

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).
 
Is it possible to turn this into passive ability?

Yes, though if you want to be able to remove the ability, it will require some more code. But to just make them spawn based on some ability, you can change this:

Code:
function IsUnitPlane takes unit whichunit returns boolean
        return HaveSavedInteger(Hash, TYPES_KEY, GetUnitTypeId(whichunit))
endfunction

To this:

Code:
    function IsUnitPlane takes unit whichunit returns boolean
        if HaveSavedInteger(Hash, TYPES_KEY, GetUnitTypeId(whichunit)) then
            return true
        elseif GetUnitAbilityLevel(whichunit, 'abil') > 0 then
            return true
        endif
        return false
    endfunction

where 'abil' is the raw ID of your ability.
To remove a plane from a unit, you would do this:

Code:
local Plane plane = Plane.get(unit)
if plane != 0 then
    call plane.destroy()
endif

You would also have to change "private static method get" to "public static method get" in the system.
 
Level 8
Joined
Jun 26, 2019
Messages
318
@Fingolfin Wait. More codes if there's more reasons with the ability? Bah. Hmmm. I have an idea. What about to add the condition to your system to be equals to the level of hero? Example.

Airplane System trigger system level 1 with 522 speed equals to any hero that is level 1, and all the level 1 heros get 522 speed at all the time.

Airplane System trigger system level 2 with 1000 speed equals to any hero that is level 2, and all the level 2 heros get 1000 speed at all the time.

Airplane System trigger system level 3 with 1580 speed equals to any hero that is level 3, and all the level 3 heros get 1580 speed at all the time.

Airplane System trigger system level 4 with 2100 speed equals to any hero that is level 4, and all the level 4 heros get 2100 speed at all the time.

Airplane System trigger system level 5+ with 3000 speed equals to any hero that is level 5 and up, and the level 5 and up on all heros get 3000 speed at all the time.

Understand what I am saying? :)
 
With JASS code, you can do practically anything you want. For instance, let's say that in the method updatePosition you add:

Code:
local real speed = 0

And then after "exitwhen this == 0" you add this:

Code:
// Basically, "(1-AIR_FRICTION)*UPDATE_INTERVAL*SPEED_FACTOR" is something you multiply any max speed with to compensate for the fact that planes have inertia and accumulate speed over time.
// 'abil' in this case should be replaced with the raw ID of your ability. You can see these by pressing CTRL+D in the object editor.

speed = GetUnityAbilityLevel(.plane, 'abil') * 500 * (1-AIR_FRICTION)*UPDATE_INTERVAL*SPEED_FACTOR

And then you replace ".speed" in the rest of the method with just "speed".
It's a bit less efficient though but it works. Ideally you'll want to precalculate the max speed from the ability and set it to the .speed variable. You can do that with the code i PM:ed you. Just have a trigger detect when a unit gains an ability, and then set the speed from there.
 
The realistic airplane system is a resource that few users expect. As a somewhat original system (this may be changed later), this deserves some recognition on that front. It forces the units registered to the system to move in an airplane-like fashion, as intended. Caution must be exercised while registering certain unit types to this system; this will prevent certain units from shift-queuing their orders. It does not leak memory (such memory leaks can be attributed to units themselves) and is quite optimized.

Code:

  • The following remarks are suggestions at best:
    • On enforcing map bounds, in Plane.updatePosition(), the following lines could be reduced drastically:

      Code:
      if x > MAP_MAX_X then
          set x = MAP_MAX_X
      elseif x < MAP_MIN_X then
          set x = MAP_MIN_X
      endif

      to

      Code:
      set x = IMinBJ(IMaxBJ(x, MAP_MIN_X), MAP_MAX_X)

      In a similar fashion, the variable y can be reduced like x, only with a few letters changed.

Status:


Approved
 
The realistic airplane system is a resource that few users expect. As a somewhat original system (this may be changed later), this deserves some recognition on that front. It forces the units registered to the system to move in an airplane-like fashion, as intended. Caution must be exercised while registering certain unit types to this system; this will prevent certain units from shift-queuing their orders. It does not leak memory (such memory leaks can be attributed to units themselves) and is quite optimized.

Code:

  • The following remarks are suggestions at best:
    • On enforcing map bounds, in Plane.updatePosition(), the following lines could be reduced drastically:

      Code:
      if x > MAP_MAX_X then
      set x = MAP_MAX_X
      elseif x < MAP_MIN_X then
      set x = MAP_MIN_X
      endif

      to

      Code:
      set x = IMinBJ(IMaxBJ(x, MAP_MIN_X), MAP_MAX_X)

      In a similar fashion, the variable y can be reduced like x, only with a few letters changed.

Status:


Approved

Thank you for the approval!!

Regarding the feedback, we in the old school of JASS-users have been taught to inline BJ functions inside tight loops to reduce the overhead associated with function calls. This is also something that different mods can have different opinions on the necessity of. :)
 
Level 3
Joined
Aug 25, 2018
Messages
29
Man, I can't get aaaaanything to work. All I get is errors errors errors. I'm stupid.

Ok, first off, cool system. Here is my problem: All I did was download this test map, and I literally scrolled down to this section:

private function InitPlaneTypes takes nothing returns nothing
//REGISTER YOUR PLANE TYPES LIKE THIS
call RegisterPlaneType('jetf')
call RegisterPlaneType('bomb')
call RegisterPlaneType('rdra')
endfunction

I copy pasted the third line: call RegisterPlaneType('rdra')

and I added a 4th line with a new unit ID, the ID for a druid of the talon in storm crow form, because I was curious if this cool system would support units that could morph between land and flying forms. So it looked like this:

private function InitPlaneTypes takes nothing returns nothing
//REGISTER YOUR PLANE TYPES LIKE THIS
call RegisterPlaneType('jetf')
call RegisterPlaneType('bomb')
call RegisterPlaneType('rdra')
call RegisterPlaneType('xxxx') (I forget the exact code of the talon form druid as I post this)
endfunction

I also did go into the unit manager and changed(using shift click) the turn rate of the talon form druid to .07

Anyways I went to save the map before testing and it wouldn't let me save due to a huge list of errors. Can this system theoretically support units that morph between ground and air form?
 
Level 38
Joined
Feb 27, 2007
Messages
4,951
The errors aren’t from the system. JASSHelper is no longer enabled by default because the Reforged team doesn’t know what they’re doing.

In the trigger editor navigate to the top bar and make sure JASSHelper > Enable JASSHelper as well as JASSHelper > Enable vJASS are both checked on.
 
Top