+//////////////////////////////////////////////////////////////////////
+//
// mpplayer.cxx -- routines for a player within a multiplayer Flightgear
//
// Written by Duncan McCreanor, started February 2003.
// duncan.mccreanor@airservicesaustralia.com
//
+// With additions by Vivian Meazza, January 2006
+//
// Copyright (C) 2003 Airservices Australia
+// Copyright (C) 2005 Oliver Schroeder
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
+//////////////////////////////////////////////////////////////////////
#ifdef HAVE_CONFIG_H
#include <config.h>
*
******************************************************************/
+#include <simgear/timing/timestamp.hxx>
+
#include "mpplayer.hxx"
#include <stdlib.h>
#if !(defined(_MSC_VER) || defined(__MINGW32__))
-# include <netdb.h>
-# include <sys/socket.h>
-# include <netinet/in.h>
-# include <arpa/inet.h>
+# include <netdb.h>
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
#endif
#include <plib/netSocket.h>
#include <plib/sg.h>
+#include <simgear/math/sg_geodesy.hxx>
+#include <simgear/props/props.hxx>
#include <simgear/scene/model/modellib.hxx>
#include <simgear/scene/model/placementtrans.hxx>
+#include <AIModel/AIBase.hxx>
+#include <AIModel/AIManager.hxx>
+#include <AIModel/AIMultiplayer.hxx>
#include <Main/globals.hxx>
-#include <Scenery/scenery.hxx>
-
+//#include <Scenery/scenery.hxx>
// These constants are provided so that the ident command can list file versions.
const char sMPPLAYER_BID[] = "$Id$";
const char sMPPLAYER_HID[] = MPPLAYER_HID;
-
-/******************************************************************
-* Name: MPPlayer
-* Description: Constructor.
-******************************************************************/
-MPPlayer::MPPlayer() {
-
- // Initialise private members
- m_bInitialised = false;
- m_LastUpdate = 0;
+//////////////////////////////////////////////////////////////////////
+//
+// constructor
+//
+//////////////////////////////////////////////////////////////////////
+MPPlayer::MPPlayer()
+{
+ m_Initialised = false;
+ m_LastUpdate = 0;
+ m_LastUpdate = 0;
+ m_LastTime = 0;
+ m_LastUTime = 0;
+ m_Elapsed = 0;
+ m_TimeOffset = 0.0;
+ m_Callsign = "none";
m_PlayerAddress.set("localhost", 0);
- m_sCallsign = "none";
-
-
-}
-
+ m_AIModel = NULL;
+} // MPPlayer::MPPlayer()
+//////////////////////////////////////////////////////////////////////
/******************************************************************
* Name: ~MPPlayer
* Description: Destructor.
******************************************************************/
-MPPlayer::~MPPlayer() {
-
+MPPlayer::~MPPlayer()
+{
Close();
-
}
-
/******************************************************************
* Name: Open
* Description: Initialises class.
******************************************************************/
-bool MPPlayer::Open(const string &sAddress, const int &iPort, const string &sCallsign, const string &sModelName, bool bLocalPlayer) {
-
- bool bSuccess = true;
-
- if (!m_bInitialised) {
-
- m_PlayerAddress.set(sAddress.c_str(), iPort);
- m_sCallsign = sCallsign;
- m_sModelName = sModelName;
- m_bLocalPlayer = bLocalPlayer;
-
+bool
+MPPlayer::Open
+ (
+ const string &Address,
+ const int &Port,
+ const string &Callsign,
+ const string &ModelName,
+ bool LocalPlayer
+ )
+{
+ bool Success = true;
+
+ if (!m_Initialised)
+ {
+ m_PlayerAddress.set(Address.c_str(), Port);
+ m_Callsign = Callsign;
+ m_ModelName = ModelName;
+ m_LocalPlayer = LocalPlayer;
+ SG_LOG( SG_NETWORK, SG_ALERT, "Initialising " << m_Callsign
+ << " using '" << m_ModelName << "'" );
// If the player is remote then load the model
- if (!bLocalPlayer) {
+ if (!LocalPlayer)
+ {
try {
- LoadModel();
+ LoadAI();
} catch (...) {
- SG_LOG( SG_NETWORK, SG_ALERT, "Failed to load remote model '" << sModelName << "'." );
+ SG_LOG( SG_NETWORK, SG_ALERT,
+ "Failed to load remote model '" << ModelName << "'." );
return false;
}
}
-
- m_bInitialised = bSuccess;
-
- } else {
- SG_LOG( SG_NETWORK, SG_ALERT, "MPPlayer::Open - Attempt to open an already open player connection." );
- bSuccess = false;
+ m_Initialised = Success;
+ }
+ else
+ {
+ SG_LOG( SG_NETWORK, SG_ALERT, "MPPlayer::Open - "
+ << "Attempt to open an already opened player connection." );
+ Success = false;
}
-
-
/* Return true if open succeeds */
- return bSuccess;
-
+ return Success;
}
-
/******************************************************************
* Name: Close
* Description: Resets the object.
******************************************************************/
-void MPPlayer::Close(void) {
-
+void
+MPPlayer::Close(void)
+{
// Remove the model from the game
- if (m_bInitialised && !m_bLocalPlayer) {
-
- // Disconnect the model from the transform, then the transform from the scene.
- m_ModelTrans->removeKid(m_Model);
- globals->get_scenery()->unregister_placement_transform(m_ModelTrans);
- globals->get_scenery()->get_aircraft_branch()->removeKid( m_ModelTrans);
-
- // Flush the model loader so that it erases the model from its list of
- // models.
- globals->get_model_lib()->flush1();
-
- // Assume that plib/ssg deletes the model and transform as their
- // refcounts should be zero.
-
+ if (m_Initialised && !m_LocalPlayer)
+ {
+ m_Initialised = 0;
+
+ // get the model manager
+ FGAIManager *aiModelMgr = (FGAIManager *) globals->get_subsystem("ai_model");
+ if (!aiModelMgr) {
+ SG_LOG( SG_NETWORK, SG_ALERT, "MPPlayer::Close - "
+ << "Cannot find AI model manager!" );
+ return;
+ }
+
+ // remove it
+ aiModelMgr->destroyObject(m_AIModel->getID());
+ m_AIModel = NULL;
}
-
- m_bInitialised = false;
- m_bUpdated = false;
- m_LastUpdate = 0;
- m_sCallsign = "none";
-
+ m_Initialised = false;
+ m_Updated = false;
+ m_LastUpdate = 0;
+ m_LastUpdate = 0;
+ m_LastTime = 0;
+ m_LastUTime = 0;
+ m_Elapsed = 0;
+ m_TimeOffset = 0.0;
+ m_Callsign = "none";
}
+/******************************************************************
+* Name: CheckTime
+* Description: Checks if the time is valid for a position update
+* and perhaps sets the time offset
+******************************************************************/
+bool MPPlayer::CheckTime(int time, int timeusec)
+{
+ double curOffset;
+ int toff, utoff;
+
+ SGTimeStamp now;
+
+ // calculate the offset
+ toff = ((int) now.get_seconds()) - time;
+ utoff = ((int) now.get_usec()) - timeusec;
+ while (utoff < 0) {
+ toff--;
+ utoff += 1000000;
+ }
+
+ // set it
+ curOffset = ((double)toff) + (((double)utoff) / 1000000);
+
+ if (m_LastUpdate == 0) {
+ // set the main offset
+ m_TimeOffset = curOffset;
+ m_Elapsed = 0;
+ return true;
+ } else {
+ // check it
+ if (time < m_LastTime ||
+ (time == m_LastTime && timeusec <= m_LastUTime)) {
+ return false;
+ } else {
+ // set the current offset
+ m_LastOffset = curOffset;
+ // calculate the Hz
+ toff = time - m_LastTime;
+ utoff = timeusec - m_LastUTime;
+ while (utoff < 0) {
+ toff--;
+ utoff += 1000000;
+ }
+ m_Elapsed = ((double)toff) + (((double)utoff)/1000000);
+
+ m_LastTime = time;
+ m_LastUTime = timeusec;
+
+ return true;
+ }
+ }
+}
/******************************************************************
* Name: SetPosition
* Description: Updates position data held for this player and resets
* the last update time.
******************************************************************/
-void MPPlayer::SetPosition(const sgQuat PlayerOrientation,
- const sgdVec3 PlayerPosition) {
-
-
+void
+MPPlayer::SetPosition
+ (
+ const double lat, const double lon, const double alt,
+ const double heading, const double roll, const double pitch,
+ const double speedN, const double speedE, const double speedD,
+ const double left_aileron, const double right_aileron, const double elevator, const double rudder,
+ //const double rpms[6],
+ const double rateH, const double rateR, const double rateP,
+ const double accN, const double accE, const double accD
+ )
+{
// Save the position matrix and update time
- if (m_bInitialised) {
- sgdCopyVec3(m_ModelPosition, PlayerPosition);
- sgCopyVec4(m_ModelOrientation, PlayerOrientation);
+ if (m_Initialised)
+ {
+ // calculate acceleration
+ /*if (m_Elapsed > 0) {
+ m_accN = (speedN - m_speedN) / m_Elapsed;
+ m_accE = (speedE - m_speedE) / m_Elapsed;
+ m_accD = (speedD - m_speedD) / m_Elapsed;
+ } else {
+ m_accN = 0;
+ m_accE = 0;
+ m_accD = 0;
+ }*/
+
+ // store the position
+ m_lat = lat;
+ m_lon = lon;
+ m_alt = alt;
+ m_hdg = heading;
+ m_roll = roll;
+ m_pitch = pitch;
+ m_speedN = speedN;
+ m_speedE = speedE;
+ m_speedD = speedD;
+ m_accN = accN;
+ m_accE = accE;
+ m_accD = accD;
+ m_left_aileron = left_aileron;
+ m_right_aileron = right_aileron;
+ m_elevator = elevator;
+ m_rudder = rudder;
+
+ /*for (int i = 0; i < 6; i++) {
+ m_rpms[i] = rpms[i];
+ }
+ m_rateH = rateH;
+ m_rateR = rateR;
+ m_rateP = rateP;*/
+
+ if (!m_LocalPlayer) {
+ m_AIModel->setLatitude(m_lat);
+ m_AIModel->setLongitude(m_lon);
+ m_AIModel->setAltitude(m_alt);
+ m_AIModel->setHeading(m_hdg);
+ m_AIModel->setBank(m_roll);
+ m_AIModel->setPitch(m_pitch);
+ m_AIModel->setSpeedN(m_speedN);
+ m_AIModel->setSpeedE(m_speedE);
+ m_AIModel->setSpeedD(m_speedD);
+ m_AIModel->setAccN(m_accN);
+ m_AIModel->setAccE(m_accE);
+ m_AIModel->setAccD(m_accD);
+ m_AIModel->setRateH(m_rateH);
+ m_AIModel->setRateR(m_rateR);
+ m_AIModel->setRateP(m_rateP);
+
+ // set properties
+ SGPropertyNode *root = m_AIModel->getProps();
+ root->getNode("surface-positions/left-aileron-pos-norm", true)->setDoubleValue(m_left_aileron);
+ root->getNode("surface-positions/right-aileron-pos-norm", true)->setDoubleValue(m_right_aileron);
+ root->getNode("surface-positions/elevator-pos-norm", true)->setDoubleValue(m_elevator);
+ root->getNode("surface-positions/rudder-pos-norm", true)->setDoubleValue(m_rudder);
+ /*root->getNode("engines/engine/rpm", true)->setDoubleValue(m_rpms[0]);
+ root->getNode("engines/engine[1]/rpm", true)->setDoubleValue(m_rpms[1]);
+ root->getNode("engines/engine[2]/rpm", true)->setDoubleValue(m_rpms[2]);
+ root->getNode("engines/engine[3]/rpm", true)->setDoubleValue(m_rpms[3]);
+ root->getNode("engines/engine[4]/rpm", true)->setDoubleValue(m_rpms[4]);
+ root->getNode("engines/engine[5]/rpm", true)->setDoubleValue(m_rpms[5]);*/
+
+ // Adjust by the last offset
+ //cout << "OFFSET: " << (m_LastOffset - m_TimeOffset) << endl;
+
+ //m_AIModel->timewarp(m_LastOffset - m_TimeOffset);
+
+ // set the timestamp for the data update (sim elapsed time (secs))
+ m_AIModel->setTimeStamp();
+ }
+
time(&m_LastUpdate);
- m_bUpdated = true;
+
+ m_Updated = true;
}
-
-
}
+/******************************************************************
+ * Name: SetProperty
+ * Description: Sets a property of this player.
+ ******************************************************************/
+void MPPlayer::SetProperty(string property, SGPropertyNode::Type type, double val)
+{
+ // get rid of any leading /
+ while (property[0] == '/') property = property.substr(1);
+
+ // get our root node
+ SGPropertyNode *node = m_AIModel->getProps()->getNode(property.c_str(), true);
+
+ // set the property
+ switch (type) {
+ case 2:
+ node->setBoolValue( val != 0.0 );
+ break;
+ case 3:
+ node->setIntValue((int) val);
+ break;
+ case 4:
+ node->setLongValue((long) val);
+ break;
+ case 5:
+ node->setFloatValue((float) val);
+ break;
+ case 6:
+ default:
+ node->setDoubleValue(val);
+ }
+}
/******************************************************************
* Name: Draw
* Description: Updates the position for the player's model
* The state of the player's data is returned.
******************************************************************/
-MPPlayer::TPlayerDataState MPPlayer::Draw(void) {
-
+MPPlayer::TPlayerDataState
+MPPlayer::Draw (void)
+{
MPPlayer::TPlayerDataState eResult = PLAYER_DATA_NOT_AVAILABLE;
-
- if (m_bInitialised && !m_bLocalPlayer) {
- if ((time(NULL) - m_LastUpdate < TIME_TO_LIVE)) {
+ if (m_Initialised && !m_LocalPlayer)
+ {
+ if ((time(NULL) - m_LastUpdate < TIME_TO_LIVE))
+ {
// Peform an update if it has changed since the last update
- if (m_bUpdated) {
-
- // Transform and update player model
- sgMat4 orMat;
- sgMakeIdentMat4(orMat);
- sgQuatToMatrix(orMat, m_ModelOrientation);
- m_ModelTrans->setTransform(m_ModelPosition, orMat);
-
+ if (m_Updated)
+ {
+ /*
+ m_AIModel->setLatitude(m_lat);
+ m_AIModel->setLongitude(m_lon);
+ m_AIModel->setAltitude(m_alt);
+ m_AIModel->setHeading(m_hdg);
+ m_AIModel->setBank(m_roll);
+ m_AIModel->setPitch(m_pitch);
+ m_AIModel->setSpeedN(m_speedN);
+ m_AIModel->setSpeedE(m_speedE);
+ m_AIModel->_setVS_fps(m_speedU*60.0); // it needs input in fpm
+ m_AIModel->setRateH(m_rateH);
+ m_AIModel->setRateR(m_rateR);
+ m_AIModel->setRateP(m_rateP);
+
+ // set properties
+ SGPropertyNode *root = m_AIModel->getProps();
+ root->getNode("controls/flight/aileron", true)->setDoubleValue(m_aileron);
+ root->getNode("controls/flight/elevator", true)->setDoubleValue(m_elevator);
+ root->getNode("controls/flight/rudder", true)->setDoubleValue(m_rudder);
+ root->getNode("engines/engine/rpm", true)->setDoubleValue(m_rpms[0]);
+ root->getNode("engines/engine[1]/rpm", true)->setDoubleValue(m_rpms[1]);
+ root->getNode("engines/engine[2]/rpm", true)->setDoubleValue(m_rpms[2]);
+ root->getNode("engines/engine[3]/rpm", true)->setDoubleValue(m_rpms[3]);
+ root->getNode("engines/engine[4]/rpm", true)->setDoubleValue(m_rpms[4]);
+ root->getNode("engines/engine[5]/rpm", true)->setDoubleValue(m_rpms[5]);
+
+ // Adjust by the last offset
+ m_AIModel->update(m_LastOffset - m_TimeOffset);
+ */
eResult = PLAYER_DATA_AVAILABLE;
-
+
// Clear the updated flag so that the position data
// is only available if it has changed
- m_bUpdated = false;
+ m_Updated = false;
}
-
- // Data has not been updated for some time.
- } else {
+ }
+ else
+ { // Data has not been updated for some time.
eResult = PLAYER_DATA_EXPIRED;
}
-
}
-
return eResult;
-
}
-
/******************************************************************
* Name: Callsign
* Description: Returns the player's callsign.
******************************************************************/
-string MPPlayer::Callsign(void) const {
-
- return m_sCallsign;
-
+string
+MPPlayer::Callsign(void) const
+{
+ return m_Callsign;
}
-
/******************************************************************
* Name: CompareCallsign
* Description: Returns true if the player's callsign matches
* the given callsign.
******************************************************************/
-bool MPPlayer::CompareCallsign(const char *sCallsign) const {
-
- return (m_sCallsign == sCallsign);
-
+bool
+MPPlayer::CompareCallsign(const char *Callsign) const
+{
+ return (m_Callsign == Callsign);
}
-
/******************************************************************
-* Name: LoadModel
-* Description: Loads the player's aircraft model.
+ * Name: LoadAI
+ * Description: Loads the AI model into the AI core.
******************************************************************/
-void MPPlayer::LoadModel(void) {
-
-
- m_ModelTrans = new ssgPlacementTransform;
-
- // Load the model
- m_Model = globals->get_model_lib()->load_model( globals->get_fg_root(),
- m_sModelName,
- globals->get_props(),
- globals->get_sim_time_sec() );
- m_Model->clrTraversalMaskBits( SSGTRAV_HOT );
-
- // Add model to transform
- m_ModelTrans->addKid( m_Model );
-
- // Optimise model and transform
- ssgFlatten( m_Model );
- ssgStripify( m_ModelTrans );
-
- // Place on scene under aircraft branch
- globals->get_scenery()->get_aircraft_branch()->addKid( m_ModelTrans );
- globals->get_scenery()->register_placement_transform( m_ModelTrans);
-
-
+void
+MPPlayer::LoadAI(void)
+{
+ // then get the model manager
+ FGAIManager *aiModelMgr = (FGAIManager *) globals->get_subsystem("ai_model");
+ if (!aiModelMgr) {
+ SG_LOG( SG_NETWORK, SG_ALERT, "MPPlayer::LoadAI - "
+ << "Cannot find AI model manager!" );
+ return;
+ }
+
+ // then get the model
+ fgSetBool("/sim/freeze/clock", true);
+ m_AIModel = new FGAIMultiplayer;
+ m_AIModel->setAcType("Multiplayer");
+ m_AIModel->setCompany(m_Callsign);
+ m_AIModel->setPath(m_ModelName.c_str());
+ aiModelMgr->attach(m_AIModel);
+ fgSetBool("/sim/freeze/clock", false);
}
-
/******************************************************************
* Name: FillPosMsg
* Description: Populates the header and data for a position message.
******************************************************************/
-void MPPlayer::FillPosMsg(T_MsgHdr *MsgHdr, T_PositionMsg *PosMsg) {
+void
+MPPlayer::FillPosMsg
+ (
+ T_MsgHdr *MsgHdr,
+ T_PositionMsg *PosMsg
+ )
+{
+ SGTimeStamp now;
FillMsgHdr(MsgHdr, POS_DATA_ID);
-
- strncpy(PosMsg->sModel, m_sModelName.c_str(), MAX_MODEL_NAME_LEN);
- PosMsg->sModel[MAX_MODEL_NAME_LEN - 1] = '\0';
- sgdCopyVec3(PosMsg->PlayerPosition, m_ModelPosition);
- sgCopyQuat(PosMsg->PlayerOrientation, m_ModelOrientation);
-
-
+ strncpy(PosMsg->Model, m_ModelName.c_str(), MAX_MODEL_NAME_LEN);
+ PosMsg->Model[MAX_MODEL_NAME_LEN - 1] = '\0';
+ PosMsg->time = XDR_encode_uint32 (now.get_seconds());
+ PosMsg->timeusec = XDR_encode_uint32 (now.get_usec());
+ PosMsg->lat = XDR_encode_double (m_lat);
+ PosMsg->lon = XDR_encode_double (m_lon);
+ PosMsg->alt = XDR_encode_double (m_alt);
+ PosMsg->hdg = XDR_encode_double (m_hdg);
+ PosMsg->roll = XDR_encode_double (m_roll);
+ PosMsg->pitch = XDR_encode_double (m_pitch);
+ PosMsg->speedN = XDR_encode_double (m_speedN);
+ PosMsg->speedE = XDR_encode_double (m_speedE);
+ PosMsg->speedD = XDR_encode_double (m_speedD);
+ PosMsg->left_aileron = XDR_encode_float ((float) m_left_aileron);
+ PosMsg->right_aileron = XDR_encode_float ((float) m_right_aileron);
+ PosMsg->elevator = XDR_encode_float ((float) m_elevator);
+ PosMsg->rudder = XDR_encode_float ((float) m_rudder);
+ /*for (int i = 0; i < 6; i++) {
+ PosMsg->rpms[i] = XDR_encode_float ((float) m_rpms[i]);
+ }*/
+ PosMsg->rateH = XDR_encode_float ((float) m_rateH);
+ PosMsg->rateR = XDR_encode_float ((float) m_rateR);
+ PosMsg->rateP = XDR_encode_float ((float) m_rateP);
+ PosMsg->accN = XDR_encode_float ((float) m_accN);
+ PosMsg->accE = XDR_encode_float ((float) m_accE);
+ PosMsg->accD = XDR_encode_float ((float) m_accD);
}
-
/******************************************************************
* Name: FillMsgHdr
* Description: Populates the header of a multiplayer message.
******************************************************************/
-void MPPlayer::FillMsgHdr(T_MsgHdr *MsgHdr, const int iMsgId) {
-
- struct in_addr address;
-
- MsgHdr->MsgId = iMsgId;
-
- switch (iMsgId) {
+void
+MPPlayer::FillMsgHdr
+ (
+ T_MsgHdr *MsgHdr,
+ const int MsgId
+ )
+{
+ uint32_t len;
+
+ switch (MsgId)
+ {
case CHAT_MSG_ID:
- MsgHdr->iMsgLen = sizeof(T_MsgHdr) + sizeof(T_ChatMsg);
+ len = sizeof(T_MsgHdr) + sizeof(T_ChatMsg);
break;
case POS_DATA_ID:
- MsgHdr->iMsgLen = sizeof(T_MsgHdr) + sizeof(T_PositionMsg);
+ len = sizeof(T_MsgHdr) + sizeof(T_PositionMsg);
+ break;
+ case PROP_MSG_ID:
+ len = sizeof(T_MsgHdr) + sizeof(T_PropertyMsg);
break;
default:
- MsgHdr->iMsgLen = sizeof(T_MsgHdr);
+ len = sizeof(T_MsgHdr);
break;
}
-
- address.s_addr = inet_addr( m_PlayerAddress.getHost() );
- MsgHdr->lReplyAddress = address.s_addr;
-
- MsgHdr->iReplyPort = m_PlayerAddress.getPort();
-
- strncpy(MsgHdr->sCallsign, m_sCallsign.c_str(), MAX_CALLSIGN_LEN);
- MsgHdr->sCallsign[MAX_CALLSIGN_LEN - 1] = '\0';
-
-
+ MsgHdr->Magic = XDR_encode_uint32 (MSG_MAGIC);
+ MsgHdr->Version = XDR_encode_uint32 (PROTO_VER);
+ MsgHdr->MsgId = XDR_encode_uint32 (MsgId);
+ MsgHdr->MsgLen = XDR_encode_uint32 (len);
+ // inet_addr returns address in network byte order
+ // no need to encode it
+ MsgHdr->ReplyAddress = inet_addr( m_PlayerAddress.getHost() );
+ MsgHdr->ReplyPort = XDR_encode_uint32 (m_PlayerAddress.getPort());
+ strncpy(MsgHdr->Callsign, m_Callsign.c_str(), MAX_CALLSIGN_LEN);
+ MsgHdr->Callsign[MAX_CALLSIGN_LEN - 1] = '\0';
}
#endif // FG_MPLAYER_AS