public static void SendCurrentGameData(bool async) { var mapsData = new Dictionary <int, byte[]>(OnMainThread.cachedMapData); var gameData = OnMainThread.cachedGameData; void Send() { var writer = new ByteWriter(); writer.WriteInt32(mapsData.Count); foreach (var mapData in mapsData) { writer.WriteInt32(mapData.Key); writer.WritePrefixedBytes(GZipStream.CompressBuffer(mapData.Value)); } writer.WritePrefixedBytes(GZipStream.CompressBuffer(gameData)); byte[] data = writer.ToArray(); OnMainThread.Enqueue(() => Multiplayer.Client.SendFragmented(Packets.Client_AutosavedData, data)); }; if (async) { ThreadPool.QueueUserWorkItem(c => Send()); } else { Send(); } }
public void HandleCommand(ByteReader data) { ScheduledCommand cmd = ScheduledCommand.Deserialize(data); cmd.issuedBySelf = data.ReadBool(); OnMainThread.ScheduleCommand(cmd); }
private static void TickSync() { foreach (SyncField f in Sync.bufferedFields) { if (!f.inGameLoop) { continue; } Sync.bufferedChanges[f].RemoveAll((k, data) => { if (OnMainThread.CheckShouldRemove(f, k, data)) { return(true); } if (!data.sent && TickPatch.Timer - data.timestamp > 30) { f.DoSync(k.first, data.toSend, k.second); data.sent = true; data.timestamp = TickPatch.Timer; } return(false); }); } }
private static void Rehost() { LongEventHandler.QueueLongEvent(() => { Find.GameInfo.permadeathMode = false; // todo handle the other faction def too Multiplayer.DummyFaction.def = FactionDefOf.Ancients; OnMainThread.StopMultiplayer(); var doc = SaveLoad.SaveGame(); MemoryUtility.ClearAllMapsAndWorld(); Current.Game = new Game(); Current.Game.InitData = new GameInitData(); Current.Game.InitData.gameToLoad = "play"; LoadPatch.gameToLoad = doc; LongEventHandler.ExecuteWhenFinished(() => { HostWindow window = new HostWindow(null, true); window.forcePause = true; window.absorbInputAroundWindow = true; Find.WindowStack.Add(window); }); }, "Play", "MpConverting", true, null); }
public static void TryConnect(IPAddress address, int port) { EventBasedNetListener listener = new EventBasedNetListener(); Multiplayer.session = new MultiplayerSession(); NetManager netClient = new NetManager(listener); netClient.Start(); netClient.ReconnectDelay = 300; netClient.MaxConnectAttempts = 8; listener.PeerConnectedEvent += peer => { IConnection conn = new MpNetConnection(peer); conn.username = Multiplayer.username; conn.State = ConnectionStateEnum.ClientJoining; Multiplayer.session.client = conn; MpLog.Log("Net client connected"); }; listener.PeerDisconnectedEvent += (peer, info) => { string reason; if (info.AdditionalData.AvailableBytes > 0) { reason = info.AdditionalData.GetString(); } else { reason = DisconnectReasonString(info.Reason); if (info.SocketErrorCode != SocketError.Success) { reason += ": " + info.SocketErrorCode; } } Multiplayer.session.disconnectNetReason = reason; ConnectionStatusListeners.TryNotifyAll_Disconnected(); OnMainThread.StopMultiplayer(); MpLog.Log("Net client disconnected"); }; listener.NetworkReceiveEvent += (peer, reader, method) => { byte[] data = reader.GetRemainingBytes(); Multiplayer.HandleReceive(new ByteReader(data), method == DeliveryMethod.ReliableOrdered); }; listener.NetworkErrorEvent += (endpoint, error) => { Log.Warning($"Net client error {error}"); }; Multiplayer.session.netClient = netClient; netClient.Connect(address.ToString(), port, ""); }
public override void PostClose() { base.PostClose(); if (shouldCloseConnection) { OnMainThread.StopMultiplayer(); } }
public override void PostClose() { OnMainThread.StopMultiplayer(); if (returnToServerBrowser) { Find.WindowStack.Add(new ServerBrowser()); } }
public void HandleCommand(ByteReader data) { ScheduledCommand cmd = ScheduledCommand.Deserialize(data); cmd.issuedBySelf = data.ReadBool(); OnMainThread.ScheduleCommand(cmd); Multiplayer.session.localCmdId++; Multiplayer.session.ProcessTimeControl(); }
public void Add(SyncInfo info) { if (Multiplayer.session.desynced) { return; } if (TickPatch.Skipping) { return; } if (buffer.Count == 0) { buffer.Add(info); return; } if (buffer[0].local == info.local) { buffer.Add(info); if (buffer.Count > 30) { buffer.RemoveAt(0); } } else { while (buffer.Count > 0 && buffer[0].startTick < info.startTick) { buffer.RemoveAt(0); } if (buffer.Count == 0) { buffer.Add(info); } else if (buffer.First().startTick == info.startTick) { var first = buffer.RemoveFirst(); var error = first.Compare(info); if (error != null) { MpLog.Log($"Desynced {lastValidTick}: {error}"); Multiplayer.session.desynced = true; OnMainThread.Enqueue(() => OnDesynced(first, info, error)); } else { lastValidTick = first.startTick; lastValidArbiter = Multiplayer.session.ArbiterPlaying; } } } }
public void OnPeerDisconnected(NetPeer peer, DisconnectInfo info) { var reader = new ByteReader(info.AdditionalData.GetRemainingBytes()); Multiplayer.session.HandleDisconnectReason((MpDisconnectReason)reader.ReadByte(), reader.ReadPrefixedBytes()); ConnectionStatusListeners.TryNotifyAll_Disconnected(); OnMainThread.StopMultiplayer(); MpLog.Log("Net client disconnected"); }
public override void DoWindowContents(Rect inRect) { Text.Font = GameFont.Small; Text.Anchor = TextAnchor.UpperCenter; Widgets.Label(new Rect(0, 0, inRect.width, 40), $"{"MpDesynced".Translate()}\n{text}"); Text.Anchor = TextAnchor.UpperLeft; float buttonWidth = 120 * 4 + 10 * 3; var buttonRect = new Rect((inRect.width - buttonWidth) / 2, 40, buttonWidth, 35); GUI.BeginGroup(buttonRect); float x = 0; if (Widgets.ButtonText(new Rect(x, 0, 120, 35), "MpTryResync".Translate())) { TickPatch.skipToTickUntil = true; TickPatch.skipTo = 0; TickPatch.afterSkip = () => Multiplayer.Client.Send(Packets.Client_WorldReady); Multiplayer.session.desynced = false; ClientJoiningState.ReloadGame(OnMainThread.cachedMapData.Keys.ToList(), false); } x += 120 + 10; if (Widgets.ButtonText(new Rect(x, 0, 120, 35), "Save".Translate())) { Find.WindowStack.Add(new Dialog_SaveReplay()); } x += 120 + 10; if (Widgets.ButtonText(new Rect(x, 0, 120, 35), "MpChatButton".Translate())) { Find.WindowStack.Add(new ChatWindow() { closeOnClickedOutside = true, absorbInputAroundWindow = true }); } x += 120 + 10; if (Widgets.ButtonText(new Rect(x, 0, 120, 35), "Quit".Translate())) { OnMainThread.StopMultiplayer(); GenScene.GoToMainMenu(); } GUI.EndGroup(); }
public static void HostServer(ServerSettings settings, bool fromReplay, bool withSimulation = false, bool debugMode = false, bool logDesyncTraces = false) { Log.Message($"Starting the server"); var session = Multiplayer.session = new MultiplayerSession(); session.myFactionId = Faction.OfPlayer.loadID; session.localSettings = settings; session.gameName = settings.gameName; var localServer = new MultiplayerServer(settings); if (withSimulation) { localServer.savedGame = GZipStream.CompressBuffer(OnMainThread.cachedGameData); localServer.mapData = OnMainThread.cachedMapData.ToDictionary(kv => kv.Key, kv => GZipStream.CompressBuffer(kv.Value)); localServer.mapCmds = OnMainThread.cachedMapCmds.ToDictionary(kv => kv.Key, kv => kv.Value.Select(c => c.Serialize()).ToList()); } else { OnMainThread.ClearCaches(); } localServer.debugMode = debugMode; localServer.debugOnlySyncCmds = new HashSet <int>(Sync.handlers.Where(h => h.debugOnly).Select(h => h.syncId)); localServer.hostOnlySyncCmds = new HashSet <int>(Sync.handlers.Where(h => h.hostOnly).Select(h => h.syncId)); localServer.hostUsername = Multiplayer.username; localServer.coopFactionId = Faction.OfPlayer.loadID; localServer.rwVersion = session.mods.remoteRwVersion = VersionControl.CurrentVersionString; localServer.modNames = session.mods.remoteModNames = LoadedModManager.RunningModsListForReading.Select(m => m.Name).ToArray(); localServer.modIds = session.mods.remoteModIds = LoadedModManager.RunningModsListForReading.Select(m => m.PackageId).ToArray(); localServer.workshopModIds = session.mods.remoteWorkshopModIds = ModManagement.GetEnabledWorkshopMods().ToArray(); localServer.defInfos = session.mods.defInfo = Multiplayer.localDefInfos; Log.Message($"MP Host modIds: {string.Join(", ", localServer.modIds)}"); Log.Message($"MP Host workshopIds: {string.Join(", ", localServer.workshopModIds)}"); if (settings.steam) { localServer.NetTick += SteamIntegration.ServerSteamNetTick; } if (fromReplay) { localServer.gameTimer = TickPatch.Timer; } MultiplayerServer.instance = localServer; session.localServer = localServer; if (!fromReplay) { SetupGame(); } foreach (var tickable in TickPatch.AllTickables) { tickable.Cmds.Clear(); } Find.PlaySettings.usePlanetDayNightSystem = false; Multiplayer.RealPlayerFaction = Faction.OfPlayer; localServer.playerFactions[Multiplayer.username] = Faction.OfPlayer.loadID; SetupLocalClient(); Find.MainTabsRoot.EscapeCurrentTab(false); Multiplayer.session.AddMsg("If you are having a issue with the mod and would like some help resolving it, then please reach out to us on our discord server:", false); Multiplayer.session.AddMsg(new ChatMsg_Url("https://discord.gg/S4bxXpv"), false); if (withSimulation) { StartServerThread(); } else { var timeSpeed = TimeSpeed.Paused; Multiplayer.WorldComp.TimeSpeed = timeSpeed; foreach (var map in Find.Maps) { map.AsyncTime().TimeSpeed = timeSpeed; } Multiplayer.WorldComp.UpdateTimeSpeed(); Multiplayer.WorldComp.debugMode = debugMode; Multiplayer.WorldComp.logDesyncTraces = logDesyncTraces; LongEventHandler.QueueLongEvent(() => { SaveLoad.CacheGameData(SaveLoad.SaveAndReload()); SaveLoad.SendCurrentGameData(false); StartServerThread(); }, "MpSaving", false, null); } void StartServerThread() { var netStarted = localServer.StartListeningNet(); var lanStarted = localServer.StartListeningLan(); string text = "Server started."; if (netStarted != null) { text += (netStarted.Value ? $" Direct at {settings.bindAddress}:{localServer.NetPort}." : " Couldn't bind direct."); } if (lanStarted != null) { text += (lanStarted.Value ? $" LAN at {settings.lanAddress}:{localServer.LanPort}." : " Couldn't bind LAN."); } session.serverThread = new Thread(localServer.Run) { Name = "Local server thread" }; session.serverThread.Start(); Messages.Message(text, MessageTypeDefOf.SilentInput, false); Log.Message(text); } }
public void HandleJoinData(ByteReader data) { Multiplayer.session.gameName = data.ReadString(); Multiplayer.session.playerId = data.ReadInt32(); var remoteInfo = new RemoteData { remoteRwVersion = data.ReadString(), remoteMpVersion = data.ReadString(), remoteAddress = Multiplayer.session.address, remotePort = Multiplayer.session.port, remoteSteamHost = Multiplayer.session.steamHost }; var defDiff = false; var defsData = new ByteReader(data.ReadPrefixedBytes()); foreach (var local in MultiplayerData.localDefInfos) { var status = (DefCheckStatus)defsData.ReadByte(); local.Value.status = status; if (status != DefCheckStatus.Ok) { defDiff = true; } } JoinData.ReadServerData(data.ReadPrefixedBytes(), remoteInfo); OnMainThread.Schedule(Complete, 0.3f); void Complete() { if (JoinData.CompareToLocal(remoteInfo) && !defDiff) { StartDownloading(); return; } if (defDiff) { Multiplayer.StopMultiplayer(); } var connectingWindow = Find.WindowStack.WindowOfType <BaseConnectingWindow>(); MpUI.ClearWindowStack(); var defDiffStr = "\n\n" + MultiplayerData.localDefInfos .Where(kv => kv.Value.status != DefCheckStatus.Ok) .Take(10) .Join(kv => $"{kv.Key}: {kv.Value.status}", "\n"); Find.WindowStack.Add(new JoinDataWindow(remoteInfo) { connectAnywayDisabled = defDiff ? "MpMismatchDefsDiff".Translate() + defDiffStr : null, connectAnywayCallback = () => { Find.WindowStack.Add(connectingWindow); StartDownloading(); } }); void StartDownloading() { connection.Send(Packets.Client_WorldRequest); subState = JoiningState.Waiting; } } }
/// <summary> /// Adds a client opinion to the <see cref="knownClientOpinions"/> list and checks that it matches the most recent currently in there. If not, a desync event is fired. /// </summary> /// <param name="newOpinion">The <see cref="ClientSyncOpinion"/> to add and check.</param> public void AddClientOpinionAndCheckDesync(ClientSyncOpinion newOpinion) { //If we've already desynced, don't even bother if (Multiplayer.session.desynced) { return; } //If we're skipping ticks, again, don't bother if (TickPatch.Skipping) { return; } //If this is the first client opinion we have nothing to compare it with, so just add it if (knownClientOpinions.Count == 0) { knownClientOpinions.Add(newOpinion); return; } if (knownClientOpinions[0].isLocalClientsOpinion == newOpinion.isLocalClientsOpinion) { knownClientOpinions.Add(newOpinion); if (knownClientOpinions.Count > 30) { knownClientOpinions.RemoveAt(0); } } else { //Remove all opinions that started before this one, as it's the most up to date one while (knownClientOpinions.Count > 0 && knownClientOpinions[0].startTick < newOpinion.startTick) { knownClientOpinions.RemoveAt(0); } //If there are none left, we don't need to compare this new one if (knownClientOpinions.Count == 0) { knownClientOpinions.Add(newOpinion); } else if (knownClientOpinions.First().startTick == newOpinion.startTick) { //If these two contain the same tick range - i.e. they start at the same time, cause they should continue to the current tick, then do a comparison. var oldOpinion = knownClientOpinions.RemoveFirst(); //Actually do the comparison to find any desync var desyncMessage = oldOpinion.CheckForDesync(newOpinion); if (desyncMessage != null) { MpLog.Log($"Desynced after tick {lastValidTick}: {desyncMessage}"); Multiplayer.session.desynced = true; OnMainThread.Enqueue(() => HandleDesync(oldOpinion, newOpinion, desyncMessage)); } else { //Update fields lastValidTick = oldOpinion.startTick; arbiterWasPlayingOnLastValidTick = Multiplayer.session.ArbiterPlaying; } } } }
public static void HostServer(ServerSettings settings, bool fromReplay) { Log.Message($"Starting the server"); OnMainThread.ClearCaches(); var session = Multiplayer.session = new MultiplayerSession(); session.myFactionId = Faction.OfPlayer.loadID; session.localSettings = settings; session.gameName = settings.gameName; var localServer = new MultiplayerServer(settings); localServer.debugOnlySyncCmds = new HashSet <int>(Sync.handlers.Where(h => h.debugOnly).Select(h => h.SyncId)); localServer.hostUsername = Multiplayer.username; localServer.coopFactionId = Faction.OfPlayer.loadID; if (settings.steam) { localServer.NetTick += SteamIntegration.ServerSteamNetTick; } if (fromReplay) { localServer.gameTimer = TickPatch.Timer; } MultiplayerServer.instance = localServer; session.localServer = localServer; if (!fromReplay) { SetupGame(); } Find.PlaySettings.usePlanetDayNightSystem = false; Multiplayer.RealPlayerFaction = Faction.OfPlayer; localServer.playerFactions[Multiplayer.username] = Faction.OfPlayer.loadID; SetupLocalClient(); Find.MainTabsRoot.EscapeCurrentTab(false); Multiplayer.session.AddMsg("Wiki on desyncs:"); Multiplayer.session.AddMsg(new ChatMsg_Url("https://github.com/Zetrith/Multiplayer/wiki/Desyncs")); Multiplayer.session.hasUnread = false; LongEventHandler.QueueLongEvent(() => { SaveLoad.CacheGameData(SaveLoad.SaveAndReload()); SaveLoad.SendCurrentGameData(false); localServer.StartListening(); session.serverThread = new Thread(localServer.Run) { Name = "Local server thread" }; session.serverThread.Start(); string text = "Server started."; if (settings.bindAddress != null) { text += $" Bound to {settings.bindAddress}:{localServer.NetPort}."; } if (settings.lanAddress != null) { text += $" LAN at {settings.lanAddress}:{localServer.LanPort}."; } Messages.Message(text, MessageTypeDefOf.SilentInput, false); Log.Message(text); }, "MpSaving", false, null); }