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