diff --git a/Module/SkinModHelperCelesteNet.cs b/Module/SkinModHelperCelesteNet.cs new file mode 100644 index 00000000..ac54c1e5 --- /dev/null +++ b/Module/SkinModHelperCelesteNet.cs @@ -0,0 +1,94 @@ +using Celeste.Mod.CelesteNet; +using Celeste.Mod.CelesteNet.Client; +using Celeste.Mod.CelesteNet.Client.Entities; +using Celeste.Mod.CelesteNet.DataTypes; +using MonoMod.RuntimeDetour; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace SkinModHelper.Module +{ + + public class SkinModHelperCelesteNet + { + public static void HandleDataReady(CelesteNetConnection con, DataReady data) + { + con.Send(new SkinModHelperChange {SkinID = SkinModHelperModule.Settings.SelectedSkinMod}); + } + + private static DataHandler handleDataReady; + + public static void HandleSkinModHelperChange(CelesteNetConnection con, SkinModHelperChange data) + { + SkinModHelperModule.GhostIDRefreshSet.Add(data.ChangePlayer.ID); + } + + private static DataHandler handleSkinModHelperChange; + + public static void Load() + { + CelesteNetClientContext.OnInit += clientContext => + { + CelesteNetClient client = clientContext.Client; + handleDataReady = client.Data.RegisterHandler(HandleDataReady); + handleSkinModHelperChange = client.Data.RegisterHandler(HandleSkinModHelperChange); + }; + } + + public static void Unload() + { + CelesteNetClient client = CelesteNetClientModule.Instance.Client; + if (client != null) + { + if (handleDataReady != null) + { + client.Data.UnregisterHandler(typeof(DataReady), handleDataReady); + handleDataReady = null; + } + + if (handleSkinModHelperChange != null) + { + client.Data.UnregisterHandler(typeof(SkinModHelperChange), handleSkinModHelperChange); + handleSkinModHelperChange = null; + } + } + } + } + + public class SkinModHelperChange : DataType + { + static SkinModHelperChange() + { + DataID = "skinmodhelperChange"; + } + + public DataPlayerInfo ChangePlayer; + public string SkinID; + + public override MetaType[] GenerateMeta(DataContext ctx) + { + return new MetaType[] + { + new MetaPlayerPrivateState(ChangePlayer), + new MetaBoundRef(DataType.DataID, ChangePlayer?.ID ?? uint.MaxValue, true) + }; + } + + public override void FixupMeta(DataContext ctx) + { + ChangePlayer = Get(ctx).Player; + Get(ctx).ID = ChangePlayer?.ID ?? uint.MaxValue; + } + + protected override void Read(CelesteNetBinaryReader reader) + { + SkinID = reader.ReadNetString(); + } + + protected override void Write(CelesteNetBinaryWriter writer) + { + writer.WriteNetString(SkinID); + } + } +} \ No newline at end of file diff --git a/Module/SkinModHelperModule.cs b/Module/SkinModHelperModule.cs index 2786f12b..1b4db4a1 100644 --- a/Module/SkinModHelperModule.cs +++ b/Module/SkinModHelperModule.cs @@ -1,5 +1,9 @@ using Celeste; using Celeste.Mod; +using Celeste.Mod.CelesteNet; +using Celeste.Mod.CelesteNet.Client; +using Celeste.Mod.CelesteNet.Client.Entities; +using Celeste.Mod.CelesteNet.DataTypes; using FMOD.Studio; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -10,8 +14,12 @@ using MonoMod.Utils; using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using Logger = Celeste.Mod.Logger; +using LogLevel = Celeste.Mod.LogLevel; namespace SkinModHelper.Module { @@ -33,11 +41,15 @@ public class SkinModHelperModule : EverestModule "player", "player_no_backpack", "badeline", "player_badeline", "player_playback" }; + public static HashSet GhostIDRefreshSet; + public SkinModHelperModule() { Instance = this; UI = new SkinModHelperUI(); skinConfigs = new Dictionary(); + + GhostIDRefreshSet = new HashSet(); } public override void Load() @@ -55,6 +67,7 @@ public override void Load() On.Celeste.Player.UpdateHair += PlayerUpdateHairHook; On.Celeste.PlayerDeadBody.Render += PlayerDeadBodyRenderHook; On.Celeste.PlayerHair.GetHairTexture += PlayerHairGetHairTextureHook; + On.Celeste.PlayerSprite.Render += PlayerSpriteRenderHook; IL.Celeste.CS06_Campfire.Question.ctor += CampfireQuestionHook; IL.Celeste.DreamBlock.ctor_Vector2_float_float_Nullable1_bool_bool_bool += DreamBlockHook; @@ -64,6 +77,8 @@ public override void Load() TextboxRunRoutineHook = new ILHook( typeof(Textbox).GetMethod("RunRoutine", BindingFlags.NonPublic | BindingFlags.Instance).GetStateMachineTarget(), SwapTextboxHook); + + SkinModHelperCelesteNet.Load(); } public override void LoadContent(bool firstLoad) @@ -95,6 +110,8 @@ public override void Unload() On.Celeste.Player.UpdateHair -= PlayerUpdateHairHook; On.Celeste.PlayerHair.GetHairTexture -= PlayerHairGetHairTextureHook; + + SkinModHelperCelesteNet.Unload(); } public override void CreateModMenuSection(TextMenu menu, bool inGame, EventInstance snapshot) @@ -270,9 +287,70 @@ private void PlayerUpdateHairHook(On.Celeste.Player.orig_UpdateHair orig, Player } } + private void PlayerSpriteRenderHook(On.Celeste.PlayerSprite.orig_Render orig, PlayerSprite self) + { + DynamicData dd = new DynamicData(self); + + if (self.Entity is Ghost g && GhostIDRefreshSet.Contains(g.PlayerInfo.ID)) + { + dd.Set("patchedBySkinModHelper", false); + GhostIDRefreshSet.Remove(g.PlayerInfo.ID); + } + + if (!(dd.TryGet("patchedBySkinModHelper", out object patched) && (bool) patched)) + { + string spriteName = dd.Get("spriteName"); + + if (self.Entity is Ghost ghost) + { + // If the current PlayerSprite belongs to a CelesteNet Ghost, use their selected skin + CelesteNetClient client = CelesteNetClientModule.Instance.Client; + if ( + client.Data.TryGetBoundRef( + ghost.PlayerInfo.ID, + out SkinModHelperChange recentChange + ) && + recentChange != null + ) + { + string suffix = recentChange.SkinID; + ReplacePlayerSprite(self, spriteName, suffix); + } + } + else + { + // Otherwise, we're (most likely) the player, + string suffix = Settings.SelectedSkinMod; + ReplacePlayerSprite(self, spriteName, suffix); + } + + dd.Add("patchedBySkinModHelper", true); + } + orig(self); + } + + private void ReplacePlayerSprite(PlayerSprite self, string spriteName, string suffix) + { + if (suffix != null && suffix != DEFAULT) + { + string newId = spriteName + '_' + suffix; + if (GFX.SpriteBank.SpriteData.ContainsKey(newId)) + GFX.SpriteBank.CreateOn(self, newId); + else + GFX.SpriteBank.CreateOn(self, spriteName); + } + else + { + GFX.SpriteBank.CreateOn(self, spriteName); + } + } + // If our current skinmod has an overridden sprite bank, use that sprite data instead private Sprite SpriteBankCreateOnHook(On.Monocle.SpriteBank.orig_CreateOn orig, SpriteBank self, Sprite sprite, string id) { + if (sprite is PlayerSprite) + return orig(self, sprite, id); // We handle PlayerSprites separately + String newId = id + "_" + Settings.SelectedSkinMod; if (self.SpriteData.ContainsKey(newId)) { @@ -569,19 +647,18 @@ private void PatchSprite(Sprite origSprite, Sprite newSprite) public static void UpdateSkin(string newSkinId) { Settings.SelectedSkinMod = newSkinId; + + CelesteNetClient client = CelesteNetClientModule.Instance.Client; + client?.Con?.Send(new SkinModHelperChange{SkinID = newSkinId}); + UpdateParticles(); Player player = (Engine.Scene)?.Tracker.GetEntity(); if (player != null) { - if (player.Active) - { - player.ResetSpriteNextFrame(player.Sprite.Mode); - } - else - { - player.ResetSprite(player.Sprite.Mode); - } + DynamicData dd = new DynamicData(player.Sprite); + + dd.Add("patchedBySkinModHelper", false); } } diff --git a/SkinModHelper.csproj b/SkinModHelper.csproj index 26f14c7e..f024d865 100644 --- a/SkinModHelper.csproj +++ b/SkinModHelper.csproj @@ -24,6 +24,12 @@ + + False + + + False + \ No newline at end of file diff --git a/everest.yaml b/everest.yaml index 51254a92..1da238fb 100644 --- a/everest.yaml +++ b/everest.yaml @@ -3,4 +3,6 @@ DLL: bin/Debug/SkinModHelper.dll Dependencies: - Name: Everest - Version: 1.2742.0 \ No newline at end of file + Version: 1.2742.0 + - Name: CelesteNet.Client + Version: 2.0.0 diff --git a/lib-stripped/CelesteNet.Client.dll b/lib-stripped/CelesteNet.Client.dll new file mode 100644 index 00000000..797dd78e Binary files /dev/null and b/lib-stripped/CelesteNet.Client.dll differ diff --git a/lib-stripped/CelesteNet.Shared.dll b/lib-stripped/CelesteNet.Shared.dll new file mode 100644 index 00000000..80aa44c5 Binary files /dev/null and b/lib-stripped/CelesteNet.Shared.dll differ