558 lines
24 KiB
C#
558 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using Monocle;
|
|
using MonoMod.Cil;
|
|
using MonoMod.Utils;
|
|
using MonoMod.RuntimeDetour;
|
|
|
|
using Celeste.Mod.CelesteNet.Client;
|
|
using Celeste.Mod.CelesteNet.Client.Entities;
|
|
using Celeste.Mod.CelesteNet.DataTypes;
|
|
|
|
using ColorChoice = Celeste.Mod.AvaliSkin.AvaliSkinSettings.ColorChoice;
|
|
using DashColorMode = Celeste.Mod.AvaliSkin.AvaliSkinSettings.DashColorMode;
|
|
using ColorMode = Celeste.Mod.AvaliSkin.AvaliSkinSettings.ColorMode;
|
|
|
|
|
|
namespace Celeste.Mod.AvaliSkin {
|
|
// This code was built off of max480's code for the Pro Banana Skin.
|
|
// Other mods I referenced include SkinModHelper, Hyperline, Styleline, and Kayden Fox skin.
|
|
public class AvaliSkinModule : EverestModule {
|
|
public static AvaliSkinModule Instance;
|
|
public override Type SettingsType => typeof(AvaliSkinSettings);
|
|
public static AvaliSkinSettings Settings => (AvaliSkinSettings) Instance._Settings;
|
|
|
|
public static AvaliConfig PlayerConfig {
|
|
get => new AvaliConfig {
|
|
Enabled = Settings.Enabled,
|
|
DashColorMode = Settings.DashColorModeOpt,
|
|
DashColors =
|
|
Settings.DashColorModeOpt == DashColorMode.ManualPreset
|
|
? Settings.DashPreset.Select(preset => ColorUtil.SettingToColor(preset)).ToList()
|
|
: Settings.DashRGBColor,
|
|
LightBody = Settings.BodyColorModeOpt == ColorMode.ManualPreset ? ColorUtil.SettingToColor(Settings.LightBodyPreset) : Settings.LightBodyRGBColor,
|
|
DarkBody = Settings.BodyColorModeOpt == ColorMode.ManualPreset ? ColorUtil.SettingToColor(Settings.DarkBodyPreset) : Settings.DarkBodyRGBColor,
|
|
};
|
|
}
|
|
public static AvaliConfig EveryoneHasSkinConfig {
|
|
get => new AvaliConfig {
|
|
Enabled = true,
|
|
DashColorMode = DashColorMode.ExternalDash,
|
|
LightBody = PlayerConfig.LightBody,
|
|
DarkBody = PlayerConfig.DarkBody,
|
|
};
|
|
}
|
|
private static Effect FxRecolor;
|
|
|
|
public static EverestModuleMetadata CelesteNetMeta = new EverestModuleMetadata() {
|
|
Name = "CelesteNet.Client",
|
|
Version = new Version(2, 0, 0)
|
|
};
|
|
|
|
public AvaliSkinModule() => Instance = this;
|
|
|
|
|
|
public override void Load() {
|
|
Logger.Log(LogLevel.Info, "AvaliSkin", $"Hooking stuff...");
|
|
|
|
On.Celeste.LevelLoader.ctor += onLevelLoaderctor;
|
|
On.Celeste.Player.Render += onPlayerRender;
|
|
|
|
if (Everest.Loader.DependencyLoaded(CelesteNetMeta)) {
|
|
Logger.Log(LogLevel.Info, "AvaliSkin", $"Hooking for CelesteNet...");
|
|
On.Celeste.PlayerSprite.Render += onPlayerSpriteRenderCelestenet;
|
|
On.Celeste.PlayerSprite.Render += onPlayerSpriteRenderCelestenetMisc;
|
|
} else {
|
|
using (new DetourContext("AvaliSkinModule") {
|
|
After = { "Hyperline" }
|
|
}) {
|
|
On.Celeste.PlayerSprite.Render += onPlayerSpriteRenderMisc;
|
|
}
|
|
}
|
|
|
|
using (new DetourContext("AvaliSkinModule") {
|
|
After = { "Hyperline" }
|
|
}) {
|
|
On.Celeste.Player.GetCurrentTrailColor += onPlayerGetTrailColor;
|
|
On.Celeste.Player.UpdateHair += onPlayerUpdateHair;
|
|
On.Celeste.Player.DashUpdate += onPlayerDashUpdate;
|
|
}
|
|
|
|
using (new DetourContext("AvaliSkinModule") {
|
|
After = { "FastPlayerDie" }
|
|
}) {
|
|
On.Celeste.PlayerDeadBody.Render += onPlayerDeadBodyRender;
|
|
}
|
|
IL.Celeste.DeathEffect.Draw += DeathEffectDrawHook;
|
|
On.Celeste.Payphone.ctor += onPayphoneConstructor;
|
|
On.Celeste.Lookout.ctor += onLookoutConstructor;
|
|
}
|
|
|
|
|
|
public override void LoadContent(bool firstLoad) {
|
|
base.LoadContent(firstLoad);
|
|
|
|
IGraphicsDeviceService graphicsDeviceService =
|
|
Engine.Instance.Content.ServiceProvider
|
|
.GetService(typeof(IGraphicsDeviceService))
|
|
as IGraphicsDeviceService;
|
|
|
|
ModAsset asset = Everest.Content.Get("Effects/AvaliRecolor.o", true);
|
|
FxRecolor = new Effect(graphicsDeviceService.GraphicsDevice, asset.Data);
|
|
}
|
|
|
|
|
|
public override void Unload() {
|
|
Logger.Log(LogLevel.Info, "AvaliSkin", $"Unhooking stuff...");
|
|
|
|
On.Celeste.LevelLoader.ctor -= onLevelLoaderctor;
|
|
On.Celeste.Player.Render -= onPlayerRender;
|
|
|
|
if (Everest.Loader.DependencyLoaded(CelesteNetMeta)) {
|
|
Logger.Log(LogLevel.Info, "AvaliSkin", $"Unhooking hooks for CelesteNet...");
|
|
On.Celeste.PlayerSprite.Render -= onPlayerSpriteRenderCelestenet;
|
|
On.Celeste.PlayerSprite.Render -= onPlayerSpriteRenderCelestenetMisc;
|
|
} else {
|
|
On.Celeste.PlayerSprite.Render -= onPlayerSpriteRenderMisc;
|
|
}
|
|
|
|
using (new DetourContext("AvaliSkinModule") {
|
|
After = { "Hyperline" }
|
|
}) {
|
|
On.Celeste.Player.GetCurrentTrailColor -= onPlayerGetTrailColor;
|
|
On.Celeste.Player.UpdateHair -= onPlayerUpdateHair;
|
|
On.Celeste.Player.DashUpdate -= onPlayerDashUpdate;
|
|
}
|
|
|
|
using (new DetourContext("AvaliSkinModule") {
|
|
After = { "FastPlayerDie" }
|
|
}) {
|
|
On.Celeste.PlayerDeadBody.Render -= onPlayerDeadBodyRender;
|
|
}
|
|
IL.Celeste.DeathEffect.Draw -= DeathEffectDrawHook;
|
|
On.Celeste.Payphone.ctor -= onPayphoneConstructor;
|
|
On.Celeste.Lookout.ctor -= onLookoutConstructor;
|
|
|
|
FxRecolor.Dispose();
|
|
}
|
|
|
|
|
|
// Checks if the enabled status of the sprite needs to be updated, and if
|
|
// so, then swaps out the sprite at runtime with the Avali spritebank.
|
|
private void trySpriteSwap(PlayerSprite sprite, bool enabled) {
|
|
DynamicData dd = DynamicData.For(sprite);
|
|
bool oldEnabled = false;
|
|
// TryGet crashes with value types (of course; obviously!)
|
|
// So we use a boxed type with the nullable bool... this is a bug in monomod?
|
|
if (dd.TryGet<bool?>("avaliskin_enabled", out bool? ddoldEnabled)) {
|
|
oldEnabled = (bool) ddoldEnabled;
|
|
}
|
|
|
|
if (oldEnabled != enabled) {
|
|
string spriteID = "";
|
|
switch (sprite.Mode) {
|
|
case PlayerSpriteMode.Madeline:
|
|
spriteID = enabled ? "player_avali" : "player"; break;
|
|
case PlayerSpriteMode.MadelineNoBackpack:
|
|
spriteID = enabled ? "player_avali_no_backpack" : "player_no_backpack"; break;
|
|
case PlayerSpriteMode.Playback:
|
|
spriteID = enabled ? "player_avali_playback" : "player_playback"; break;
|
|
default: return;
|
|
}
|
|
|
|
dd.Set("avaliskin_enabled", (bool?) enabled);
|
|
|
|
// CreateOn doesn't preserve some of the properties that we need to keep here!
|
|
// Copy them over manually instead...
|
|
Vector2 pos = sprite.Position;
|
|
Color color = sprite.Color;
|
|
string currentAnimationID = sprite.CurrentAnimationID;
|
|
int currentAnimationFrame = sprite.CurrentAnimationFrame;
|
|
|
|
GFX.SpriteBank.CreateOn(sprite, spriteID);
|
|
|
|
sprite.Position = pos;
|
|
sprite.SetColor(color);
|
|
if (currentAnimationID != "") {
|
|
sprite.Play(currentAnimationID);
|
|
sprite.SetAnimationFrame(currentAnimationFrame);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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["recolor1_threshold"].SetValue(0.03f);
|
|
FxRecolor.Parameters["recolor1_rgb_from"].SetValue(
|
|
(new Color((byte) 0x1a, 0xd5, 0x89, 0xff)).ToVector4()
|
|
);
|
|
FxRecolor.Parameters["recolor1_rgb_to"].SetValue(dashColor.ToVector4());
|
|
|
|
// works at 0.12 minus the dark brown (actually works for real)
|
|
FxRecolor.Parameters["rehue1_threshold"].SetValue(0.12f);
|
|
FxRecolor.Parameters["rehue1_threshold_mul"].SetValue(
|
|
new Vector4(1f, 0.1f, 0.3f, 0f)
|
|
);
|
|
FxRecolor.Parameters["rehue1_hsv_from"].SetValue(
|
|
(new Color((byte) 0xa2, 0x88, 0x5c, 0xff)).ToHSV() // #a2885c
|
|
);
|
|
FxRecolor.Parameters["rehue1_hsv_to"].SetValue(
|
|
lightColor2.ToHSV()
|
|
);
|
|
|
|
FxRecolor.Parameters["rehue2_threshold"].SetValue(0.07f);
|
|
FxRecolor.Parameters["rehue2_threshold_mul"].SetValue(
|
|
new Vector4(1f, .8f, .8f, 0f)
|
|
);
|
|
FxRecolor.Parameters["rehue2_hsv_from"].SetValue(
|
|
(new Color((byte) 0x4e, 0x4e, 0x4e, 0xff)).ToHSV() // #4e4e4e
|
|
);
|
|
FxRecolor.Parameters["rehue2_hsv_to"].SetValue(
|
|
darkColor2.ToHSV()
|
|
);
|
|
|
|
Viewport viewport = Engine.Graphics.GraphicsDevice.Viewport;
|
|
Camera camera = (Engine.Scene as Level).Camera;
|
|
|
|
Matrix projection = Matrix.CreateOrthographicOffCenter(
|
|
0, viewport.Width, viewport.Height, 0, 0, 1
|
|
);
|
|
|
|
FxRecolor.Parameters["TransformMatrix"].SetValue(projection);
|
|
FxRecolor.Parameters["ViewMatrix"].SetValue(camera?.Matrix ?? Matrix.Identity);
|
|
|
|
FxRecolor.CurrentTechnique = FxRecolor.Techniques["Recolor"];
|
|
}
|
|
|
|
|
|
private void onLevelLoaderctor(
|
|
On.Celeste.LevelLoader.orig_ctor orig, LevelLoader self,
|
|
Session session, Vector2? startPosition = null
|
|
) {
|
|
orig(self, session, startPosition);
|
|
|
|
// This only needs to be ran once, but we can't run this in LoadContent
|
|
// because the sprites are not loaded yet... see the original Everest
|
|
// source for this function
|
|
PlayerSprite.CreateFramesMetadata("player_avali");
|
|
PlayerSprite.CreateFramesMetadata("player_avali_no_backpack");
|
|
PlayerSprite.CreateFramesMetadata("player_avali_playback");
|
|
}
|
|
|
|
|
|
private void onPlayerRender(On.Celeste.Player.orig_Render orig, Player self) {
|
|
PlayerSprite sprite = self.Sprite;
|
|
if (sprite.Scene != null && PlayerConfig.IsEnabled(self)) {
|
|
// swap out the player's spritebank if the enabled state changed
|
|
trySpriteSwap(sprite, true);
|
|
Color color = PlayerConfig.GetColor(self);
|
|
|
|
// apply the recolor effect to the player
|
|
spriteRecolor(color, PlayerConfig.LightBody, PlayerConfig.DarkBody);
|
|
|
|
DynData<SpriteBatch> spriteData = new DynData<SpriteBatch>(Draw.SpriteBatch);
|
|
Matrix matrix = (Matrix)spriteData["transformMatrix"];
|
|
|
|
Draw.SpriteBatch.End();
|
|
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, FxRecolor, matrix);
|
|
|
|
// render Avali...
|
|
orig(self);
|
|
|
|
// ... and reset rendering to stop using the effect
|
|
Draw.SpriteBatch.End();
|
|
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, null, matrix);
|
|
} else if (self.Scene != null) {
|
|
trySpriteSwap(sprite, false);
|
|
orig(self);
|
|
} else {
|
|
orig(self);
|
|
}
|
|
}
|
|
|
|
|
|
private void onPlayerSpriteRenderMisc(On.Celeste.PlayerSprite.orig_Render orig, PlayerSprite self) {
|
|
Player player = Engine.Scene.Tracker.GetEntity<Player>();
|
|
if (player == null) {
|
|
orig(self);
|
|
return;
|
|
}
|
|
|
|
// This handles rendering of misc instances of PlayerSprites:
|
|
// usually player playback entities and the such.
|
|
if (
|
|
self.Scene != null
|
|
&& (
|
|
self.Entity == null ||
|
|
!(self.Entity is Player || self.Entity is PlayerDeadBody)
|
|
)
|
|
) {
|
|
if (PlayerConfig.IsEnabled(player)) {
|
|
trySpriteSwap(self, true);
|
|
Color color = PlayerConfig.GetColor(player);
|
|
|
|
// if (self.Entity is PlayerPlayback playback) {
|
|
// // handle someday soon...
|
|
// }
|
|
|
|
// apply the recolor effect to the sprite
|
|
spriteRecolor(color, PlayerConfig.LightBody, PlayerConfig.DarkBody);
|
|
|
|
DynData<SpriteBatch> spriteData = new DynData<SpriteBatch>(Draw.SpriteBatch);
|
|
Matrix matrix = (Matrix)spriteData["transformMatrix"];
|
|
|
|
Draw.SpriteBatch.End();
|
|
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, FxRecolor, matrix);
|
|
|
|
// render the sprite...
|
|
orig(self);
|
|
|
|
// ... and reset rendering to stop using the effect
|
|
Draw.SpriteBatch.End();
|
|
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, null, matrix);
|
|
} else {
|
|
trySpriteSwap(self, false);
|
|
orig(self);
|
|
}
|
|
} else {
|
|
orig(self);
|
|
}
|
|
}
|
|
|
|
private void onPlayerSpriteRenderCelestenetMisc(On.Celeste.PlayerSprite.orig_Render orig, PlayerSprite self) {
|
|
// This handles rendering of misc instances of PlayerSprites:
|
|
// usually player playback entities and the such.
|
|
if (
|
|
self.Scene != null && (
|
|
self.Entity == null
|
|
|| !(self.Entity is Player || self.Entity is PlayerDeadBody || self.Entity is Ghost)
|
|
)
|
|
) {
|
|
onPlayerSpriteRenderMisc(orig, self);
|
|
} else {
|
|
orig(self);
|
|
}
|
|
}
|
|
|
|
|
|
// CelesteNet must be loaded when calling this function.
|
|
private void onPlayerSpriteRenderCelestenet(On.Celeste.PlayerSprite.orig_Render orig, PlayerSprite self) {
|
|
// CelesteNet players are not actually Player classes, but instead this custom Ghost class.
|
|
// 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;
|
|
Ghost ghost;
|
|
AvaliConfig config;
|
|
if (
|
|
self.Scene != null && self.Entity != null
|
|
&& self.Entity is Ghost ghost2 && ghost2.PlayerInfo != null
|
|
&& client != null
|
|
&& client.Data.TryGetBoundRef<DataPlayerInfo, DataPlayerAvaliSkin>(
|
|
ghost2.PlayerInfo.ID,
|
|
out DataPlayerAvaliSkin data
|
|
) && data != null
|
|
&& data.Config.IsEnabled(ghost2)
|
|
) {
|
|
ghost = ghost2;
|
|
config = data.Config;
|
|
} else if (
|
|
Settings.CelesteNetEveryoneHasSkin
|
|
&& self.Scene != null && self.Entity != null
|
|
&& self.Entity is Ghost ghost3
|
|
&& EveryoneHasSkinConfig.IsEnabled(ghost3)
|
|
) {
|
|
ghost = ghost3;
|
|
config = EveryoneHasSkinConfig;
|
|
} else if (
|
|
self.Scene != null && self.Entity != null
|
|
&& self.Entity is Ghost
|
|
) {
|
|
trySpriteSwap(self, false);
|
|
orig(self);
|
|
return;
|
|
} else {
|
|
orig(self);
|
|
return;
|
|
}
|
|
|
|
// swap out the ghost's spritebank if the enabled state changed
|
|
trySpriteSwap(self, true);
|
|
|
|
// apply the recolor effect to the ghost
|
|
spriteRecolor(config.GetColor(ghost), config.LightBody, config.DarkBody);
|
|
|
|
DynData<SpriteBatch> spriteData = new DynData<SpriteBatch>(Draw.SpriteBatch);
|
|
Matrix matrix = (Matrix)spriteData["transformMatrix"];
|
|
|
|
Draw.SpriteBatch.End();
|
|
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, FxRecolor, matrix);
|
|
|
|
// render the ghost...
|
|
orig(self);
|
|
|
|
// ... and reset rendering to stop using the effect
|
|
Draw.SpriteBatch.End();
|
|
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, null, matrix);
|
|
}
|
|
|
|
|
|
public void onPlayerDeadBodyRender(On.Celeste.PlayerDeadBody.orig_Render orig, PlayerDeadBody self) {
|
|
DynamicData dd = DynamicData.For(self);
|
|
Player player = dd.Get<Player>("player");
|
|
PlayerSprite sprite = dd.Get<PlayerSprite>("sprite");
|
|
|
|
if (sprite.Scene != null && PlayerConfig.IsEnabled(player)) {
|
|
// swap out the body's spritebank if the enabled state changed
|
|
trySpriteSwap(sprite, true);
|
|
|
|
// apply the recolor effect to the body
|
|
spriteRecolor(
|
|
PlayerConfig.GetColor(player),
|
|
PlayerConfig.LightBody,
|
|
PlayerConfig.DarkBody
|
|
);
|
|
|
|
DynData<SpriteBatch> spriteData = new DynData<SpriteBatch>(Draw.SpriteBatch);
|
|
Matrix matrix = (Matrix)spriteData["transformMatrix"];
|
|
|
|
Draw.SpriteBatch.End();
|
|
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, FxRecolor, matrix);
|
|
|
|
// render the dead Avali :(
|
|
orig(self);
|
|
|
|
// ... and reset rendering to stop using the effect
|
|
Draw.SpriteBatch.End();
|
|
Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap, DepthStencilState.None, RasterizerState.CullNone, null, matrix);
|
|
} else if (self.Scene != null) {
|
|
trySpriteSwap(sprite, false);
|
|
orig(self);
|
|
} else {
|
|
orig(self);
|
|
}
|
|
}
|
|
|
|
|
|
private void DeathEffectDrawHook(ILContext il) {
|
|
// replace death particle, just like SkinModHelper
|
|
ILCursor cursor = new ILCursor(il);
|
|
while (cursor.TryGotoNext(MoveType.After, instr => instr.MatchLdstr("characters/player/hair00"))) {
|
|
cursor.EmitDelegate<Func<string, string>>(ReplaceDeathParticle);
|
|
}
|
|
}
|
|
|
|
|
|
private static string ReplaceDeathParticle(string deathParticle) {
|
|
if (PlayerConfig.Enabled) {
|
|
string newDeathParticle = "characters/Avali/death_particle";
|
|
return newDeathParticle;
|
|
}
|
|
return deathParticle;
|
|
}
|
|
|
|
|
|
private Color onPlayerGetTrailColor(On.Celeste.Player.orig_GetCurrentTrailColor orig, Player 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.
|
|
// Furthermore: naively doing this will look strange because the white
|
|
// hair flash right after dashing will be copied to the trail.
|
|
if (PlayerConfig.DashColorMode != DashColorMode.ExternalDash) {
|
|
// replace trail colors with marking colors
|
|
return PlayerConfig.GetColor(self).Premultiply();
|
|
}
|
|
}
|
|
|
|
// skin disabled, keep original colors
|
|
return orig(self);
|
|
}
|
|
|
|
|
|
private void onPlayerUpdateHair(On.Celeste.Player.orig_UpdateHair orig, Player self, bool applyGravity) {
|
|
orig(self, applyGravity);
|
|
|
|
// Don't change the hair color if another mod is in control of it!
|
|
if (PlayerConfig.IsEnabled(self) && PlayerConfig.DashColorMode != DashColorMode.ExternalDash) {
|
|
// change player hair color to match dash colors.
|
|
// (hair is invisible, but that influences other things like the orbs when the Avali dies and respawns)
|
|
self.Hair.Color = PlayerConfig.GetColor(self).Premultiply();
|
|
}
|
|
}
|
|
|
|
|
|
private int onPlayerDashUpdate(On.Celeste.Player.orig_DashUpdate orig, Player self) {
|
|
if (!(
|
|
PlayerConfig.IsEnabled(self)
|
|
// We can't exfiltrate a dash color in this mode because we can't extract that
|
|
// without integrating with the dash color mod, and besides, whatever mod that
|
|
// changed the dash color should have also changed these particles regardless.
|
|
&& PlayerConfig.DashColorMode != DashColorMode.ExternalDash
|
|
)) {
|
|
// disabled, just run vanilla code
|
|
return orig(self);
|
|
}
|
|
|
|
Color color = PlayerConfig.GetColor(self).Premultiply();
|
|
|
|
// back up vanilla particles
|
|
ParticleType bakDashA = Player.P_DashA;
|
|
ParticleType bakDashB = Player.P_DashB;
|
|
ParticleType bakDashBadB = Player.P_DashBadB;
|
|
|
|
// Replace them with recolored ones.
|
|
// We need to generate these dash particles on the fly because multiple players may
|
|
// have different colors (e.g. Celestenet).
|
|
Player.P_DashA = new ParticleType(Player.P_DashA) {
|
|
Color = color,
|
|
Color2 = color,
|
|
};
|
|
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);
|
|
|
|
// restore vanilla particles
|
|
Player.P_DashA = bakDashA;
|
|
Player.P_DashB = bakDashB;
|
|
Player.P_DashBadB = bakDashBadB;
|
|
|
|
return result;
|
|
}
|
|
|
|
// todo: apply shader to these entities
|
|
|
|
private void onPayphoneConstructor(On.Celeste.Payphone.orig_ctor orig, Payphone self, Vector2 pos) {
|
|
orig(self, pos);
|
|
if (Settings.Enabled) {
|
|
// replace payphone sprites
|
|
self.Remove(self.Sprite);
|
|
self.Add(self.Sprite = GFX.SpriteBank.Create("payphone_avali"));
|
|
self.Sprite.Play("idle");
|
|
}
|
|
}
|
|
|
|
|
|
private void onLookoutConstructor(On.Celeste.Lookout.orig_ctor orig, Lookout self, EntityData data, Vector2 pos) {
|
|
orig(self, data, pos);
|
|
if (Settings.Enabled) {
|
|
// replace lookout (binoculars) sprites
|
|
DynamicData lookoutData = new DynamicData(self);
|
|
Sprite origSpr = lookoutData.Get<Sprite>("sprite");
|
|
GFX.SpriteBank.CreateOn(origSpr, "lookout_avali");
|
|
}
|
|
}
|
|
}
|
|
}
|