public void RxMsgFromClient(Player.Player player, byte[] msg) { if (msg[0] == 0x01 && msg.Length == 1) { // Keepalive byte[] omsg = { 0xFF }; player.SendMsgToClient(omsg); } else if (msg[0] == 0x01 && msg.Length == 75) { // Save login information. int pos = 3; string accountid = FLMsgType.GetUnicodeStringLen16(msg, ref pos); //string accDirPath = player.Runner.Server.AcctPath + Path.DirectorySeparatorChar + // FLMsgType.FLNameToFile(accountid); // If the account directory does not exist, create it and save the account id file. //if (!Directory.Exists(accDirPath)) // Directory.CreateDirectory(accDirPath); //FLUtility.WriteAccountID(accDirPath, accountid); byte[] omsg = { 0x02, 0x02 }; // If the account is banned kick the player. var accs = Old.CharacterDB.Database.GetAccount(accountid); //TODO: check if banning works; possibly make separate table for ID bans bool isbanned = false; if (accs != null) { if (accs[0].IsBanned) { isbanned = true; } } //if (File.Exists(accDirPath + Path.DirectorySeparatorChar + "banned")) //{ // FLMsgType.AddUInt8(ref omsg, FLMsgType.MSG_TYPE_LOGIN_REPORT_TYPE_BANNED); //} // If the account is already logged in, reject the login // fixme: this is not thread safe //else if (isbanned) { FLMsgType.AddUInt8(ref omsg, FLMsgType.MSG_TYPE_LOGIN_REPORT_TYPE_BANNED); } else if (player.Runner.Server.FindPlayerByAccountID(accountid) != null) { FLMsgType.AddUInt8(ref omsg, FLMsgType.MSG_TYPE_LOGIN_REPORT_TYPE_INUSE); } else { FLMsgType.AddUInt8(ref omsg, FLMsgType.MSG_TYPE_LOGIN_REPORT_TYPE_OKAY); } player.AccountID = accountid; player.SendMsgToClient(omsg); } else if (msg[0] == 0x05 && msg[1] == 0x03) { // char info request player.SaveCharFile(); player.SetState(DPCSelectingCharacterState.Instance()); } else { // Unexpected packet. Log and ignore it. player.Log.AddLog(LogType.FL_MSG, "Unexpected message: client rx", player.DPSess, msg); } }
public void RxMsgFromClient(Player.Player player, byte[] msg) { if (msg[0] == 0x01 && msg.Length == 1) { // Keepalive byte[] omsg = { 0xFF }; player.SendMsgToClient(omsg); } else if (msg[0] == 0x05 && msg[1] == 0x03) { // FLPACKET_CLIENT_REQUESTCHARINFO player.Log.AddLog(LogType.FL_MSG, "FLPACKET_CLIENT_REQUESTCHARINFO"); Packets.SendCharInfoRequestResponse(player); } else if (msg[0] == 0x06 && msg[1] == 0x03) { // FLPACKET_CLIENT_SELECTCHARACTER var nameBefore = player.Name; var pos = 2; var charfilename = FLMsgType.GetAsciiStringLen16(msg, ref pos); var acct = Old.CharacterDB.Database.GetOneAccount(player.AccountID, charfilename); var result = player.LoadCharFile(acct, player.Log); if (result != null) { player.Log.AddLog(LogType.ERROR, "error: cannot load character accdir={0} charfile={1} reason={2}", acct.CharName, result); return; } player.Log.AddLog(LogType.GENERAL, "FLPACKET_CLIENT_SELECTCHARACTER charfilename={0} name={1} system={2}", charfilename, player.Name, player.Ship.System.Nickname); if (player.Ship != null && player.Ship.Objid != 0) { player.Runner.DelSimObject(player.Ship); } player.OnCharacterSelected(nameBefore == player.Name, nameBefore == null); player.Update(); } else if (msg[0] == 0x39 && msg[1] == 0x03) { player.Log.AddLog(LogType.FL_MSG, "FLPACKET_CLIENT_CREATENEWCHAR"); // New character int pos = 2; string charname = FLMsgType.GetUnicodeStringLen16(msg, ref pos); //TODO: do nothing when charname exists? if (Old.CharacterDB.Database.GetAccount(@"CharName", charname) != null) { return; } Old.CharacterDB.Database.AddAccount(player.AccountID, charname); //string charfile = player.Runner.Server.AcctPath + // Path.DirectorySeparatorChar + FLMsgType.FLNameToFile(player.AccountID) + // Path.DirectorySeparatorChar + FLMsgType.FLNameToFile(charname) + ".fl"; //if (!File.Exists(charfile)) //{ // var file = // new FLDataFile(player.Runner.Server.AcctPath + Path.DirectorySeparatorChar + "default.fl", true); // file.AddSetting("Player", "name", new object[] {FLUtility.EncodeUnicodeHex(charname)}); // file.SaveSettings(charfile, false); //} Packets.SendCharInfoRequestResponse(player); } else if (msg[0] == 0x3a && msg[1] == 0x03) { player.Log.AddLog(LogType.FL_MSG, "FLPACKET_CLIENT_DESTROYCHAR"); // Delete character var pos = 2; var charfile = FLMsgType.GetAsciiStringLen16(msg, ref pos); Old.CharacterDB.Database.DelAccount(player.AccountID, charfile); Packets.SendCharInfoRequestResponse(player); } else { // Unexpected packet. Log and ignore it. player.Log.AddLog(LogType.ERROR, "Unexpected message: client rx", player.DPSess, msg); } }
/// <summary> /// Try to send a dframe or a keep alive if no user data is waiting to be sent. /// </summary> /// <param name="sess"></param> /// <returns>Return true if a dframe was sent</returns> public bool SendDFrame(Session sess) { lock (sess) { if (sess.UserData.Count > 0) { // If the window is full, don't send any thing if (IsAckWindowFull(sess)) { return(false); } // If we have sent a user data message that had to be carried in multiple // dframes then stop sending. We can't have more than one of these on the // wire at one time (if I've intepreted the specs correctly). if (sess.MultipleDframePacket) { return(false); } // The retry time should start at 100 + rtt * 2.5 according to the specs but we use // 2 as this is a round number. uint retry_time = 100 + (sess.Rtt * 2); while (sess.UserData.Count > 0) { byte[] ud = sess.UserData.First(); sess.UserData.RemoveFirst(); // Break the user data block into sizes smaller than the ethernet mtu. We // assume an MTU of 1450 as some infrastructure steals some bytes. int offset = 0; bool first_packet = true; bool last_packet = false; while (offset < ud.Length) { int length; if ((ud.Length - offset) > 1450) { length = 1450; sess.MultipleDframePacket = true; } else { length = ud.Length - offset; last_packet = true; } byte[] pkt = { 0x07, 0x00, sess.NextTxSeq, sess.NextRxSeq }; // If this is the first packet, set the flag to indicate this. if (first_packet) { pkt[0] |= 0x10; } // If this is the last packet, set the flag to indicate this if (last_packet) { pkt[0] |= 0x20; } // If the session isn't fully connected then this must be a session establishment // message if (sess.SessionState == Session.State.CONNECTING_SESSINFO) { pkt[0] |= 0x40; } FLMsgType.AddArray(ref pkt, ud, offset, length); var spkt = new Session.Pkt(); spkt.Data = pkt; spkt.RetryTime = DateTime.UtcNow.AddMilliseconds(retry_time); spkt.SendTime = DateTime.UtcNow; sess.UserDataPendingAck[sess.NextTxSeq] = spkt; sess.BytesTx += pkt.Length; TxStart(pkt, sess.Client); // Increase the retry times if multiple packets are sent so that // we're less likely to send a massive burst of packets to retry. retry_time += 5; sess.NextTxSeq++; first_packet = false; offset += length; // fixme: it's possible for a multi-dframe user data message to overrun // the valid seq window size. this is bad and the connection will fail. } // If we have sent a user data message that had to be carried in multiple // dframes then stop sending. We can't have more than one of these on the // wire at one time (if I've intepreted the specs correctly). if (sess.MultipleDframePacket) { break; } // If the window is full, don't send any more if (IsAckWindowFull(sess)) { break; } } return(true); } } return(false); }
/// <summary> /// Send a dummy trans /// </summary> /// <param name="sess"></param> private void SendTUDSessionInfo(Session sess) { var pkt = new byte[0]; FLMsgType.AddUInt32(ref pkt, 0xC2); // dwPacketType FLMsgType.AddUInt32(ref pkt, 0); // dwReplyOffset FLMsgType.AddUInt32(ref pkt, 0); // dwReplySize FLMsgType.AddUInt32(ref pkt, 0x50); // dwApplicationDescSize FLMsgType.AddUInt32(ref pkt, 0x01); // dwFlags FLMsgType.AddUInt32(ref pkt, max_players + 1); // dwMaxPlayers FLMsgType.AddUInt32(ref pkt, (uint)dplay_sessions.Count + 1); // dwCurrentPlayers FLMsgType.AddUInt32(ref pkt, 0x6C + 0x60); // dwSessionNameOffset FLMsgType.AddUInt32(ref pkt, (uint)server_name.Length * 2); // dwSessionNameSize FLMsgType.AddUInt32(ref pkt, 0); // dwPasswordOffset FLMsgType.AddUInt32(ref pkt, 0); // dwPasswordSize FLMsgType.AddUInt32(ref pkt, 0); // dwReservedDataOffset FLMsgType.AddUInt32(ref pkt, 0); // dwReservedDataSize FLMsgType.AddUInt32(ref pkt, 0); // dwApplicationReservedDataOffset FLMsgType.AddUInt32(ref pkt, 0); // dwApplicationReservedDataSize FLMsgType.AddArray(ref pkt, ApplicationInstanceGUID); FLMsgType.AddArray(ref pkt, ApplicationGUID); FLMsgType.AddUInt32(ref pkt, sess.DPlayID); // dpnid FLMsgType.AddUInt32(ref pkt, sess.DPlayID); // dwVersion FLMsgType.AddUInt32(ref pkt, 0); // dwVersionNotUsed FLMsgType.AddUInt32(ref pkt, 2); // dwEntryCount FLMsgType.AddUInt32(ref pkt, 0); // dwMembershipCount // server name table entry FLMsgType.AddUInt32(ref pkt, 1); // dpnid FLMsgType.AddUInt32(ref pkt, 0); // dpnidOwner FLMsgType.AddUInt32(ref pkt, 0x000402); // dwFlags FLMsgType.AddUInt32(ref pkt, 2); // dwVersion FLMsgType.AddUInt32(ref pkt, 0); // dwVersionNotUsed FLMsgType.AddUInt32(ref pkt, 7); // dwDNETVersion FLMsgType.AddUInt32(ref pkt, 0); // dwNameOffset FLMsgType.AddUInt32(ref pkt, 0); // dwNameSize FLMsgType.AddUInt32(ref pkt, 0); // dwDataOffset FLMsgType.AddUInt32(ref pkt, 0); // dwDataSize FLMsgType.AddUInt32(ref pkt, 0); // dwURLOffset FLMsgType.AddUInt32(ref pkt, 0); // dwURLSize // connecting client name table entry FLMsgType.AddUInt32(ref pkt, sess.DPlayID); // dpnid FLMsgType.AddUInt32(ref pkt, 0); // dpnidOwner FLMsgType.AddUInt32(ref pkt, 0x020000); // dwFlags FLMsgType.AddUInt32(ref pkt, sess.DPlayID); // dwVersion FLMsgType.AddUInt32(ref pkt, 0); // dwVersionNotUsed FLMsgType.AddUInt32(ref pkt, 7); // dwDNETVersion FLMsgType.AddUInt32(ref pkt, 0); // dwNameOffset FLMsgType.AddUInt32(ref pkt, 0); // dwNameSize FLMsgType.AddUInt32(ref pkt, 0); // dwDataOffset FLMsgType.AddUInt32(ref pkt, 0); // dwDataSize FLMsgType.AddUInt32(ref pkt, 0); // dwURLOffset FLMsgType.AddUInt32(ref pkt, 0); // dwURLSize FLMsgType.AddUnicodeStringLen0(ref pkt, server_name); sess.UserData.AddLast(pkt); SendDFrame(sess); }
public void ProcessPktFromClient(byte[] pkt, IPEndPoint client) { log.AddLog(LogType.DPLAY_MSG, "c>s client={0} pkt={1}", client, pkt); // If this message is too short, chuck it away if (pkt.Length < 2) { return; } // If this message is a enum server status then reply to the query. This tricks people // into thinking the server has a better ping than it does. int pos = 0; uint cmd = FLMsgType.GetUInt8(pkt, ref pos); if (cmd == 0x00 && pkt.Length >= 4) { uint opcode = FLMsgType.GetUInt8(pkt, ref pos); if (opcode == 0x02 && pkt.Length >= 4) { uint enum_payload = FLMsgType.GetUInt16(pkt, ref pos); SendCmdEnumResponse(client, (ushort)enum_payload); } } // If the data is at least 12 bytes and the first byte is // either 0x80 or 0x88 (PACKET_COMMAND_CFRAME or PACKET_COMMAND_CFRAME | // PACKET_COMMAND_POLL), it MUST process the message as a CFRAME // (section 3.1.5.1) command frame. else if ((cmd == 0x80 || cmd == 0x88) && pkt.Length >= 12) { uint opcode = FLMsgType.GetUInt8(pkt, ref pos); // The CONNECT packet is used to request a connection. If accepted, the response // is a CONNECTED (section 2.2.1.2) packet if (opcode == 0x01) { byte msg_id = FLMsgType.GetUInt8(pkt, ref pos); byte rsp_id = FLMsgType.GetUInt8(pkt, ref pos); uint version = FLMsgType.GetUInt32(pkt, ref pos); uint dplayid = FLMsgType.GetUInt32(pkt, ref pos); uint timestamp = FLMsgType.GetUInt32(pkt, ref pos); // Create a new session. Session sess = GetSession(client); if (sess == null) { sess = new Session(client); sess.DPlayID = dplayid; lock (dplay_sessions) { dplay_sessions[client] = sess; } } lock (sess) { // If the session id has changed, assume that the server is wrong // and kill the existing connection and start a new one. // This behaviour differs from the dplay specification. if (sess.DPlayID != 0 && sess.DPlayID != dplayid) { Destroy(sess, "changed dsessid"); } // If the session is fully connected because the client has // sent us a connect acknowledge then ignore this. if (sess.SessionState == Session.State.CONNECTED) { return; } // Otherwise this is a new connection. Reset the session information. sess.SessionState = Session.State.CONNECTING; sess.LastClientRxTime = DateTime.UtcNow; sess.StartTime = DateTime.Now; sess.Rtt = 200; sess.LostRx = 0; sess.BytesRx = 0; sess.LostTx = 0; sess.BytesTx = 0; sess.NextRxSeq = 0; sess.NextTxSeq = 0; sess.MsgID = 0; sess.OutOfOrder.Clear(); sess.UserData.Clear(); sess.UserDataPendingAck.Clear(); sess.MultipleDframePacket = false; sess.SessionTimer = new Timer(SessionTimer, sess, 100, 20); SendCmdConnectAccept(sess, msg_id); } } // Receive a SACK and process it else if (opcode == 0x06) { byte flags = FLMsgType.GetUInt8(pkt, ref pos); byte retry = FLMsgType.GetUInt8(pkt, ref pos); // The seq field indicates the seq of the next message that the client will send. byte seq = FLMsgType.GetUInt8(pkt, ref pos); // The next_rx field indicates the message seq that the client is waiting to receive byte nrcv = FLMsgType.GetUInt8(pkt, ref pos); pos += 2; // skip padding uint timestamp = FLMsgType.GetUInt32(pkt, ref pos); // Ignore packets for sessions that don't exist Session sess = GetSession(client); if (sess == null) { return; } lock (sess) { sess.LastClientRxTime = DateTime.UtcNow; sess.BytesRx += pkt.Length; // If the hi sack mask is present, resend any requested packets. if ((flags & 0x02) == 0x02) { uint mask = FLMsgType.GetUInt32(pkt, ref pos); DoRetryOnSACKMask(sess, mask, nrcv); } // If the hi sack mask is present, resend any requested packets. if ((flags & 0x04) == 0x04) { uint mask = FLMsgType.GetUInt32(pkt, ref pos); DoRetryOnSACKMask(sess, mask, (byte)(nrcv + 32)); } // At this point bSeq sequence ID is valid, the bNRcv field // is to be inspected. All previously sent TRANS_USERDATA_HEADER packets that // are covered by the bNRcv sequence ID, that is, those packets that had been sent // with bSeq values less than bNRcv (accounting for 8-bit counter wrapping) are // acknowledged. These packets do not have to be remembered any longer, and their // retry timers can be canceled. DoAcknowledgeUserData(sess, nrcv); // Try to send data if there's data waiting to be sent and send a // selective acknowledgement if we didn't sent a dframe and the client // requested an acknowledgement. if (!SendDFrame(sess) && cmd == 0x88) { SendCmdSACK(sess); } } } } // If a packet arrives, the recipient SHOULD first check whether // it is large enough to be a minimal data frame (DFRAME) (4 bytes) // and whether the first byte has the low bit (PACKET_COMMAND_DATA) set. else if ((cmd & 0x01) == 0x01 && pkt.Length >= 4) { uint control = FLMsgType.GetUInt8(pkt, ref pos); byte seq = FLMsgType.GetUInt8(pkt, ref pos); byte nrcv = FLMsgType.GetUInt8(pkt, ref pos); // Ignore packets for sessions that don't exist Session sess = GetSession(client); if (sess == null) { return; } lock (sess) { sess.LastClientRxTime = DateTime.UtcNow; sess.BytesRx += pkt.Length; // This is a disconnect. We ignore the soft disconnect and immediately // drop the session repeating the disconnect a few times to improve the // probability of it getting through. if ((control & 0x08) == 0x08) { Destroy(sess, "client request"); return; } // TRANS_USERDATA_HEADER bSeq field MUST be either the next sequence // ID expected or within 63 packets beyond the ID expected by the receiver. // If the sequence ID is not within this range, the payload MUST be ignored. // In addition, a SACK packet SHOULD be sent indicating the expected sequence ID. if (!InWindow(seq, sess.NextRxSeq)) { SendCmdSACK(sess); return; } // If the sequence ID is out of order, but still within 63 packets, // the receiver SHOULD queue the payload until it receives either: // - A delayed or retried transmission of the missing packet or packets, // and can now process the sequence in order. // - A subsequent packet with a send mask indicating that the missing // packet or packets did not use PACKET_COMMAND_RELIABLE and will never // be retried. Therefore, the receiver should advance its sequence as if // it had already received and processed the packets. if (seq != sess.NextRxSeq) { log.AddLog(LogType.DPLAY_MSG, "c>s out of order pkt received client={0} queuing seq={1:X} next_rx_seq={2:X}", sess.Client, seq, sess.NextRxSeq); sess.OutOfOrder[seq] = pkt; SendCmdSACK(sess); return; } //Test code to simulate packet loss //if (rand.Next(5) == 1) //{ // log.AddLog(String.Format("c>s: DROPPING THE PACKET NOW {0:X}", seq)); // return; //} // Note if this was a retried dframe. if ((control & 0x01) == 0x01) { sess.LostRx++; } // When one or both of the optional SACK mask 32-bit fields is present, and one // or more bits are set in the fields, the sender is indicating that it received a // packet or packets out of order, presumably due to packet loss. The two 32-bit, // little-endian fields MUST be considered as one 64-bit field, where dwSACKMask1 // is the low 32 bits and dwSACKMask2 is the high 32 bits. If either 32-bit field // is not available, the entire contents of the 64-bit field MUST be considered as all 0. // The receiver of a SACK mask SHOULD loop through each bit of the combined 64-bit value // in the ascending order of significance. Each bit corresponds to a sequence ID after // bNRcv. If the bit is set, it indicates that the corresponding packet was received // out of order. // The receiver of a SACK mask SHOULD shorten the retry timer for the first frame of // the window to speed recovery from the packet loss. The recommended duration is // 10 milliseconds. This value can be modified according to application and network // requirements. The receiver MAY also choose to remove the selectively acknowledged // packets from its list to retry. if ((control & 0x10) == 0x10) { uint mask = FLMsgType.GetUInt32(pkt, ref pos); DoRetryOnSACKMask(sess, mask, nrcv); } if ((control & 0x20) == 0x20) { uint mask = FLMsgType.GetUInt32(pkt, ref pos); DoRetryOnSACKMask(sess, mask, (byte)(nrcv + 32)); } // When one or both of the optional send mask 32-bit fields is present, and one or // more bits are set the fields, the sender is indicating that it sent a packet or // packets that were not marked as reliable and did not receive an acknowledgement yet. // The two 32-bit, little-endian fields MUST be considered as one 64-bit field, where // dwSendMask1 is the low 32 bits and dwSendMask2 is the high 32 bits. If either 32-bit // field is not available, the entire contents of the 64-bit field MUST be considered // as all 0. // The receiver of a send mask SHOULD loop through each bit of the combined 64-bit // value from the least significant bit to the most significant in little-endian byte // order. Each bit corresponds to a sequence ID prior to bSeq, and if that is the bit // that is set, it indicates that the corresponding packet was not sent reliably and // will not be retried. If the recipient of the send mask had not received the packet // and had not already processed a send mask that identified the sequence ID, it SHOULD // consider the packet as dropped and release its placeholder in the sequence. That is, // any sequential messages that could not be indicated because of the gap in the sequence // where the packet that was not marked as reliable had been SHOULD now be reported to // the upper layer. if ((control & 0x40) == 0x40) { FLMsgType.GetUInt32(pkt, ref pos); } if ((control & 0x80) == 0x80) { FLMsgType.GetUInt32(pkt, ref pos); } // However, freelancer always uses reliable packets and so ignore sendmasks. // At this point, we've received the packet we wanted to. Advance the sequence number count // and process this message. sess.NextRxSeq++; ProcessTransUserData(sess, pkt, pos); // If there are queued out of order packets, try to process these. while (sess.OutOfOrder.ContainsKey(sess.NextRxSeq)) { log.AddLog(LogType.DPLAY_MSG, "c>s unqueuing out of order pkt client={0} seq={1:X}", sess.Client, sess.NextRxSeq); pkt = sess.OutOfOrder[sess.NextRxSeq]; sess.OutOfOrder.Remove(sess.NextRxSeq); sess.NextRxSeq++; ProcessTransUserData(sess, pkt, pos); // fixme: pos could be wrong if we received a sack mask } // At this point bSeq sequence ID is valid, the bNRcv field // is to be inspected. All previously sent TRANS_USERDATA_HEADER packets that // are covered by the bNRcv sequence ID, that is, those packets that had been sent // with bSeq values less than bNRcv (accounting for 8-bit counter wrapping) are // acknowledged. These packets do not have to be remembered any longer, and their // retry timers can be canceled. DoAcknowledgeUserData(sess, nrcv); // We always do an immediate acknowledge as bandwidth isn't a particular concern // but fast recovery from lost packets is. if (!SendDFrame(sess)) { SendCmdSACK(sess); } } } }
// FLPACKET_SERVER_CREATESHIP public byte[] BuildCreateShip(Ship.Ship ship) { Log.AddLog(LogType.FL_MSG, "tx FLPACKET_SERVER_CREATESHIP objid={0}", ship.Objid); byte[] omsg = { 0x04, 0x02 }; FLMsgType.AddUInt32(ref omsg, ship.Objid); FLMsgType.AddUInt16(ref omsg, ship.Arch.SmallID); FLMsgType.AddUInt32(ref omsg, 0); FLMsgType.AddUInt32(ref omsg, ship.player != null ? ship.player.FLPlayerID : 0); FLMsgType.AddUInt32(ref omsg, ship.com_body); FLMsgType.AddUInt32(ref omsg, ship.com_head); FLMsgType.AddUInt8(ref omsg, (uint)ship.Accessories.Count); foreach (uint accessory in ship.Accessories) { FLMsgType.AddUInt32(ref omsg, accessory); } FLMsgType.AddUInt32(ref omsg, ship.voiceid); FLMsgType.AddFloat(ref omsg, (float)ship.Position.x); FLMsgType.AddFloat(ref omsg, (float)ship.Position.y); FLMsgType.AddFloat(ref omsg, (float)ship.Position.z); Quaternion q = Quaternion.MatrixToQuaternion(ship.Orientation); FLMsgType.AddInt8(ref omsg, (int)(q.I * 127)); FLMsgType.AddInt8(ref omsg, (int)(q.J * 127)); FLMsgType.AddInt8(ref omsg, (int)(q.K * 127)); FLMsgType.AddInt8(ref omsg, (int)(q.W * 127)); FLMsgType.AddUInt8(ref omsg, (uint)(ship.Health * 255)); FLMsgType.AddUInt16(ref omsg, (uint)(ship.Items.Count)); foreach (ShipItem item in ship.Items.Values) { byte flag = 0; if (item.mounted) { flag |= 0x01; } if (item.mission) { flag |= 0x02; } if (item.count == 1) { flag |= 0x80; } else { flag |= 0x04; } if (item.health == 1.0f) { flag |= 0x40; } if (item.hpname.Length > 0) { flag |= 0x10; } else { flag |= 0x20; } FLMsgType.AddUInt8(ref omsg, flag); if (item.count != 1) { FLMsgType.AddUInt32(ref omsg, item.count); } if (item.health != 1.0f) { FLMsgType.AddUInt8(ref omsg, (uint)(item.health * 255)); } FLMsgType.AddUInt16(ref omsg, item.arch.SmallID); FLMsgType.AddUInt8(ref omsg, item.hpid); if (item.hpname.Length > 0) { FLMsgType.AddAsciiStringLen8(ref omsg, item.hpname + "\0"); } } FLMsgType.AddUInt8(ref omsg, (uint)(ship.cols.Count)); foreach (CollisionGroup col in ship.cols) { FLMsgType.AddUInt8(ref omsg, col.id); FLMsgType.AddUInt8(ref omsg, (uint)(col.health * col.max_hit_pts * 255)); } FLMsgType.AddUInt8(ref omsg, (ship.player != null) ? 4u : 0u); // flag FLMsgType.AddFloat(ref omsg, 0); // x FLMsgType.AddFloat(ref omsg, 0); // y FLMsgType.AddFloat(ref omsg, 0); // z FLMsgType.AddInt8(ref omsg, 0); FLMsgType.AddUInt16(ref omsg, 0); // dunno? FLMsgType.AddUInt8(ref omsg, ship.Rank); if (ship.player != null) { FLMsgType.AddUInt8(ref omsg, ship.player.FLPlayerID); FLMsgType.AddUInt16(ref omsg, 0); FLMsgType.AddUnicodeStringLen8(ref omsg, ship.player.Name); } else { var patrol_name = new FLFormatString(0x3f20); patrol_name.AddString(0x3016b); patrol_name.AddString(0x4074); patrol_name.AddString(0x30401); patrol_name.AddNumber(0x09); FLMsgType.AddArray(ref omsg, patrol_name.GetBytes()); var ship_name = new FLFormatString(0x3f21); ship_name.AddString(0x301a4); ship_name.AddString(0x37bac); ship_name.AddString(0x37c2b); FLMsgType.AddArray(ref omsg, ship_name.GetBytes()); } // The faction associated with the ship. For player ships this can be // -1 but for NPCs it needs to be set to a faction ID or the NPC will // not have a name shown in space or in the radar/scanner FLMsgType.AddUInt32(ref omsg, ship.faction.FactionID); // The reputation with reference to the faction..but it doesn't seem to // do much FLMsgType.AddInt8(ref omsg, -127); return(omsg); }