support reskinning body colors

This commit is contained in:
micycle 2023-05-21 00:40:34 -04:00 committed by yosh
parent 584ca0a0fc
commit 91bbbb4cb4
6 changed files with 280 additions and 62 deletions

View File

@ -19,6 +19,10 @@ AVALI_SKIN_DASH3=Three Dashes
AVALI_SKIN_DASH4=Four Dashes
AVALI_SKIN_DASH5=Five+ Dashes
AVALI_SKIN_BODY=Body colors
AVALI_SKIN_LIGHTBODY=Light
AVALI_SKIN_DARKBODY=Dark
AVALI_SKIN_RED=Red
AVALI_SKIN_GREEN=Green
AVALI_SKIN_BLUE=Blue

View File

@ -9,27 +9,87 @@
#define SAMPLE_TEXTURE(Name, texCoord) tex2D(Name##Sampler, texCoord)
DECLARE_TEXTURE(text, 0); // The texture to be recolored
// https://web.archive.org/web/20200207113336/http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
float4 rgb2hsv(float4 c) {
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g));
float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r));
uniform float4 color_replace_from; // #1ad589
uniform float4 color_replace_to;
uniform float threshold; // 0.01
// hsls provides distance instead of this...
// float fast_distance4(float4 a, float4 b) {
// float4 diff = a - b;
// return diff.x * diff.x + diff.y * diff.y + diff.z * diff.z + diff.w * diff.w;
// }
float4 ps_recolor(float4 pos: SV_Position, float4 in_color: COLOR0, float2 uv: TEXCOORD0): COLOR {
float4 color = SAMPLE_TEXTURE(text, uv);
return (distance(color, color_replace_from) < threshold ? color_replace_to : color) * in_color;
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return float4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, c.a);
}
float4 hsv2rgb(float4 c) {
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
float3 h = c.z * lerp(K.xxx, saturate(p - K.xxx), c.y);
return float4(h.rgb, c.a);
}
DECLARE_TEXTURE(sprite, 0); // The sprite texture
uniform float recolor1_threshold;
uniform float4 recolor1_rgb_from;
uniform float4 recolor1_rgb_to;
uniform float rehue1_threshold;
uniform float4 rehue1_threshold_mul;
uniform float4 rehue1_rgb_from;
uniform float4 rehue1_rgb_to;
static const float rehue1_threshold_norm = rehue1_threshold * length(rehue1_threshold_mul);
static const float4 rehue1_hsv_from = rgb2hsv(rehue1_rgb_from);
static const float4 rehue1_hsv_to = rgb2hsv(rehue1_rgb_to);
uniform float rehue2_threshold;
uniform float4 rehue2_threshold_mul;
uniform float4 rehue2_rgb_from;
uniform float4 rehue2_rgb_to;
static const float rehue2_threshold_norm = rehue2_threshold * length(rehue2_threshold_mul);
static const float4 rehue2_hsv_from = rgb2hsv(rehue2_rgb_from);
static const float4 rehue2_hsv_to = rgb2hsv(rehue2_rgb_to);
float4 hueshift(
float4 hsv_color, float4 hsv_from, float4 hsv_to
) {
float4 hsv = hsv_to - hsv_from + hsv_color;
float4 hsv_clamp = float4((hsv.r + 1.0) % 1.0, saturate(hsv.gba));
return hsv2rgb(hsv_clamp);
}
float4 ps_main(float4 pos: SV_Position, float4 sprite_color: COLOR0, float2 uv: TEXCOORD0): COLOR {
float4 tex_rgb = SAMPLE_TEXTURE(sprite, uv);
float4 tex_hsv = rgb2hsv(tex_rgb);
// recolor the sprite color if it is normal
if (distance(sprite_color, recolor1_rgb_from) < recolor1_threshold) {
sprite_color = recolor1_rgb_to;
}
// replace recolor1_rgb_from with recolor1_rgb_to if in threshold
if (distance(tex_rgb, recolor1_rgb_from) < recolor1_threshold) {
return recolor1_rgb_to * sprite_color;
}
// hue-shift tex_hsv by the difference between rehue1_hsv_to - rehue1_hsv_from,
// only if it is in threshold scaled by the threshold multipler
if (distance(tex_hsv * rehue1_threshold_mul, rehue1_hsv_from * rehue1_threshold_mul) < rehue1_threshold_norm) {
return hueshift(tex_hsv, rehue1_hsv_from, rehue1_hsv_to) * sprite_color;
}
if (distance(tex_hsv * rehue2_threshold_mul, rehue2_hsv_from * rehue2_threshold_mul) < rehue2_threshold_norm) {
return hueshift(tex_hsv, rehue2_hsv_from, rehue2_hsv_to) * sprite_color;
}
return tex_rgb * sprite_color;
}
technique Recolor {
pass {
PixelShader = compile ps_2_0 ps_recolor();
PixelShader = compile ps_2_0 ps_main();
}
}

