]> git.mxchange.org Git - flightgear.git/blob - src/ATC/AIMgr.cxx
Melchior FRANZ:
[flightgear.git] / src / ATC / AIMgr.cxx
1 // AIMgr.cxx - implementation of FGAIMgr 
2 // - a global management class for FlightGear generated AI traffic
3 //
4 // Written by David Luff, started March 2002.
5 //
6 // Copyright (C) 2002  David C Luff - david.luff@nottingham.ac.uk
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 #include <simgear/misc/sg_path.hxx>
23
24 #include <Main/fg_props.hxx>
25 #include <Main/globals.hxx>
26 #include <simgear/math/sg_random.h>
27
28 #include <list>
29
30 #ifdef _MSC_VER
31 #  include <io.h>
32 #else
33 #  include <sys/types.h>        // for directory reading
34 #  include <dirent.h>           // for directory reading
35 #endif
36
37 #include <Environment/environment_mgr.hxx>
38 #include <Environment/environment.hxx>
39
40 #include "AIMgr.hxx"
41 #include "AILocalTraffic.hxx"
42 #include "AIGAVFRTraffic.hxx"
43 #include "ATCutils.hxx"
44 #include "commlist.hxx"
45
46 SG_USING_STD(list);
47 SG_USING_STD(cout);
48
49 FGAIMgr::FGAIMgr() {
50         ATC = globals->get_ATC_mgr();
51         initDone = false;
52         ai_callsigns_used["CFGFS"] = 1; // so we don't inadvertently use this
53         // TODO - use the proper user callsign when it becomes user settable.
54         removalList.clear();
55         activated.clear();
56         _havePiperModel = true;
57 }
58
59 FGAIMgr::~FGAIMgr() {
60         _defaultModel->deRef();
61         if(_havePiperModel) _piperModel->deRef();
62 }
63
64 void FGAIMgr::init() {
65         //cout << "AIMgr::init called..." << endl;
66         
67         // Pointers to user's position
68         lon_node = fgGetNode("/position/longitude-deg", true);
69         lat_node = fgGetNode("/position/latitude-deg", true);
70         elev_node = fgGetNode("/position/altitude-ft", true);
71
72         lon = lon_node->getDoubleValue();
73         lat = lat_node->getDoubleValue();
74         elev = elev_node->getDoubleValue();
75         
76         // Load up models at the start to avoid pausing later
77         // Hack alert - Hardwired paths!!
78         string planepath = "Aircraft/c172r/Models/c172-dpm.ac";
79         bool _loadedDefaultOK = true;
80         try {
81                 _defaultModel = sgLoad3DModel( globals->get_fg_root(),
82                                            planepath.c_str(),
83                                                                            globals->get_props(),
84                                                                            globals->get_sim_time_sec() );
85         } catch(sg_exception& e) {
86                 _loadedDefaultOK = false;
87         }
88         
89         if(!_loadedDefaultOK ) {
90                 // Just load the same 3D model as the default user plane - that's *bound* to exist!
91                 // TODO - implement robust determination of availability of GA AI aircraft models
92                 planepath = "Aircraft/c172p/Models/c172p.ac";
93                 _defaultModel = sgLoad3DModel( globals->get_fg_root(),
94                                            planepath.c_str(),
95                                                                            globals->get_props(),
96                                                                            globals->get_sim_time_sec() );
97         }
98
99         planepath = "Aircraft/pa28-161/Models/pa28-161.ac";
100         try {
101                 _piperModel = sgLoad3DModel( globals->get_fg_root(),
102                                          planepath.c_str(),
103                                                                          globals->get_props(),
104                                                                          globals->get_sim_time_sec() );
105         } catch(sg_exception& e) {
106                 _havePiperModel = false;
107         }
108
109         // We need to keep one ref of the models open to stop ssg deleting them behind our back!
110         _defaultModel->ref();
111         if(_havePiperModel) _piperModel->ref();
112
113         // go through the $FG_ROOT/ATC directory and find all *.taxi files
114         SGPath path(globals->get_fg_root());
115         path.append("ATC/");
116         string dir = path.dir();
117         string ext;
118         string file, f_ident;
119         int pos;
120         
121         ulDir *d;
122         struct ulDirEnt *de;
123         
124         if ( (d = ulOpenDir( dir.c_str() )) == NULL ) {
125                 SG_LOG(SG_ATC, SG_WARN, "cannot open directory " << dir);
126         } else {
127                 // load all .taxi files
128                 while ( (de = ulReadDir(d)) != NULL ) {
129                         file = de->d_name;
130                         pos = file.find(".");
131                         ext = file.substr(pos + 1);
132                         if(ext == "taxi") {
133                                 f_ident = file.substr(0, pos);
134                                 FGAirport a;
135                                 if(dclFindAirportID(f_ident, &a)) {
136                                         SGBucket sgb(a.getLongitude(), a.getLatitude());
137                                         int idx = sgb.gen_index();
138                                         if(facilities.find(idx) != facilities.end()) {
139                                                 facilities[idx]->push_back(f_ident);
140                                         } else {
141                                                 ID_list_type* apts = new ID_list_type;
142                                                 apts->push_back(f_ident);
143                                                 facilities[idx] = apts;
144                                         }
145                                         SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx);
146                                 }
147                         }
148                 }               
149                 ulCloseDir(d);
150         }
151
152         // See if are in range at startup and activate if necessary
153         SearchByPos(15.0);
154         
155         initDone = true;
156         
157         //cout << "AIMgr::init done..." << endl;
158         
159         /*
160         // TESTING
161         FGATCAlignedProjection ortho;
162         ortho.Init(dclGetAirportPos("KEMT"), 205.0);    // Guess of rwy19 heading
163         //Point3D ip = ortho.ConvertFromLocal(Point3D(6000, 1000, 1000));       // 90 deg entry
164         //Point3D ip = ortho.ConvertFromLocal(Point3D(-7000, 3000, 1000));      // 45 deg entry
165         Point3D ip = ortho.ConvertFromLocal(Point3D(1000, -7000, 1000));        // straight-in
166         ATC->AIRegisterAirport("KEMT");
167         FGAIGAVFRTraffic* p = new FGAIGAVFRTraffic();
168         p->SetModel(_defaultModel);
169         p->Init(ip, "KEMT", GenerateShortForm(GenerateUniqueCallsign()));
170         ai_list.push_back(p);
171         traffic[ident].push_back(p);
172         activated["KEMT"] = 1;
173         */      
174 }
175
176 void FGAIMgr::bind() {
177 }
178
179 void FGAIMgr::unbind() {
180 }
181
182 void FGAIMgr::update(double dt) {
183         if(!initDone) {
184                 init();
185                 SG_LOG(SG_ATC, SG_WARN, "Warning - AIMgr::update(...) called before AIMgr::init()");
186         }
187         
188         //cout << activated.size() << '\n';
189         
190         Point3D userPos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
191         
192         // TODO - make these class variables!!
193         static int i = 0;
194         static int j = 0;
195
196         // Don't update any planes for first 50 runs through - this avoids some possible initialisation anomalies
197         // Might not need it now we have fade-in though?
198         if(i < 50) {
199                 ++i;
200                 return;
201         }
202         
203         if(j == 215) {
204                 SearchByPos(25.0);
205                 j = 0;
206         } else if(j == 200) {
207                 // Go through the list of activated airports and remove those out of range
208                 //cout << "The following airports have been activated by the AI system:\n";
209                 ai_activated_map_iterator apt_itr = activated.begin();
210                 while(apt_itr != activated.end()) {
211                         //cout << "FIRST IS " << (*apt_itr).first << '\n';
212                         if(dclGetHorizontalSeparation(userPos, dclGetAirportPos((*apt_itr).first)) > (35.0 * 1600.0)) {
213                                 // Then get rid of it and make sure the iterator is left pointing to the next one!
214                                 string s = (*apt_itr).first;
215                                 if(traffic.find(s) != traffic.end()) {
216                                         //cout << "s = " << s << ", traffic[s].size() = " << traffic[s].size() << '\n';
217                                         if(traffic[s].size()) {
218                                                 apt_itr++;
219                                         } else {
220                                                 //cout << "Erasing " << (*apt_itr).first << " and traffic" << '\n';
221                                                 activated.erase(apt_itr++);
222                                                 traffic.erase(s);
223                                         }
224                                 } else {
225                                                 //cout << "Erasing " << (*apt_itr).first << ' ' << (*apt_itr).second << '\n';
226                                                 activated.erase(apt_itr++);
227                                 }
228                         } else {
229                                 apt_itr++;
230                         }
231                 }
232         } else if(j == 180) {
233                 // Go through the list of activated airports and do the random airplane generation
234                 ai_traffic_map_iterator it = traffic.begin();
235                 while(it != traffic.end()) {
236                         string s = (*it).first;
237                         //cout << "s = " << s << " size = " << (*it).second.size() << '\n';
238                         // Only generate extra traffic if within a certain distance of the user,
239                         // TODO - maybe take users's tuned freq into account as well.
240                         double d = dclGetHorizontalSeparation(userPos, dclGetAirportPos(s)); 
241                         if(d < (15.0 * 1600.0)) {
242                                 double cd = 0.0;
243                                 bool gen = false;
244                                 //cout << "Size of list is " << (*it).second.size() << " at " << s << '\n';
245                                 if((*it).second.size()) {
246                                         FGAIEntity* e = *((*it).second.rbegin());       // Get the last airplane currently scheduled to arrive at this airport.
247                                         cd = dclGetHorizontalSeparation(e->GetPos(), dclGetAirportPos(s));
248                                         if(cd < (d < 5000 ? 10000 : d + 5000)) {
249                                                 gen = true;
250                                         }
251                                 } else {
252                                         gen = true;
253                                         cd = 0.0;
254                                 }
255                                 if(gen) {
256                                         //cout << "Generating extra traffic at airport " << s << ", at least " << cd << " meters out\n";
257                                         //GenerateSimpleAirportTraffic(s, cd);
258                                         GenerateSimpleAirportTraffic(s, cd + 3000.0);   // The random seems a bit wierd - traffic could get far too bunched without the +3000.
259                                         // TODO - make the anti-random constant variable depending on the ai-traffic level.
260                                 }
261                         }
262                         ++it;
263                 }
264         }
265         
266         ++j;
267         
268         //cout << "Size of AI list is " << ai_list.size() << '\n';
269         
270         // TODO - need to add a check of if any activated airports have gone out of range
271         
272         string rs;      // plane to be removed, if one.
273         if(removalList.size()) {
274                 rs = *(removalList.begin());
275                 removalList.pop_front();
276         } else {
277                 rs = "";
278         }
279         
280         // Traverse the list of active planes and run all their update methods
281         // TODO - spread the load - not all planes should need updating every frame.
282         // Note that this will require dt to be calculated for each plane though
283         // since they rely on it to calculate distance travelled.
284         ai_list_itr = ai_list.begin();
285         while(ai_list_itr != ai_list.end()) {
286                 FGAIEntity *e = *ai_list_itr;
287                 if(rs.size() && e->GetCallsign() == rs) {
288                         //cout << "Removing " << rs << " from ai_list\n";
289                         ai_list_itr = ai_list.erase(ai_list_itr);
290                         delete e;
291                         // This is a hack - we should deref this plane from the airport count!
292                 } else {
293                         e->Update(dt);
294                         ++ai_list_itr;
295                 }
296         }
297
298         //cout << "Size of AI list is " << ai_list.size() << '\n';
299 }
300
301 void FGAIMgr::ScheduleRemoval(string s) {
302         //cout << "Scheduling removal of plane " << s << " from AIMgr\n";
303         removalList.push_back(s);
304 }
305
306 // Activate AI traffic at an airport
307 void FGAIMgr::ActivateAirport(string ident) {
308         ATC->AIRegisterAirport(ident);
309         // TODO - need to start the traffic more randomly
310         FGAILocalTraffic* local_traffic = new FGAILocalTraffic;
311         local_traffic->SetModel(_defaultModel); // currently hardwired to cessna.
312         //local_traffic->Init(ident, IN_PATTERN, TAKEOFF_ROLL);
313         local_traffic->Init(GenerateShortForm(GenerateUniqueCallsign()), ident);
314         local_traffic->FlyCircuits(1, true);    // Fly 2 circuits with touch & go in between
315         ai_list.push_back(local_traffic);
316         traffic[ident].push_back(local_traffic);
317         //cout << "******** ACTIVATING AIRPORT, ident = " << ident << '\n';
318         activated[ident] = 1;
319 }
320
321 // Hack - Generate AI traffic at an airport with no facilities file
322 void FGAIMgr::GenerateSimpleAirportTraffic(string ident, double min_dist) {
323         // Ugly hack - don't let VFR Cessnas operate at a hardwired list of major airports
324         // This will go eventually once airport .xml files specify the traffic profile
325         if(ident == "KSFO" || ident == "KDFW" || ident == "EGLL" || ident == "KORD" || ident == "KJFK" 
326                            || ident == "KMSP" || ident == "KLAX" || ident == "KBOS" || ident == "KEDW"
327                                            || ident == "KSEA" || ident == "EHAM") {
328                 return;
329         }
330         
331         /*
332         // TODO - check for military airports - this should be in the current data.
333         // UGGH - there's no point at the moment - everything is labelled civil in basic.dat!
334         FGAirport a;
335         if(dclFindAirportID(ident, &a)) {
336                 cout << "CODE IS " << a.code << '\n';
337         } else {
338                 // UG - can't find the airport!
339                 return;
340         }
341         */
342         
343         Point3D aptpos = dclGetAirportPos(ident);       // TODO - check for elev of -9999
344         //cout << "ident = " << ident << ", elev = " << aptpos.elev() << '\n';
345         
346         // Operate from airports at 3000ft and below only to avoid the default cloud layers and since we don't degrade AI performance with altitude.
347         if(aptpos.elev() > 3000) {
348                 //cout << "High alt airports not yet supported - returning\n";
349                 return;
350         }
351         
352         // Rough hack for plane type - make 70% of the planes cessnas, the rest pipers.
353         bool cessna = true;
354         
355         // Get the time and only operate VFR in the (approximate) daytime.
356         time_t now = globals->get_time_params()->get_cur_time() + time_t(aptpos.lon() * 240.0);
357         int loc_time = gmtime(&now)->tm_hour;
358         //cout << "loc_time = " << loc_time << '\n';
359         if(loc_time < 7 || loc_time > 19) return;
360         
361         // Check that the visibility is OK for IFR operation.
362         double visibility;
363         FGEnvironment stationweather =
364             ((FGEnvironmentMgr *)globals->get_subsystem("environment"))
365               ->getEnvironment(aptpos.lat(), aptpos.lon(), aptpos.elev());      // TODO - check whether this should take ft or m for elev.
366         visibility = stationweather.get_visibility_m();
367         // Technically we can do VFR down to 1 mile (1600m) but that's pretty murky!
368         //cout << "vis = " << visibility << '\n';
369         if(visibility < 3000) return;
370         
371         ATC->AIRegisterAirport(ident);
372         
373         // Next - get the distance from user to the airport.
374         Point3D userpos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
375         double d = dclGetHorizontalSeparation(userpos, aptpos); // in meters
376         
377         int lev = fgGetInt("/sim/ai-traffic/level");
378         if(lev < 1 || lev > 3) lev = 2;
379         if(visibility < 6000) lev = 1;
380         //cout << "level = " << lev << '\n';
381         
382         // Next - generate any local / circuit traffic
383
384         /*
385         // --------------------------- THIS BLOCK IS JUST FOR TESTING - COMMENT OUT BEFORE RELEASE ---------------
386         // Finally - generate VFR approaching traffic
387         //if(d > 2000) {
388         if(ident == "KPOC") {
389                 double ad = 2000.0;
390                 double avd = 3000.0;    // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
391                 //while(ad < (d < 10000 ? 12000 : d + 2000)) {
392                 for(int i=0; i<8; ++i) {
393                         double dd = sg_random() * avd;
394                         // put a minimum spacing in for now since I don't think tower will cope otherwise!
395                         if(dd < 1500) dd = 1500; 
396                         //ad += dd;
397                         ad += dd;
398                         double dir = int(sg_random() * 36);
399                         if(dir == 36) dir--;
400                         dir *= 10;
401                         //dir = 180;
402                         if(sg_random() < 0.3) cessna = false;
403                         else cessna = true;
404                         string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
405                         FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
406                         t->SetModel(cessna ? _defaultModel : _piperModel);
407                         //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
408                         Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
409                         if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0);
410                         t->Init(tpos, ident, s);
411                         ai_list.push_back(t);
412                 }
413         }
414         activated[ident] = 1;
415         return;
416         //---------------------------------------------------------------------------------------------------
417         */
418         
419         double ad;   // Minimum distance out of first arriving plane in meters.
420         double mind; // Minimum spacing of traffic in meters
421         double avd;  // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
422         // Finally - generate VFR approaching traffic
423         //if(d > 2000) {
424         if(1) {
425                 if(lev == 3) {
426                         ad = 5000.0;
427                         mind = 2000.0;
428                         avd = 6000.0;
429                 } else if(lev == 2) {
430                         ad = 8000.0;
431                         mind = 4000.0;
432                         avd = 10000.0;
433                 } else {
434                         ad = 9000.0;    // Start the first aircraft at least 9K out for now.
435                         mind = 6000.0;
436                         avd = 15000.0;
437                 }
438                 /*
439                 // Check if there is already arriving traffic at this airport
440                 cout << "BING A " << ident << '\n';
441                 if(traffic.find(ident) != traffic.end()) {
442                         cout << "BING B " << ident << '\n';
443                         ai_list_type lst = traffic[ident];
444                         cout << "BING C " << ident << '\n';
445                         if(lst.size()) {
446                                 cout << "BING D " << ident << '\n';
447                                 double cd = dclGetHorizontalSeparation(aptpos, (*lst.rbegin())->GetPos());
448                                 cout << "ident = " << ident << ", cd = " << cd << '\n';
449                                 if(cd > ad) ad = cd;
450                         }
451                 }
452                 */
453                 if(min_dist != 0) ad = min_dist;
454                 //cout << "ident = " << ident << ", ad = " << ad << '\n';
455                 while(ad < (d < 5000 ? 15000 : d + 10000)) {
456                         double dd = mind + (sg_random() * (avd - mind));
457                         ad += dd;
458                         double dir = int(sg_random() * 36);
459                         if(dir == 36) dir--;
460                         dir *= 10;
461                         
462                         if(sg_random() < 0.3) cessna = false;
463                         else cessna = true;
464                         string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
465                         FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
466                         t->SetModel(cessna ? _defaultModel : (_havePiperModel ? _piperModel : _defaultModel));
467                         //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
468                         Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
469                         if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0);        // FEET yuk :-(
470                         t->Init(tpos, ident, s);
471                         ai_list.push_back(t);
472                         traffic[ident].push_back(t);
473                 }
474         }       
475 }
476
477 /*
478 // Generate a VFR arrival at airport apt, at least distance d (meters) out.
479 void FGAIMgr::GenerateVFRArrival(string apt, double d) {
480 }
481 */
482
483 // Search for valid airports in the vicinity of the user and activate them if necessary
484 void FGAIMgr::SearchByPos(double range) {
485         //cout << "In SearchByPos(...)" << endl;
486         
487         // get bucket number for plane position
488         lon = lon_node->getDoubleValue();
489         lat = lat_node->getDoubleValue();
490         elev = elev_node->getDoubleValue() * SG_FEET_TO_METER;
491         SGBucket buck(lon, lat);
492
493         // get neigboring buckets
494         int bx = (int)( range*SG_NM_TO_METER / buck.get_width_m() / 2);
495         //cout << "bx = " << bx << endl;
496         int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 );
497         //cout << "by = " << by << endl;
498         
499         // Search for airports with facitities files --------------------------
500         // loop over bucket range 
501         for ( int i=-bx; i<=bx; i++) {
502                 //cout << "i loop\n";
503                 for ( int j=-by; j<=by; j++) {
504                         //cout << "j loop\n";
505                         buck = sgBucketOffset(lon, lat, i, j);
506                         long int bucket = buck.gen_index();
507                         //cout << "bucket is " << bucket << endl;
508                         if(facilities.find(bucket) != facilities.end()) {
509                                 ID_list_type* apts = facilities[bucket];
510                                 ID_list_iterator current = apts->begin();
511                                 ID_list_iterator last = apts->end();
512                                 
513                                 //cout << "Size of apts is " << apts->size() << endl;
514                                 
515                                 //double rlon = lon * SGD_DEGREES_TO_RADIANS;
516                                 //double rlat = lat * SGD_DEGREES_TO_RADIANS;
517                                 //Point3D aircraft = sgGeodToCart( Point3D(rlon, rlat, elev) );
518                                 //Point3D airport;
519                                 for(; current != last; ++current) {
520                                         //cout << "Found " << *current << endl;;
521                                         if(activated.find(*current) == activated.end()) {
522                                                 //cout << "Activating " << *current << endl;
523                                                 //FGAirport a;
524                                                 //if(dclFindAirportID(*current, &a)) {
525                                                         //      // We can do something here based on distance from the user if we wish.
526                                                 //}
527                                                 //string s = *current;
528                                                 //cout << "s = " << s << '\n';
529                                                 ActivateAirport(*current);
530                                                 //ActivateSimpleAirport(*current);      // TODO - put this back to ActivateAirport when that code is done.
531                                                 //cout << "Activation done" << endl;
532                                         } else {
533                                                 //cout << *current << " already activated" << endl;
534                                         }
535                                 }
536                         }
537                 }
538         }
539         //-------------------------------------------------------------
540         
541         // Search for any towered airports in the vicinity ------------
542         comm_list_type towered;
543         comm_list_iterator twd_itr;
544         
545         int num_twd = current_commlist->FindByPos(lon, lat, elev, range, &towered, TOWER);
546         if (num_twd != 0) {
547                 double closest = 1000000;
548                 string s = "";
549                 for(twd_itr = towered.begin(); twd_itr != towered.end(); twd_itr++) {
550                         // Only activate the closest airport not already activated each time.
551                         if(activated.find(twd_itr->ident) == activated.end()) {
552                                 double sep = dclGetHorizontalSeparation(Point3D(lon, lat, elev), dclGetAirportPos(twd_itr->ident));
553                                 if(sep < closest) {
554                                         closest = sep;
555                                         s = twd_itr->ident;
556                                 }
557                                 
558                         }
559                 }
560                 if(s.size()) {
561                         // TODO - find out why empty strings come through here when all in-range airports done.
562                         GenerateSimpleAirportTraffic(s);
563                         //cout << "**************ACTIVATING SIMPLE AIRPORT, ident = " << s << '\n';
564                         activated[s] = 1;
565                 }
566         }
567 }
568
569 string FGAIMgr::GenerateCallsign() {
570         // For now we'll just generate US callsigns until we can regionally identify airports.
571         string s = "N";
572         // Add 3 to 5 numbers and make up to 5 with letters.
573         //sg_srandom_time();
574         double d = sg_random();
575         int n = int(d * 3);
576         if(n == 3) --n;
577         //cout << "First n, n = " << n << '\n';
578         int j = 3 + n;
579         //cout << "j = " << j << '\n';
580         for(int i=0; i<j; ++i) { 
581                 int n = int(sg_random() * 10);
582                 if(n == 10) --n;
583                 s += (char)('0' + n);
584         }
585         for(int i=j; i<5; ++i) {
586                 int n = int(sg_random() * 26);
587                 if(n == 26) --n;
588                 //cout << "Alpha, n = " << n << '\n';
589                 s += (char)('A' + n);
590         }
591         //cout << "s = " << s << '\n';
592         return(s);
593 }
594
595 string FGAIMgr::GenerateUniqueCallsign() {
596         while(1) {
597                 string s = GenerateCallsign();
598                 if(!ai_callsigns_used[s]) {
599                         ai_callsigns_used[s] = 1;
600                         return(s);
601                 }
602         }
603 }
604
605 // This will be moved somewhere else eventually!!!!
606 string FGAIMgr::GenerateShortForm(string callsign, string plane_str, bool local) {
607         //cout << callsign << '\n';
608         string s;
609         if(local) s = "Trainer-";
610         else s = plane_str;
611         for(int i=3; i>0; --i) {
612                 char c = callsign[callsign.size() - i];
613                 //cout << c << '\n';
614                 string tmp = "";
615                 tmp += c;
616                 if(isalpha(c)) s += GetPhoneticIdent(c);
617                 else s += ConvertNumToSpokenDigits(tmp);
618                 if(i > 1) s += '-';
619         }
620         return(s);
621 }