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
+}