Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions Module/SkinModHelperCelesteNet.cs
Original file line number Diff line number Diff line change
@@ -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<DataReady>(HandleDataReady);
handleSkinModHelperChange = client.Data.RegisterHandler<SkinModHelperChange>(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<SkinModHelperChange>
{
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<DataPlayerInfo>.DataID, ChangePlayer?.ID ?? uint.MaxValue, true)
};
}

public override void FixupMeta(DataContext ctx)
{
ChangePlayer = Get<MetaPlayerPrivateState>(ctx).Player;
Get<MetaBoundRef>(ctx).ID = ChangePlayer?.ID ?? uint.MaxValue;
}

protected override void Read(CelesteNetBinaryReader reader)
{
SkinID = reader.ReadNetString();
}

protected override void Write(CelesteNetBinaryWriter writer)
{
writer.WriteNetString(SkinID);
}
}
}
93 changes: 85 additions & 8 deletions Module/SkinModHelperModule.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
{
Expand All @@ -33,11 +41,15 @@ public class SkinModHelperModule : EverestModule
"player", "player_no_backpack", "badeline", "player_badeline", "player_playback"
};

public static HashSet<uint> GhostIDRefreshSet;

public SkinModHelperModule()
{
Instance = this;
UI = new SkinModHelperUI();
skinConfigs = new Dictionary<string, SkinModHelperConfig>();

GhostIDRefreshSet = new HashSet<uint>();
}

public override void Load()
Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<string>("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<DataPlayerInfo, SkinModHelperChange>(
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))
{
Expand Down Expand Up @@ -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<Player>();
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);
}
}

Expand Down
6 changes: 6 additions & 0 deletions SkinModHelper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
<Reference Include="MMHOOK_Celeste" HintPath="..\..\MMHOOK_Celeste.dll" />
<Reference Include="YamlDotNet" HintPath="..\..\YamlDotNet.dll" />
<Reference Include="FNA" HintPath="..\..\FNA.dll" />
<Reference Include=".\lib-stripped\CelesteNet.Client.dll">
<Private>False</Private>
</Reference>
<Reference Include=".\lib-stripped\CelesteNet.Shared.dll">
<Private>False</Private>
</Reference>
</ItemGroup>

</Project>
4 changes: 3 additions & 1 deletion everest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
DLL: bin/Debug/SkinModHelper.dll
Dependencies:
- Name: Everest
Version: 1.2742.0
Version: 1.2742.0
- Name: CelesteNet.Client
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely think CN should be an optional dependency, so this will need some logic to only run the CN logic if the dependency is loaded

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I gotta add some more checkboxes to the first post

Version: 2.0.0
Binary file added lib-stripped/CelesteNet.Client.dll
Binary file not shown.
Binary file added lib-stripped/CelesteNet.Shared.dll
Binary file not shown.