public static void StopMultiplayer() { if (Multiplayer.session != null) { Multiplayer.session.Stop(); Multiplayer.session = null; Prefs.Apply(); } Multiplayer.game = null; TickPatch.ClearSkipping(); TickPatch.Timer = 0; TickPatch.tickUntil = 0; TickPatch.accumulator = 0; Find.WindowStack?.WindowOfType <ServerBrowser>()?.Cleanup(true); foreach (var entry in Sync.bufferedChanges) { entry.Value.Clear(); } ClearCaches(); if (Multiplayer.arbiterInstance) { Multiplayer.arbiterInstance = false; Application.Quit(); } }
static void DrawSkippingWindow() { if (Multiplayer.Client == null || TickPatch.skipTo < 0) { return; } string text = $"{"MpSimulating".Translate()}{MpUtil.FixedEllipsis()}"; float textWidth = Text.CalcSize(text).x; float windowWidth = Math.Max(240f, textWidth + 40f); Rect rect = new Rect(0, 0, windowWidth, 75f).CenterOn(new Rect(0, 0, UI.screenWidth, UI.screenHeight)); if (Multiplayer.IsReplay && !TickPatch.disableSkipCancel && Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.Escape) { TickPatch.ClearSkipping(); Event.current.Use(); } Find.WindowStack.ImmediateWindow(SkippingWindowId, rect, WindowLayer.Super, () => { Text.Anchor = TextAnchor.MiddleCenter; Text.Font = GameFont.Small; Widgets.Label(rect.AtZero(), text); Text.Anchor = TextAnchor.UpperLeft; }, absorbInputAroundWindow: true); }
public static void LoadReplay(FileInfo file, bool toEnd = false, Action after = null, Action cancel = null, string simTextKey = null) { var session = Multiplayer.session = new MultiplayerSession(); session.client = new ReplayConnection(); session.client.State = ConnectionStateEnum.ClientPlaying; session.replay = true; var replay = ForLoading(file); replay.LoadInfo(); var sectionIndex = toEnd ? (replay.info.sections.Count - 1) : 0; replay.LoadCurrentData(sectionIndex); // todo ensure everything is read correctly session.myFactionId = replay.info.playerFaction; session.replayTimerStart = replay.info.sections[sectionIndex].start; int tickUntil = replay.info.sections[sectionIndex].end; session.replayTimerEnd = tickUntil; TickPatch.tickUntil = tickUntil; TickPatch.SkipTo(toEnd ? tickUntil : session.replayTimerStart, onFinish: after, onCancel: cancel, simTextKey: simTextKey); ClientJoiningState.ReloadGame(OnMainThread.cachedMapData.Keys.ToList()); }
static void HandleSimulatingEvents() { if (TickPatch.simulating.canEsc && Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.Escape) { TickPatch.ClearSimulating(); Event.current.Use(); } }
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())) { Multiplayer.session.resyncing = true; TickPatch.SkipTo( toTickUntil: true, onFinish: () => { Multiplayer.session.resyncing = false; Multiplayer.Client.Send(Packets.Client_WorldReady); }, cancelButtonKey: "Quit", onCancel: GenScene.GoToMainMenu ); 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())) { MainMenuPatch.AskQuitToMainMenu(); } GUI.EndGroup(); }
public void HandleWorldData(ByteReader data) { connection.State = ConnectionStateEnum.ClientPlaying; Log.Message("Game data size: " + data.Length); int factionId = data.ReadInt32(); Multiplayer.session.myFactionId = factionId; int tickUntil = data.ReadInt32(); byte[] worldData = GZipStream.UncompressBuffer(data.ReadPrefixedBytes()); OnMainThread.cachedGameData = worldData; List <int> mapsToLoad = new List <int>(); int mapCmdsCount = data.ReadInt32(); for (int i = 0; i < mapCmdsCount; i++) { int mapId = data.ReadInt32(); int mapCmdsLen = data.ReadInt32(); List <ScheduledCommand> mapCmds = new List <ScheduledCommand>(mapCmdsLen); for (int j = 0; j < mapCmdsLen; j++) { mapCmds.Add(ScheduledCommand.Deserialize(new ByteReader(data.ReadPrefixedBytes()))); } OnMainThread.cachedMapCmds[mapId] = mapCmds; } int mapDataCount = data.ReadInt32(); for (int i = 0; i < mapDataCount; i++) { int mapId = data.ReadInt32(); byte[] rawMapData = data.ReadPrefixedBytes(); byte[] mapData = GZipStream.UncompressBuffer(rawMapData); OnMainThread.cachedMapData[mapId] = mapData; mapsToLoad.Add(mapId); } Multiplayer.session.localCmdId = data.ReadInt32(); TickPatch.tickUntil = tickUntil; TickPatch.SkipTo( toTickUntil: true, onFinish: () => Multiplayer.Client.Send(Packets.Client_WorldReady), cancelButtonKey: "Quit", onCancel: GenScene.GoToMainMenu ); ReloadGame(mapsToLoad); }
static bool Prefix() { if (Multiplayer.Client == null || !TimeControlsMarker.drawingTimeControls) { return(true); } if (TickPatch.Timer < TickPatch.tickUntil) { var replaySpeed = TickPatch.replayTimeSpeed; TickPatch.replayTimeSpeed = TimeSpeed.Normal; TickPatch.accumulator = 1; TickPatch.Tick(); TickPatch.accumulator = 0; TickPatch.replayTimeSpeed = replaySpeed; } return(false); }
static void DrawSkippingWindow() { if (Multiplayer.Client == null || !TickPatch.Skipping) { return; } string text = $"{TickPatch.skippingTextKey.Translate()}{MpUtil.FixedEllipsis()}"; float textWidth = Text.CalcSize(text).x; float windowWidth = Math.Max(240f, textWidth + 40f); float windowHeight = TickPatch.cancelSkip != null ? 100f : 75f; Rect rect = new Rect(0, 0, windowWidth, windowHeight).CenterOn(new Rect(0, 0, UI.screenWidth, UI.screenHeight)); if (TickPatch.canESCSkip && Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.Escape) { TickPatch.ClearSkipping(); Event.current.Use(); } Find.WindowStack.ImmediateWindow(SkippingWindowId, rect, WindowLayer.Super, () => { var textRect = rect.AtZero(); if (TickPatch.cancelSkip != null) { textRect.yMin += 5f; textRect.height -= 50f; } Text.Anchor = TextAnchor.MiddleCenter; Text.Font = GameFont.Small; Widgets.Label(textRect, text); Text.Anchor = TextAnchor.UpperLeft; if (TickPatch.cancelSkip != null && Widgets.ButtonText(new Rect(0, textRect.yMax, 100f, 35f).CenteredOnXIn(textRect), TickPatch.skipCancelButtonKey.Translate())) { TickPatch.cancelSkip(); } }, absorbInputAroundWindow: true); }
public void HandleWorldData(ByteReader data) { connection.State = ConnectionStateEnum.ClientPlaying; Log.Message("Game data size: " + data.Length); int factionId = data.ReadInt32(); Multiplayer.session.myFactionId = factionId; int tickUntil = data.ReadInt32(); var dataSnapshot = new GameDataSnapshot(); byte[] worldData = GZipStream.UncompressBuffer(data.ReadPrefixedBytes()); dataSnapshot.gameData = worldData; byte[] semiPersistentData = GZipStream.UncompressBuffer(data.ReadPrefixedBytes()); dataSnapshot.semiPersistentData = semiPersistentData; List <int> mapsToLoad = new List <int>(); int mapCmdsCount = data.ReadInt32(); for (int i = 0; i < mapCmdsCount; i++) { int mapId = data.ReadInt32(); int mapCmdsLen = data.ReadInt32(); List <ScheduledCommand> mapCmds = new List <ScheduledCommand>(mapCmdsLen); for (int j = 0; j < mapCmdsLen; j++) { mapCmds.Add(ScheduledCommand.Deserialize(new ByteReader(data.ReadPrefixedBytes()))); } dataSnapshot.mapCmds[mapId] = mapCmds; } int mapDataCount = data.ReadInt32(); for (int i = 0; i < mapDataCount; i++) { int mapId = data.ReadInt32(); byte[] rawMapData = data.ReadPrefixedBytes(); byte[] mapData = GZipStream.UncompressBuffer(rawMapData); dataSnapshot.mapData[mapId] = mapData; mapsToLoad.Add(mapId); } Session.dataSnapshot = dataSnapshot; Multiplayer.session.localCmdId = data.ReadInt32(); TickPatch.shouldPause = data.ReadBool(); TickPatch.tickUntil = tickUntil; TickPatch.SetSimulation( toTickUntil: true, onFinish: () => Multiplayer.Client.Send(Packets.Client_WorldReady), cancelButtonKey: "Quit", onCancel: GenScene.GoToMainMenu // Calls StopMultiplayer through a patch ); ReloadGame(mapsToLoad, true, false); }
static void DrawTimelineWindow() { if (Multiplayer.Client == null) { return; } Rect rect = new Rect(0, 30f, UI.screenWidth - TimelineMargin * 2, TimelineHeight); Widgets.DrawBoxSolid(rect, new Color(0.6f, 0.6f, 0.6f, 0.8f)); int timerStart = Multiplayer.session.replayTimerStart >= 0 ? Multiplayer.session.replayTimerStart : OnMainThread.cachedAtTime; int timerEnd = Multiplayer.session.replayTimerEnd >= 0 ? Multiplayer.session.replayTimerEnd : TickPatch.tickUntil; int timeLen = timerEnd - timerStart; Widgets.DrawLine(new Vector2(rect.xMin + 2f, rect.yMin), new Vector2(rect.xMin + 2f, rect.yMax), Color.white, 4f); Widgets.DrawLine(new Vector2(rect.xMax - 2f, rect.yMin), new Vector2(rect.xMax - 2f, rect.yMax), Color.white, 4f); float progress = (TickPatch.Timer - timerStart) / (float)timeLen; float progressX = rect.xMin + progress * rect.width; Widgets.DrawLine(new Vector2(progressX, rect.yMin), new Vector2(progressX, rect.yMax), Color.green, 7f); float mouseX = Event.current.mousePosition.x; ReplayEvent mouseEvent = null; foreach (var ev in Multiplayer.session.events) { if (ev.time < timerStart || ev.time > timerEnd) { continue; } var pointX = rect.xMin + (ev.time - timerStart) / (float)timeLen * rect.width; //GUI.DrawTexture(new Rect(pointX - 12f, rect.yMin - 24f, 24f, 24f), texture); Widgets.DrawLine(new Vector2(pointX, rect.yMin), new Vector2(pointX, rect.yMax), ev.color, 5f); if (Mouse.IsOver(rect) && Math.Abs(mouseX - pointX) < 10) { mouseX = pointX; mouseEvent = ev; } } if (Mouse.IsOver(rect)) { float mouseProgress = (mouseX - rect.xMin) / rect.width; int mouseTimer = timerStart + (int)(timeLen * mouseProgress); Widgets.DrawLine(new Vector2(mouseX, rect.yMin), new Vector2(mouseX, rect.yMax), Color.blue, 3f); if (Event.current.type == EventType.MouseUp) { TickPatch.SkipTo(mouseTimer, canESC: true); if (mouseTimer < TickPatch.Timer) { ClientJoiningState.ReloadGame(OnMainThread.cachedMapData.Keys.ToList(), false); } } if (Event.current.isMouse) { Event.current.Use(); } string tooltip = $"Tick {mouseTimer}"; if (mouseEvent != null) { tooltip = $"{mouseEvent.name}\n{tooltip}"; } TooltipHandler.TipRegion(rect, new TipSignal(tooltip, 215462143)); // No delay between the mouseover and showing if (TooltipHandler.activeTips.TryGetValue(215462143, out ActiveTip tip)) { tip.firstTriggerTime = 0; } } if (TickPatch.Skipping) { float pct = (TickPatch.skipTo - timerStart) / (float)timeLen; float skipToX = rect.xMin + rect.width * pct; Widgets.DrawLine(new Vector2(skipToX, rect.yMin), new Vector2(skipToX, rect.yMax), Color.yellow, 4f); } }
static void DoButtons() { float y = 10f; const float btnHeight = 27f; const float btnWidth = 80f; float x = UI.screenWidth - btnWidth - 10f; var session = Multiplayer.session; if (session != null && !Multiplayer.IsReplay) { var btnRect = new Rect(x, y, btnWidth, btnHeight); var chatColor = session.players.Any(p => p.status == PlayerStatus.Desynced) ? "#ff5555" : "#dddddd"; var hasUnread = session.hasUnread ? "*" : ""; var chatLabel = $"{"MpChatButton".Translate()} <color={chatColor}>({session.players.Count})</color>{hasUnread}"; if (Widgets.ButtonText(btnRect, chatLabel)) { OpenChat(); } if (!TickPatch.Skipping) { IndicatorInfo(out Color color, out string text, out bool slow); var indRect = new Rect(btnRect.x - 25f - 5f + 6f / 2f, btnRect.y + 6f / 2f, 19f, 19f); var biggerRect = new Rect(btnRect.x - 25f - 5f + 2f / 2f, btnRect.y + 2f / 2f, 23f, 23f); if (slow && Widgets.ButtonInvisible(biggerRect)) { TickPatch.SkipTo(toTickUntil: true, canESC: true); } Widgets.DrawRectFast(biggerRect, new Color(color.r * 0.6f, color.g * 0.6f, color.b * 0.6f)); Widgets.DrawRectFast(indRect, color); TooltipHandler.TipRegion(indRect, new TipSignal(text, 31641624)); } y += btnHeight; } if (Multiplayer.ShowDevInfo && Multiplayer.PacketLog != null) { if (Widgets.ButtonText(new Rect(x, y, btnWidth, btnHeight), $"Sync ({Multiplayer.PacketLog.nodes.Count})")) { Find.WindowStack.Add(Multiplayer.PacketLog); } y += btnHeight; } if (Multiplayer.Client != null && Multiplayer.WorldComp.trading.Any()) { if (Widgets.ButtonText(new Rect(x, y, btnWidth, btnHeight), "MpTradingButton".Translate())) { Find.WindowStack.Add(new TradingWindow()); } y += btnHeight; } if (Multiplayer.Client != null && Multiplayer.WorldComp.debugMode) { Text.Font = GameFont.Tiny; Text.Anchor = TextAnchor.MiddleCenter; Widgets.Label(new Rect(x, y, btnWidth, 30f), $"Debug mode"); Text.Anchor = TextAnchor.UpperLeft; Text.Font = GameFont.Small; } }
static void DrawTimelineWindow() { Rect rect = new Rect(0, 30f, UI.screenWidth - TimelineMargin * 2, TimelineHeight); Widgets.DrawBoxSolid(rect, new Color(0.6f, 0.6f, 0.6f, 0.8f)); int timerStart = Multiplayer.session.replayTimerStart >= 0 ? Multiplayer.session.replayTimerStart : Multiplayer.session.dataSnapshot.cachedAtTime; int timerEnd = Multiplayer.session.replayTimerEnd >= 0 ? Multiplayer.session.replayTimerEnd : TickPatch.tickUntil; int timeLen = timerEnd - timerStart; MpUI.DrawRotatedLine(new Vector2(rect.xMin + 2f, rect.center.y), TimelineHeight, 20f, 90f, Color.white); MpUI.DrawRotatedLine(new Vector2(rect.xMax - 2f, rect.center.y), TimelineHeight, 20f, 90f, Color.white); float progress = (TickPatch.Timer - timerStart) / (float)timeLen; float progressX = rect.xMin + progress * rect.width; MpUI.DrawRotatedLine(new Vector2((int)progressX, rect.center.y), TimelineHeight, 20f, 90f, Color.green); float mouseX = Event.current.mousePosition.x; ReplayEvent mouseEvent = null; foreach (var ev in Multiplayer.session.events) { if (ev.time < timerStart || ev.time > timerEnd) { continue; } var pointX = rect.xMin + (ev.time - timerStart) / (float)timeLen * rect.width; //GUI.DrawTexture(new Rect(pointX - 12f, rect.yMin - 24f, 24f, 24f), texture); MpUI.DrawRotatedLine(new Vector2(pointX, rect.center.y), TimelineHeight, 20f, 90f, ev.color); if (Mouse.IsOver(rect) && Math.Abs(mouseX - pointX) < 10) { mouseX = pointX; mouseEvent = ev; } } if (Mouse.IsOver(rect)) { float mouseProgress = (mouseX - rect.xMin) / rect.width; int mouseTimer = timerStart + (int)(timeLen * mouseProgress); MpUI.DrawRotatedLine(new Vector2(mouseX, rect.center.y), TimelineHeight, 15f, 90f, Color.blue); if (Event.current.type == EventType.MouseUp) { TickPatch.SetSimulation(mouseTimer, canESC: true); if (mouseTimer < TickPatch.Timer) { ClientJoiningState.ReloadGame(Multiplayer.session.dataSnapshot.mapData.Keys.ToList(), false, Multiplayer.GameComp.asyncTime); } } if (Event.current.isMouse) { Event.current.Use(); } string tooltip = $"Tick {mouseTimer}"; if (mouseEvent != null) { tooltip = $"{mouseEvent.name}\n{tooltip}"; } const int TickTipId = 215462143; TooltipHandler.TipRegion(rect, new TipSignal(tooltip, TickTipId)); // Remove delay between the mouseover and showing if (TooltipHandler.activeTips.TryGetValue(TickTipId, out ActiveTip tip)) { tip.firstTriggerTime = 0; } } if (TickPatch.Simulating) { float pct = (TickPatch.simulating.target.Value - timerStart) / (float)timeLen; float simulateToX = rect.xMin + rect.width * pct; MpUI.DrawRotatedLine(new Vector2(simulateToX, rect.center.y), TimelineHeight, 15f, 90f, Color.yellow); } }
static void DoButtons() { float x = UI.screenWidth - btnWidth - btnMargin; float y = btnMargin; var session = Multiplayer.session; if (session != null && !Multiplayer.IsReplay) { var btnRect = new Rect(x, y, btnWidth, btnHeight); var chatColor = session.players.Any(p => p.status == PlayerStatus.Desynced) ? "#ff5555" : "#dddddd"; var hasUnread = session.hasUnread ? "*" : ""; var chatLabel = $"{"MpChatButton".Translate()} <color={chatColor}>({session.players.Count})</color>{hasUnread}"; TooltipHandler.TipRegion(btnRect, "MpChatHotkeyInfo".Translate() + " " + MultiplayerStatic.ToggleChatDef.MainKeyLabel); if (Widgets.ButtonText(btnRect, chatLabel)) { ChatWindow.OpenChat(); } if (!TickPatch.Simulating) { IndicatorInfo(out Color color, out string text, out bool slow); var indRect = new Rect(btnRect.x - 25f - 5f + 6f / 2f, btnRect.y + 6f / 2f, 19f, 19f); var biggerRect = new Rect(btnRect.x - 25f - 5f + 2f / 2f, btnRect.y + 2f / 2f, 23f, 23f); if (slow && Widgets.ButtonInvisible(biggerRect)) { TickPatch.SetSimulation(toTickUntil: true, canESC: true); } Widgets.DrawRectFast(biggerRect, new Color(color.r * 0.6f, color.g * 0.6f, color.b * 0.6f)); Widgets.DrawRectFast(indRect, color); TooltipHandler.TipRegion(indRect, new TipSignal(text, 31641624)); } y += btnHeight; } if (Multiplayer.ShowDevInfo && Multiplayer.WriterLog != null) { if (Widgets.ButtonText(new Rect(x, y, btnWidth, btnHeight), $"Write ({Multiplayer.WriterLog.NodeCount})")) { Find.WindowStack.Add(Multiplayer.WriterLog); } y += btnHeight; if (Widgets.ButtonText(new Rect(x, y, btnWidth, btnHeight), $"Read ({Multiplayer.ReaderLog.NodeCount})")) { Find.WindowStack.Add(Multiplayer.ReaderLog); } y += btnHeight; } if (Multiplayer.Client != null && Multiplayer.GameComp.debugMode) { Text.Font = GameFont.Tiny; Text.Anchor = TextAnchor.MiddleCenter; Widgets.Label(new Rect(x, y, btnWidth, 30f), $"Debug mode"); Text.Anchor = TextAnchor.UpperLeft; Text.Font = GameFont.Small; } }