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

Add an event to get the helpers's animations and fill the player with them #870

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
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
33 changes: 32 additions & 1 deletion Celeste.Mod.mm/Mod/Everest/Everest.Events.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Celeste.Mod.UI;
using Celeste.Mod.UI;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -318,6 +318,37 @@ public static class SubHudRenderer {
internal static void BeforeRender(_SubHudRenderer renderer, Scene scene)
=> OnBeforeRender?.Invoke(renderer, scene);
}

public static class PlayerSprite {
public delegate void GetIdsUsedFillAnimForIdHandler(List<string> ids, string id);

/// <summary>
/// Called during <see cref="patch_PlayerSprite.CreateFramesMetadata(string)"/>, unless <see cref="DoNotFillAnimFor"/> contains its given string.
/// <br/> <br/>
/// Mods using this should always add an id to <see cref="List{T}"/> ids whether what the <see cref="string"/> id was given, to avoid certain <see cref="string"/> id missing their animations
AAA1459 marked this conversation as resolved.
Show resolved Hide resolved
/// <br/>e.g: <code>
/// Everest.Event.PlayerSprite.OnGetIdsUsedFillAnimForId += ((ids, id) => {
/// if (id == "player_no_backpack")
/// ids.Add("MyHelper_Anims_NB")
/// else
/// ids.Add("MyHelper_Anims")
/// });
/// </code></summary>
public static event GetIdsUsedFillAnimForIdHandler OnGetIdsUsedFillAnimForId;

internal static List<string> GetIdsUsedFillAnimFor(string id) {
List<string> ids = new();
OnGetIdsUsedFillAnimForId?.Invoke(new(), id);
return ids;
}

// Put this field here make it so visible
/// <summary>
/// Filter IDs that should not be fill anim by other IDs during <see cref="patch_PlayerSprite.CreateFramesMetadata(string)"/>,
/// <br/>prevent e.g "MyHelper_Anims", ""MyHelper_Anims_NB", "OthersHelper_Anims" ids from senseless filling each other's animations
/// </summary>
public static HashSet<string> DoNotFillAnimFor = new(StringComparer.CurrentCultureIgnoreCase);
AAA1459 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
103 changes: 86 additions & 17 deletions Celeste.Mod.mm/Patches/PlayerSprite.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,86 @@
using Celeste;
using Mono.Cecil;
using MonoMod;
using System;

namespace Celeste {
public class patch_PlayerSprite : PlayerSprite {

public patch_PlayerSprite(PlayerSpriteMode mode) : base(mode) {
// Do nothing here. MonoMod will ignore this.
}

[MonoModIgnore]
[ForceNoInlining]
public new extern static void ClearFramesMetadata();
}
}
using Celeste;
using Celeste.Mod;
using Microsoft.Xna.Framework;
using Mono.Cecil;
using Monocle;
using MonoMod;
using MonoMod.Cil;
using MonoMod.InlineRT;
using System;
using System.Collections.Generic;
using MonoMod.Utils;
using System.Reflection.Emit;

namespace Celeste {
class patch_PlayerSprite : PlayerSprite {
public patch_PlayerSprite(PlayerSpriteMode mode) : base(mode) {
// no-op. MonoMod ignores this - we only need this to make the compiler shut up.
}

#pragma warning disable CS0108 // Hides inherited member
[MonoModIgnore]
[PatchPlayerSpriteCreateFramesMetadata]
public static void CreateFramesMetadata(string id) {
}

[MonoModIgnore]
[ForceNoInlining]
public new extern static void ClearFramesMetadata();
#pragma warning restore CS0108

private static void fillAnimForID(string id) {
List<string> ids = Everest.Events.PlayerSprite.GetIdsUsedFillAnimFor(id);
if (ids.Count == 0)
return;
Dictionary<string, patch_Sprite.Animation> existingAnim = (GFX.SpriteBank.SpriteData[id].Sprite as patch_Sprite).Animations;
foreach (string id2 in ids) {
foreach (KeyValuePair<string, patch_Sprite.Animation> anim in (GFX.SpriteBank.SpriteData[id2].Sprite as patch_Sprite).Animations) {
if (!existingAnim.ContainsKey(anim.Key)) {
existingAnim[anim.Key] = anim.Value;
}
}
}
}
}
}

namespace MonoMod {
[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchPlayerSpriteCreateFramesMetadata))]
class PatchPlayerSpriteCreateFramesMetadata : Attribute {
}

static partial class MonoModRules {
public static void PatchPlayerSpriteCreateFramesMetadata(ILContext context, CustomAttribute attrib) {
MethodReference m_PlayerSprite_fillAnimForID = context.Method.DeclaringType.FindMethod("fillAnimForID");

FieldReference f_DoNotFillAnimFor = MonoModRule.Modder.Module.GetType("Celeste.Mod.Everest/Events/PlayerSprite").FindField("DoNotFillAnimFor");

//Explicitly instantiate the generic HashSet<T> type
GenericInstanceType genericInst = (GenericInstanceType) context.Module.ImportReference(f_DoNotFillAnimFor.FieldType);

MethodReference m_temp = context.Module.ImportReference(genericInst.Resolve().FindMethod("Contains"));
MethodReference m_HashSet_string_Contains = context.Module.ImportReference(new MethodReference(m_temp.Name, m_temp.ReturnType, genericInst) {
HasThis = m_temp.HasThis,
ExplicitThis = m_temp.ExplicitThis,
CallingConvention = m_temp.CallingConvention,
});
m_HashSet_string_Contains.Parameters.AddRange(m_temp.Parameters);

ILCursor cursor = new ILCursor(context);

// if (DoNotFillAnimFor.Contains(id)) {
ILLabel If = cursor.DefineLabel();
cursor.EmitLdsfld(f_DoNotFillAnimFor);
cursor.EmitLdarg0();
cursor.EmitCallvirt(m_HashSet_string_Contains);
cursor.EmitBrtrue(If);

// fillAnimForID(id);
cursor.EmitLdarg0();
cursor.EmitCall(m_PlayerSprite_fillAnimForID);
// }
cursor.EmitBr(If);
cursor.MarkLabel(If);
}
}
}