From 55cfc64d4fca66fc64a4ab8e0327d033e28dab83 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Mon, 18 Apr 2022 00:54:47 +0200 Subject: [PATCH 01/17] Fixes for Player list ping updates: * only show ping for player's own channel in channel view * no need to rebuild much of the list, since split lists will not be affected * fix minor oversight in that toggling list splitting on/off in settings didn't force rebuild list --- .../CelesteNetPlayerListComponent.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs index 322ff3e6..da283db1 100644 --- a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs @@ -91,6 +91,7 @@ private bool SplitViewPartially { } } private bool SplitSuccessfully = false; + private bool LastAllowSplit; public enum ListModes { Channels, @@ -157,6 +158,9 @@ public void RebuildListClassic(ref List list, ref DataPlayerInfo[] all) { if (Client.Data.TryGetBoundRef(player, out DataPlayerState state)) GetState(blob, state); + if (ShowPing && Client.Data.TryGetBoundRef(player, out DataConnectionInfo conInfo)) + blob.PingMs = conInfo.UDPPingMs ?? conInfo.TCPPingMs; + list.Add(blob); } @@ -386,6 +390,9 @@ private DataPlayerInfo ListPlayerUnderChannel(BlobPlayer blob, DataPlayerInfo pl if (locationMode != LocationModes.OFF && Client.Data.TryGetBoundRef(player, out DataPlayerState state)) GetState(blob, state); + if (ShowPing && locationMode != LocationModes.OFF && Client.Data.TryGetBoundRef(player, out DataConnectionInfo conInfo)) + blob.PingMs = conInfo.UDPPingMs ?? conInfo.TCPPingMs; + return player; } else { @@ -477,30 +484,21 @@ public void Handle(CelesteNetConnection con, DataConnectionInfo info) { if (playerBlob == null) return; + DataChannelList.Channel own = Channels.List.FirstOrDefault(c => c.Players.Contains(Client.PlayerInfo.ID)); + if (ListMode == ListModes.Channels && !own.Players.Contains(info.Player.ID)) + return; + + PrepareRenderLayout(out float scale, out float y, out Vector2 sizeAll, out float spaceWidth, out float locationSeparatorWidth, out float idleIconWidth); + // Update the player's ping playerBlob.PingMs = info.UDPPingMs ?? info.TCPPingMs; // Regenerate the player blob playerBlob.Generate(); - // Re-measure the list - // This doesn't handle line splitting/etc, but is good enough - if (!SpaceWidth.HasValue || !LocationSeparatorWidth.HasValue || !IdleIconWidth.HasValue) { - // This should never happen, as the list has already been rendered at least once - // Still check just in case - Logger.Log(LogLevel.WRN, "playerlist", "!!!DEAD CODE REACHED!!! Player list layout values still uninitalized in ping update code!"); - return; - } + Vector2 size = playerBlob.Measure(spaceWidth, locationSeparatorWidth, idleIconWidth); - Vector2 sizeAll = Vector2.Zero; - foreach (Blob blob in List) { - Vector2 size = blob.Measure(SpaceWidth.Value, LocationSeparatorWidth.Value, IdleIconWidth.Value); - sizeAll.X = Math.Max(sizeAll.X, size.X); - sizeAll.Y += size.Y + 10f * Scale; - } - SizeAll = sizeAll; - SizeUpper = sizeAll; - SizeColumn = Vector2.Zero; + SizeAll.X = Math.Max(size.X, SizeAll.X); }); } @@ -522,10 +520,12 @@ public override void Update(GameTime gameTime) { if (LastListMode != ListMode || LastLocationMode != LocationMode || LastShowPing != ShowPing || + LastAllowSplit != Settings.PlayerListAllowSplit || ShouldRebuild) { LastListMode = ListMode; LastLocationMode = LocationMode; LastShowPing = ShowPing; + LastAllowSplit = Settings.PlayerListAllowSplit; ShouldRebuild = false; RebuildList(); } From b2e9467f1df673614015850a86d6a6a42019864e Mon Sep 17 00:00:00 2001 From: RedFlames Date: Mon, 18 Apr 2022 01:22:01 +0200 Subject: [PATCH 02/17] Change 'Spoofed' placeholder to '???ms' on invalid ping data. --- CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs index da283db1..ed07533d 100644 --- a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs @@ -694,10 +694,10 @@ protected override void Generate(StringBuilder sb) { if (PingMs.HasValue) { int ping = PingMs.Value; - if (0 < ping) + if (ping > 0) PingBlob.Name = $"{ping}ms"; else - PingBlob.Name = "SPOOFED!"; // Someone messed with the packets + PingBlob.Name = "???ms"; // Someone messed with the packets, or server has no data yet } else PingBlob.Name = string.Empty; From 9404249db7f253f6d70bca47074dbcfa27a341e7 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Tue, 19 Apr 2022 21:45:58 +0200 Subject: [PATCH 03/17] Use an expression-body property for Settings.PlayerListAllowSplit like with the other settings. --- .../Components/CelesteNetPlayerListComponent.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs index ed07533d..53e4d01a 100644 --- a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs @@ -67,6 +67,9 @@ public class CelesteNetPlayerListComponent : CelesteNetGameComponent { public bool ShowPing => Settings.PlayerListShowPing; private bool LastShowPing; + public bool AllowSplit => Settings.PlayerListAllowSplit; + private bool LastAllowSplit; + private float? SpaceWidth; private float? LocationSeparatorWidth; private float? IdleIconWidth; @@ -79,7 +82,7 @@ public class CelesteNetPlayerListComponent : CelesteNetGameComponent { private bool _splitViewPartially = false; private bool SplitViewPartially { get { - if (ListMode != ListModes.Channels || !Settings.PlayerListAllowSplit) + if (ListMode != ListModes.Channels || !AllowSplit) return _splitViewPartially = false; // only flip value after passing threshold to prevent flipping on +1/-1s at threshold if (!_splitViewPartially && SplittablePlayerCount > SplitThresholdUpper) @@ -91,7 +94,6 @@ private bool SplitViewPartially { } } private bool SplitSuccessfully = false; - private bool LastAllowSplit; public enum ListModes { Channels, @@ -520,12 +522,12 @@ public override void Update(GameTime gameTime) { if (LastListMode != ListMode || LastLocationMode != LocationMode || LastShowPing != ShowPing || - LastAllowSplit != Settings.PlayerListAllowSplit || + LastAllowSplit != AllowSplit || ShouldRebuild) { LastListMode = ListMode; LastLocationMode = LocationMode; LastShowPing = ShowPing; - LastAllowSplit = Settings.PlayerListAllowSplit; + LastAllowSplit = AllowSplit; ShouldRebuild = false; RebuildList(); } From 3f10a194327ff1c78ec3e7fc7159ae6c562a1cbf Mon Sep 17 00:00:00 2001 From: Popax21 Date: Tue, 24 May 2022 20:13:49 +0200 Subject: [PATCH 04/17] Fix ghost life cycle edge cases --- .../Components/CelesteNetMainComponent.cs | 95 ++++++++----------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetMainComponent.cs b/CelesteNet.Client/Components/CelesteNetMainComponent.cs index f58480cb..df4f0628 100644 --- a/CelesteNet.Client/Components/CelesteNetMainComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetMainComponent.cs @@ -128,15 +128,7 @@ protected override void Dispose(bool disposing) { } public void Cleanup() { - Player = null; - PlayerBody = null; - Session = null; - WasIdle = false; - WasInteractive = false; - - foreach (Ghost ghost in Ghosts.Values) - ghost?.RemoveSelf(); - Ghosts.Clear(); + ResetState(); if (IsGrabbed && Player.StateMachine.State == Player.StFrozen) Player.StateMachine.State = Player.StNormal; @@ -168,8 +160,7 @@ public void Handle(CelesteNetConnection con, DataPlayerInfo player) { return; if (string.IsNullOrEmpty(player.DisplayName)) { - ghost.RunOnUpdate(ghost => ghost.NameTag.Name = ""); - Ghosts.TryRemove(player.ID, out _); + RemoveGhost(player); LastFrames.TryRemove(player.ID, out _); Client.Data.FreeOrder(player.ID); return; @@ -193,13 +184,9 @@ public void Handle(CelesteNetConnection con, DataChannelMove move) { } } else { - if (!Ghosts.TryGetValue(move.Player.ID, out Ghost ghost) || - ghost == null) + if (!RemoveGhost(move.Player)) return; - ghost.RunOnUpdate(ghost => ghost.NameTag.Name = ""); - Ghosts.TryRemove(move.Player.ID, out _); - foreach (DataType data in Client.Data.GetBoundRefs(move.Player)) if (data.TryGet(Client.Data, out MetaPlayerPrivateState state)) Client.Data.FreeBoundRef(data); @@ -221,8 +208,7 @@ public void Handle(CelesteNetConnection con, DataPlayerState state) { Session session = Session; if (session != null && (state.SID != session.Area.SID || state.Mode != session.Area.Mode || state.Level == LevelDebugMap)) { - ghost.RunOnUpdate(ghost => ghost.NameTag.Name = ""); - Ghosts.TryRemove(id, out _); + RemoveGhost(state.Player); // If we get here, id must belong to a valid ghost, so it can't be uint.MaxValue and state.Player mustn't be null return; } @@ -597,9 +583,11 @@ protected Ghost CreateGhost(Level level, DataPlayerInfo player, DataPlayerGraphi return ghost; } - protected void RemoveGhost(DataPlayerInfo info) { - Ghosts.TryRemove(info.ID, out Ghost ghost); + protected bool RemoveGhost(DataPlayerInfo player) { + if (!Ghosts.TryRemove(player.ID, out Ghost ghost)) + return false; ghost?.RunOnUpdate(g => g.NameTag.Name = ""); + return true; } public void UpdateIdleTag(Entity target, ref GhostEmote idleTag, bool idle) { @@ -670,25 +658,18 @@ public override void Update(GameTime gameTime) { GrabbedBy = null; if (ready && Engine.Scene is MapEditor) { - Player = null; - PlayerBody = null; - Session = null; - WasIdle = false; - WasInteractive = false; + ResetState(); AreaKey area = (AreaKey) f_MapEditor_area.GetValue(null); if (MapEditorArea == null || MapEditorArea.Value.SID != area.SID || MapEditorArea.Value.Mode != area.Mode) { MapEditorArea = area; + // FIXME: NOTE BEFORE MERGING: can we move the ResetState() call here, which would be more inline with the below if? SendState(); } } if (Player != null && MapEditorArea == null) { - Player = null; - PlayerBody = null; - Session = null; - WasIdle = false; - WasInteractive = false; + ResetState(); SendState(); } return; @@ -725,12 +706,9 @@ public override void Update(GameTime gameTime) { } if (Player == null || Player.Scene != level) { - Player = level.Tracker.GetEntity(); - if (Player != null) { - PlayerBody = Player; - Session = level.Session; - WasIdle = false; - WasInteractive = false; + Player player = level.Tracker.GetEntity(); + if (player != null) { + ResetState(player, level.Session); StateUpdated |= true; SendGraphics(); } @@ -797,17 +775,14 @@ public void OnSetActualDepth(On.Monocle.Scene.orig_SetActualDepth orig, Scene sc public void OnLoadLevel(On.Celeste.Level.orig_LoadLevel orig, Level level, Player.IntroTypes playerIntro, bool isFromLoader = false) { orig(level, playerIntro, isFromLoader); - Session = level.Session; - WasIdle = false; - WasInteractive = false; + Player player = null; + if (Client != null) + player = level.Tracker.GetEntity(); - if (Client == null) - return; + ResetState(player, level.Session); - Player = level.Tracker.GetEntity(); - PlayerBody = Player; - - SendState(); + if (Client != null) + SendState(); } public void OnExitLevel(Level level, LevelExit exit, LevelExit.Mode mode, Session session, HiresSnow snow) { @@ -832,17 +807,15 @@ private Player OnLoadNewPlayer(On.Celeste.Level.orig_LoadNewPlayer orig, Vector2 private void OnPlayerAdded(On.Celeste.Player.orig_Added orig, Player self, Scene scene) { orig(self, scene); - Session = (scene as Level)?.Session; - WasIdle = false; - WasInteractive = false; - Player = self; - PlayerBody = self; - + ResetState(self, (scene as Level)?.Session); SendState(); SendGraphics(); - foreach (DataPlayerFrame frame in LastFrames.Values.ToArray()) - Handle(null, frame); + // We can't directly handle the frames here, as then ghost creation logic could fail if we're currently loading a level in OnEndOfFrame + scene.OnEndOfFrame += () => { + foreach (DataPlayerFrame frame in LastFrames.Values.ToArray()) + Handle(null, frame); + }; } private PlayerDeadBody OnPlayerDie(On.Celeste.Player.orig_Die orig, Player self, Vector2 direction, bool evenIfInvincible, bool registerDeathInStats) { @@ -908,6 +881,22 @@ private void ILTransitionRoutine(ILContext il) { #endregion + public void ResetState(Player player = null, Session ses = null) { + // Clear ghosts if the scene changed + if (player?.Scene != Player?.Scene) { + lock (Ghosts) { + foreach (Ghost ghost in Ghosts.Values) + ghost?.RemoveSelf(); + Ghosts.Clear(); + } + } + + Player = player; + PlayerBody = player; + Session = ses; + WasIdle = false; + WasInteractive = false; + } #region Send From b04ac86d0d6cc4655597a703de7dbd54bbc8d93a Mon Sep 17 00:00:00 2001 From: RedFlames Date: Tue, 7 Jun 2022 16:47:13 +0200 Subject: [PATCH 05/17] Check for (BlurRT != null) in CelesteNetRenderHelperComponent.cs --- .../Components/CelesteNetRenderHelperComponent.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetRenderHelperComponent.cs b/CelesteNet.Client/Components/CelesteNetRenderHelperComponent.cs index a96ab2fa..cb9cd758 100644 --- a/CelesteNet.Client/Components/CelesteNetRenderHelperComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetRenderHelperComponent.cs @@ -56,11 +56,15 @@ public void Rect(float x, float y, float width, float height, Color color) { Rectangle rect = new(xi, yi, wi, hi); - MDraw.SpriteBatch.Draw( - BlurRT, - rect, rect, - Color.White * Math.Min(1f, color.A / 255f * 2f) - ); + if (BlurRT != null) { + MDraw.SpriteBatch.Draw( + BlurRT, + rect, rect, + Color.White * Math.Min(1f, color.A / 255f * 2f) + ); + } else { + Logger.LogDetailed("cnet-rndrhlp", "BlurRT is null!"); + } MDraw.Rect(xi, yi, wi, hi, color); } From ea9d15d0264ed458bd8abceede6c69e6a6629455 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Tue, 7 Jun 2022 16:48:56 +0200 Subject: [PATCH 06/17] This List?.First(...) should've been a FirstOrDefault() since it should return null and not throw anything when there's no elements. --- CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs index 322ff3e6..3264c77e 100644 --- a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs @@ -473,7 +473,7 @@ public void Handle(CelesteNetConnection con, DataConnectionInfo info) { // Don't rebuild the entire list // Try to find the player's blob - BlobPlayer playerBlob = (BlobPlayer) List?.First(b => b is BlobPlayer pb && pb.Player == info.Player); + BlobPlayer playerBlob = (BlobPlayer) List?.FirstOrDefault(b => b is BlobPlayer pb && pb.Player == info.Player); if (playerBlob == null) return; From 8adda05395ce00414b8cdd7d113d6c3213b80c3b Mon Sep 17 00:00:00 2001 From: RedFlames Date: Tue, 7 Jun 2022 16:50:04 +0200 Subject: [PATCH 07/17] Replace the Fonts.Load() with a Fonts.Get() in CelesteNetClientFontMono.cs to avoid calling Emoji.Fill() whenever the font is used e.g. for rendering. --- CelesteNet.Client/CelesteNetClientFontMono.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CelesteNet.Client/CelesteNetClientFontMono.cs b/CelesteNet.Client/CelesteNetClientFontMono.cs index 86e142b1..e7ce678a 100644 --- a/CelesteNet.Client/CelesteNetClientFontMono.cs +++ b/CelesteNet.Client/CelesteNetClientFontMono.cs @@ -23,7 +23,7 @@ namespace Celeste.Mod.CelesteNet.Client { public static class CelesteNetClientFontMono { // English is always loaded. Other language fonts must be loaded manually. Load only loads once. - public static PixelFont Font => Fonts.Load(Dialog.Languages["japanese"].FontFace); + public static PixelFont Font => Fonts.Get(Dialog.Languages["japanese"].FontFace); public static PixelFontSize FontSize => Font.Get(BaseSize); From 924fe3a3076129beec4046acea0ec438821da5b7 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Tue, 7 Jun 2022 17:20:54 +0200 Subject: [PATCH 08/17] Didn't realize one call to Load will be needed. No idea why I thought taking it out worked; turns out it didn't. --- CelesteNet.Client/CelesteNetClientFontMono.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CelesteNet.Client/CelesteNetClientFontMono.cs b/CelesteNet.Client/CelesteNetClientFontMono.cs index e7ce678a..14d2c39a 100644 --- a/CelesteNet.Client/CelesteNetClientFontMono.cs +++ b/CelesteNet.Client/CelesteNetClientFontMono.cs @@ -22,8 +22,10 @@ namespace Celeste.Mod.CelesteNet.Client { // Copy of ActiveFont that always uses a font with monospace Latin characters / Arabic numbers. public static class CelesteNetClientFontMono { - // English is always loaded. Other language fonts must be loaded manually. Load only loads once. - public static PixelFont Font => Fonts.Get(Dialog.Languages["japanese"].FontFace); + private static string FontFace => Dialog.Languages["japanese"].FontFace; + + // English is always loaded. Other language fonts must be loaded manually. Only load once. + public static PixelFont Font => Fonts.Get(FontFace) ?? Fonts.Load(FontFace); public static PixelFontSize FontSize => Font.Get(BaseSize); From b458760f7bd1afcd739302e7fa0f42fc51a63695 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Sat, 11 Jun 2022 01:24:30 +0200 Subject: [PATCH 09/17] Fix non-interactive players always responding with 'release me' packets when receiving any grab packet. --- CelesteNet.Client/Components/CelesteNetMainComponent.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CelesteNet.Client/Components/CelesteNetMainComponent.cs b/CelesteNet.Client/Components/CelesteNetMainComponent.cs index f58480cb..f4f31fda 100644 --- a/CelesteNet.Client/Components/CelesteNetMainComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetMainComponent.cs @@ -219,6 +219,9 @@ public void Handle(CelesteNetConnection con, DataPlayerState state) { ghost == null) return; + if (Settings.Interactions != state.Interactive && ghost == GrabbedBy) + SendReleaseMe(); + Session session = Session; if (session != null && (state.SID != session.Area.SID || state.Mode != session.Area.Mode || state.Level == LevelDebugMap)) { ghost.RunOnUpdate(ghost => ghost.NameTag.Name = ""); @@ -447,9 +450,12 @@ public void Handle(CelesteNetConnection con, DataMoveTo target) { public void Handle(CelesteNetConnection con, DataPlayerGrabPlayer grab) { Player player = Player; - if (Engine.Scene is not Level level || level.Paused || player == null || !Settings.Interactions) + if (player != null && !Settings.Interactions && (grab.Player.ID == Client.PlayerInfo.ID || grab.Grabbing.ID == Client.PlayerInfo.ID)) goto Release; + if (Engine.Scene is not Level level || level.Paused || player == null || !Settings.Interactions) + return; + if (grab.Player.ID != Client.PlayerInfo.ID && grab.Grabbing.ID == Client.PlayerInfo.ID) { if (GrabCooldown > 0f) { GrabCooldown = GrabCooldownMax; From f80b8f0186eb17e46594388f72d6d477515a1040 Mon Sep 17 00:00:00 2001 From: Popax21 Date: Tue, 28 Jun 2022 21:18:26 +0200 Subject: [PATCH 10/17] Fix GravityHelper issue --- CelesteNet.Client/Entities/GhostNameTag.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CelesteNet.Client/Entities/GhostNameTag.cs b/CelesteNet.Client/Entities/GhostNameTag.cs index 857f43ce..25e2dd70 100644 --- a/CelesteNet.Client/Entities/GhostNameTag.cs +++ b/CelesteNet.Client/Entities/GhostNameTag.cs @@ -45,7 +45,7 @@ public override void Render() { float scale = level.GetScreenScale(); - Vector2 pos = Tracking?.Position ?? Position; + Vector2 pos = Tracking?.BottomCenter ?? Position; pos.Y -= 16f; pos = level.WorldToScreen(pos); From 7e6b7405083e7dfe1bf0d6fcd22ccb82de87309e Mon Sep 17 00:00:00 2001 From: RedFlames Date: Fri, 26 Aug 2022 22:02:30 +0200 Subject: [PATCH 11/17] Prevent chat from opening if keyboard is used for text entry dialogs, like OuiFileNaming and OuiModOptionString. --- CelesteNet.Client/Components/CelesteNetChatComponent.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 04829a22..5e3885de 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -180,9 +180,12 @@ public override void Update(GameTime gameTime) { _Time += Engine.RawDeltaTime; _TimeSinceCursorMove += Engine.RawDeltaTime; + Overworld overworld = Engine.Scene as Overworld; bool isRebinding = Engine.Scene == null || Engine.Scene.Entities.FindFirst() != null || - Engine.Scene.Entities.FindFirst() != null; + Engine.Scene.Entities.FindFirst() != null || + ((overworld?.Current ?? overworld?.Next) is OuiFileNaming naming && naming.UseKeyboardInput) || + ((overworld?.Current ?? overworld?.Next) is UI.OuiModOptionString stringInput && stringInput.UseKeyboardInput); if (!(Engine.Scene?.Paused ?? true) || isRebinding) { string typing = Typing; From 26c344c3898e420c64ac9eabeab0535d03587f41 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Tue, 30 Aug 2022 01:14:22 +0200 Subject: [PATCH 12/17] TODO: not detecting end of EH cutscene properly yet. --- CelesteNet.Client/Components/CelesteNetEmoteComponent.cs | 5 +++-- CelesteNet.Client/Components/CelesteNetMainComponent.cs | 1 + CelesteNet.Client/Entities/GhostEmoteWheel.cs | 9 +++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetEmoteComponent.cs b/CelesteNet.Client/Components/CelesteNetEmoteComponent.cs index aa486d2a..dda80159 100644 --- a/CelesteNet.Client/Components/CelesteNetEmoteComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetEmoteComponent.cs @@ -103,7 +103,8 @@ public override void Update(GameTime gameTime) { if (Wheel == null) level.Add(Wheel = new(Player)); - if (!level.Paused && Settings.EmoteWheel && !Player.Dead) { + // TimeRate check is for Prologue Dash prompt freeze + if (!level.Paused && Settings.EmoteWheel && !Player.Dead && Engine.TimeRate > 0.05f) { Wheel.Shown = CelesteNetClientModule.Instance.JoystickEmoteWheel.Value.LengthSquared() >= 0.36f; int selected = Wheel.Selected; if (Wheel.Shown && selected != -1 && CelesteNetClientModule.Instance.ButtonEmoteSend.Pressed) { @@ -148,7 +149,7 @@ public override void Update(GameTime gameTime) { private void OnHeartGemCollect(On.Celeste.HeartGem.orig_Collect orig, HeartGem self, Player player) { orig(self, player); - Wheel?.TimeRateSkip.Add("HeartGem"); + Wheel?.TimeRateSkip.Add(self.IsFake ? "EmptySpaceHeart" : "HeartGem"); } private void OnHeartGemEndCutscene(On.Celeste.HeartGem.orig_EndCutscene orig, HeartGem self) { diff --git a/CelesteNet.Client/Components/CelesteNetMainComponent.cs b/CelesteNet.Client/Components/CelesteNetMainComponent.cs index f58480cb..a5ad95f7 100644 --- a/CelesteNet.Client/Components/CelesteNetMainComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetMainComponent.cs @@ -774,6 +774,7 @@ public override void Update(GameTime gameTime) { level.Add(PlayerNameTag = new(Player, Client.PlayerInfo.DisplayName)); } PlayerNameTag.Alpha = Settings.ShowOwnName ? 1f : 0f; + PlayerNameTag.Name = $"{Engine.TimeRate} {level.Frozen} {level.FrozenOrPaused} {(level.Tracker == null ? "no tr" : Context?.Get() is CelesteNetEmoteComponent e ? e.Wheel?.TimeRateSkip.FirstOrDefault() : "no eco")} {level.InCutscene} {Player.StateMachine.State}"; } public override void Tick() { diff --git a/CelesteNet.Client/Entities/GhostEmoteWheel.cs b/CelesteNet.Client/Entities/GhostEmoteWheel.cs index d285c1a1..3e5be9ad 100644 --- a/CelesteNet.Client/Entities/GhostEmoteWheel.cs +++ b/CelesteNet.Client/Entities/GhostEmoteWheel.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using Celeste.Mod.CelesteNet.Client.Components; +using Microsoft.Xna.Framework; using Monocle; using System; using System.Collections.Generic; @@ -48,7 +49,11 @@ public GhostEmoteWheel(Entity tracking) public override void Update() { // Update only runs while the level is "alive" (scene not paused or frozen). - if (TimeRateSkip.Count == 0 || ForceSetTimeRate) { + if (TimeRateSkip.Contains("EmptySpaceHeart") && Tracking?.Scene.Tracker.GetEntity() is Player p && p.StateMachine.State != Player.StDummy) + TimeRateSkip.Remove("EmptySpaceHeart"); + + // TimeRate check is for Prologue Dash prompt freeze + if (Engine.TimeRate > 0.05f && (TimeRateSkip.Count == 0 || ForceSetTimeRate)) { if (Shown && !timeRateSet) { Engine.TimeRate = 0.25f; timeRateSet = true; From d2a809633fbc13c83d6ddd2586e47fa2fb7b7fc1 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Sun, 4 Sep 2022 21:07:13 +0200 Subject: [PATCH 13/17] Introduced Delay before removing EmptySpaceHeart from TimeRateSkip since there's at least a few frames between the Gem triggering and the cutscene starting. Without the delay, the Wheel's Update would remove EmptySpaceHeart right away. --- .../Components/CelesteNetEmoteComponent.cs | 2 ++ .../Components/CelesteNetMainComponent.cs | 1 - CelesteNet.Client/Entities/GhostEmoteWheel.cs | 16 ++++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetEmoteComponent.cs b/CelesteNet.Client/Components/CelesteNetEmoteComponent.cs index dda80159..f32e7c2e 100644 --- a/CelesteNet.Client/Components/CelesteNetEmoteComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetEmoteComponent.cs @@ -150,6 +150,8 @@ public override void Update(GameTime gameTime) { private void OnHeartGemCollect(On.Celeste.HeartGem.orig_Collect orig, HeartGem self, Player player) { orig(self, player); Wheel?.TimeRateSkip.Add(self.IsFake ? "EmptySpaceHeart" : "HeartGem"); + if (self.IsFake && Wheel != null) + Wheel.timeSkipForcedDelay = 10f; } private void OnHeartGemEndCutscene(On.Celeste.HeartGem.orig_EndCutscene orig, HeartGem self) { diff --git a/CelesteNet.Client/Components/CelesteNetMainComponent.cs b/CelesteNet.Client/Components/CelesteNetMainComponent.cs index a5ad95f7..f58480cb 100644 --- a/CelesteNet.Client/Components/CelesteNetMainComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetMainComponent.cs @@ -774,7 +774,6 @@ public override void Update(GameTime gameTime) { level.Add(PlayerNameTag = new(Player, Client.PlayerInfo.DisplayName)); } PlayerNameTag.Alpha = Settings.ShowOwnName ? 1f : 0f; - PlayerNameTag.Name = $"{Engine.TimeRate} {level.Frozen} {level.FrozenOrPaused} {(level.Tracker == null ? "no tr" : Context?.Get() is CelesteNetEmoteComponent e ? e.Wheel?.TimeRateSkip.FirstOrDefault() : "no eco")} {level.InCutscene} {Player.StateMachine.State}"; } public override void Tick() { diff --git a/CelesteNet.Client/Entities/GhostEmoteWheel.cs b/CelesteNet.Client/Entities/GhostEmoteWheel.cs index 3e5be9ad..f02eb548 100644 --- a/CelesteNet.Client/Entities/GhostEmoteWheel.cs +++ b/CelesteNet.Client/Entities/GhostEmoteWheel.cs @@ -1,11 +1,7 @@ -using Celeste.Mod.CelesteNet.Client.Components; -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using Monocle; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Celeste.Mod.CelesteNet.Client.Entities { // TODO: This is taken mostly as is from GhostNet and can be improved. @@ -23,6 +19,7 @@ public class GhostEmoteWheel : Entity { protected bool timeRateSet = false; public HashSet TimeRateSkip = new(); + public float timeSkipForcedDelay = -1f; public bool ForceSetTimeRate; public float Angle = 0f; @@ -49,8 +46,15 @@ public GhostEmoteWheel(Entity tracking) public override void Update() { // Update only runs while the level is "alive" (scene not paused or frozen). - if (TimeRateSkip.Contains("EmptySpaceHeart") && Tracking?.Scene.Tracker.GetEntity() is Player p && p.StateMachine.State != Player.StDummy) + if (TimeRateSkip.Contains("EmptySpaceHeart") && + timeSkipForcedDelay <= 0f && + Engine.Scene is Level l && !l.InCutscene) { TimeRateSkip.Remove("EmptySpaceHeart"); + } + + if (timeSkipForcedDelay >= 0f) { + timeSkipForcedDelay -= Engine.RawDeltaTime; + } // TimeRate check is for Prologue Dash prompt freeze if (Engine.TimeRate > 0.05f && (TimeRateSkip.Count == 0 || ForceSetTimeRate)) { From abeaaf5a5e7ae26c04d0b65c44d882a7a619f4f7 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Thu, 15 Sep 2022 20:26:57 +0200 Subject: [PATCH 14/17] Prevent chat component NREs when Client is null --- CelesteNet.Client/Components/CelesteNetChatComponent.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 04829a22..e3aa8cef 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -142,6 +142,9 @@ public void Send(string text) { } public void Handle(CelesteNetConnection con, DataChat msg) { + if (Client == null) + return; + lock (Log) { if (msg.Player?.ID == Client.PlayerInfo?.ID) { foreach (DataChat pending in Pending.Values) { @@ -177,6 +180,11 @@ public void Handle(CelesteNetConnection con, DataChat msg) { public override void Update(GameTime gameTime) { base.Update(gameTime); + if (Client == null) { + Active = false; + return; + } + _Time += Engine.RawDeltaTime; _TimeSinceCursorMove += Engine.RawDeltaTime; From 6867414c2003d115704a603d1e2f492a7da2a28a Mon Sep 17 00:00:00 2001 From: RedFlames Date: Thu, 22 Sep 2022 14:28:07 +0200 Subject: [PATCH 15/17] Resend graphics when hair length change detected, and stop hair sim on idle (pause) --- .../Components/CelesteNetMainComponent.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CelesteNet.Client/Components/CelesteNetMainComponent.cs b/CelesteNet.Client/Components/CelesteNetMainComponent.cs index f58480cb..524e9927 100644 --- a/CelesteNet.Client/Components/CelesteNetMainComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetMainComponent.cs @@ -38,6 +38,7 @@ public class CelesteNetMainComponent : CelesteNetGameComponent { private AreaKey? MapEditorArea; private bool WasIdle; private bool WasInteractive; + private int SentHairLength = 0; public HashSet ForceIdle = new(); public bool StateUpdated; @@ -268,6 +269,7 @@ public void Handle(CelesteNetConnection con, DataPlayerFrame frame) { if (ghost == null) { if (!Client.Data.TryGetBoundRef(frame.Player, out DataPlayerGraphics graphics) || graphics == null) return; + Logger.Log("HAIR", $"Creating ghost for {frame.Player.Name}: {graphics.HairCount}"); ghost = CreateGhost(level, frame.Player, graphics); } @@ -278,7 +280,7 @@ public void Handle(CelesteNetConnection con, DataPlayerFrame frame) { UpdateIdleTag(ghost, ref ghost.IdleTag, state.Idle); ghost.UpdateGeneric(frame.Position, frame.Scale, frame.Color, frame.Facing, frame.Speed); ghost.UpdateAnimation(frame.CurrentAnimationID, frame.CurrentAnimationFrame); - ghost.UpdateHair(frame.Facing, frame.HairColors, frame.HairTexture0, frame.HairSimulateMotion); + ghost.UpdateHair(frame.Facing, frame.HairColors, frame.HairTexture0, frame.HairSimulateMotion && !state.Idle); ghost.UpdateDash(frame.DashWasB, frame.DashDir); // TODO: Get rid of this, sync particles separately! ghost.UpdateDead(frame.Dead && state.Level == session.Level); ghost.UpdateFollowers((Settings.Entities & CelesteNetClientSettings.SyncMode.Receive) == 0 ? Dummy.EmptyArray : frame.Followers); @@ -736,6 +738,12 @@ public override void Update(GameTime gameTime) { } } + if (Player != null && Player.Sprite != null && SentHairLength != Player.Sprite.HairCount) { + Logger.Log("HAIR", $"Resending graphics because: {SentHairLength} != {Player.Sprite.HairCount}"); + SendGraphics(); + } + + bool idle = level.FrozenOrPaused || level.Overlay != null; if (WasIdle != idle) { WasIdle = idle; @@ -774,6 +782,8 @@ public override void Update(GameTime gameTime) { level.Add(PlayerNameTag = new(Player, Client.PlayerInfo.DisplayName)); } PlayerNameTag.Alpha = Settings.ShowOwnName ? 1f : 0f; + if (Player.Hair != null) + PlayerNameTag.Name = $"{Player.Hair.SimulateMotion} {Player.Hair.Active} {Player.Sprite.Active} {Player.Active}"; } public override void Tick() { @@ -966,6 +976,7 @@ public void SendGraphics() { HairScales = hairScales, HairTextures = hairTextures }); + SentHairLength = hairCount; } catch (Exception e) { Logger.Log(LogLevel.INF, "client-main", $"Error in SendGraphics:\n{e}"); Context.DisposeSafe(); From aef046e642ebc330a129874d9550e52f864982ff Mon Sep 17 00:00:00 2001 From: RedFlames Date: Thu, 22 Sep 2022 14:39:39 +0200 Subject: [PATCH 16/17] Remove hair debug stuff --- CelesteNet.Client/Components/CelesteNetMainComponent.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetMainComponent.cs b/CelesteNet.Client/Components/CelesteNetMainComponent.cs index 524e9927..eef00435 100644 --- a/CelesteNet.Client/Components/CelesteNetMainComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetMainComponent.cs @@ -269,7 +269,6 @@ public void Handle(CelesteNetConnection con, DataPlayerFrame frame) { if (ghost == null) { if (!Client.Data.TryGetBoundRef(frame.Player, out DataPlayerGraphics graphics) || graphics == null) return; - Logger.Log("HAIR", $"Creating ghost for {frame.Player.Name}: {graphics.HairCount}"); ghost = CreateGhost(level, frame.Player, graphics); } @@ -738,11 +737,8 @@ public override void Update(GameTime gameTime) { } } - if (Player != null && Player.Sprite != null && SentHairLength != Player.Sprite.HairCount) { - Logger.Log("HAIR", $"Resending graphics because: {SentHairLength} != {Player.Sprite.HairCount}"); + if (Player != null && Player.Sprite != null && SentHairLength != Player.Sprite.HairCount) SendGraphics(); - } - bool idle = level.FrozenOrPaused || level.Overlay != null; if (WasIdle != idle) { @@ -782,8 +778,6 @@ public override void Update(GameTime gameTime) { level.Add(PlayerNameTag = new(Player, Client.PlayerInfo.DisplayName)); } PlayerNameTag.Alpha = Settings.ShowOwnName ? 1f : 0f; - if (Player.Hair != null) - PlayerNameTag.Name = $"{Player.Hair.SimulateMotion} {Player.Hair.Active} {Player.Sprite.Active} {Player.Active}"; } public override void Tick() { From 3eda670950b2945ea39ee8663e7a75f2683a0ded Mon Sep 17 00:00:00 2001 From: RedFlames Date: Fri, 30 Sep 2022 22:14:09 +0200 Subject: [PATCH 17/17] PR #29 suggested fixes --- CelesteNet.Client/Components/CelesteNetMainComponent.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetMainComponent.cs b/CelesteNet.Client/Components/CelesteNetMainComponent.cs index df4f0628..1d6cf5bb 100644 --- a/CelesteNet.Client/Components/CelesteNetMainComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetMainComponent.cs @@ -575,7 +575,7 @@ protected Ghost CreateGhost(Level level, DataPlayerInfo player, DataPlayerGraphi UnsupportedSpriteModes.Add(graphics.SpriteMode); RunOnMainThread(() => { level.Add(ghost); - level.OnEndOfFrame += () => ghost.Active = true; + //level.OnEndOfFrame += () => ghost.Active = true; ghost.UpdateGraphics(graphics); }); ghost.UpdateGraphics(graphics); @@ -675,6 +675,10 @@ public override void Update(GameTime gameTime) { return; } + foreach (Ghost g in Ghosts.Values) + if (g != null) + g.Active = true; + bool grabReleased = false; grabReleased |= IsGrabbed && (GrabTimeout += Engine.RawDeltaTime) >= GrabTimeoutMax; grabReleased |= GrabbedBy != null && GrabbedBy.Scene != level; @@ -883,7 +887,7 @@ private void ILTransitionRoutine(ILContext il) { public void ResetState(Player player = null, Session ses = null) { // Clear ghosts if the scene changed - if (player?.Scene != Player?.Scene) { + if (player != null && player.Scene != Player?.Scene) { lock (Ghosts) { foreach (Ghost ghost in Ghosts.Values) ghost?.RemoveSelf();