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