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 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);