Basic player input syncing

There's currently a bug where the inputs might be read out of order or more than once, which leads to desync. I'll feel really dumb when I find what's going on here.

Also, I'm using placeholder player spawning because CharacterInfo doesn't seem to be fully functional yet.
This commit is contained in:
juanjp600
2016-09-22 21:20:46 -03:00
parent edab86f730
commit 44e12ffed2
6 changed files with 300 additions and 36 deletions

View File

@@ -702,9 +702,9 @@ namespace Barotrauma
}
if (isFrozen)
{
for (int i=0;i < Limbs.Length;i++)
foreach (Limb l in Limbs)
{
Limbs[i].body.PhysEnabled = true;
l.body.PhysEnabled = true;
}
isFrozen = false;
}

View File

@@ -27,6 +27,7 @@ namespace Barotrauma
get { return netStateID; }
}
byte dequeuedInput = 0; byte prevDequeuedInput = 0;
List<byte> memInput = new List<byte>();
List<float> memMouseX = new List<float>();
List<float> memMouseY = new List<float>();
@@ -74,7 +75,7 @@ namespace Barotrauma
private CharacterInventory inventory;
public float LastNetworkUpdate;
private UInt32 LastNetworkUpdateID = 0;
//public int LargeUpdateTimer;
@@ -638,11 +639,54 @@ namespace Barotrauma
public bool IsKeyHit(InputType inputType)
{
if (GameMain.Server != null && Character.Controlled != null)
{
switch (inputType)
{
case InputType.Left:
return ((dequeuedInput & 1) > 0) && !((prevDequeuedInput & 1) > 0);
//break;
case InputType.Right:
return ((dequeuedInput & 2) > 0) && !((prevDequeuedInput & 2) > 0);
//break;
case InputType.Up:
return ((dequeuedInput & 4) > 0) && !((prevDequeuedInput & 4) > 0);
//break;
case InputType.Down:
return ((dequeuedInput & 8) > 0) && !((prevDequeuedInput & 8) > 0);
//break;
default:
return false;
//break;
}
}
return keys[(int)inputType].Hit;
}
public bool IsKeyDown(InputType inputType)
{
if (GameMain.Server!=null && Character.Controlled!=this)
{
bool retVal = false;
switch (inputType)
{
case InputType.Left:
retVal = (dequeuedInput & 1) > 0;
break;
case InputType.Right:
retVal = (dequeuedInput & 2) > 0;
break;
case InputType.Up:
retVal = (dequeuedInput & 4) > 0;
break;
case InputType.Down:
retVal = (dequeuedInput & 8) > 0;
break;
}
return retVal;
}
return keys[(int)inputType].Held;
}
@@ -1146,7 +1190,38 @@ namespace Barotrauma
{
if (GameMain.Server != null)
{
if (!IsDead)
{
if (memInput.Count > 0)
{
AnimController.Frozen = false;
prevDequeuedInput = dequeuedInput;
dequeuedInput = memInput[memInput.Count - 1];
memInput.RemoveAt(memInput.Count - 1);
}
else
{
AnimController.Frozen = true;
return;
}
}
}
}
else
{
if (GameMain.Client != null)
{
byte newInput = 0;
newInput |= IsKeyDown(InputType.Left) ? (byte)1 : (byte)0;
newInput |= IsKeyDown(InputType.Right) ? (byte)2 : (byte)0;
newInput |= IsKeyDown(InputType.Up) ? (byte)4 : (byte)0;
newInput |= IsKeyDown(InputType.Down) ? (byte)8 : (byte)0;
memInput.Insert(0,newInput);
LastNetworkUpdateID++;
while (memInput.Count>60)
{
memInput.RemoveAt(memInput.Count - 1);
}
}
}
/*if (networkUpdateSent)
@@ -1585,12 +1660,48 @@ namespace Barotrauma
}
public virtual void ClientWrite(NetOutgoingMessage msg)
{
//TODO: write inputs
{
if (GameMain.Server != null) return;
msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
while (memInput.Count > 60)
{
memInput.RemoveAt(memInput.Count - 1);
}
msg.Write(LastNetworkUpdateID);
byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
msg.Write(inputCount);
for (int i = 0; i < inputCount; i++)
{
msg.Write(memInput[i]);
}
}
public virtual void ServerRead(NetIncomingMessage msg, Client c)
{
//TODO: read inputs
{
if (GameMain.Server == null) return;
UInt32 networkUpdateID = msg.ReadUInt32();
byte inputCount = msg.ReadByte();
for (int i=0;i<inputCount;i++)
{
byte newInput = msg.ReadByte();
if ((i < (networkUpdateID-LastNetworkUpdateID)) && (i<60))
{
memInput.Insert(0, newInput);
}
}
if (networkUpdateID > LastNetworkUpdateID)
{
LastNetworkUpdateID = networkUpdateID;
}
while (memInput.Count > 60)
{
//deleting inputs from the queue here means the server is way behind and data needs to be dropped
memInput.RemoveAt(memInput.Count - 1);
}
}
public virtual void ServerWrite(NetOutgoingMessage msg, Client c)

View File

@@ -28,7 +28,7 @@ namespace Barotrauma.Networking
public NetConnection Connection { get; set; }
public string version;
public bool inGame;
public UInt32 lastRecvLobbyUpdate = 0;
public UInt32 lastRecvGeneralUpdate = 0;
public bool hasLobbyData = false;
public UInt32 lastSentChatMsgID = 0; //last msg this client said

View File

@@ -485,6 +485,10 @@ namespace Barotrauma.Networking
{
SendLobbyUpdate();
}
else
{
SendIngameUpdate();
}
// Update current time
updateTimer = DateTime.Now + updateInterval;
@@ -513,6 +517,9 @@ namespace Barotrauma.Networking
case ServerPacketHeader.UPDATE_LOBBY:
ReadLobbyUpdate(inc);
break;
case ServerPacketHeader.UPDATE_INGAME:
ReadIngameUpdate(inc);
break;
case ServerPacketHeader.QUERY_STARTGAME:
string subName = inc.ReadString();
string subHash = inc.ReadString();
@@ -560,6 +567,9 @@ namespace Barotrauma.Networking
bool respawnAllowed = inc.ReadBoolean();
float posX = inc.ReadFloat();
float posY = inc.ReadFloat();
GameModePreset gameMode = GameModePreset.list.Find(gm => gm.Name == modeName);
if (gameMode == null)
@@ -591,6 +601,10 @@ namespace Barotrauma.Networking
GameMain.GameScreen.Select();
DebugConsole.NewMessage(Convert.ToString(posX) + "," + Convert.ToString(posY), Color.Lime);
Character myChar = Character.Create(Character.HumanConfigFile, new Vector2(posX, posY), null, true, false);
Character.Controlled = myChar;
yield return CoroutineStatus.Success;
}
@@ -661,6 +675,32 @@ namespace Barotrauma.Networking
}
}
private void ReadIngameUpdate(NetIncomingMessage inc)
{
ServerNetObject objHeader;
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
{
switch (objHeader)
{
case ServerNetObject.SYNC_IDS:
lastSentChatMsgID = inc.ReadUInt32();
break;
case ServerNetObject.CHARACTER_POSITION:
bool dead = inc.ReadBoolean();
inc.ReadPadBits();
if (Character.Controlled != null)
{
if (dead && !Character.Controlled.IsDead)
Character.Controlled.Kill(CauseOfDeath.Damage);
}
break;
case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc);
break;
}
}
}
private void SendLobbyUpdate()
{
NetOutgoingMessage outmsg = client.CreateMessage();
@@ -683,6 +723,34 @@ namespace Barotrauma.Networking
client.SendMessage(outmsg, NetDeliveryMethod.Unreliable);
}
private void SendIngameUpdate()
{
NetOutgoingMessage outmsg = client.CreateMessage();
outmsg.Write((byte)ClientPacketHeader.UPDATE_INGAME);
outmsg.Write((byte)ClientNetObject.SYNC_IDS);
outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.Write(ChatMessage.LastID);
ChatMessage removeMsg;
while ((removeMsg = chatMsgQueue.Find(cMsg => cMsg.NetStateID <= lastSentChatMsgID)) != null)
{
chatMsgQueue.Remove(removeMsg);
}
foreach (ChatMessage cMsg in chatMsgQueue)
{
cMsg.ClientWrite(outmsg);
}
if (Character.Controlled != null)
{
Character.Controlled.ClientWrite(outmsg);
}
outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE);
client.SendMessage(outmsg, NetDeliveryMethod.Unreliable);
}
public override void SendChatMessage(string message, ChatMessageType? type = null)
{
if (client.ServerConnection == null) return;

View File

@@ -505,7 +505,7 @@ namespace Barotrauma.Networking
ClientReadLobby(inc);
break;
case ClientPacketHeader.UPDATE_INGAME:
//TODO
ClientReadIngame(inc);
break;
}
}
@@ -558,7 +558,7 @@ namespace Barotrauma.Networking
inc.SenderConnection.Disconnect("You're not a connected client.");
return;
}
ClientNetObject objHeader;
while ((objHeader=(ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE)
{
@@ -566,10 +566,10 @@ namespace Barotrauma.Networking
{
case ClientNetObject.SYNC_IDS:
//TODO: might want to use a clever class for this
UInt32 lastLobbyUpdID = inc.ReadUInt32();
if (lastLobbyUpdID > c.lastRecvLobbyUpdate)
UInt32 lastGeneralUpdID = inc.ReadUInt32();
if (lastGeneralUpdID > c.lastRecvGeneralUpdate)
{
c.lastRecvLobbyUpdate = lastLobbyUpdID;
c.lastRecvGeneralUpdate = lastGeneralUpdID;
}
UInt32 lastChatID = inc.ReadUInt32();
if (lastChatID > c.lastRecvChatMsgID)
@@ -580,6 +580,9 @@ namespace Barotrauma.Networking
case ClientNetObject.CHAT_MESSAGE:
ChatMessage.ServerRead(inc, c);
break;
default:
return;
//break;
}
}
@@ -593,18 +596,78 @@ namespace Barotrauma.Networking
inc.SenderConnection.Disconnect("You're not a connected client.");
return;
}
ClientNetObject objHeader;
while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE)
{
switch (objHeader)
{
case ClientNetObject.SYNC_IDS:
//TODO: might want to use a clever class for this
UInt32 lastGeneralUpdID = inc.ReadUInt32();
if (lastGeneralUpdID > c.lastRecvGeneralUpdate)
{
c.lastRecvGeneralUpdate = lastGeneralUpdID;
}
UInt32 lastChatID = inc.ReadUInt32();
if (lastChatID > c.lastRecvChatMsgID)
{
c.lastRecvChatMsgID = lastChatID;
}
break;
case ClientNetObject.CHAT_MESSAGE:
ChatMessage.ServerRead(inc, c);
break;
case ClientNetObject.CHARACTER_INPUT:
if (c.Character != null && !c.Character.IsDead && !c.Character.IsUnconscious)
{
c.Character.ServerRead(inc, c);
}
break;
default:
return;
//break;
}
}
}
private void ClientWriteIngame(Client c)
{
NetOutgoingMessage outmsg = server.CreateMessage();
outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME);
outmsg.Write((byte)ServerNetObject.SYNC_IDS);
outmsg.Write(c.lastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
foreach (GUIComponent gc in GameMain.NetLobbyScreen.ChatBox.children)
{
if (gc is GUITextBlock)
{
if (gc.UserData is ChatMessage)
{
ChatMessage cMsg = (ChatMessage)gc.UserData;
if (cMsg.NetStateID > c.lastRecvChatMsgID)
{
cMsg.ServerWrite(outmsg, c);
}
}
}
}
outmsg.Write((byte)ServerNetObject.CHARACTER_POSITION);
if (c.Character != null && !c.Character.IsDead)
{
outmsg.Write(false); //not dead
outmsg.WritePadBits();
}
else
{
outmsg.Write(true); //dead
outmsg.WritePadBits();
}
outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE);
server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable);
}
private void ClientWriteLobby(Client c)
@@ -614,7 +677,7 @@ namespace Barotrauma.Networking
outmsg.Write((byte)ServerNetObject.SYNC_IDS);
if (c.lastRecvLobbyUpdate<GameMain.NetLobbyScreen.LastUpdateID)
if (c.lastRecvGeneralUpdate<GameMain.NetLobbyScreen.LastUpdateID)
{
outmsg.Write(true);
outmsg.WritePadBits();
@@ -622,7 +685,7 @@ namespace Barotrauma.Networking
outmsg.Write(GameMain.NetLobbyScreen.GetServerName());
outmsg.Write(GameMain.NetLobbyScreen.ServerMessage);
var subList = GameMain.NetLobbyScreen.GetSubList();
if (c.lastRecvLobbyUpdate < 1)
if (c.lastRecvGeneralUpdate < 1)
{
outmsg.Write((UInt16)subList.Count);
for (int i = 0; i < subList.Count; i++)
@@ -776,8 +839,24 @@ namespace Barotrauma.Networking
if (AllowRespawn) respawnManager = new RespawnManager(this, selectedShuttle);
var startMessage = CreateStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset);
server.SendMessage(startMessage, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
List<CharacterInfo> characterInfos = new List<CharacterInfo>();
foreach (Client c in connectedClients)
{
c.inGame = true;
WayPoint spawnPoint = WayPoint.GetRandom(SpawnType.Human);
Vector2 spawnPosition = spawnPoint.WorldPosition;
DebugConsole.NewMessage(Convert.ToString(spawnPosition.X) + "," + Convert.ToString(spawnPosition.Y), Color.Lime);
Character spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition, null, true, false);
c.Character = spawnedCharacter;
GameMain.GameSession.CrewManager.characters.Add(c.Character);
}
CreateStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset, connectedClients);
//var startMessage = CreateStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset);
//server.SendMessage(startMessage, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
yield return CoroutineStatus.Running;
@@ -802,28 +881,34 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Success;
}
private NetOutgoingMessage CreateStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode)
private void CreateStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode, List<Client> clients)
{
NetOutgoingMessage msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.STARTGAME);
foreach (Client c in clients)
{
NetOutgoingMessage msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.STARTGAME);
msg.Write(seed);
msg.Write(seed);
msg.Write(GameMain.NetLobbyScreen.LevelSeed);
msg.Write(GameMain.NetLobbyScreen.LevelSeed);
msg.Write((byte)GameMain.NetLobbyScreen.MissionTypeIndex);
msg.Write((byte)GameMain.NetLobbyScreen.MissionTypeIndex);
msg.Write(selectedSub.Name);
msg.Write(selectedSub.MD5Hash.Hash);
msg.Write(selectedSub.Name);
msg.Write(selectedSub.MD5Hash.Hash);
msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name);
msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash);
msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name);
msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash);
msg.Write(selectedMode.Name);
msg.Write(selectedMode.Name);
msg.Write(AllowRespawn);
return msg;
msg.Write(AllowRespawn);
msg.Write(c.Character.WorldPosition.X);
msg.Write(c.Character.WorldPosition.Y);
c.Connection.SendMessage(msg, NetDeliveryMethod.ReliableUnordered,0);
}
}
public void EndGame()

View File

@@ -14,7 +14,7 @@ namespace Barotrauma.Networking
REQUEST_AUTH, //ask the server if a password is needed, if so we'll get nonce for encryption
REQUEST_INIT, //ask the server to give you initialization
UPDATE_LOBBY, //update state in lobby
UPDATE_INGAME, //update state ingame while alive
UPDATE_INGAME, //update state ingame
RESPONSE_STARTGAME //tell the server whether you're ready to start
}
@@ -33,7 +33,7 @@ namespace Barotrauma.Networking
AUTH_RESPONSE, //tell the player if they require a password to log in
AUTH_FAILURE, //the server won't authorize player yet, however connection is still alive
UPDATE_LOBBY, //update state in lobby (votes and chat messages)
UPDATE_INGAME, //update state ingame while alive (character input and chat messages)
UPDATE_INGAME, //update state ingame (character input and chat messages)
QUERY_STARTGAME, //ask the clients whether they're ready to start
STARTGAME //start a new round