]> git.mxchange.org Git - flightgear.git/blob - src/MultiPlayer/multiplaymgr.cxx
Remove unused files.
[flightgear.git] / src / MultiPlayer / multiplaymgr.cxx
1 //////////////////////////////////////////////////////////////////////
2 //
3 // multiplaymgr.hpp
4 //
5 // Written by Duncan McCreanor, started February 2003.
6 // duncan.mccreanor@airservicesaustralia.com
7 //
8 // Copyright (C) 2003  Airservices Australia
9 // Copyright (C) 2005  Oliver Schroeder
10 // Copyright (C) 2006  Mathias Froehlich
11 //
12 // This program is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU General Public License as
14 // published by the Free Software Foundation; either version 2 of the
15 // License, or (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 // General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 //
26 // $Id$
27 //  
28 //////////////////////////////////////////////////////////////////////
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33
34 #include <plib/netSocket.h>
35
36 #include <simgear/timing/timestamp.hxx>
37 #include <simgear/debug/logstream.hxx>
38
39 #include <AIModel/AIManager.hxx>
40 #include <Main/fg_props.hxx>
41
42 #include "multiplaymgr.hxx"
43 #include "mpmessages.hxx"
44
45 #define MAX_PACKET_SIZE 1200
46
47 // These constants are provided so that the ident 
48 // command can list file versions
49 const char sMULTIPLAYMGR_BID[] = "$Id$";
50 const char sMULTIPLAYMGR_HID[] = MULTIPLAYTXMGR_HID;
51
52 // A static map of protocol property id values to property paths,
53 // This should be extendable dynamically for every specific aircraft ...
54 // For now only that static list
55 FGMultiplayMgr::IdPropertyList
56 FGMultiplayMgr::sIdPropertyList[] = {
57   {100, "surface-positions/left-aileron-pos-norm"},
58   {101, "surface-positions/right-aileron-pos-norm"},
59   {102, "surface-positions/elevator-pos-norm"},
60   {103, "surface-positions/rudder-pos-norm"},
61   {104, "surface-positions/flap-pos-norm"},
62   {105, "surface-positions/speedbrake-pos-norm"},
63   {106, "gear/tailhook/position-norm"},
64
65   {200, "gear/gear[0]/compression-norm"},
66   {201, "gear/gear[0]/position-norm"},
67   {210, "gear/gear[1]/compression-norm"},
68   {211, "gear/gear[1]/position-norm"},
69   {220, "gear/gear[2]/compression-norm"},
70   {221, "gear/gear[2]/position-norm"},
71   {230, "gear/gear[3]/compression-norm"},
72   {231, "gear/gear[3]/position-norm"},
73   {240, "gear/gear[4]/compression-norm"},
74   {241, "gear/gear[4]/position-norm"},
75
76   {300, "engines/engine[0]/n1"},
77   {301, "engines/engine[0]/n2"},
78   {302, "engines/engine[0]/rpm"},
79   {310, "engines/engine[1]/n1"},
80   {311, "engines/engine[1]/n2"},
81   {312, "engines/engine[1]/rpm"},
82   {320, "engines/engine[2]/n1"},
83   {321, "engines/engine[2]/n2"},
84   {322, "engines/engine[2]/rpm"},
85   {330, "engines/engine[3]/n1"},
86   {331, "engines/engine[3]/n2"},
87   {332, "engines/engine[3]/rpm"},
88   {340, "engines/engine[4]/n1"},
89   {341, "engines/engine[4]/n2"},
90   {342, "engines/engine[4]/rpm"},
91   {350, "engines/engine[5]/n1"},
92   {351, "engines/engine[5]/n2"},
93   {352, "engines/engine[5]/rpm"},
94   {360, "engines/engine[6]/n1"},
95   {361, "engines/engine[6]/n2"},
96   {362, "engines/engine[6]/rpm"},
97   {370, "engines/engine[7]/n1"},
98   {371, "engines/engine[7]/n2"},
99   {372, "engines/engine[7]/rpm"},
100   {380, "engines/engine[8]/n1"},
101   {381, "engines/engine[8]/n2"},
102   {382, "engines/engine[8]/rpm"},
103   {390, "engines/engine[9]/n1"},
104   {391, "engines/engine[9]/n2"},
105   {392, "engines/engine[9]/rpm"},
106
107   {1001, "controls/flight/slats"},
108   {1002, "controls/flight/speedbrake"},
109   {1003, "controls/flight/spoilers"},
110   {1004, "controls/gear/gear-down"},
111   {1005, "controls/lighting/nav-lights"},
112
113   /// termination
114   {0, 0}
115 };
116
117 //////////////////////////////////////////////////////////////////////
118 //
119 //  MultiplayMgr constructor
120 //
121 //////////////////////////////////////////////////////////////////////
122 FGMultiplayMgr::FGMultiplayMgr() 
123 {
124   mInitialised   = false;
125   mHaveServer    = false;
126 } // FGMultiplayMgr::FGMultiplayMgr()
127 //////////////////////////////////////////////////////////////////////
128
129 //////////////////////////////////////////////////////////////////////
130 //
131 //  MultiplayMgr destructor
132 //
133 //////////////////////////////////////////////////////////////////////
134 FGMultiplayMgr::~FGMultiplayMgr() 
135 {
136   Close();
137 } // FGMultiplayMgr::~FGMultiplayMgr()
138 //////////////////////////////////////////////////////////////////////
139
140 //////////////////////////////////////////////////////////////////////
141 //
142 //  Initialise object
143 //
144 //////////////////////////////////////////////////////////////////////
145 bool
146 FGMultiplayMgr::init (void) 
147 {
148   //////////////////////////////////////////////////
149   //  Initialise object if not already done
150   //////////////////////////////////////////////////
151   if (mInitialised) {
152     SG_LOG(SG_NETWORK, SG_WARN, "FGMultiplayMgr::init - already initialised");
153     return false;
154   }
155   //////////////////////////////////////////////////
156   //  Set members from property values
157   //////////////////////////////////////////////////
158   short rxPort = fgGetInt("/sim/multiplay/rxport");
159   if (rxPort <= 0)
160     rxPort = 5000;
161   mCallsign = fgGetString("/sim/multiplay/callsign");
162   if (mCallsign.empty())
163     // FIXME: use getpwuid
164     mCallsign = "JohnDoe"; 
165   string rxAddress = fgGetString("/sim/multiplay/rxhost");
166   if (rxAddress.empty())
167     rxAddress = "127.0.0.1";
168   short txPort = fgGetInt("/sim/multiplay/txport");
169   string txAddress = fgGetString("/sim/multiplay/txhost");
170   if (txPort > 0 && !txAddress.empty()) {
171     mHaveServer = true;
172     mServer.set(txAddress.c_str(), txPort);
173   }
174   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-txaddress= "<<txAddress);
175   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-txport= "<<txPort );
176   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxaddress="<<rxAddress );
177   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxport= "<<rxPort);
178   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-callsign= "<<mCallsign);
179   mSocket = new netSocket();
180   if (!mSocket->open(false)) {
181     SG_LOG( SG_NETWORK, SG_ALERT,
182             "FGMultiplayMgr::init - Failed to create data socket" );
183     return false;
184   }
185   mSocket->setBlocking(false);
186   mSocket->setBroadcast(true);
187   if (mSocket->bind(rxAddress.c_str(), rxPort) != 0) {
188     perror("bind");
189     SG_LOG( SG_NETWORK, SG_ALERT,
190             "FGMultiplayMgr::Open - Failed to bind receive socket" );
191     return false;
192   }
193   mInitialised = true;
194   return true;
195 } // FGMultiplayMgr::init()
196 //////////////////////////////////////////////////////////////////////
197
198 //////////////////////////////////////////////////////////////////////
199 //
200 //  Closes and deletes the local player object. Closes
201 //  and deletes the tx socket. Resets the object state to unitialised.
202 //
203 //////////////////////////////////////////////////////////////////////
204 void
205 FGMultiplayMgr::Close (void) 
206 {
207   mMultiPlayerMap.clear();
208
209   if (mSocket) {
210     mSocket->close();
211     delete mSocket;
212     mSocket = 0;
213   }
214   mInitialised = false;
215 } // FGMultiplayMgr::Close(void)
216 //////////////////////////////////////////////////////////////////////
217
218 //////////////////////////////////////////////////////////////////////
219 //
220 //  Description: Sends the position data for the local position.
221 //
222 //////////////////////////////////////////////////////////////////////
223 void
224 FGMultiplayMgr::SendMyPosition(const FGExternalMotionData& motionInfo)
225 {
226   if ((! mInitialised) || (! mHaveServer)) {
227     if (! mInitialised)
228       SG_LOG( SG_NETWORK, SG_ALERT,
229               "FGMultiplayMgr::SendMyPosition - not initialised" );
230     if (! mHaveServer)
231       SG_LOG( SG_NETWORK, SG_ALERT,
232               "FGMultiplayMgr::SendMyPosition - no server" );
233     return;
234   }
235
236   T_PositionMsg PosMsg;
237   strncpy(PosMsg.Model, fgGetString("/sim/model/path"), MAX_MODEL_NAME_LEN);
238   PosMsg.Model[MAX_MODEL_NAME_LEN - 1] = '\0';
239   
240   PosMsg.time = XDR_encode_double (motionInfo.time);
241   PosMsg.lag = XDR_encode_double (motionInfo.lag);
242   for (unsigned i = 0 ; i < 3; ++i)
243     PosMsg.position[i] = XDR_encode_double (motionInfo.position(i));
244   SGVec3f angleAxis;
245   motionInfo.orientation.getAngleAxis(angleAxis);
246   for (unsigned i = 0 ; i < 3; ++i)
247     PosMsg.orientation[i] = XDR_encode_float (angleAxis(i));
248   for (unsigned i = 0 ; i < 3; ++i)
249     PosMsg.linearVel[i] = XDR_encode_float (motionInfo.linearVel(i));
250   for (unsigned i = 0 ; i < 3; ++i)
251     PosMsg.angularVel[i] = XDR_encode_float (motionInfo.angularVel(i));
252   for (unsigned i = 0 ; i < 3; ++i)
253     PosMsg.linearAccel[i] = XDR_encode_float (motionInfo.linearAccel(i));
254   for (unsigned i = 0 ; i < 3; ++i)
255     PosMsg.angularAccel[i] = XDR_encode_float (motionInfo.angularAccel(i));
256
257   char Msg[MAX_PACKET_SIZE];
258   memcpy(Msg + sizeof(T_MsgHdr), &PosMsg, sizeof(T_PositionMsg));
259   
260   char* ptr = Msg + sizeof(T_MsgHdr) + sizeof(T_PositionMsg);
261   std::vector<FGFloatPropertyData>::const_iterator it;
262   it = motionInfo.properties.begin();
263   while (it != motionInfo.properties.end()
264          && ptr < (Msg + MAX_PACKET_SIZE - sizeof(T_PropertyMsg))) {
265     T_PropertyMsg pMsg;
266     pMsg.id = XDR_encode_uint32(it->id);
267     pMsg.value = XDR_encode_float(it->value);
268     memcpy(ptr, &pMsg, sizeof(T_PropertyMsg));
269     ptr += sizeof(T_PropertyMsg);
270     ++it;
271   }
272
273   T_MsgHdr MsgHdr;
274   FillMsgHdr(&MsgHdr, POS_DATA_ID, ptr - Msg);
275   memcpy(Msg, &MsgHdr, sizeof(T_MsgHdr));
276
277   mSocket->sendto(Msg, ptr - Msg, 0, &mServer);
278   SG_LOG(SG_NETWORK, SG_DEBUG, "FGMultiplayMgr::SendMyPosition");
279 } // FGMultiplayMgr::SendMyPosition()
280 //////////////////////////////////////////////////////////////////////
281
282 //////////////////////////////////////////////////////////////////////
283 //
284 //  Name: SendTextMessage
285 //  Description: Sends a message to the player. The message must
286 //  contain a valid and correctly filled out header and optional
287 //  message body.
288 //
289 //////////////////////////////////////////////////////////////////////
290 void
291 FGMultiplayMgr::SendTextMessage(const string &MsgText)
292 {
293   if (!mInitialised || !mHaveServer)
294     return;
295
296   T_MsgHdr MsgHdr;
297   FillMsgHdr(&MsgHdr, CHAT_MSG_ID);
298   //////////////////////////////////////////////////
299   // Divide the text string into blocks that fit
300   // in the message and send the blocks.
301   //////////////////////////////////////////////////
302   unsigned iNextBlockPosition = 0;
303   T_ChatMsg ChatMsg;
304   char Msg[sizeof(T_MsgHdr) + sizeof(T_ChatMsg)];
305   while (iNextBlockPosition < MsgText.length()) {
306     strncpy (ChatMsg.Text, 
307              MsgText.substr(iNextBlockPosition, MAX_CHAT_MSG_LEN - 1).c_str(),
308              MAX_CHAT_MSG_LEN);
309     ChatMsg.Text[MAX_CHAT_MSG_LEN - 1] = '\0';
310     memcpy (Msg, &MsgHdr, sizeof(T_MsgHdr));
311     memcpy (Msg + sizeof(T_MsgHdr), &ChatMsg, sizeof(T_ChatMsg));
312     mSocket->sendto (Msg, sizeof(T_MsgHdr) + sizeof(T_ChatMsg), 0, &mServer);
313     iNextBlockPosition += MAX_CHAT_MSG_LEN - 1;
314   }
315 } // FGMultiplayMgr::SendTextMessage ()
316 //////////////////////////////////////////////////////////////////////
317
318 //////////////////////////////////////////////////////////////////////
319 //
320 //  Name: ProcessData
321 //  Description: Processes data waiting at the receive socket. The
322 //  processing ends when there is no more data at the socket.
323 //  
324 //////////////////////////////////////////////////////////////////////
325 void
326 FGMultiplayMgr::Update(void) 
327 {
328   if (!mInitialised)
329     return;
330
331   /// Just for expiry
332   SGTimeStamp timestamper;
333   timestamper.stamp();
334   long stamp = timestamper.get_seconds();
335
336   //////////////////////////////////////////////////
337   //  Read the receive socket and process any data
338   //////////////////////////////////////////////////
339   int bytes;
340   do {
341     char Msg[MAX_PACKET_SIZE];
342     //////////////////////////////////////////////////
343     //  Although the recv call asks for 
344     //  MAX_PACKET_SIZE of data, the number of bytes
345     //  returned will only be that of the next
346     //  packet waiting to be processed.
347     //////////////////////////////////////////////////
348     netAddress SenderAddress;
349     bytes = mSocket->recvfrom(Msg, sizeof(Msg), 0, &SenderAddress);
350     //////////////////////////////////////////////////
351     //  no Data received
352     //////////////////////////////////////////////////
353     if (bytes <= 0) {
354       if (errno != EAGAIN)
355         perror("FGMultiplayMgr::MP_ProcessData");
356       break;
357     }
358     if (bytes <= sizeof(T_MsgHdr)) {
359       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
360               << "received message with insufficient data" );
361       break;
362     }
363     //////////////////////////////////////////////////
364     //  Read header
365     //////////////////////////////////////////////////
366     T_MsgHdr* MsgHdr = (T_MsgHdr *)Msg;
367     MsgHdr->Magic       = XDR_decode_uint32 (MsgHdr->Magic);
368     MsgHdr->Version     = XDR_decode_uint32 (MsgHdr->Version);
369     MsgHdr->MsgId       = XDR_decode_uint32 (MsgHdr->MsgId);
370     MsgHdr->MsgLen      = XDR_decode_uint32 (MsgHdr->MsgLen);
371     MsgHdr->ReplyPort   = XDR_decode_uint32 (MsgHdr->ReplyPort);
372     if (MsgHdr->Magic != MSG_MAGIC) {
373       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
374               << "message has invalid magic number!" );
375     }
376     if (MsgHdr->Version != PROTO_VER) {
377       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
378               << "message has invalid protocoll number!" );
379     }
380     if (MsgHdr->MsgLen != bytes) {
381       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
382               << "message has invalid length!" );
383     }
384     //////////////////////////////////////////////////
385     //  Process messages
386     //////////////////////////////////////////////////
387     switch (MsgHdr->MsgId) {
388     case CHAT_MSG_ID:
389       ProcessChatMsg(Msg, SenderAddress);
390       break;
391     case POS_DATA_ID:
392       ProcessPosMsg(Msg, SenderAddress, bytes, stamp);
393       break;
394     case UNUSABLE_POS_DATA_ID:
395     case OLD_OLD_POS_DATA_ID:
396     case OLD_PROP_MSG_ID:
397     case OLD_POS_DATA_ID:
398       break;
399     default:
400       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
401               << "Unknown message Id received: " << MsgHdr->MsgId );
402       break;
403     }
404   } while (bytes > 0);
405
406   // check for expiry
407   MultiPlayerMap::iterator it = mMultiPlayerMap.begin();
408   while (it != mMultiPlayerMap.end()) {
409     if (it->second->getLastTimestamp() + 10 < stamp) {
410       std::string name = it->first;
411       it->second->setDie(true);
412       mMultiPlayerMap.erase(it);
413       it = mMultiPlayerMap.upper_bound(name);
414     } else
415       ++it;
416   }
417 } // FGMultiplayMgr::ProcessData(void)
418 //////////////////////////////////////////////////////////////////////
419
420 //////////////////////////////////////////////////////////////////////
421 //
422 //  handle a position message
423 //
424 //////////////////////////////////////////////////////////////////////
425 void
426 FGMultiplayMgr::ProcessPosMsg(const char *Msg, netAddress & SenderAddress,
427                               unsigned len, long stamp)
428 {
429   T_MsgHdr* MsgHdr = (T_MsgHdr *)Msg;
430   if (MsgHdr->MsgLen < sizeof(T_MsgHdr) + sizeof(T_PositionMsg)) {
431     SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
432             << "Position message received with insufficient data" );
433     return;
434   }
435   T_PositionMsg* PosMsg = (T_PositionMsg *)(Msg + sizeof(T_MsgHdr));
436   FGExternalMotionData motionInfo;
437   motionInfo.time = XDR_decode_double(PosMsg->time);
438   motionInfo.lag = XDR_decode_double(PosMsg->lag);
439   for (unsigned i = 0; i < 3; ++i)
440     motionInfo.position(i) = XDR_decode_double(PosMsg->position[i]);
441   SGVec3f angleAxis;
442   for (unsigned i = 0; i < 3; ++i)
443     angleAxis(i) = XDR_decode_float(PosMsg->orientation[i]);
444   motionInfo.orientation = SGQuatf::fromAngleAxis(angleAxis);
445   for (unsigned i = 0; i < 3; ++i)
446     motionInfo.linearVel(i) = XDR_decode_float(PosMsg->linearVel[i]);
447   for (unsigned i = 0; i < 3; ++i)
448     motionInfo.angularVel(i) = XDR_decode_float(PosMsg->angularVel[i]);
449   for (unsigned i = 0; i < 3; ++i)
450     motionInfo.linearAccel(i) = XDR_decode_float(PosMsg->linearAccel[i]);
451   for (unsigned i = 0; i < 3; ++i)
452     motionInfo.angularAccel(i) = XDR_decode_float(PosMsg->angularAccel[i]);
453
454   T_PropertyMsg* PropMsg
455     = (T_PropertyMsg*)(Msg + sizeof(T_MsgHdr) + sizeof(T_PositionMsg));
456   while ((char*)PropMsg < Msg + len) {
457     FGFloatPropertyData pData;
458     pData.id = XDR_decode_uint32(PropMsg->id);
459     pData.value = XDR_decode_float(PropMsg->value);
460     motionInfo.properties.push_back(pData);
461     ++PropMsg;
462   }
463   
464   FGAIMultiplayer* mp = getMultiplayer(MsgHdr->Callsign);
465   if (!mp)
466     mp = addMultiplayer(MsgHdr->Callsign, PosMsg->Model);
467   mp->addMotionInfo(motionInfo, stamp);
468 } // FGMultiplayMgr::ProcessPosMsg()
469 //////////////////////////////////////////////////////////////////////
470
471 //////////////////////////////////////////////////////////////////////
472 //
473 //  handle a chat message
474 //  FIXME: display chat message withi flightgear
475 //
476 //////////////////////////////////////////////////////////////////////
477 void
478 FGMultiplayMgr::ProcessChatMsg(const char *Msg, netAddress& SenderAddress)
479 {
480   T_MsgHdr* MsgHdr = (T_MsgHdr *)Msg;
481   if (MsgHdr->MsgLen < sizeof(T_MsgHdr) + 1) {
482     SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
483             << "Chat message received with insufficient data" );
484     return;
485   }
486   
487   char MsgBuf[MsgHdr->MsgLen - sizeof(T_MsgHdr)];
488   strncpy(MsgBuf, ((T_ChatMsg *)(Msg + sizeof(T_MsgHdr)))->Text,
489           MsgHdr->MsgLen - sizeof(T_MsgHdr));
490   MsgBuf[MsgHdr->MsgLen - sizeof(T_MsgHdr) - 1] = '\0';
491   
492   T_ChatMsg* ChatMsg = (T_ChatMsg *)(Msg + sizeof(T_MsgHdr));
493   SG_LOG ( SG_NETWORK, SG_ALERT, "Chat [" << MsgHdr->Callsign << "]"
494            << " " << MsgBuf << endl);
495 } // FGMultiplayMgr::ProcessChatMsg ()
496 //////////////////////////////////////////////////////////////////////
497
498 void
499 FGMultiplayMgr::FillMsgHdr(T_MsgHdr *MsgHdr, int MsgId, unsigned _len)
500 {
501   uint32_t len;
502   switch (MsgId) {
503   case CHAT_MSG_ID:
504     len = sizeof(T_MsgHdr) + sizeof(T_ChatMsg);
505     break;
506   case POS_DATA_ID:
507     len = _len;
508     break;
509   default:
510     len = sizeof(T_MsgHdr);
511     break;
512   }
513   MsgHdr->Magic           = XDR_encode_uint32(MSG_MAGIC);
514   MsgHdr->Version         = XDR_encode_uint32(PROTO_VER);
515   MsgHdr->MsgId           = XDR_encode_uint32(MsgId);
516   MsgHdr->MsgLen          = XDR_encode_uint32(len);
517   MsgHdr->ReplyAddress    = 0; // Are obsolete, keep them for the server for
518   MsgHdr->ReplyPort       = 0; // now
519   strncpy(MsgHdr->Callsign, mCallsign.c_str(), MAX_CALLSIGN_LEN);
520   MsgHdr->Callsign[MAX_CALLSIGN_LEN - 1] = '\0';
521 }
522
523 FGAIMultiplayer*
524 FGMultiplayMgr::addMultiplayer(const std::string& callsign,
525                                const std::string& modelName)
526 {
527   if (0 < mMultiPlayerMap.count(callsign))
528     return mMultiPlayerMap[callsign];
529
530   FGAIMultiplayer* mp = new FGAIMultiplayer;
531   mp->setPath(modelName.c_str());
532   mp->setCallSign(callsign);
533   mMultiPlayerMap[callsign] = mp;
534
535   FGAIManager *aiMgr = (FGAIManager*)globals->get_subsystem("ai_model");
536   if (aiMgr) {
537     aiMgr->attach(mp);
538
539     /// FIXME: that must follow the attach ATM ...
540     unsigned i = 0;
541     while (sIdPropertyList[i].name) {
542       mp->addPropertyId(sIdPropertyList[i].id, sIdPropertyList[i].name);
543       ++i;
544     }
545   }
546
547   return mp;
548 }
549
550 FGAIMultiplayer*
551 FGMultiplayMgr::getMultiplayer(const std::string& callsign)
552 {
553   if (0 < mMultiPlayerMap.count(callsign))
554     return mMultiPlayerMap[callsign];
555   else
556     return 0;
557 }