View File

@ -29,9 +29,19 @@ namespace Celeste.Mod.AvaliSkin {
Enabled = Settings.Enabled,
ColorMode = Settings.ColorModeOpt,
ManualPreset = Settings.DashPreset,
ManualRGB = Settings.DashRGBColor
ManualRGB = Settings.DashRGBColor,
LightBody = Settings.ColorModeOpt == ColorMode.ManualPreset ? ColorUtil.SettingToColor(Settings.LightBodyPreset) : Settings.LightBodyRGBColor,
DarkBody = Settings.ColorModeOpt == ColorMode.ManualPreset ? ColorUtil.SettingToColor(Settings.DarkBodyPreset) : Settings.DarkBodyRGBColor,
};
}
public static AvaliConfig EveryoneHasSkinConfig {
get => new AvaliConfig {
Enabled = true,
ColorMode = ColorMode.ExternalDash,
LightBody = PlayerConfig.LightBody,
DarkBody = PlayerConfig.DarkBody,
}
};
private static Effect FxRecolor;
public static EverestModuleMetadata CelesteNetMeta = new EverestModuleMetadata() {
@ -53,7 +63,11 @@ namespace Celeste.Mod.AvaliSkin {
On.Celeste.PlayerSprite.Render += onPlayerSpriteRenderCelestenet;
On.Celeste.PlayerSprite.Render += onPlayerSpriteRenderCelestenetMisc;
} else {
On.Celeste.PlayerSprite.Render += onPlayerSpriteRenderMisc;
using (new DetourContext("AvaliSkinModule") {
After = { "Hyperline" }
}) {
On.Celeste.PlayerSprite.Render += onPlayerSpriteRenderMisc;
}
}
using (new DetourContext("AvaliSkinModule") {
@ -150,29 +164,60 @@ namespace Celeste.Mod.AvaliSkin {
// CreateOn doesn't preserve some of the properties that we need to keep here!
// Copy them over manually instead...
Vector2 pos = sprite.RenderPosition;
Vector2 pos = sprite.Position;
Color color = sprite.Color;
string currentAnimationID = sprite.CurrentAnimationID;
int CurrentAnimationFrame = sprite.CurrentAnimationFrame;
int currentAnimationFrame = sprite.CurrentAnimationFrame;
GFX.SpriteBank.CreateOn(sprite, spriteID);
sprite.RenderPosition = pos;
sprite.Position = pos;
sprite.SetColor(color);
if (currentAnimationID != "") {
sprite.Play(currentAnimationID);
sprite.SetAnimationFrame(CurrentAnimationFrame);
sprite.SetAnimationFrame(currentAnimationFrame);
}
}
}
private void spriteRecolor(Color dashColor) {
private void spriteRecolor(Color dashColor, Color? lightColor = null, Color? darkColor = null) {
Color lightColor2 = lightColor != null
? (Color) lightColor : ColorUtil.HexToColor("a2885c");
Color darkColor2 = darkColor != null
? (Color) darkColor : ColorUtil.HexToColor("4e4e4e");
// apply the recolor effect to the player:
// replace the color #1ad589 in the sprite with color
FxRecolor.Parameters["threshold"].SetValue(0.03f);
FxRecolor.Parameters["color_replace_from"].SetValue(
FxRecolor.Parameters["recolor1_threshold"].SetValue(0.03f);
FxRecolor.Parameters["recolor1_rgb_from"].SetValue(
(new Color((byte) 0x1a, 0xd5, 0x89, 0xff)).ToVector4()
);
FxRecolor.Parameters["color_replace_to"].SetValue(dashColor.ToVector4());
FxRecolor.Parameters["recolor1_rgb_to"].SetValue(dashColor.ToVector4());
// works at 0.12 minus the dark brown
FxRecolor.Parameters["rehue1_threshold"].SetValue(0.03f);
FxRecolor.Parameters["rehue1_threshold_mul"].SetValue(
new Vector4(1f, 0.1f, 0.1f, 0f)
);
FxRecolor.Parameters["rehue1_rgb_from"].SetValue(
(new Color((byte) 0xa2, 0x88, 0x5c, 0xff)).ToVector4() // #a2885c
);
FxRecolor.Parameters["rehue1_rgb_to"].SetValue(
lightColor2.ToVector4()
); // #433722
FxRecolor.Parameters["rehue2_threshold"].SetValue(0.07f);
FxRecolor.Parameters["rehue2_threshold_mul"].SetValue(
new Vector4(1f, .8f, .8f, 0f)
);
FxRecolor.Parameters["rehue2_rgb_from"].SetValue(
(new Color((byte) 0x4e, 0x4e, 0x4e, 0xff)).ToVector4() // #4e4e4e
);
FxRecolor.Parameters["rehue2_rgb_to"].SetValue(
darkColor2.ToVector4()
);
FxRecolor.CurrentTechnique = FxRecolor.Techniques["Recolor"];
}
@ -200,7 +245,7 @@ namespace Celeste.Mod.AvaliSkin {
Color color = PlayerConfig.GetColor(self);
// apply the recolor effect to the player
spriteRecolor(color);
spriteRecolor(color, PlayerConfig.LightBody, PlayerConfig.DarkBody);
Draw.SpriteBatch.End();
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, FxRecolor, (self.Scene as Level).GameplayRenderer.Camera.Matrix);
@ -245,7 +290,7 @@ namespace Celeste.Mod.AvaliSkin {
}
// apply the recolor effect to the sprite
spriteRecolor(color);
spriteRecolor(color, PlayerConfig.LightBody, PlayerConfig.DarkBody);
Draw.SpriteBatch.End();
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, FxRecolor, (self.Scene as Level).GameplayRenderer.Camera.Matrix);
@ -287,9 +332,8 @@ namespace Celeste.Mod.AvaliSkin {
// Ghosts have their own custom hair and sprite which we are able to recolor like the player.
// We need to be really paranoid here cuz celestenet jank...
CelesteNetClient client = CelesteNetClientModule.Instance.Client;
AvaliConfig everyoneHasSkin = new AvaliConfig { Enabled = true, ColorMode = ColorMode.ExternalDash};
Color color;
Ghost ghost;
AvaliConfig config;
if (
self.Scene != null && self.Entity != null
&& self.Entity is Ghost ghost2 && ghost2.PlayerInfo != null
@ -301,15 +345,15 @@ namespace Celeste.Mod.AvaliSkin {
&& data.Config.IsEnabled(ghost2)
) {
ghost = ghost2;
color = data.Config.GetColor(ghost);
config = data.Config;
} else if (
Settings.CelesteNetEveryoneHasSkin
&& self.Scene != null && self.Entity != null
&& self.Entity is Ghost ghost3
&& everyoneHasSkin.IsEnabled(ghost3)
&& EveryoneHasSkinConfig.IsEnabled(ghost3)
) {
ghost = ghost3;
color = everyoneHasSkin.GetColor(ghost);
config = EveryoneHasSkinConfig;
} else if (
self.Scene != null && self.Entity != null
&& self.Entity is Ghost _
@ -326,7 +370,7 @@ namespace Celeste.Mod.AvaliSkin {
trySpriteSwap(self, true);
// apply the recolor effect to the ghost
spriteRecolor(color);
spriteRecolor(config.GetColor(ghost), config.LightBody, config.DarkBody);
Draw.SpriteBatch.End();
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, FxRecolor, (self.Scene as Level).GameplayRenderer.Camera.Matrix);
@ -348,10 +392,13 @@ namespace Celeste.Mod.AvaliSkin {
if (sprite.Scene != null && PlayerConfig.IsEnabled(player)) {
// swap out the body's spritebank if the enabled state changed
trySpriteSwap(sprite, true);
Color color = PlayerConfig.GetColor(player);
// apply the recolor effect to the body
spriteRecolor(color);
spriteRecolor(
PlayerConfig.GetColor(player),
PlayerConfig.LightBody,
PlayerConfig.DarkBody
);
Draw.SpriteBatch.End();
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, FxRecolor, (self.Scene as Level).GameplayRenderer.Camera.Matrix);
@ -390,7 +437,6 @@ namespace Celeste.Mod.AvaliSkin {
private Color onPlayerGetTrailColor(On.Celeste.Player.orig_GetCurrentTrailColor orig, Player self) {
Color orig_color = orig(self);
if (PlayerConfig.IsEnabled(self)) {
// Don't change the trail color if another mod is in control of it!
// The hair mod should do be doing that instead of us.
@ -403,7 +449,7 @@ namespace Celeste.Mod.AvaliSkin {
}
// skin disabled, keep original colors
return orig_color;
return orig(self);
}
@ -445,8 +491,8 @@ namespace Celeste.Mod.AvaliSkin {
Color = color,
Color2 = color,
};
Player.P_DashB = Player.P_DashA;
Player.P_DashBadB = Player.P_DashA;
Player.P_DashB = new ParticleType(Player.P_DashA);
Player.P_DashBadB = new ParticleType(Player.P_DashA);
// run vanilla code: if it emits particles, it will use recolored ones.
int result = orig(self);
@ -459,6 +505,7 @@ namespace Celeste.Mod.AvaliSkin {
return result;
}
// todo: apply shader to these entities
private void onPayphoneConstructor(On.Celeste.Payphone.orig_ctor orig, Payphone self, Vector2 pos) {
orig(self, pos);

View File

@ -37,12 +37,12 @@ namespace Celeste.Mod.AvaliSkin {
ColorSubmenuItem.Visible = ColorModeOpt != ColorMode.ExternalDash;
// disable and change item visibility based on main toggle or colormode
foreach (var item in DashRGBItems) {
foreach (var item in RGBItems) {
item.Disabled = !Enabled;
item.Visible = ColorModeOpt == ColorMode.ManualRGB;
}
foreach (var item in DashPresetItems) {
foreach (var item in PresetItems) {
item.Disabled = !Enabled;
item.Visible = ColorModeOpt == ColorMode.ManualPreset;
}
@ -125,25 +125,24 @@ namespace Celeste.Mod.AvaliSkin {
ColorModeOptNote = (TextMenu.SubHeader) items[items.IndexOf(ColorModeOptItem) + 1];
}
#region AvaliColorSettings
// ColorSubmenu is a dummy setting that is only used to position the
// Submenu. This setting is never used, hence why it is Void!
[YamlIgnore]
public Void ColorSubmenu { get; set; }
private TextMenuExt.OptionSubMenu ColorSubmenuItem;
private TextMenuExt.OptionSubMenu ColorSubmenuItem, BodyColorSubmenuItem;
public void CreateColorSubmenuEntry(TextMenu menu, bool inGame) {
DashRGBItems.Clear();
DashPresetItems.Clear();
RGBItems.Clear();
PresetItems.Clear();
ColorSubmenuItem = new TextMenuExt.OptionSubMenu(
"AVALI_SKIN_COLORS".DialogOrKey()
);
// This generates n submenus, one for each dash
TextMenuExt.IntSlider DashRItem, DashGItem, DashBItem;
TextMenu.Option<ColorChoice> DashColorItem;
for (int i = 0; i < DashRGBColor.Count; i++) {
TextMenuExt.IntSlider DashRItem, DashGItem, DashBItem;
TextMenu.Option<ColorChoice> DashColorItem;
int j = i; // C# lambda are wierd: capturing i directly mutates the captured variable
@ -156,19 +155,19 @@ namespace Celeste.Mod.AvaliSkin {
).Change(
// C# is stupidly pendatic and doesn't support property assignment in List elements
// so we have to do this ugly shit to avoid breaking up this expression into two
c => DashRGBColor[j] = new Color((byte) c, DashRGBColor[j].B, DashRGBColor[j].G)
c => DashRGBColor[j] = new Color((byte) c, DashRGBColor[j].G, DashRGBColor[j].B)
)),
(DashGItem = new TextMenuExt.IntSlider(
"AVALI_SKIN_BLUE".DialogOrKey(),
"AVALI_SKIN_GREEN".DialogOrKey(),
0, 255, DashRGBColor[j].G
).Change(
c => DashRGBColor[j] = new Color(DashRGBColor[j].R, (byte) c, DashRGBColor[j].G)
c => DashRGBColor[j] = new Color(DashRGBColor[j].R, (byte) c, DashRGBColor[j].B)
)),
(DashBItem = new TextMenuExt.IntSlider(
"AVALI_SKIN_GREEN".DialogOrKey(),
"AVALI_SKIN_BLUE".DialogOrKey(),
0, 255, DashRGBColor[j].B
).Change(
c => DashRGBColor[j] = new Color(DashRGBColor[j].R, DashRGBColor[j].B, (byte) c)
c => DashRGBColor[j] = new Color(DashRGBColor[j].R, DashRGBColor[j].G, (byte) c)
)),
(DashColorItem = new TextMenuExt.EnumSlider<ColorChoice>(
"AVALI_SKIN_COLOR".DialogOrKey(),
@ -177,13 +176,84 @@ namespace Celeste.Mod.AvaliSkin {
}
);
DashRGBItems.Add(DashRItem);
DashRGBItems.Add(DashGItem);
DashRGBItems.Add(DashBItem);
DashPresetItems.Add(DashColorItem);
RGBItems.Add(DashRItem);
RGBItems.Add(DashGItem);
RGBItems.Add(DashBItem);
PresetItems.Add(DashColorItem);
}
menu.Add(ColorSubmenuItem);
BodyColorSubmenuItem = new TextMenuExt.OptionSubMenu(
"AVALI_SKIN_BODY".DialogOrKey()
);
BodyColorSubmenuItem.Add(
"AVALI_SKIN_LIGHTBODY".DialogOrKey(),
new List<TextMenu.Item> {
(DashRItem = new TextMenuExt.IntSlider(
"AVALI_SKIN_RED".DialogOrKey(),
0, 255, LightBodyRGBColor.R
).Change(
c => LightBodyRGBColor = new Color((byte) c, LightBodyRGBColor.G, LightBodyRGBColor.B)
)),
(DashGItem = new TextMenuExt.IntSlider(
"AVALI_SKIN_GREEN".DialogOrKey(),
0, 255, LightBodyRGBColor.G
).Change(
c => LightBodyRGBColor = new Color(LightBodyRGBColor.R, (byte) c, LightBodyRGBColor.B)
)),
(DashBItem = new TextMenuExt.IntSlider(
"AVALI_SKIN_BLUE".DialogOrKey(),
0, 255, LightBodyRGBColor.B
).Change(
c => LightBodyRGBColor = new Color(LightBodyRGBColor.R, LightBodyRGBColor.G, (byte) c)
)),
(DashColorItem = new TextMenuExt.EnumSlider<ColorChoice>(
"AVALI_SKIN_COLOR".DialogOrKey(),
LightBodyPreset
).Change(c => LightBodyPreset = c)),
}
);
RGBItems.Add(DashRItem);
RGBItems.Add(DashGItem);
RGBItems.Add(DashBItem);
PresetItems.Add(DashColorItem);
BodyColorSubmenuItem.Add(
"AVALI_SKIN_DARKBODY".DialogOrKey(),
new List<TextMenu.Item> {
(DashRItem = new TextMenuExt.IntSlider(
"AVALI_SKIN_RED".DialogOrKey(),
0, 255, DarkBodyRGBColor.R
).Change(
c => DarkBodyRGBColor = new Color((byte) c, DarkBodyRGBColor.G, DarkBodyRGBColor.B)
)),
(DashGItem = new TextMenuExt.IntSlider(
"AVALI_SKIN_GREEN".DialogOrKey(),
0, 255, DarkBodyRGBColor.G
).Change(
c => DarkBodyRGBColor = new Color(DarkBodyRGBColor.R, (byte) c, DarkBodyRGBColor.B)
)),
(DashBItem = new TextMenuExt.IntSlider(
"AVALI_SKIN_BLUE".DialogOrKey(),
0, 255, DarkBodyRGBColor.B
).Change(
c => DarkBodyRGBColor = new Color(DarkBodyRGBColor.R, DarkBodyRGBColor.G, (byte) c)
)),
(DashColorItem = new TextMenuExt.EnumSlider<ColorChoice>(
"AVALI_SKIN_COLOR".DialogOrKey(),
DarkBodyPreset
).Change(c => DarkBodyPreset = c)),
}
);
RGBItems.Add(DashRItem);
RGBItems.Add(DashGItem);
RGBItems.Add(DashBItem);
PresetItems.Add(DashColorItem);
menu.Add(BodyColorSubmenuItem);
}
@ -199,6 +269,7 @@ namespace Celeste.Mod.AvaliSkin {
ColorUtil.SettingToColor(ColorChoice.Yellow),
ColorUtil.SettingToColor(ColorChoice.Red),
};
// this will get (de)serialized from/into a yaml list
[SettingIgnore]
public IEnumerable<string> DashRGB {
@ -207,8 +278,30 @@ namespace Celeste.Mod.AvaliSkin {
DashRGBColor = value.Select(c => ColorUtil.HexToColor(c)).ToList();
}
}
[SettingIgnore]
[YamlIgnore]
public Color LightBodyRGBColor { get; set; } = ColorUtil.HexToColor("a2885c");
[SettingIgnore]
public string LightBodyRGB {
get => ColorUtil.ColorToHex(LightBodyRGBColor);
set { LightBodyRGBColor = ColorUtil.HexToColor(value); }
}
[SettingIgnore]
[YamlIgnore]
public Color DarkBodyRGBColor { get; set; } = ColorUtil.HexToColor("4e4e4e");
[SettingIgnore]
public string DarkBodyRGB {
get => ColorUtil.ColorToHex(DarkBodyRGBColor);
set { DarkBodyRGBColor = ColorUtil.HexToColor(value); }
}
// Stores submenu items that are enabled/disabled when colormode is RGB
private List<TextMenuExt.IntSlider> DashRGBItems = new List<TextMenuExt.IntSlider>();
private List<TextMenuExt.IntSlider> RGBItems = new List<TextMenuExt.IntSlider>();
[SettingIgnore]
@ -220,8 +313,15 @@ namespace Celeste.Mod.AvaliSkin {
ColorChoice.Yellow,
ColorChoice.Red,
};
[SettingIgnore]
public ColorChoice LightBodyPreset { get; set; } = ColorChoice.Brown;
[SettingIgnore]
public ColorChoice DarkBodyPreset { get; set; } = ColorChoice.GreyDark;
// Stores submenu items that are enabled/disabled when colormode is preset
private List<TextMenu.Option<ColorChoice>> DashPresetItems = new List<TextMenu.Option<ColorChoice>>();
private List<TextMenu.Option<ColorChoice>> PresetItems = new List<TextMenu.Option<ColorChoice>>();
@ -297,7 +397,6 @@ namespace Celeste.Mod.AvaliSkin {
updateOptions();
}
#endregion
public enum ColorChoice
{

View File

@ -65,13 +65,17 @@ namespace Celeste.Mod.AvaliSkin {
protected override void Read(CelesteNetBinaryReader reader) {
Config = new AvaliConfig {
Enabled = reader.ReadBoolean(),
ColorMode = ColorMode.ExternalDash
ColorMode = ColorMode.ExternalDash,
LightBody = reader.ReadColorNoA(),
DarkBody = reader.ReadColorNoA(),
};
}
protected override void Write(CelesteNetBinaryWriter writer) {
writer.Write(Config.Enabled);
if (Config.Enabled) {
writer.WriteNoA(Config.LightBody);
writer.WriteNoA(Config.DarkBody);
// byte lengthRGB = Math.Min(ManualRGB.Count, 5);
// writer.Write((byte) ManualRGB.Count);
// foreach (var color in ManualRGB) {

View File

@ -19,6 +19,8 @@ namespace Celeste.Mod.AvaliSkin {
public ColorMode ColorMode;
public List<ColorChoice> ManualPreset = new List<ColorChoice>();
public List<Color> ManualRGB = new List<Color>();
public Color LightBody;
public Color DarkBody;
public bool IsEnabled(Player player) {
@ -46,6 +48,8 @@ namespace Celeste.Mod.AvaliSkin {
if (player.Hair != null && player.Sprite.HairCount > 0) {
return player.Hair.GetHairColor(0);
} else {
Logger.Log(LogLevel.Warn, "AvaliSkin", $"Player hair is missing!");
// If the player does have no hair, then just default to
// whatever preset... this only happens momentarily anyways
return ColorUtil.SettingToColor(