]> git.mxchange.org Git - flightgear.git/blob - src/MultiPlayer/multiplaymgr.cxx
Don't restore initial screen geometry because there is nothing in fg_os* to resize...
[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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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   {800, "rotors/main/rpm"},
108   {801, "rotors/tail/rpm"},
109   {810, "rotors/main/blade1_pos"},
110   {811, "rotors/main/blade2_pos"},
111   {812, "rotors/main/blade3_pos"},
112   {813, "rotors/main/blade4_pos"},
113   {820, "rotors/main/blade1_flap"},
114   {821, "rotors/main/blade2_flap"},
115   {822, "rotors/main/blade3_flap"},
116   {823, "rotors/main/blade4_flap"},
117   {830, "rotors/tail/blade1_pos"},
118   {831, "rotors/tail/blade2_pos"},
119
120   {1001, "controls/flight/slats"},
121   {1002, "controls/flight/speedbrake"},
122   {1003, "controls/flight/spoilers"},
123   {1004, "controls/gear/gear-down"},
124   {1005, "controls/lighting/nav-lights"},
125
126   /// termination
127   {0, 0}
128 };
129
130 //////////////////////////////////////////////////////////////////////
131 //
132 //  MultiplayMgr constructor
133 //
134 //////////////////////////////////////////////////////////////////////
135 FGMultiplayMgr::FGMultiplayMgr() 
136 {
137   mInitialised   = false;
138   mHaveServer    = false;
139 } // FGMultiplayMgr::FGMultiplayMgr()
140 //////////////////////////////////////////////////////////////////////
141
142 //////////////////////////////////////////////////////////////////////
143 //
144 //  MultiplayMgr destructor
145 //
146 //////////////////////////////////////////////////////////////////////
147 FGMultiplayMgr::~FGMultiplayMgr() 
148 {
149   Close();
150 } // FGMultiplayMgr::~FGMultiplayMgr()
151 //////////////////////////////////////////////////////////////////////
152
153 //////////////////////////////////////////////////////////////////////
154 //
155 //  Initialise object
156 //
157 //////////////////////////////////////////////////////////////////////
158 bool
159 FGMultiplayMgr::init (void) 
160 {
161   //////////////////////////////////////////////////
162   //  Initialise object if not already done
163   //////////////////////////////////////////////////
164   if (mInitialised) {
165     SG_LOG(SG_NETWORK, SG_WARN, "FGMultiplayMgr::init - already initialised");
166     return false;
167   }
168   //////////////////////////////////////////////////
169   //  Set members from property values
170   //////////////////////////////////////////////////
171   short rxPort = fgGetInt("/sim/multiplay/rxport");
172   if (rxPort <= 0)
173     rxPort = 5000;
174   mCallsign = fgGetString("/sim/multiplay/callsign");
175   if (mCallsign.empty())
176     // FIXME: use getpwuid
177     mCallsign = "JohnDoe"; 
178   string rxAddress = fgGetString("/sim/multiplay/rxhost");
179   if (rxAddress.empty())
180     rxAddress = "127.0.0.1";
181   short txPort = fgGetInt("/sim/multiplay/txport");
182   string txAddress = fgGetString("/sim/multiplay/txhost");
183   if (txPort > 0 && !txAddress.empty()) {
184     mHaveServer = true;
185     mServer.set(txAddress.c_str(), txPort);
186   }
187   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-txaddress= "<<txAddress);
188   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-txport= "<<txPort );
189   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxaddress="<<rxAddress );
190   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxport= "<<rxPort);
191   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-callsign= "<<mCallsign);
192   mSocket = new netSocket();
193   if (!mSocket->open(false)) {
194     SG_LOG( SG_NETWORK, SG_ALERT,
195             "FGMultiplayMgr::init - Failed to create data socket" );
196     return false;
197   }
198   mSocket->setBlocking(false);
199   mSocket->setBroadcast(true);
200   if (mSocket->bind(rxAddress.c_str(), rxPort) != 0) {
201     perror("bind");
202     SG_LOG( SG_NETWORK, SG_ALERT,
203             "FGMultiplayMgr::Open - Failed to bind receive socket" );
204     return false;
205   }
206   mInitialised = true;
207   return true;
208 } // FGMultiplayMgr::init()
209 //////////////////////////////////////////////////////////////////////
210
211 //////////////////////////////////////////////////////////////////////
212 //
213 //  Closes and deletes the local player object. Closes
214 //  and deletes the tx socket. Resets the object state to unitialised.
215 //
216 //////////////////////////////////////////////////////////////////////
217 void
218 FGMultiplayMgr::Close (void) 
219 {
220   mMultiPlayerMap.clear();
221
222   if (mSocket) {
223     mSocket->close();
224     delete mSocket;
225     mSocket = 0;
226   }
227   mInitialised = false;
228 } // FGMultiplayMgr::Close(void)
229 //////////////////////////////////////////////////////////////////////
230
231 //////////////////////////////////////////////////////////////////////
232 //
233 //  Description: Sends the position data for the local position.
234 //
235 //////////////////////////////////////////////////////////////////////
236 void
237 FGMultiplayMgr::SendMyPosition(const FGExternalMotionData& motionInfo)
238 {
239   if ((! mInitialised) || (! mHaveServer)) {
240     if (! mInitialised)
241       SG_LOG( SG_NETWORK, SG_ALERT,
242               "FGMultiplayMgr::SendMyPosition - not initialised" );
243     if (! mHaveServer)
244       SG_LOG( SG_NETWORK, SG_ALERT,
245               "FGMultiplayMgr::SendMyPosition - no server" );
246     return;
247   }
248
249   T_PositionMsg PosMsg;
250   strncpy(PosMsg.Model, fgGetString("/sim/model/path"), MAX_MODEL_NAME_LEN);
251   PosMsg.Model[MAX_MODEL_NAME_LEN - 1] = '\0';
252   
253   PosMsg.time = XDR_encode_double (motionInfo.time);
254   PosMsg.lag = XDR_encode_double (motionInfo.lag);
255   for (unsigned i = 0 ; i < 3; ++i)
256     PosMsg.position[i] = XDR_encode_double (motionInfo.position(i));
257   SGVec3f angleAxis;
258   motionInfo.orientation.getAngleAxis(angleAxis);
259   for (unsigned i = 0 ; i < 3; ++i)
260     PosMsg.orientation[i] = XDR_encode_float (angleAxis(i));
261   for (unsigned i = 0 ; i < 3; ++i)
262     PosMsg.linearVel[i] = XDR_encode_float (motionInfo.linearVel(i));
263   for (unsigned i = 0 ; i < 3; ++i)
264     PosMsg.angularVel[i] = XDR_encode_float (motionInfo.angularVel(i));
265   for (unsigned i = 0 ; i < 3; ++i)
266     PosMsg.linearAccel[i] = XDR_encode_float (motionInfo.linearAccel(i));
267   for (unsigned i = 0 ; i < 3; ++i)
268     PosMsg.angularAccel[i] = XDR_encode_float (motionInfo.angularAccel(i));
269
270   char Msg[MAX_PACKET_SIZE];
271   memcpy(Msg + sizeof(T_MsgHdr), &PosMsg, sizeof(T_PositionMsg));
272   
273   char* ptr = Msg + sizeof(T_MsgHdr) + sizeof(T_PositionMsg);
274   std::vector<FGFloatPropertyData>::const_iterator it;
275   it = motionInfo.properties.begin();
276   while (it != motionInfo.properties.end()
277          && ptr < (Msg + MAX_PACKET_SIZE - sizeof(T_PropertyMsg))) {
278     T_PropertyMsg pMsg;
279     pMsg.id = XDR_encode_uint32(it->id);
280     pMsg.value = XDR_encode_float(it->value);
281     memcpy(ptr, &pMsg, sizeof(T_PropertyMsg));
282     ptr += sizeof(T_PropertyMsg);
283     ++it;
284   }
285
286   T_MsgHdr MsgHdr;
287   FillMsgHdr(&MsgHdr, POS_DATA_ID, ptr - Msg);
288   memcpy(Msg, &MsgHdr, sizeof(T_MsgHdr));
289
290   mSocket->sendto(Msg, ptr - Msg, 0, &mServer);
291   SG_LOG(SG_NETWORK, SG_DEBUG, "FGMultiplayMgr::SendMyPosition");
292 } // FGMultiplayMgr::SendMyPosition()
293 //////////////////////////////////////////////////////////////////////
294
295 //////////////////////////////////////////////////////////////////////
296 //
297 //  Name: SendTextMessage
298 //  Description: Sends a message to the player. The message must
299 //  contain a valid and correctly filled out header and optional
300 //  message body.
301 //
302 //////////////////////////////////////////////////////////////////////
303 void
304 FGMultiplayMgr::SendTextMessage(const string &MsgText)
305 {
306   if (!mInitialised || !mHaveServer)
307     return;
308
309   T_MsgHdr MsgHdr;
310   FillMsgHdr(&MsgHdr, CHAT_MSG_ID);
311   //////////////////////////////////////////////////
312   // Divide the text string into blocks that fit
313   // in the message and send the blocks.
314   //////////////////////////////////////////////////
315   unsigned iNextBlockPosition = 0;
316   T_ChatMsg ChatMsg;
317   char Msg[sizeof(T_MsgHdr) + sizeof(T_ChatMsg)];
318   while (iNextBlockPosition < MsgText.length()) {
319     strncpy (ChatMsg.Text, 
320              MsgText.substr(iNextBlockPosition, MAX_CHAT_MSG_LEN - 1).c_str(),
321              MAX_CHAT_MSG_LEN);
322     ChatMsg.Text[MAX_CHAT_MSG_LEN - 1] = '\0';
323     memcpy (Msg, &MsgHdr, sizeof(T_MsgHdr));
324     memcpy (Msg + sizeof(T_MsgHdr), &ChatMsg, sizeof(T_ChatMsg));
325     mSocket->sendto (Msg, sizeof(T_MsgHdr) + sizeof(T_ChatMsg), 0, &mServer);
326     iNextBlockPosition += MAX_CHAT_MSG_LEN - 1;
327   }
328 } // FGMultiplayMgr::SendTextMessage ()
329 //////////////////////////////////////////////////////////////////////
330
331 //////////////////////////////////////////////////////////////////////
332 //
333 //  Name: ProcessData
334 //  Description: Processes data waiting at the receive socket. The
335 //  processing ends when there is no more data at the socket.
336 //  
337 //////////////////////////////////////////////////////////////////////
338 void
339 FGMultiplayMgr::Update(void) 
340 {
341   if (!mInitialised)
342     return;
343
344   /// Just for expiry
345   SGTimeStamp timestamper;
346   timestamper.stamp();
347   long stamp = timestamper.get_seconds();
348
349   //////////////////////////////////////////////////
350   //  Read the receive socket and process any data
351   //////////////////////////////////////////////////
352   int bytes;
353   do {
354     char Msg[MAX_PACKET_SIZE];
355     //////////////////////////////////////////////////
356     //  Although the recv call asks for 
357     //  MAX_PACKET_SIZE of data, the number of bytes
358     //  returned will only be that of the next
359     //  packet waiting to be processed.
360     //////////////////////////////////////////////////
361     netAddress SenderAddress;
362     bytes = mSocket->recvfrom(Msg, sizeof(Msg), 0, &SenderAddress);
363     //////////////////////////////////////////////////
364     //  no Data received
365     //////////////////////////////////////////////////
366     if (bytes <= 0) {
367       if (errno != EAGAIN && errno != 0) // MSVC output "NoError" otherwise
368         perror("FGMultiplayMgr::MP_ProcessData");
369       break;
370     }
371     if (bytes <= sizeof(T_MsgHdr)) {
372       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
373               << "received message with insufficient data" );
374       break;
375     }
376     //////////////////////////////////////////////////
377     //  Read header
378     //////////////////////////////////////////////////
379     T_MsgHdr* MsgHdr = (T_MsgHdr *)Msg;
380     MsgHdr->Magic       = XDR_decode_uint32 (MsgHdr->Magic);
381     MsgHdr->Version     = XDR_decode_uint32 (MsgHdr->Version);
382     MsgHdr->MsgId       = XDR_decode_uint32 (MsgHdr->MsgId);
383     MsgHdr->MsgLen      = XDR_decode_uint32 (MsgHdr->MsgLen);
384     MsgHdr->ReplyPort   = XDR_decode_uint32 (MsgHdr->ReplyPort);
385     if (MsgHdr->Magic != MSG_MAGIC) {
386       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
387               << "message has invalid magic number!" );
388     }
389     if (MsgHdr->Version != PROTO_VER) {
390       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
391               << "message has invalid protocoll number!" );
392     }
393     if (MsgHdr->MsgLen != bytes) {
394       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
395               << "message has invalid length!" );
396     }
397     //////////////////////////////////////////////////
398     //  Process messages
399     //////////////////////////////////////////////////
400     switch (MsgHdr->MsgId) {
401     case CHAT_MSG_ID:
402       ProcessChatMsg(Msg, SenderAddress);
403       break;
404     case POS_DATA_ID:
405       ProcessPosMsg(Msg, SenderAddress, bytes, stamp);
406       break;
407     case UNUSABLE_POS_DATA_ID:
408     case OLD_OLD_POS_DATA_ID:
409     case OLD_PROP_MSG_ID:
410     case OLD_POS_DATA_ID:
411       break;
412     default:
413       SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
414               << "Unknown message Id received: " << MsgHdr->MsgId );
415       break;
416     }
417   } while (bytes > 0);
418
419   // check for expiry
420   MultiPlayerMap::iterator it = mMultiPlayerMap.begin();
421   while (it != mMultiPlayerMap.end()) {
422     if (it->second->getLastTimestamp() + 10 < stamp) {
423       std::string name = it->first;
424       it->second->setDie(true);
425       mMultiPlayerMap.erase(it);
426       it = mMultiPlayerMap.upper_bound(name);
427     } else
428       ++it;
429   }
430 } // FGMultiplayMgr::ProcessData(void)
431 //////////////////////////////////////////////////////////////////////
432
433 //////////////////////////////////////////////////////////////////////
434 //
435 //  handle a position message
436 //
437 //////////////////////////////////////////////////////////////////////
438 void
439 FGMultiplayMgr::ProcessPosMsg(const char *Msg, netAddress & SenderAddress,
440                               unsigned len, long stamp)
441 {
442   T_MsgHdr* MsgHdr = (T_MsgHdr *)Msg;
443   if (MsgHdr->MsgLen < sizeof(T_MsgHdr) + sizeof(T_PositionMsg)) {
444     SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
445             << "Position message received with insufficient data" );
446     return;
447   }
448   T_PositionMsg* PosMsg = (T_PositionMsg *)(Msg + sizeof(T_MsgHdr));
449   FGExternalMotionData motionInfo;
450   motionInfo.time = XDR_decode_double(PosMsg->time);
451   motionInfo.lag = XDR_decode_double(PosMsg->lag);
452   for (unsigned i = 0; i < 3; ++i)
453     motionInfo.position(i) = XDR_decode_double(PosMsg->position[i]);
454   SGVec3f angleAxis;
455   for (unsigned i = 0; i < 3; ++i)
456     angleAxis(i) = XDR_decode_float(PosMsg->orientation[i]);
457   motionInfo.orientation = SGQuatf::fromAngleAxis(angleAxis);
458   for (unsigned i = 0; i < 3; ++i)
459     motionInfo.linearVel(i) = XDR_decode_float(PosMsg->linearVel[i]);
460   for (unsigned i = 0; i < 3; ++i)
461     motionInfo.angularVel(i) = XDR_decode_float(PosMsg->angularVel[i]);
462   for (unsigned i = 0; i < 3; ++i)
463     motionInfo.linearAccel(i) = XDR_decode_float(PosMsg->linearAccel[i]);
464   for (unsigned i = 0; i < 3; ++i)
465     motionInfo.angularAccel(i) = XDR_decode_float(PosMsg->angularAccel[i]);
466
467   T_PropertyMsg* PropMsg
468     = (T_PropertyMsg*)(Msg + sizeof(T_MsgHdr) + sizeof(T_PositionMsg));
469   while ((char*)PropMsg < Msg + len) {
470     FGFloatPropertyData pData;
471     pData.id = XDR_decode_uint32(PropMsg->id);
472     pData.value = XDR_decode_float(PropMsg->value);
473     motionInfo.properties.push_back(pData);
474     ++PropMsg;
475   }
476   
477   FGAIMultiplayer* mp = getMultiplayer(MsgHdr->Callsign);
478   if (!mp)
479     mp = addMultiplayer(MsgHdr->Callsign, PosMsg->Model);
480   mp->addMotionInfo(motionInfo, stamp);
481 } // FGMultiplayMgr::ProcessPosMsg()
482 //////////////////////////////////////////////////////////////////////
483
484 //////////////////////////////////////////////////////////////////////
485 //
486 //  handle a chat message
487 //  FIXME: display chat message withi flightgear
488 //
489 //////////////////////////////////////////////////////////////////////
490 void
491 FGMultiplayMgr::ProcessChatMsg(const char *Msg, netAddress& SenderAddress)
492 {
493   T_MsgHdr* MsgHdr = (T_MsgHdr *)Msg;
494   if (MsgHdr->MsgLen < sizeof(T_MsgHdr) + 1) {
495     SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::MP_ProcessData - "
496             << "Chat message received with insufficient data" );
497     return;
498   }
499   
500   char *MsgBuf = new char[MsgHdr->MsgLen - sizeof(T_MsgHdr)];
501   strncpy(MsgBuf, ((T_ChatMsg *)(Msg + sizeof(T_MsgHdr)))->Text,
502           MsgHdr->MsgLen - sizeof(T_MsgHdr));
503   MsgBuf[MsgHdr->MsgLen - sizeof(T_MsgHdr) - 1] = '\0';
504   
505   T_ChatMsg* ChatMsg = (T_ChatMsg *)(Msg + sizeof(T_MsgHdr));
506   SG_LOG ( SG_NETWORK, SG_ALERT, "Chat [" << MsgHdr->Callsign << "]"
507            << " " << MsgBuf << endl);
508   delete [] MsgBuf;
509 } // FGMultiplayMgr::ProcessChatMsg ()
510 //////////////////////////////////////////////////////////////////////
511
512 void
513 FGMultiplayMgr::FillMsgHdr(T_MsgHdr *MsgHdr, int MsgId, unsigned _len)
514 {
515   uint32_t len;
516   switch (MsgId) {
517   case CHAT_MSG_ID:
518     len = sizeof(T_MsgHdr) + sizeof(T_ChatMsg);
519     break;
520   case POS_DATA_ID:
521     len = _len;
522     break;
523   default:
524     len = sizeof(T_MsgHdr);
525     break;
526   }
527   MsgHdr->Magic           = XDR_encode_uint32(MSG_MAGIC);
528   MsgHdr->Version         = XDR_encode_uint32(PROTO_VER);
529   MsgHdr->MsgId           = XDR_encode_uint32(MsgId);
530   MsgHdr->MsgLen          = XDR_encode_uint32(len);
531   MsgHdr->ReplyAddress    = 0; // Are obsolete, keep them for the server for
532   MsgHdr->ReplyPort       = 0; // now
533   strncpy(MsgHdr->Callsign, mCallsign.c_str(), MAX_CALLSIGN_LEN);
534   MsgHdr->Callsign[MAX_CALLSIGN_LEN - 1] = '\0';
535 }
536
537 FGAIMultiplayer*
538 FGMultiplayMgr::addMultiplayer(const std::string& callsign,
539                                const std::string& modelName)
540 {
541   if (0 < mMultiPlayerMap.count(callsign))
542     return mMultiPlayerMap[callsign];
543
544   FGAIMultiplayer* mp = new FGAIMultiplayer;
545   mp->setPath(modelName.c_str());
546   mp->setCallSign(callsign);
547   mMultiPlayerMap[callsign] = mp;
548
549   FGAIManager *aiMgr = (FGAIManager*)globals->get_subsystem("ai_model");
550   if (aiMgr) {
551     aiMgr->attach(mp);
552
553     /// FIXME: that must follow the attach ATM ...
554     unsigned i = 0;
555     while (sIdPropertyList[i].name) {
556       mp->addPropertyId(sIdPropertyList[i].id, sIdPropertyList[i].name);
557       ++i;
558     }
559   }
560
561   return mp;
562 }
563
564 FGAIMultiplayer*
565 FGMultiplayMgr::getMultiplayer(const std::string& callsign)
566 {
567   if (0 < mMultiPlayerMap.count(callsign))
568     return mMultiPlayerMap[callsign];
569   else
570     return 0;
571 }