diff --git a/Celeste.Mod.mm/Mod/Entities/ISpeed.cs b/Celeste.Mod.mm/Mod/Entities/ISpeed.cs new file mode 100644 index 000000000..b0d46a2f2 --- /dev/null +++ b/Celeste.Mod.mm/Mod/Entities/ISpeed.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.Xna.Framework; +using Mono.Cecil; +using MonoMod.InlineRT; + +namespace Celeste.Mod { + /// + /// + public interface ISpeed { + /// + /// + public Vector2 Speed { get; set; } + } +} + +namespace MonoMod { + + /// + /// Patch the given class to tack on the ISpeed interface + /// + [MonoModCustomAttribute(nameof(MonoModRules.PatchSpeedInterface))] + class PatchSpeedInterfaceAttribute : Attribute { } + + static partial class MonoModRules { + + public static void PatchSpeedInterface(ICustomAttributeProvider provider, CustomAttribute attrib) { + InterfaceImplementation i_ISpeed = new InterfaceImplementation(MonoModRule.Modder.FindType("Celeste.Mod.ISpeed")); + + ((TypeDefinition) provider).Interfaces.Add(i_ISpeed); + } + + } +} diff --git a/Celeste.Mod.mm/MonoModRules.cs b/Celeste.Mod.mm/MonoModRules.cs index d8f3bf84a..41ff5c83e 100644 --- a/Celeste.Mod.mm/MonoModRules.cs +++ b/Celeste.Mod.mm/MonoModRules.cs @@ -8,6 +8,7 @@ using System.Reflection; using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider; using MethodAttributes = Mono.Cecil.MethodAttributes; +using PropertyAttributes = Mono.Cecil.PropertyAttributes; namespace MonoMod { #region Helper Patch Attributes @@ -23,6 +24,12 @@ class MakeEntryPointAttribute : Attribute { } [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchInterface))] class PatchInterfaceAttribute : Attribute { } + /// + /// Helper for patching properties force-implemented by an interface + /// + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchInterfaceProperty))] + class PatchInterfacePropertyAttribute : Attribute { } + /// /// Forcibly changes a given member's name. /// @@ -228,6 +235,12 @@ public static void PatchInterface(MethodDefinition method, CustomAttribute attri method.Attributes |= flags; } + public static void PatchInterfaceProperty(PropertyDefinition property, CustomAttribute attrib) { + MethodAttributes flags = MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot; + if (property.GetMethod is {} @get) @get.Attributes |= flags; + if (property.SetMethod is {} @set) @set.Attributes |= flags; + } + public static void ForceName(ICustomAttributeProvider cap, CustomAttribute attrib) { if (cap is IMemberDefinition member) member.Name = (string) attrib.ConstructorArguments[0].Value; diff --git a/Celeste.Mod.mm/Patches/Glider.cs b/Celeste.Mod.mm/Patches/Glider.cs index 4da373834..ae8d9333b 100644 --- a/Celeste.Mod.mm/Patches/Glider.cs +++ b/Celeste.Mod.mm/Patches/Glider.cs @@ -2,13 +2,18 @@ #pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +using Celeste.Mod; using Microsoft.Xna.Framework; using MonoMod; namespace Celeste { - class patch_Glider : Glider { + [PatchSpeedInterface] + class patch_Glider : Glider, ISpeed { public patch_Holdable Hold; // avoids extra cast + [PatchInterfaceProperty] + Vector2 ISpeed.Speed { get => Speed; set => Speed = value; } + public patch_Glider(Vector2 position, bool bubble, bool tutorial) : base(position, bubble, tutorial) { } @@ -21,4 +26,4 @@ public void ctor(Vector2 position, bool bubble, bool tutorial) { Hold.SpeedSetter = (speed) => { Speed = speed; }; } } -} \ No newline at end of file +} diff --git a/Celeste.Mod.mm/Patches/Holdable.cs b/Celeste.Mod.mm/Patches/Holdable.cs index 481debb18..898f4dcf1 100644 --- a/Celeste.Mod.mm/Patches/Holdable.cs +++ b/Celeste.Mod.mm/Patches/Holdable.cs @@ -3,11 +3,16 @@ using Microsoft.Xna.Framework; using MonoMod; using System; +using Celeste.Mod; namespace Celeste { - class patch_Holdable : Holdable { + [PatchSpeedInterface] + class patch_Holdable : Holdable, ISpeed { public Action SpeedSetter; + [PatchInterfaceProperty] + public Vector2 Speed { get => GetSpeed(); set => SetSpeed(value); } + [MonoModLinkTo("Celeste.Holdable", "System.Void .ctor(System.Single)")] [MonoModForceCall] [MonoModRemove] diff --git a/Celeste.Mod.mm/Patches/MoveBlock.cs b/Celeste.Mod.mm/Patches/MoveBlock.cs index 237c67563..d254cf2b1 100644 --- a/Celeste.Mod.mm/Patches/MoveBlock.cs +++ b/Celeste.Mod.mm/Patches/MoveBlock.cs @@ -7,8 +7,10 @@ using MonoMod.Cil; using MonoMod.InlineRT; using MonoMod.Utils; +using Celeste.Mod; namespace Celeste { + [PatchSpeedInterface] class patch_MoveBlock : MoveBlock { public patch_MoveBlock(Vector2 position, int width, int height, MoveBlock.Directions direction, bool canSteer, bool fast) @@ -19,6 +21,13 @@ public patch_MoveBlock(Vector2 position, int width, int height, MoveBlock.Direct [MonoModIgnore] [PatchMoveBlockController] private extern IEnumerator Controller(); + + class patch_Debris : ISpeed { + private Vector2 speed; + + [PatchInterfaceProperty] + Vector2 ISpeed.Speed { get => speed; set => speed = value; } + } } } diff --git a/Celeste.Mod.mm/Patches/Player.cs b/Celeste.Mod.mm/Patches/Player.cs index 8d8d1d2bc..327d0f229 100644 --- a/Celeste.Mod.mm/Patches/Player.cs +++ b/Celeste.Mod.mm/Patches/Player.cs @@ -20,7 +20,8 @@ using _Player = Celeste.Player; namespace Celeste { - class patch_Player : Player { + [PatchSpeedInterface] + class patch_Player : Player, ISpeed { // We're effectively in Player, but still need to "expose" private fields to our mod. private bool wasDashB; @@ -43,7 +44,6 @@ class patch_Player : Player { } } - public bool IsIntroState { get { int state = StateMachine.State; @@ -51,6 +51,9 @@ public bool IsIntroState { } } + [PatchInterfaceProperty] + Vector2 ISpeed.Speed { get => Speed; set => Speed = value; } + public patch_Player(Vector2 position, PlayerSpriteMode spriteMode) : base(position, spriteMode) { // no-op. MonoMod ignores this - we only need this to make the compiler shut up. diff --git a/Celeste.Mod.mm/Patches/Seeker.cs b/Celeste.Mod.mm/Patches/Seeker.cs index 777f793e8..2a3b15a65 100644 --- a/Celeste.Mod.mm/Patches/Seeker.cs +++ b/Celeste.Mod.mm/Patches/Seeker.cs @@ -11,11 +11,15 @@ using _Seeker = Celeste.Seeker; namespace Celeste { - public class patch_Seeker : Seeker { + [PatchSpeedInterface] + public class patch_Seeker : Seeker, ISpeed { // We're effectively in Seeker, but still need to "expose" private fields to our mod. private StateMachine State; + [PatchInterfaceProperty] + Vector2 ISpeed.Speed { get => Speed; set => Speed = value; } + // no-op - only here to make public patch_Seeker(EntityData data, Vector2 offset) : base(data, offset) { diff --git a/Celeste.Mod.mm/Patches/Solid.cs b/Celeste.Mod.mm/Patches/Solid.cs index 53c3d1502..d22a13f6a 100644 --- a/Celeste.Mod.mm/Patches/Solid.cs +++ b/Celeste.Mod.mm/Patches/Solid.cs @@ -1,4 +1,6 @@ -using Mono.Cecil; +using Celeste.Mod; +using Microsoft.Xna.Framework; +using Mono.Cecil; using Mono.Cecil.Cil; using Monocle; using MonoMod; @@ -8,22 +10,30 @@ using System; namespace Celeste { - internal class patch_Solid { + [PatchSpeedInterface] + internal class patch_Solid : Solid, ISpeed { + [PatchInterfaceProperty] + Vector2 ISpeed.Speed { get => Speed; set => Speed = value; } + + public patch_Solid(Vector2 position, float width, float height, bool safe) : base(position, width, height, safe) { + // no-op. MonoMod ignores this - we only need this to make the compiler shut up. + } + [MonoModIgnore] [PatchSolidAwake] - public extern void Awake(Scene scene); + public extern new void Awake(Scene scene); [MonoModIgnore] [ForceNoInlining] - public extern bool HasPlayerClimbing(); + public extern new bool HasPlayerClimbing(); [MonoModIgnore] [ForceNoInlining] - public extern bool HasPlayerOnTop(); + public extern new bool HasPlayerOnTop(); [MonoModIgnore] [ForceNoInlining] - public extern bool HasPlayerRider(); + public extern new bool HasPlayerRider(); } } diff --git a/Celeste.Mod.mm/Patches/TheoCrystal.cs b/Celeste.Mod.mm/Patches/TheoCrystal.cs index 78c3765b4..d82ba9289 100644 --- a/Celeste.Mod.mm/Patches/TheoCrystal.cs +++ b/Celeste.Mod.mm/Patches/TheoCrystal.cs @@ -2,13 +2,18 @@ #pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +using Celeste.Mod; using Microsoft.Xna.Framework; using MonoMod; namespace Celeste { - class patch_TheoCrystal : TheoCrystal { + [PatchSpeedInterface] + class patch_TheoCrystal : TheoCrystal, ISpeed { public patch_Holdable Hold; // avoids extra cast + [PatchInterfaceProperty] + Vector2 ISpeed.Speed { get => Speed; set => Speed = value; } + public patch_TheoCrystal(EntityData data, Vector2 offset) : base(data, offset) { } @@ -21,4 +26,4 @@ public void ctor(Vector2 position) { Hold.SpeedSetter = (speed) => { Speed = speed; }; } } -} \ No newline at end of file +}