Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make DeathEffect support transparent colors and custom texture #807

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
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
88 changes: 86 additions & 2 deletions Celeste.Mod.mm/Patches/DeathEffect.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,50 @@
using System;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Monocle;
using MonoMod;
using MonoMod.Cil;
using MonoMod.InlineRT;
using MonoMod.Utils;

namespace Celeste {
class patch_DeathEffect : DeathEffect {

// no-op. MonoMod ignores this - we only need this to make the compiler shut up.
public patch_DeathEffect(Color color, Vector2 offset)
: base(color, offset) { }


public MTexture Texture;

/// <summary>
/// a temporary texture used to the next DeathEffect.Draw get call
/// </summary>
private static MTexture _texture;

[MonoModIgnore]
[PatchDeathEffectUpdate]
public override extern void Update();

[MonoModReplace]
public override void Render() {
if (Entity != null)
if (Entity != null) {
_texture = Texture;
Draw(Entity.Position + Position, Color, Percent);
}
}

[MonoModIgnore]
[PatchDeathEffectDraw]
public static new void Draw(Vector2 position, Color color, float ease) { }

// Because of the auto naming rules of .Hook, we shouldn't name this method same as 'Draw'
public static void DrawWithTexture(MTexture texture, Vector2 position, Color color, float ease) {
_texture = texture;
Draw(position, color, ease);
}
}
}
Expand All @@ -30,13 +56,71 @@ namespace MonoMod {
[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchDeathEffectUpdate))]
class PatchDeathEffectUpdateAttribute : Attribute { }

static partial class MonoModRules {
[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchDeathEffectDraw))]
class PatchDeathEffectDrawAttribute : Attribute {
}

static partial class MonoModRules {
public static void PatchDeathEffectUpdate(ILContext context, CustomAttribute attrib) {
ILCursor cursor = new ILCursor(context);
cursor.GotoNext(instr => instr.OpCode == OpCodes.Ble_Un_S);
cursor.Next.OpCode = OpCodes.Blt_Un_S;
}

public static void PatchDeathEffectDraw(ILContext context, CustomAttribute attrib) {
TypeDefinition t_DeathEffect = MonoModRule.Modder.Module.GetType("Celeste.DeathEffect");
FieldReference m_DeathEffect_texture = t_DeathEffect.FindField("_texture");

TypeReference t_Color = MonoModRule.Modder.Module.ImportReference(MonoModRule.Modder.FindType("Microsoft.Xna.Framework.Color").Resolve());
MethodReference m_Color_get_Black = MonoModRule.Modder.Module.ImportReference(t_Color.Resolve().FindProperty("Black").GetMethod);
VariableDefinition v_outline = new VariableDefinition(t_Color);
context.Body.Variables.Add(v_outline);

MethodReference m_Color_get_A = MonoModRule.Modder.Module.ImportReference(t_Color.Resolve().FindProperty("A").GetMethod);
MethodReference m_Color_op_Multiply = MonoModRule.Modder.Module.ImportReference(t_Color.Resolve().FindMethod("op_Multiply"));

ILCursor cursor = new ILCursor(context);

// Color outline = Color.Black * (this.Color.A / 255f);
cursor.EmitCall(m_Color_get_Black);
cursor.EmitLdarga(1);
cursor.EmitCall(m_Color_get_A);
cursor.EmitConvR4();
cursor.EmitLdcR4(255f);
cursor.EmitDiv();
cursor.EmitCall(m_Color_op_Multiply);
cursor.EmitStloc(v_outline);

// Color.White => Color.White * (this.Color.A / 255f);
cursor.GotoNext(MoveType.After, instr => instr.MatchCall("Microsoft.Xna.Framework.Color", "get_White"));
cursor.EmitLdarga(1);
cursor.EmitCall(m_Color_get_A);
cursor.EmitConvR4();
cursor.EmitLdcR4(255f);
cursor.EmitDiv();
cursor.EmitCall(m_Color_op_Multiply);

// GFX.Game["characters/player/hair00"] => (_texture ?? GFX.Game["characters/player/hair00"]);
ILLabel Ifnull = cursor.DefineLabel();
cursor.GotoNext(MoveType.AfterLabel, instr => instr.MatchLdsfld("Celeste.GFX", "Game"));
cursor.EmitLdsfld(m_DeathEffect_texture);
cursor.EmitDup();
cursor.Emit(OpCodes.Brtrue_S, Ifnull);
cursor.EmitPop();
cursor.GotoNext(instr => instr.MatchStloc(out int i));
cursor.MarkLabel(Ifnull);

// Color.Black => outline;
for (int i = 4; i > 0; i--) {
cursor.GotoNext(instr => instr.MatchCall("Microsoft.Xna.Framework.Color", "get_Black"));
cursor.Remove();
cursor.EmitLdloc(v_outline);
}

// _texture = null;
cursor.GotoNext(instr => instr.MatchRet());
cursor.EmitLdnull();
cursor.EmitStsfld(m_DeathEffect_texture);
}
}
}