· 8 years ago · Dec 31, 2017, 02:36 PM
1-- Air Traffic Control --
2 -- March 2017 --
3 -- by BMaster --
4 -- SAES:RPG --
5 -- Clientside --
6
7-- TO DO LIST --
8-- Check all "if then" combinations for improvement
9
10-- LOGBOOK OF CHANGES --
11-- 11-04-2017: Removed annonymous function to add a plane to gridlist and integrated in createPlane()
12 -- Applied "if not x then return end" to lines 134, 185, 220, 266, 274, 322, 360, 481, 523,
13 -- Added getSelectedPlane()
14
15-- 18-04-2017: Added new manner to determine the IAF point from the ComboBox (line 385)
16 -- Removed annonymous function to remove a SAR region, made it a full function --> deleteSAR()
17 -- Added a SAR table, so multiple SAR regions at the same time are possible.
18 -- Added exit function
19 -- Edited the time interval for a plane to be created, now being square proportional to the number of active planes.
20 -- IAF labels made dependant on resolution of the client
21 -- Removed custom event onPlaneActive
22 -- Fixed "Clear to land" button bug. You cannot clear an aircraft to land if he is not near an IAF
23
24-- 27-04-2017: Started working on player controlled planes to be added.
25 -- Client/server connections established
26
27-- 23-12-2017: Fixed 'cz' value bug in reportWhenCloseToIAF
28 -- Fixed bug so it's not possible to assign a new descend/climb level when plane is already climbing/descending. One should wait until altitude has been reached.
29 -- Fixed bug to let the plane turn/climb/descend with "Value" in the valueInst box.
30 -- Fixed SARcol bug, which used to fuck the table and gridpos up. Added reorderTable functions in the SARcol function.
31 -- Edited automated plane spawn times.
32
33-- 24-12-2017: Fixed bug to be able to switch selected aircraft when one plane is approaching an IAF. Made sure the "Clear to Land" button is not active after switching.
34 -- Fixed bug so two aircraft can approach an IAF without the onClientGUIClick being added twice.
35 -- Added ability to turn/clb/des the aircraft when an IAF has been flown over without landing clearance.
36
37-- 25-12-2017: Added new system to create flights (more realisticly), airlines flying from specific airports and with specific planes. Also made San Fierro and Los Santos the greater airports, which will get more arrivals than Las Venturas.
38
39-- 26-12-2017: Added departure points
40 -- Added sytstem that creates randomly inbound and outbound aircraft
41 -- Added "Clear for Take-off button"
42 -- Added take-off
43 -- Added reporting switch over on departure points, including reward for the controller.
44 -- Created Operating Guide (PDF) for ATC Controllers.
45
46-- 27-12-2017: Fixed bug that caused debug errors when a climbing or descending aircraft is deleted, the timer is not deleted. Now it is.
47
48-- 31-12-2017: Fixed midair collisions and flying through Search and Rescue areas.
49 -- Fixed that also departure points are deleted upon exiting the ATC job.
50
51-- Custom added Events
52addEvent("onPlaneGroundHit", false)
53addEvent("addPlayerPlanes", true)
54
55-- Custom added functions
56local fps = false
57function getFPS()
58 return fps
59end
60
61local function updateFPS(msSinceLastFrame)
62 fps = (1 / msSinceLastFrame) * 1000
63end
64
65addEventHandler("onClientPreRender", root, updateFPS)
66
67-- Marker to start the ATC job --
68ATCmarker = createMarker(1636,-2286, 12.5, "cylinder", 2.0, 255, 0, 0, 255)
69
70function ATCStartUp(hitPlayer, matchingDimension)
71 if not getPedOccupiedVehicle(hitPlayer) and matchingDimension == true then
72 setElementPosition(localPlayer, 1621, -2287, 77)
73 fadeCamera(false, 1.0, 0, 0, 0)
74
75 -- Some functions which have to be coupled to client render to detect whether they are happening.
76 addEventHandler("onClientRender", root, reportWhenCloseToIAF)
77 addEventHandler("onClientRender", root, updatePos)
78 addEventHandler("onClientRender", root, planeOffMap)
79 addEventHandler("onClientRender", root, midAirCol)
80 addEventHandler("onClientRender", root, onBlipInSARcol) -- This one because onColShapeHit apparantly doesn't work for blips.
81
82 -- Creating an aviation related map (more things on the map to be added here)
83 forcePlayerMap(true)
84 showChat(true)
85
86 local x, y, z = getElementPosition(localPlayer)
87 ATCblip = createBlip(x,y,z, 5, 1, 255, 0, 0, 255)
88 showCursor(true)
89 planes = {}
90 SARs = {}
91 IAFs = {}
92
93 -- Waypoints/IAFs (Instrument Approach Fix)
94 pointU = createBlip(384, -2576, 0, 41, 3)
95 pointV = createBlip(-518, 1063, 0, 41, 3)
96 pointA = createBlip(-2338, -736, 0, 41, 3)
97 pointB = createBlip(1467, 312, 0, 41, 3)
98 pointI = createBlip(1467, 2511, 0, 41, 3)
99 pointT = createBlip(2825, -2556, 0, 41, 3)
100
101 DELTA = createBlip(2819, 43, 0, 24, 5)
102 ROMEO = createBlip(-2310, -2625, 0, 34, 5)
103 SIERRA = createBlip(-2462, 2339, 0, 38, 5)
104
105 addEventHandler("onClientRender", root, showIAFTags)
106 SIDs = {DELTA, ROMEO, SIERRA}
107
108 table.insert(IAFs, pointU)
109 table.insert(IAFs, pointV)
110 table.insert(IAFs, pointA)
111 table.insert(IAFs, pointB)
112 table.insert(IAFs, pointI)
113 table.insert(IAFs, pointT)
114
115 -- Creating an ATC interface
116 ATC_info = guiCreateWindow(0.9, 0, 0.10, 0.5, "Transponder Info", true)
117 ATC_ctl = guiCreateWindow(0, 0.5, 0.10, 0.5, "ATC Interface", true)
118 exit_wndw = guiCreateWindow(0.9, 0.9, 0.1, 0.1, "Exit", true)
119
120 guiSetAlpha(ATC_info, 1)
121 guiSetAlpha(ATC_ctl, 1)
122
123 local callsign = guiCreateLabel(0.1, 0.1, 1, 0.1, "Callsign:", true, ATC_info)
124 local orig = guiCreateLabel(0.1, 0.2, 1, 0.1, "Origin:", true, ATC_info)
125 local dest = guiCreateLabel(0.1, 0.3, 1, 0.1, "Destination:", true, ATC_info)
126 local type = guiCreateLabel(0.1, 0.4, 1, 0.1, "Type:", true, ATC_info)
127 local Heading = guiCreateLabel(0.1, 0.6, 1, 0.1, "Heading:", true, ATC_info)
128 local altitude = guiCreateLabel(0.1, 0.7, 1, 0.1, "Altitude:", true, ATC_info)
129
130 -- Creating an interface to select aircraft
131 if planeSelect ~= true then
132 planeSelect = guiCreateGridList(0.1, 0.8, 0.9, 0.2, true, ATC_info)
133 local callsigncol = guiGridListAddColumn(planeSelect, "Current Flights", 0.5)
134 local dests = guiGridListAddColumn(planeSelect, "Destination", 0.5)
135 guiGridListSetSortingEnabled(planeSelect, false)
136 addEventHandler("onClientGUIClick", planeSelect, selectPlane)
137 end
138
139 -- Letting planes spawn depending on the number of planes already in the airspace.
140 if #planes < 3 then
141 t = math.random(10, 20)
142 elseif #planes > 2 and #planes < 6 then
143 t = math.random(40, 60)
144 elseif #planes > 5 then
145 t = 90
146 end
147
148 setTimer(createPedPlane, t * 1000, 0)
149
150 -- Creating the ATC Control Interface
151
152 turnCommand = guiCreateButton(0, 0.05, 1, 0.1, "Instruct Turn", true, ATC_ctl)
153 altCommand = guiCreateButton(0, 0.15, 1, 0.1, "Instruct Clb/Des", true, ATC_ctl)
154 appCommand = guiCreateButton(0, 0.25, 1, 0.1, "Select Approach", true, ATC_ctl)
155 TOCommand = guiCreateButton(0, 0.55, 1, 0.1, "Clear for Take-Off", true, ATC_ctl)
156 landCommand = guiCreateButton(0, 0.45, 1, 0.1, "Clear for landing", true, ATC_ctl)
157
158 guiSetEnabled(landCommand, false)
159 guiSetEnabled(TOCommand, false)
160
161 local valueInstLabel = guiCreateLabel(0, 0.8, 1, 0.05, "Insert Altitude/Heading", true, ATC_ctl)
162 valueInst = guiCreateEdit(0, 0.85, 1, 0.05, "Value", true, ATC_ctl)
163
164 -- Creating the exit button
165 local exitCommand = guiCreateButton(0, 0.1, 1, 0.9, "Exit ATC Control", true, exit_wndw)
166 addEventHandler("onClientGUIClick", exitCommand, exitATC, false)
167
168 -- Find out which (real)planes are already in the air.
169 --triggerServerEvent("onControllerStart", resourceRoot)
170
171 end
172end -- Creating ATC Interface
173
174addEventHandler("onClientMarkerHit", ATCmarker, ATCStartUp)
175
176function showIAFTags()
177 local width,height = guiGetScreenSize()
178
179 dxDrawText("UNIFORM", width * 0.53125, height * 0.9345)
180 dxDrawText("VICTOR", width * 0.4148, height * 0.33)
181 dxDrawText("ALPHA", width * 0.171, height * 0.6308)
182 dxDrawText("BRAVO", width * 0.6673, height * 0.4541)
183 dxDrawText("INDIA", width * 0.6781, height * 0.08789)
184 dxDrawText("TANGO", width * 0.8585, height * 0.9316)
185end
186
187function createFlight(inb)
188 local Dutch = {"Amsterdam Schiphol", "Paris Charles De Gaulle", "Royal Dutch Airlines"}
189 local Gamma = {"New York JFK", "Atlanta Heartsfield Jackson", "Los Angleos Intl", "Gamma Airlines"}
190 local TransAtlantico = {"Lima Airport", "Tokyo Narita", "Sydney Kingsfield", "London Heathrow", "Miami Intl", "Liberty City Intl", "Vice City Escobar Airport", "TransAtlantico"}
191 local FireFly = {"Miami Intl", "Manilla Airport", "London Heathrow", "Vancouver Intl.", "Honolulu Intl", "Sydney Kingsfield", "Dubai Intl", "Qatar Intl", "Istanbul Atatürk", "Moscow Domodedova", "Paris Charles de Gaulle", "FireFly"}
192 local Skylines = {"Madrid Barajas", "Rome Fiumicino", "Paris Orly", "Berlin Tegel", "Frankfurt Main", "Geneva Airport", "Copenhagen Intl", "Munich Airport", "Brussels Zaventem", "Budapest Airport", "Amsterdam Schiphol", "Luxembourgh Airport", "Skylines"}
193 local HansaLuft = {"Frankfurt Main", "Liege Airport", "Luxembourgh Airport", "HansaLuft Cargo"}
194 local ALT = {"Frankfurt Main", "Amsterdam Schiphol", "Maastricht Aachen", "Miami Intl", "Phoenix SkyHarbor", "Dallas Forth Worth", "New York JFK", "Los Angelos Intl", "San Fransisco Intl.", "Mexico City Airport", "Rio De Janeiro", "London Heathrow", "Frankfurt Main", "Tokyo Narita", "ALT AirCargo"}
195 local JuankAir = {"Liberty City Intl", "Vice City Escobar Airport", "Juank Air"}
196 local AndreasAirlines = {"Liberty City Intl", "Vice City Escobar Airport", "Andreas Airlines"}
197
198 local origins = {Dutch, Gamma, TransAtlantico, FireFly, Skylines, HansaLuft, ALT, JuankAir, AndreasAirlines}
199 local types = {{"Boeing 747", "Airbus A330", "Boeing 777"},{"Boeing 767", "Boeing 767", "Boeing 747", "Boeing 757", "Boeing 787"}, {"Airbus A380", "Airbus A380", "Boeing 747", "Boeing 777"}, {"Boeing 777", "Boeing 787", "Airbus A330"}, {"Boeing 777"}, {"Boeing 747F"}, {"Andromada"}, {"AT-400", "Shamal"}, {"AT-400", "Shamal"}}
200 local destinations = {"Los Santos Intl.", "Los Santos Intl.", "Los Santos Intl.", "Kasimir Intl. Las Venturas", "EasterBay San Fierro", "EasterBay San Fierro", "EasterBay San Fierro"} -- Done so to make Los Santos and San Fierro the major airports.
201 local fltnumber = math.random(1, 300)
202 local numberInOrigins = math.random(1,#origins)
203 local flight = origins[numberInOrigins]
204 local origin = flight[math.random(1,#flight - 1)]
205 local callsign = flight[#flight]
206 local destination = destinations[math.random(1,#destinations)]
207 local type = types[numberInOrigins][math.random(1, #types[numberInOrigins])]
208
209 if inb == true then
210 return callsign, origin, destination, type, fltnumber
211 elseif inb == false then
212 local origin2 = destination
213 local destination2 = origin
214
215 return callsign, origin2, destination2, type, fltnumber
216 end
217end -- The function which "realisticly" creates either an inbound or outbound flight, including callsign, flight number, destination, origin and type.
218
219function createPedPlane(direction)
220 if not planes then return end
221 inOut = math.random(1,2)
222 if #planes < 5 then
223 if inOut == 1 then -- This part for inbound aircraft.
224 -- Initializing a random position for a plane spawning (inbound)
225 x = math.random(-3000, 3000)
226 y = math.random(-3000, 3000)
227 z = math.random(200, 500)
228
229 HDG = math.random(0, 359) -- heading of the aircraft
230
231 airline, origin, destination, type, fltnumber = createFlight(true)
232 callsign = ""..airline.." "..tostring(fltnumber)..""
233
234 outputChatBox("[San Andreas Air Traffic Control]: Flight Crew: SAN ANDREAS CENTER, "..callsign..", with you!")
235 elseif inOut == 2 then
236 airline, origin, destination, type, fltnumber = createFlight(false)
237 if origin == "Los Santos Intl." then -- Initializing start up positions for departing flights.
238 x = 2079
239 y = -2593
240 z = 14
241 HDG = 270
242 elseif origin == "Kasimir Intl. Las Venturas" then
243 x = 1478
244 y = 1198
245 z = 11
246 HDG = 0
247 elseif origin == "EasterBay San Fierro" then
248 x = -1670
249 y = -177
250 z = 15
251 HDG = 40
252 end
253 callsign = ""..airline.." "..tostring(fltnumber)..""
254 outputChatBox("[San Andreas Air Traffic Control]: Flight Crew: SAN ANDREAS CENTER, "..callsign..", ready for take-off runway 69, at "..origin..", IFR to "..destination..".")
255 end
256
257 local thePlane = createBlip(x, y, z, 0, 3, 255, 255, 0, 255)
258 if not thePlane then return end -- check if the plane was succesfully created
259
260 -- Adding all flight parameters to the created blip
261 setElementData(thePlane, "inside", false)
262 setElementData(thePlane, "InboundOrOutbound", inOut)
263 setElementData(thePlane, "callsign", callsign)
264 setElementData(thePlane, "heading", HDG)
265 setElementData(thePlane, "origin", origin)
266 setElementData(thePlane, "destination", destination)
267 setElementData(thePlane, "type", type)
268 setElementData(thePlane, "altitude", z)
269 setElementData(thePlane, "rate", 5) -- If an aircraft is not approaching a runway, climbing or descending will be done with 5 units per second.
270
271 -- Adding the plane to all planes currently visible
272 local pos = #planes + 1
273 table.insert(planes, pos, thePlane)
274 setElementData(thePlane, "pos", pos)
275
276 setElementData(thePlane, "moving", true) -- Detect whether the aircraft hasn't landed yet.
277
278 if ( ATC_info ) then -- Make sure it is added to the selection interface
279 local addedPlane = guiGridListAddRow(planeSelect, tostring(callsign), tostring(destination))
280 guiGridListSetItemData(planeSelect, addedPlane, 1, thePlane) -- adding the information of the plane to the gridlist position.
281 if inOut == 1 then
282 guiGridListSetItemColor(planeSelect, addedPlane, 1, 255, 0, 0, 255)
283 else
284 guiGridListSetItemColor(planeSelect, addedPlane, 1, 0, 0, 255, 255)
285 end
286 setElementData(thePlane, "gridpos", addedPlane)
287 end
288 end
289
290 x = nil -- Nilling the variables as local could not be used due to the if then combinations.
291 y = nil
292 z = nil
293 HDG = nil
294 airline = nil
295 origin = nil
296 destination = nil
297 type = nil
298 fltnumber = nil
299 callsign = nil
300end -- Spawn a plane
301
302addCommandHandler("plane", createPedPlane) -- TESTING PURPOSE ONLY!
303
304function updatePos() -- Function to make the plane and the blip moving. Set Element Velocity does not work for that purpose.
305 for i,thePlane in ipairs(planes) do
306 if not thePlane then return end
307 if #planes > 0 then
308 if getElementData(thePlane, "moving") == true and getElementData(thePlane, "source") ~= true then
309 -- Maths to determine the speed along each axis from the heading HDG
310 local HDG = getElementData(thePlane, "heading")
311 local spd_x = 15 * math.sin(math.rad(HDG))
312 local spd_y = 15 * math.cos(math.rad(HDG))
313 local rate = getElementData(thePlane, "rate")
314
315
316 local x, y, z = getElementPosition(thePlane)
317 local x2 = spd_x * (2/getFPS()) -- displacement = speed x time and the position is updated every frame. Therefore it is important to know the FPS of the local player, so the speed remains constant. The number which is devided by the fps can be considered as the displacement per second.
318 local y2 = spd_y * (2/getFPS()) -- the number is considered the displacement per second.
319 local z2 = (rate/getFPS()) -- Aircraft always descend or climb with 3.3 units per second unless on Final.
320
321
322 if getElementData(thePlane, "altitude") == "Climbing" then -- This part is to make a realistic world coordinate descent, to enable traffic collision further on.
323 z = z + z2
324 elseif getElementData(thePlane, "altitude") == "Descending" or getElementData(thePlane, "altitude") == "Final" then
325 z = z - z2
326 end
327
328 setElementPosition(thePlane, x + x2, y + y2, z)
329
330 if z < 25 and getElementData(thePlane, "InboundOrOutbound") ~= 4 then -- Crash/Landing detection
331 setElementData(thePlane, "moving", false)
332 triggerEvent("onPlaneGroundHit", thePlane)
333 end
334 end
335 end
336 end
337end-- Making the plane and the blip move.
338
339
340function selectPlane(button, state)
341 if not ATC_info or not source or not planes then return end
342 if button == "left" and state == "up" and guiGridListGetSelectedItem(planeSelect) > -1 and guiGetText(landCommand) == "Clear for landing" then
343
344 if ( turnCommand ) and ( altCommand ) then -- This to prevent debugscript outputs about the events further on already being handled
345 removeEventHandler("onClientGUIClick", turnCommand, turn)
346 removeEventHandler("onClientGUIClick", altCommand, alt)
347 removeEventHandler("onClientGUIClick", appCommand, assignApproach)
348 removeEventHandler("onClientGUIClick", landCommand, initiateLanding)
349 removeEventHandler("onClientGUIClick", TOCommand, takeOff)
350 end
351
352 if ( approachSelect ) then
353 destroyElement(approachSelect)
354 approachSelect = nil
355 end
356
357 local thePlane = getSelectedPlane()
358 displayTransponderInformation(thePlane)
359
360
361 if getElementData(thePlane, "altitude") == "Final" then -- This part re-adds the function to clear a plane to land if it is still close to an IAF.
362 addEventHandler("onClientGUIClick", landCommand, initiateLanding, false)
363 end
364
365 if getElementData(thePlane, "InboundOrOutbound") == 2 then -- If the selected aircraft is an aircraft ready for takeoff, then the takeoff button should be enabled.
366 guiSetEnabled(TOCommand, true)
367 addEventHandler("onClientGUIClick", TOCommand, takeOff, false)
368 else
369 guiSetEnabled(TOCommand, false)
370 end
371
372 for i,v in ipairs(planes) do -- Remove the color from the previous selected plane
373 local r,g,b = getBlipColor(v)
374 if r == 255 and g ~= 255 then
375 setBlipColor(v, 255, 255, 0, 255)
376 end
377 end
378
379 setBlipColor(thePlane, 255, 0, 0, 255) -- Give the selected blip the color
380
381 -- Enable functions for the selected aircraft
382 if ( turnCommand ) and ( altCommand ) and getElementData(thePlane, "approach") == false and getElementData(thePlane, "InboundOrOutbound") ~= 2 then
383 addEventHandler("onClientGUIClick", turnCommand, turn, false)
384 guiSetEnabled(turnCommand, true)
385 addEventHandler("onClientGUIClick", altCommand, alt, false)
386 guiSetEnabled(altCommand, true)
387 addEventHandler("onClientGUIClick", appCommand, assignApproach, false)
388 guiSetEnabled(appCommand, true)
389 elseif getElementData(thePlane, "approach") ~= false or getElementData(thePlane, "InboundOrOutbound") == 2 then
390 guiSetEnabled(turnCommand, false)
391 guiSetEnabled(altCommand, false)
392 guiSetEnabled(appCommand, false)
393 end
394
395 if getElementData(thePlane, "landing") == true then
396 guiSetEnabled(landCommand, true)
397 else
398 guiSetEnabled(landCommand, false)
399 end
400
401
402
403 end
404end -- Function to select aircraft from the GridList
405
406function getSelectedPlane()
407 if not planeSelect then return end
408 local i, t = guiGridListGetSelectedItem(planeSelect)
409 local thePlane = guiGridListGetItemData(planeSelect, i, 1)
410
411 return thePlane
412end -- Determine which plane is selected
413
414function displayTransponderInformation(thePlane, a, b, c, d, e, f) -- Seperate function to enable altering the tags displayed without running the entire selectPlane function
415 if not thePlane then return end
416 if labels ~= nil then -- if a previous plane was selected, destroy its tags on the interface
417 for i,v in ipairs(labels) do
418 destroyElement(v)
419 end
420 labels = nil
421 end
422
423 if not a then a = tostring(getElementData(thePlane, "callsign"))end
424 if not b then b = tostring(getElementData(thePlane, "origin"))end
425 if not c then c = tostring(getElementData(thePlane, "destination"))end
426 if not d then d = tostring(getElementData(thePlane, "type"))end
427 if not e then e = tostring(roundOff(getElementData(thePlane, "heading"),0))end
428 if not f then f = tostring(getElementData(thePlane, "altitude"))end
429
430 local theCallsign = guiCreateLabel(0.1, 0.15, 1, 0.1, a, true, ATC_info)
431 local theOrig = guiCreateLabel(0.1, 0.25, 1, 0.1, b, true, ATC_info)
432 local theDest = guiCreateLabel(0.1, 0.35, 1, 0.1, c, true, ATC_info)
433 local theType = guiCreateLabel(0.1, 0.45, 1, 0.1, d, true, ATC_info)
434 local theHeading = guiCreateLabel(0.1, 0.65, 1, 0.1, e, true, ATC_info)
435 local theAltitude = guiCreateLabel(0.1, 0.75, 1, 0.1, f, true, ATC_info)
436 labels = {theCallsign,theOrig,theDest,theType,theHeading,theAltitude}
437
438 for i,v in ipairs(labels) do -- Tags displayed in green
439 guiLabelSetColor(v, 0, 255, 0)
440 end
441end -- Display Transponder Tags function
442
443function turn(button, state) -- Function to make the aircraft turn when instructed
444 if button == "left" and state == "up" and guiGridListGetSelectedItem(planeSelect) ~= -1 then
445
446 local thePlane = getSelectedPlane()
447 local planeName = getElementData(thePlane, "callsign")
448
449 if ( thePlane ) and ( valueInst ) then
450 local newHDG = tonumber(guiGetText(valueInst))
451 if newHDG == nil then return end
452 if newHDG >= 0 and newHDG <= 359 and newHDG ~= getElementData(thePlane, "heading") then
453 setElementData(thePlane, "heading", newHDG)
454 displayTransponderInformation(thePlane)
455 outputChatBox("[ATC Chat]: "..tostring(getPlayerName(localPlayer))..": "..planeName..", turn heading "..tostring(newHDG)..".")
456 setTimer(outputChatBox, 2000, 1, "[San Andreas Air Traffic Control]: Flight Crew: Turn heading "..tostring(newHDG)..", "..planeName..".")
457 end
458 end
459 end
460end -- Heading Change function
461
462function alt(button, state) -- Function to make the aircraft climb/descent when instructed
463 if button == "left" and state == "up" and guiGridListGetSelectedItem(planeSelect) ~= -1 then
464
465 local i, t = guiGridListGetSelectedItem(planeSelect)
466 local thePlane = guiGridListGetItemData(planeSelect, i, 1) -- Determine which plane is selected
467
468 if not thePlane or not valueInst then return end
469
470 local planeName = getElementData(thePlane, "callsign")
471 local origALT = getElementData(thePlane, "altitude")
472 local newALT = tonumber(guiGetText(valueInst))
473
474 if origALT == "Climbing" or origALT == "Descending" then return end
475 if newALT == nil then return end
476
477
478 local rate = getElementData(thePlane, "rate")
479
480 if newALT >= 100 and newALT <= 550 and newALT ~= origALT and ( thePlane ) then
481 local t = 1000 * math.abs(origALT - newALT)/rate -- Calculate how long the plane will take to descend.
482 local ClbDesTimer = setTimer(finishClbDes, t, 1, thePlane, planeSelect, newALT) -- Annonymous function which is not altered on purpose because it determines when to stop displaying "Descending".
483 setElementData(thePlane, "Timer", ClbDesTimer)
484
485 if newALT > origALT then -- if the new altitude is higher --> aircraft should climb
486 if getElementData(thePlane, "InboundOrOutbound") ~= 4 then
487 outputChatBox("[ATC Chat]: "..tostring(getPlayerName(localPlayer))..": "..planeName..", climb and maintain "..tostring(newALT)..".")
488 setTimer(outputChatBox, 2000, 1, "[San Andreas Air Traffic Control]: Flight Crew: Roger, climb and maintain "..tostring(newALT)..", "..planeName..".")
489 end
490 setElementData(thePlane, "altitude", "Climbing")
491 displayTransponderInformation(thePlane)
492
493 else -- if the new altitude is lower --> aircraft should descent
494 if getElementData(thePlane, "InboundOrOutbound") ~= 4 then
495 outputChatBox("[ATC Chat]: "..tostring(getPlayerName(localPlayer))..": "..planeName..", descent and maintain "..tostring(newALT)..".")
496 setTimer(outputChatBox, 2000, 1, "[San Andreas Air Traffic Control]: Flight Crew: Roger, descent and maintain "..tostring(newALT)..", "..planeName..".")
497 end
498 setElementData(thePlane, "altitude", "Descending")
499 displayTransponderInformation(thePlane)
500 end
501 end
502 end
503end -- Altitude Change function
504
505function finishClbDes(thePlane, planeSelect, newALT)
506 if not thePlane or not planeSelect or not newALT then return end
507 setElementData(thePlane,"altitude", newALT)
508 setElementData(thePlane, "Timer", false)
509 if i == guiGridListGetSelectedItem(planeSelect) then
510 displayTransponderInformation(thePlane)
511 end
512end -- This function ends the climb or descend an aircraft made. The function is not annonymous anymore as the timer has to be killed if the plane is deleted during climb or descend.
513
514function takeOff(button, state)
515 if button == "left" and state == "up" then
516 local thePlane = getSelectedPlane()
517 local planeName = getElementData(thePlane, "callsign")
518
519 if not thePlane or not valueInst then return end
520
521 local inOrOut = getElementData(thePlane, "InboundOrOutbound")
522 setElementData(thePlane, "InboundOrOutbound", inOrOut + 2)
523 setElementData(thePlane, "moving", true)
524
525 removeEventHandler("onClientGUIClick", TOCommand, takeOff)
526
527 guiSetText(valueInst, "100")
528 alt("left", "up")
529
530 outputChatBox("[ATC Chat]: "..tostring(getPlayerName(localPlayer))..": "..planeName..", cleared for take-off runway 69, fly runway track, climb and maintain 100.")
531 setTimer(outputChatBox, 2000, 1, "[San Andreas Air Traffic Control]: Flight Crew: Cleared for take-off runway 69, runway track, climb 100, "..planeName..".")
532 end
533end
534
535
536
537function assignApproach(button, state)
538 if button == "left" and state == "up" and guiGridListGetSelectedItem(planeSelect) ~= -1 then
539 if not ATC_ctl then return end
540 local thePlane = getSelectedPlane()
541 local planeName = getElementData(thePlane, "callsign")
542 local fltDest = getElementData(thePlane, "destination")
543 local px, py, pz = getElementPosition(thePlane)
544
545 if approachSelect == nil then
546 if fltDest == "Los Santos Intl." then -- Initializing different approaches for the destinations
547 approaches = {{"UNIFORM",pointU}, {"TANGO",pointT}}
548 elseif fltDest == "Kasimir Intl. Las Venturas" then
549 approaches = {{"INDIA", pointI}, {"BRAVO", pointB}}
550 elseif fltDest == "EasterBay San Fierro" then
551 approaches = {{"VICTOR", pointV}, {"ALPHA", pointA}}
552 else
553 return
554 end
555
556 approachSelect = guiCreateComboBox(0, 0.35, 1, 0.3, "Select Approach", true, ATC_ctl)
557
558 local r1 = guiComboBoxAddItem(approachSelect, approaches[1][1])
559 local r2 = guiComboBoxAddItem(approachSelect, approaches[2][1])
560
561 elseif guiComboBoxGetSelected(approachSelect) ~= -1 and pz < 200 then
562 local ID = guiComboBoxGetSelected(approachSelect)
563 local app = guiComboBoxGetItemText(approachSelect, ID)
564
565 local thePoint = approaches[tonumber(ID)+1][2]
566
567 setElementData(thePlane, "app", app)
568 setElementData(thePlane, "approach", thePoint)
569
570 local x, y, z = getElementPosition(thePoint)
571 local HDG = calculateHDG(thePlane, x, y)
572 removeEventHandler("onClientGUIClick", appCommand, assignApproach)
573 guiSetEnabled(appCommand, false)
574
575 outputChatBox("[ATC Chat]: "..tostring(getPlayerName(localPlayer))..": "..planeName..", cleared direct "..app..", for the "..fltDest.." approach.")
576 setTimer(outputChatBox, 2000, 1, "[San Andreas Air Traffic Control]: Flight Crew: Cleared direct "..app..", "..planeName..".")
577
578 if getElementData(thePlane, "heading") ~= HDG then -- Makes the aircraft fly directly towards its assigned approach point.
579 setElementData(thePlane, "heading", HDG)
580 displayTransponderInformation(thePlane)
581 end
582 end
583 end
584end
585
586function resumeTrack(thePlane, planeName)
587 local x, y, z = getElementPosition(thePlane)
588
589 setElementData(thePlane, "approach", false)
590 setElementData(thePlane, "app", false)
591 setElementData(thePlane, "altitude", roundOff(z, 0))
592
593 removeEventHandler("onClientGUIClick", landCommand, initiateLanding)
594 guiSetEnabled(landCommand, false)
595
596 selectPlane("left", "up")
597 outputChatBox("[San Andreas Air Traffic Control]: Flight Crew: "..planeName.." did not recieve any approach instructions. Continuing on track, awaiting new approach.")
598
599end -- Resume track function, which disables the approach function, when no instruction recieved within 10 seconds.
600
601function calculateDistance(object, x, y)
602 if not object or not x or not y then return end
603 local cx, cy, cz = getElementPosition(object)
604 local dx = cx - x
605 local dy = cy - y
606 local dist = math.sqrt(dx*dx + dy*dy)
607
608 return dist
609end
610
611function reportWhenCloseToIAF()
612 for i,v in ipairs(planes) do
613 if #planes > 0 then
614 if getElementData(v, "approach") ~= false and getElementData(v, "InboundOrOutbound") ~= 4 then
615 local x, y = getElementPosition(getElementData(v, "approach"))
616 local cx, cy, cz = getElementPosition(v)
617
618 local app = getElementData(v, "app")
619 local planeName = getElementData(v, "callsign")
620 local fltDest = getElementData(v, "destination")
621
622 if calculateDistance(v, x, y) < 150 and cz < 200 and getElementData(v, "altitude") ~= "Final" then
623 outputChatBox("[ATC Chat]: Flight Crew: "..planeName..", approaching "..app..", inbound "..fltDest.."")
624 setElementData(v, "altitude", "Final")
625 setElementData(v, "landing", true)
626 selectPlane("left", "up")
627 resumeTimer = setTimer(resumeTrack, 10000, 1, v, planeName)
628 if v == getSelectedPlane() then
629 guiSetEnabled(landCommand, true)
630 end
631 end
632 end
633 if getElementData(v, "InboundOrOutbound") == 4 then
634 local x, y = getElementPosition(v)
635 local cx, cy, cz = getElementPosition(v)
636 local planeName = getElementData(v, "callsign")
637
638 if calculateDistance(DELTA, x, y) < 150 or calculateDistance(ROMEO, x, y) < 150 or calculateDistance(SIERRA, x, y) < 150 and cz >= 200 and cz <= 250 then
639 outputChatBox("[ATC Chat]: Flight Crew: "..planeName..", approaching "..tostring(SID)..", inbound "..getElementData(v, "destination")..". Switching frequency to Pacific Center.")
640 outputChatBox("[ATC Chat]: "..tostring(getPlayerName(localPlayer))..": "..planeName..", frequency change approved, have a safe flight.")
641 setElementData(v, "InboundOrOutbound", 5)
642 rewardPlayer(1500)
643 end
644 end
645 end
646 end
647end -- Function which makes aircraft report when close to IAF
648
649
650function calculateHDG(object, x, y)
651 local cx, cy, cz = getElementPosition(object)
652 local dx = cx - x
653 local dy = cy - y
654 local HDG = math.deg(math.atan(dx/dy))
655 if dy > 0 then
656 HDG = HDG + 180
657 end
658
659 if HDG < 0 then
660 HDG = 360 + HDG
661 end
662
663 return HDG
664end -- Calculate Heading
665
666function roundOff(value, decimals)
667 local x = 10^(decimals or 0)
668 return math.floor(value * x + 0.5) / x
669end -- Small function to estabilish a round off purpose
670
671function getWind() -- Calculates the wind speed and direction
672 local wx, wy, wz = getWindVelocity()
673 if not wx or not wy then return end
674
675 local windSpeed = roundOff(50 * math.sqrt(wx*wx + wy*wy), 0)
676 local windDir = roundOff(math.deg(math.atan(wx/math.abs(wy))), 0) -- wy may not be smaller than 0, no southerly winds.
677
678 if wy < 0 then
679 windDir = windDir + 180
680 end
681
682 if windDir ~= nil then
683 return windSpeed, windDir
684 else
685 return windSpeed, math.random(0,360)
686 end
687end -- Calculate Wind speed and direction
688
689function planeOffMap()
690 for i,v in ipairs(planes) do
691 if #planes > 0 then
692 local x, y = getElementPosition(v)
693
694 if x > 3500 or x < -3500 or y > 3500 or y < -3500 then
695
696 local planeName = getElementData(v, "callsign")
697 local gridpos = getElementData(v, "gridpos")
698 guiGridListRemoveRow(planeSelect, gridpos)
699 table.remove(planes, i)
700 reorderTable(v)
701 deletePlane(v)
702 end
703 end
704 end
705end -- Removes an aircraft when it gets out of 'radar range' from the controller. (ADD TOPBAR OUTPUT!)
706
707function reorderTable(subject)
708 for key,plane in ipairs(planes) do -- Make sure that the position on the gridlist is updated when an item on a higher position is deleted.
709 if getElementData(subject, "gridpos") < getElementData(plane, "gridpos") then
710 local gridpos2 = getElementData(plane, "gridpos") - 1
711 setElementData(plane, "gridpos", gridpos2)
712 end
713
714 if getElementData(subject, "pos") < getElementData(plane, "pos") then
715 local pos2 = getElementData(plane, "pos") - 1
716 setElementData(plane, "pos", pos2)
717 end
718 end
719end -- This function checks whether the gridlist/table is resorted after a plane has been deleted from it, and assigns the new positions for each remaining plane.
720
721function initiateLanding(button, state)
722 if button == "left" and state == "up" and guiGridListGetSelectedItem(planeSelect) ~= -1 and guiGetEnabled(landCommand) == true then
723 local thePlane = getSelectedPlane() -- Determine which plane is selected
724 local bx, by = getElementPosition(getElementData(thePlane, "approach"))
725
726 if getElementData(thePlane, "approach") == false then return end
727 if calculateDistance(thePlane, bx, by) > 150 then return end
728
729 local x, y, z = getElementPosition(thePlane)
730 local dest = getElementData(thePlane, "destination")
731 local planeName = getElementData(thePlane, "callsign")
732 local ws, wd = getWind()
733
734 removeEventHandler("onClientGUIClick", landCommand, initiateLanding)
735 guiSetEnabled(landCommand, false)
736 removeEventHandler("onClientGUIClick", appCommand, assignApproach)
737 guiSetEnabled(appCommand, false)
738 removeEventHandler("onClientGUIClick", turnCommand, turn)
739 guiSetEnabled(turnCommand, false)
740 removeEventHandler("onClientGUIClick", altCommand, alt)
741 guiSetEnabled(altCommand, false)
742
743 if isTimer(resumeTimer) == true then
744 killTimer(resumeTimer)
745 end
746
747 if dest == "Kasimir Intl. Las Venturas" then
748 airport_x = 1478
749 airport_y = 1471
750 airport_z = 11
751 elseif dest == "EasterBay San Fierro" then
752 airport_x = -1397
753 airport_y = 96
754 airport_z = 15
755 elseif dest == "Los Santos Intl." then
756 airport_x = 1796
757 airport_y = -2593
758 airport_z = 14
759 end
760
761 local HDG = calculateHDG(thePlane, airport_x, airport_y)
762
763 setElementData(thePlane, "heading", HDG)
764 displayTransponderInformation(thePlane)
765
766 local rate = (z - airport_z)/(((airport_x - x)^2 + (airport_y - y)^2)/30000) -- Determining a new rate for the aircraft to reach the airport.
767 setElementData(thePlane, "rate", rate)
768
769 addEventHandler("onPlaneGroundHit", thePlane, flightComplete) -- When the plane hits the ground, the flight is completed.
770
771 outputChatBox("[ATC Chat]: "..tostring(getPlayerName(localPlayer))..": "..planeName..", straight-in "..dest.." runway 69, cleared to land, wind "..tostring(wd).." at "..tostring(ws)..".")
772 setTimer(outputChatBox, 2000, 1, "[San Andreas Air Traffic Control]: Flight Crew: Cleared to land runway 69, "..planeName..".")
773
774 airport_x = nil
775 airport_y = nil
776 airport_z = nil
777 end
778end -- Initiate Landing
779
780function flightComplete()
781 if not source or not planeSelect then return end
782
783 local planeName = getElementData(source, "callsign")
784
785 setElementData(source, "moving", false)
786 outputChatBox("[ATC Chat]: Flight Crew: "..planeName..", runway 69 vacated, switching to Ground Control, bye")
787
788 local pos = getElementData(source, "pos") -- Get on which spot in the "planes" table the landed plane is located.
789 setTimer(outputChatBox, 1500, 1, "[ATC Chat]: "..tostring(getPlayerName(localPlayer))..": "..planeName..", roger, have a nice day!.")
790 table.remove(planes, pos)
791 guiGridListRemoveRow(planeSelect, tonumber(getElementData(source, "gridpos")))
792 reorderTable(source)
793 setTimer(deletePlane, 1500, 1, source)
794
795 -- Reward ATC controller
796 rewardPlayer(1500)
797
798end -- Flight Complete
799
800function deletePlane(thePlane)
801 if not thePlane then return end
802 if getElementData(thePlane, "Timer") ~= false then
803 killTimer(getElementData(thePlane, "Timer"))
804 end
805
806 destroyElement(thePlane)
807 thePlane = nil
808end -- Function which removes a plane
809
810
811function midAirCol()
812 for keya,thePlane in ipairs(planes) do
813 if ( thePlane ) and #planes > 0 and getElementData(thePlane, "moving") == true and getElementData(thePlane, "InboundOrOutbound") ~= 2 then
814 local x, y, z = getElementPosition(thePlane)
815
816 for keyb,target in ipairs(planes) do
817 local tx, ty, tz = getElementPosition(target)
818 if ( target ) and x ~= tx and y ~= ty and z~= tz and keya > keyb and getElementData(target, "InboundOrOutbound") ~= 2 then -- Make sure the target is not the same plane, and the function is only called for one plane (the one with the highest position in the planes-table)
819 local dist = math.sqrt(math.sqrt((math.abs(x - tx))^2 + (math.abs(y - ty))^2)^2 + (math.abs(z - tz))^2)
820
821 if dist < 250 then
822 -- TOPBAR output about a midair collision
823 setElementData(thePlane, "moving", false)
824 setElementData(target, "moving", false)
825
826 local gridpos1 = getElementData(thePlane, "gridpos")
827 local gridpos2 = getElementData(target, "gridpos")
828
829 table.remove(planes, keya)
830 table.remove(planes, keyb)
831
832 -- Create a restricted area for investigators to investigate the crash site.
833 local planeName = getElementData(thePlane, "callsign")
834 outputChatBox("[ATC Chat]: Flight Crew: MAYDAY MAYDAY MAYDAY, "..planeName..", we are down, I repeat, we are down!")
835 local SARsection = createRadarArea(x-200, y-200, 400, 400, 255, 0, 0, 255)
836
837 table.insert(SARs, 1, SARsection)
838 setTimer(deleteSAR, 60000, 1, SARsection)
839 -----------------------------------------------------------------------------
840
841 rewardPlayer(-1500)
842
843 reorderTable(thePlane)
844 reorderTable(target)
845 deletePlane(thePlane)
846 deletePlane(target)
847 guiGridListRemoveRow(planeSelect, gridpos1)
848 guiGridListRemoveRow(planeSelect, gridpos2)
849
850 thePlane = nil
851 target = nil
852 break
853 end
854 end
855 end
856 end
857 end
858end -- Function which toggles a midair collision when two planes converge within a specified distance.
859
860
861
862function onBlipInSARcol()
863 if not SARs then return end
864 for i,t in ipairs(SARs) do
865 local p, pp = getElementPosition(t)
866 local px = p + 200
867 local py = pp + 200
868 for i,v in ipairs(planes) do
869 if calculateDistance(v, px, py) <= 200 and getElementData(v, "inside") == false then
870 airspaceViolation(v, t)
871 break
872 end
873
874 if calculateDistance(v, px, py) >= 200 and getElementData(v, "inside") == true then
875 setElementData(v, "inside", false)
876 end
877 end
878 end
879end
880
881function airspaceViolation(thePlane, SARsection)
882 if getElementData(thePlane, "inside") == false then
883 outputChatBox("Airspace Violation detected!")
884 setRadarAreaFlashing(SARsection, true)
885 if isTimer(flashTimer) == false then
886 flashTimer = setTimer(setRadarAreaFlashing, 5000, 1, SARsection, false)
887 end
888 setElementData(thePlane, "inside", true)
889 rewardPlayer(-10000)
890 end
891end
892
893function deleteSAR(SARsection)
894 if not SARsection or not SARs then return end
895 if isTimer(flashTimer) == true then
896 killTimer(flashTimer)
897 end
898 destroyElement(SARsection)
899 removeEventHandler("onClientRender", root, onBlipInSARcol)
900 table.remove(SARs, tonumber(#SARs))
901end
902
903function rewardPlayer(x)
904 if not x then return end
905 local money = getPlayerMoney(localPlayer) + x
906 setPlayerMoney(money)
907end
908
909function exitATC(button, state)
910 if button == "left" and state == "up" then
911 if not ATC_ctl or not ATC_info then return end
912
913 for key, thePlane in ipairs(planes) do -- Deletes all currently flying planes
914 deletePlane(thePlane)
915 end
916 planes = nil
917
918 for key, SAR in ipairs(SARs) do -- Deletes all SAR regions
919 SAR = nil
920 end
921 SARs = nil
922
923 for key, IAF in ipairs(IAFs) do -- Deletes all IAFs
924 if not IAF then break end
925 destroyElement(IAF)
926 IAF = nil
927 end
928
929 for key, SID in ipairs(SIDs) do -- Deletes all departure points
930 if not SID then break end
931 destroyElement(SID)
932 SID = nil
933 end
934
935 removeEventHandler("onClientRender", root, showIAFTags) -- Removes tags at the Approach Fixes
936
937 -- Removing all functions coupled to onClientRender
938 removeEventHandler("onClientRender", root, reportWhenCloseToIAF) -- stops planes from reporting when approaching an IAF
939 removeEventHandler("onClientRender", root, updatePos) -- stops planes from moving
940 removeEventHandler("onClientRender", root, planeOffMap) -- stops planes from being deleted when off map
941 removeEventHandler("onClientRender", root, midAirCol) -- stops planes from being able to collide with eachother
942
943 destroyElement(ATC_ctl)
944 destroyElement(ATC_info)
945 destroyElement(exit_wndw)
946
947 -- Nilling them global variables
948 ATC_ctl = nil
949 ATC_info = nil
950 exit_wndw = nil
951 approachSelect = nil
952 planeSelect = nil
953 labels = nil
954 valueInst = nil
955 x = nil
956 y = nil
957 z = nil
958 windDir = nil
959 turnCommand = nil
960 altCommand = nil
961 landCommand = nil
962 appCommand = nil
963
964 setElementPosition(localPlayer, 1636, -2283, 13)
965 forcePlayerMap(false)
966 fadeCamera(true)
967 showCursor(false)
968 end
969end
970
971function addPlayerPlanes(playerPlanes)
972 if not playerPlanes then return end -- Check if the table of pilots exists
973
974 for key, plane in ipairs(playerPlanes) do
975 local blip = createBlipAttachedTo(plane, 0, 3, 255, 255, 0, 255)
976
977 setElementData(blip, "source", "real") -- real means it is a player controlled plane.
978
979 table.insert(planes, blip) -- Add planes to the table
980 local callsign = getElementData(plane, "callsign")
981 local destination = getElementData(plane, "destination")
982 local origin = getElementData(plane, "origin")
983
984 if ( ATC_info ) then -- Make sure it is added to the selection interface
985 local addedPlane = guiGridListAddRow(planeSelect, tostring(callsign), tostring(destination))
986 guiGridListSetItemData(planeSelect, addedPlane, 1, blip) -- adding the information of the plane to the gridlist position.
987 setElementData(blip, "gridpos", addedPlane)
988 end
989
990 -- Add all transponder information
991 setElementData(blip, "callsign", callsign)
992 setElementData(blip, "destination", destination)
993 setElementData(blip, "origin", origin)
994
995 local x1, y1, z1 = getElementPosition(plane)
996 setElementData(thePlane, "altitude", z1)
997
998 local rx, ry, rz = getElementRotation(plane)
999 setElementData(thePlane, "heading", rz)
1000 end
1001end
1002addEventHandler("addPlayerPlanes", resourceRoot, addPlayerPlanes)
1003
1004-----------------------------------------------------------------------------------------------------------------------
1005
1006-- This part is to be added to the pilot script (I suggest to create a seperate class for players who wish to use ATC.
1007
1008function startPilot(team)
1009 --if team == pilot then -- Insert appropriate team name here.
1010
1011 local interface = guiCreateWindow(0.5, 0.5, 0.5, 0.5, "Flight Plan", true)
1012end
1013
1014
1015-----------------------------------------------------------------------------------------------------------------------
1016
1017function reportZ() -- Command to retrieve current altitude for testing purposes
1018 local i, t = guiGridListGetSelectedItem(planeSelect)
1019 local thePlane = guiGridListGetItemData(planeSelect, i, 1) -- Determine which plane is selected
1020 local x, y, z = getElementPosition(thePlane)
1021 local rate = getElementData(thePlane, "rate")
1022 outputChatBox("Airplane is at "..tostring(z).." and the rate is "..tostring(rate).."")
1023end
1024
1025addCommandHandler("altitude", reportZ)
1026
1027addCommandHandler("gridpos", function()
1028 local i, t = guiGridListGetSelectedItem(planeSelect)
1029 local thePlane = guiGridListGetItemData(planeSelect, i, 1) -- Determine which plane is selected
1030
1031 local gridpos = getElementData(thePlane, "gridpos")
1032 outputChatBox(""..tostring(gridpos).."")
1033 end)
1034
1035addCommandHandler("reset", function()
1036 fadeCamera(true)
1037 setElementPosition(localPlayer, 1636, -2283, 13)
1038 forcePlayerMap(false)
1039
1040 if ( ATC_info ) and ( ATC_ctl ) then
1041 guiSetVisible(ATC_info, false)
1042 guiSetVisible(ATC_ctl, false)
1043 end
1044 end)
1045
1046
1047--addEventHandler("onClientClick", root, function(button, state, absx, absy)
1048-- outputChatBox("x= "..tostring(absx)..". y= "..tostring(absy).."")
1049-- end)