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