From 95dfbbb1b66be5a4b3eb130f90c21a1a2e99775d Mon Sep 17 00:00:00 2001 From: l-Luna Date: Fri, 28 Apr 2023 23:27:33 +0100 Subject: [PATCH 01/28] work on statemachine --- Celeste.Mod.mm/Patches/StateMachine.cs | 67 ++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Celeste.Mod.mm/Patches/StateMachine.cs diff --git a/Celeste.Mod.mm/Patches/StateMachine.cs b/Celeste.Mod.mm/Patches/StateMachine.cs new file mode 100644 index 000000000..154ba145a --- /dev/null +++ b/Celeste.Mod.mm/Patches/StateMachine.cs @@ -0,0 +1,67 @@ +using Monocle; +using MonoMod; +using System; +using System.Collections; + +namespace Celeste { + public class patch_StateMachine : StateMachine { + + // We're effectively in StateMachine, but still need to "expose" private fields to our mod. + private Action[] begins; + private Func[] updates; + private Action[] ends; + private Func[] coroutines; + + // Keep track of state's names. + private string[] names; + + public extern void orig_ctor(int maxStates = 10); + [MonoModConstructor] + public void ctor(int maxStates = 10) { + orig_ctor(maxStates); + names = new string[maxStates]; + } + + private int Expand() { + int nextIdx = begins.Length; + + Array.Resize(ref begins, begins.Length + 1); + Array.Resize(ref updates, updates.Length + 1); + Array.Resize(ref ends, ends.Length + 1); + Array.Resize(ref coroutines, coroutines.Length + 1); + Array.Resize(ref names, names.Length + 1); + + return nextIdx; + } + + public extern void orig_SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null); + public void SetCallbacks( + int state, + Func onUpdate, + Func coroutine = null, + Action begin = null, + Action end = null) { + orig_SetCallbacks(state, onUpdate, coroutine, begin, end); + names[state] = state.ToString(); + } + + /// + /// Adds a state to this StateMachine. + /// + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + int nextIdx = Expand(); + SetCallbacks(nextIdx, () => onUpdate(Entity), + () => coroutine?.Invoke(Entity), + () => begin?.Invoke(Entity), + () => end?.Invoke(Entity)); + names[nextIdx] = name; + return nextIdx; + } + + public string GetState(int state) => names[state]; + public void SetStateName(int state, string name) => names[state] = name; + + public string GetCurrentStateName() => names[State]; + } +} \ No newline at end of file From 6ad7a09d676ebc55fec76309825fe5f4572b25ad Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 12:26:10 +0100 Subject: [PATCH 02/28] now actually patches --- Celeste.Mod.mm/Patches/StateMachine.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Celeste.Mod.mm/Patches/StateMachine.cs b/Celeste.Mod.mm/Patches/StateMachine.cs index 154ba145a..e579e25cd 100644 --- a/Celeste.Mod.mm/Patches/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/StateMachine.cs @@ -51,10 +51,10 @@ public void SetCallbacks( /// The index of the new state. public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ int nextIdx = Expand(); - SetCallbacks(nextIdx, () => onUpdate(Entity), - () => coroutine?.Invoke(Entity), - () => begin?.Invoke(Entity), - () => end?.Invoke(Entity)); + SetCallbacks(nextIdx, Helper.WrapFunc(onUpdate, this), + Helper.WrapFunc(coroutine, this), + Helper.WrapAction(begin, this), + Helper.WrapAction(end, this)); names[nextIdx] = name; return nextIdx; } @@ -64,4 +64,9 @@ public int AddState(string name, Func onUpdate, Func names[State]; } + + internal static class Helper { + internal static Func WrapFunc(Func f, Component c) => f == null ? null : () => f(c.Entity); + internal static Action WrapAction(Action a, Component c) => a == null ? null : () => a(c.Entity); + } } \ No newline at end of file From 688fe5822e319e65ab8c631ceb5a84e39a1dc9d8 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 12:31:42 +0100 Subject: [PATCH 03/28] actually correct namespace --- .../Patches/{ => Monocle}/StateMachine.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) rename Celeste.Mod.mm/Patches/{ => Monocle}/StateMachine.cs (80%) diff --git a/Celeste.Mod.mm/Patches/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs similarity index 80% rename from Celeste.Mod.mm/Patches/StateMachine.cs rename to Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index e579e25cd..2dfd471ac 100644 --- a/Celeste.Mod.mm/Patches/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -1,9 +1,8 @@ -using Monocle; -using MonoMod; +using MonoMod; using System; using System.Collections; -namespace Celeste { +namespace Monocle { public class patch_StateMachine : StateMachine { // We're effectively in StateMachine, but still need to "expose" private fields to our mod. @@ -51,10 +50,10 @@ public void SetCallbacks( /// The index of the new state. public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ int nextIdx = Expand(); - SetCallbacks(nextIdx, Helper.WrapFunc(onUpdate, this), - Helper.WrapFunc(coroutine, this), - Helper.WrapAction(begin, this), - Helper.WrapAction(end, this)); + SetCallbacks(nextIdx, () => onUpdate(Entity), + coroutine == null ? null : () => coroutine(Entity), + begin == null ? null : () => begin(Entity), + end == null ? null : () => end(Entity)); names[nextIdx] = name; return nextIdx; } @@ -64,9 +63,4 @@ public int AddState(string name, Func onUpdate, Func names[State]; } - - internal static class Helper { - internal static Func WrapFunc(Func f, Component c) => f == null ? null : () => f(c.Entity); - internal static Action WrapAction(Action a, Component c) => a == null ? null : () => a(c.Entity); - } } \ No newline at end of file From 1c8b1cdf37c9612e739e610908eb6f4051444dc4 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 12:33:57 +0100 Subject: [PATCH 04/28] don't override names in `SetCallbacks` if they already exist --- Celeste.Mod.mm/Patches/Monocle/StateMachine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index 2dfd471ac..94723b0f0 100644 --- a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -41,7 +41,7 @@ public void SetCallbacks( Action begin = null, Action end = null) { orig_SetCallbacks(state, onUpdate, coroutine, begin, end); - names[state] = state.ToString(); + names[state] ??= state.ToString(); } /// From 3a964975acd13cbe50f6787aec8a8f1ffd708894 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 12:39:34 +0100 Subject: [PATCH 05/28] cleanup --- Celeste.Mod.mm/Patches/Monocle/StateMachine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index 94723b0f0..3a62b9b22 100644 --- a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -14,7 +14,7 @@ public class patch_StateMachine : StateMachine { // Keep track of state's names. private string[] names; - public extern void orig_ctor(int maxStates = 10); + private extern void orig_ctor(int maxStates = 10); [MonoModConstructor] public void ctor(int maxStates = 10) { orig_ctor(maxStates); @@ -33,7 +33,7 @@ private int Expand() { return nextIdx; } - public extern void orig_SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null); + private extern void orig_SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null); public void SetCallbacks( int state, Func onUpdate, @@ -58,7 +58,7 @@ public int AddState(string name, Func onUpdate, Func names[state]; + public string GetStateName(int state) => names[state]; public void SetStateName(int state, string name) => names[state] = name; public string GetCurrentStateName() => names[State]; From 39f390703da000fbd9a2356be8ae273fa8bff521 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 14:46:23 +0100 Subject: [PATCH 06/28] fix crashes, add type-safe methods for Player/Seeker/Oshiro states --- Celeste.Mod.mm/Patches/AngryOshiro.cs | 32 ++++++ .../Patches/Monocle/StateMachine.cs | 102 +++++++++++++----- Celeste.Mod.mm/Patches/Player.cs | 16 +++ Celeste.Mod.mm/Patches/Seeker.cs | 33 ++++++ 4 files changed, 157 insertions(+), 26 deletions(-) create mode 100644 Celeste.Mod.mm/Patches/AngryOshiro.cs create mode 100644 Celeste.Mod.mm/Patches/Seeker.cs diff --git a/Celeste.Mod.mm/Patches/AngryOshiro.cs b/Celeste.Mod.mm/Patches/AngryOshiro.cs new file mode 100644 index 000000000..7789ace31 --- /dev/null +++ b/Celeste.Mod.mm/Patches/AngryOshiro.cs @@ -0,0 +1,32 @@ +using Microsoft.Xna.Framework; +using Monocle; +using System; +using System.Collections; + +namespace Celeste.Patches { + public class patch_AngryOshiro : AngryOshiro { + + // We're effectively in AngryOshiro, but still need to "expose" private fields to our mod. + private StateMachine state; + + public patch_AngryOshiro(EntityData data, Vector2 offset) + : base(data, offset) { + // no-op. MonoMod ignores this - we only need this to make the compiler shut up. + } + + /// + /// Adds a new state to this oshiro with the given behaviour, and returns the index of the new state. + /// + /// States should always be added at the end of the AngryOshiro(Vector2, bool) constructor. + /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + return ((patch_StateMachine)state).AddState(name, onUpdate, coroutine, begin, end); + } + } +} \ No newline at end of file diff --git a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index 3a62b9b22..6fda97e5e 100644 --- a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -23,44 +23,94 @@ public void ctor(int maxStates = 10) { private int Expand() { int nextIdx = begins.Length; - - Array.Resize(ref begins, begins.Length + 1); - Array.Resize(ref updates, updates.Length + 1); - Array.Resize(ref ends, ends.Length + 1); - Array.Resize(ref coroutines, coroutines.Length + 1); - Array.Resize(ref names, names.Length + 1); + + int newLength = begins.Length + 1; + Array.Resize(ref begins, newLength); + Array.Resize(ref updates, newLength); + Array.Resize(ref ends, newLength); + Array.Resize(ref coroutines, newLength); + Array.Resize(ref names, newLength); return nextIdx; } - - private extern void orig_SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null); - public void SetCallbacks( - int state, - Func onUpdate, - Func coroutine = null, - Action begin = null, - Action end = null) { - orig_SetCallbacks(state, onUpdate, coroutine, begin, end); - names[state] ??= state.ToString(); + + /// + /// Adds a new state to this state machine with the given behaviour, and returns the index of the new state. + /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + int nextIdx = Expand(); + SetCallbacks(nextIdx, onUpdate, coroutine, begin, end); + names[nextIdx] = name; + return nextIdx; } /// - /// Adds a state to this StateMachine. + /// Adds a new state to this state machine with the given behaviour, providing access to the entity running this state machine. + /// + /// It's preferable to use the AddState methods provided in Player, AngryOshiro, and Seeker than to use this directly, as + /// they ensure the correct type is used. If the entity has the wrong type, then these functions are given null. /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The type of the entity that these functions run on. If the entity has the wrong type, the functions are given null. /// The index of the new state. - public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null) + where E : Entity + { int nextIdx = Expand(); - SetCallbacks(nextIdx, () => onUpdate(Entity), - coroutine == null ? null : () => coroutine(Entity), - begin == null ? null : () => begin(Entity), - end == null ? null : () => end(Entity)); + SetCallbacks(nextIdx, () => onUpdate(Entity as E), + coroutine == null ? null : () => coroutine(Entity as E), + begin == null ? null : () => begin(Entity as E), + end == null ? null : () => end(Entity as E)); names[nextIdx] = name; return nextIdx; } - public string GetStateName(int state) => names[state]; - public void SetStateName(int state, string name) => names[state] = name; - - public string GetCurrentStateName() => names[State]; + // Mods that expand the state machine manually won't increase the size of `names`, so we need to bounds-check + // accesses ourselves, both against `begins` ("is it an actual valid state") and `names` ("does it have a coherent name") + private void CheckBounds(int state) { + if (!(state < begins.Length && state >= 0)) + throw new IndexOutOfRangeException($"State {state} is out of range, maximum is {begins.Length}."); + } + + /// + /// Returns the name of the state with the given index, which defaults to the index in string form. + /// These names are for display purposes by mods only. + /// + /// The index of the state. + /// The display name of that state. + public string GetStateName(int state) { + CheckBounds(state); + return (state < names.Length ? names[state] : null) ?? state.ToString(); + } + + /// + /// Sets the name of the state with the given index. + /// These names are for display purposes by mods only. + /// + /// The index of the state. + /// The new display name it should use. + public void SetStateName(int state, string name) { + CheckBounds(state); + if (state >= names.Length) + Array.Resize(ref names, state + 1); + names[state] = name; + } + + /// + /// Returns the name of the current state, which defaults to the index in string form. + /// These names are for display purposes by mods only. + /// + /// The display name of the current state. + public string GetCurrentStateName() => GetStateName(State); } } \ No newline at end of file diff --git a/Celeste.Mod.mm/Patches/Player.cs b/Celeste.Mod.mm/Patches/Player.cs index bd03d3ce5..f82e934e4 100644 --- a/Celeste.Mod.mm/Patches/Player.cs +++ b/Celeste.Mod.mm/Patches/Player.cs @@ -14,6 +14,7 @@ using MonoMod.Cil; using MonoMod.InlineRT; using MonoMod.Utils; +using System.Collections; namespace Celeste { class patch_Player : Player { @@ -179,6 +180,21 @@ private Color GetTrailColor(bool wasDashB) { return wasDashB ? NormalBadelineHairColor : UsedBadelineHairColor; return wasDashB ? NormalHairColor : UsedHairColor; } + + /// + /// Adds a new state to this player with the given behaviour, and returns the index of the new state. + /// + /// States should always be added at the end of the Player constructor. + /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + return ((patch_StateMachine)StateMachine).AddState(name, onUpdate, coroutine, begin, end); + } public Vector2 ExplodeLaunch(Vector2 from, bool snapUp = true) { return ExplodeLaunch(from, snapUp, false); diff --git a/Celeste.Mod.mm/Patches/Seeker.cs b/Celeste.Mod.mm/Patches/Seeker.cs new file mode 100644 index 000000000..916604908 --- /dev/null +++ b/Celeste.Mod.mm/Patches/Seeker.cs @@ -0,0 +1,33 @@ +using Microsoft.Xna.Framework; +using Monocle; +using System; +using System.Collections; + +namespace Celeste.Patches { + public class patch_Seeker : Seeker { + + // We're effectively in Seeker, but still need to "expose" private fields to our mod. + private StateMachine State; + + // no-op - only here to make + public patch_Seeker(EntityData data, Vector2 offset) + : base(data, offset) { + // no-op. MonoMod ignores this - we only need this to make the compiler shut up. + } + + /// + /// Adds a new state to this seeker with the given behaviour, and returns the index of the new state. + /// + /// States should always be added at the end of the Seeker(Vector2, Vector2[]) constructor. + /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + return ((patch_StateMachine)State).AddState(name, onUpdate, coroutine, begin, end); + } + } +} \ No newline at end of file From d02939a7a44ece10f4f1f2cb2c05010083bda3fd Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 15:29:46 +0100 Subject: [PATCH 07/28] fix warnings --- Celeste.Mod.mm/Patches/AngryOshiro.cs | 4 +++- Celeste.Mod.mm/Patches/Monocle/StateMachine.cs | 4 +++- Celeste.Mod.mm/Patches/Seeker.cs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Celeste.Mod.mm/Patches/AngryOshiro.cs b/Celeste.Mod.mm/Patches/AngryOshiro.cs index 7789ace31..c9101a4e1 100644 --- a/Celeste.Mod.mm/Patches/AngryOshiro.cs +++ b/Celeste.Mod.mm/Patches/AngryOshiro.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null + +using Microsoft.Xna.Framework; using Monocle; using System; using System.Collections; diff --git a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index 6fda97e5e..a472255e0 100644 --- a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -1,4 +1,6 @@ -using MonoMod; +#pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it + +using MonoMod; using System; using System.Collections; diff --git a/Celeste.Mod.mm/Patches/Seeker.cs b/Celeste.Mod.mm/Patches/Seeker.cs index 916604908..3bf2893bf 100644 --- a/Celeste.Mod.mm/Patches/Seeker.cs +++ b/Celeste.Mod.mm/Patches/Seeker.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null + +using Microsoft.Xna.Framework; using Monocle; using System; using System.Collections; From fbd08d83095ac7a0abde68eb0ac437ccfa02b27c Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Thu, 3 Aug 2023 15:54:36 +0800 Subject: [PATCH 08/28] Make Add(entity) and Remove(entity) methods to crash when entity is null, so modders can catch bug in time --- Celeste.Mod.mm/Patches/Monocle/EntityList.cs | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Celeste.Mod.mm/Patches/Monocle/EntityList.cs b/Celeste.Mod.mm/Patches/Monocle/EntityList.cs index 8e6bab233..d9afad552 100644 --- a/Celeste.Mod.mm/Patches/Monocle/EntityList.cs +++ b/Celeste.Mod.mm/Patches/Monocle/EntityList.cs @@ -33,7 +33,16 @@ internal void ClearEntities() { [MonoModIgnore] [PatchEntityListUpdateLists] internal extern void UpdateLists(); + + [MonoModIgnore] + [PatchEntityListAddAndRemove] + internal extern void Add(Entity entity); + + [MonoModIgnore] + [PatchEntityListAddAndRemove] + internal extern void Remove(Entity entity); } + public static class EntityListExt { // Mods can't access patch_ classes directly. @@ -61,6 +70,13 @@ class PatchEntityListUpdateAttribute : Attribute { } [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchEntityListUpdateLists))] class PatchEntityListUpdateListsAttribute : Attribute { } + /// + /// Make Add(entity) and Remove(entity) methods to crash when entity is null + /// so modders can catch bugs in time + /// + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchEntityListAddAndRemove))] + class PatchEntityListAddAndRemoveAttribute : Attribute { } + static partial class MonoModRules { public static void PatchEntityListUpdate(ILContext context, CustomAttribute attrib) { @@ -115,5 +131,24 @@ public static void PatchEntityListUpdateLists(ILContext context, CustomAttribute cursor.Next.Operand = hashRemoveOperand; } + public static void PatchEntityListAddAndRemove(ILContext context, CustomAttribute attrib) { + // insert the following code at the beginning of the method + // if (entity == null) throw new ArgumentNullException("entity") + + TypeDefinition t_ArgumentNullException = MonoModRule.Modder.FindType("System.ArgumentNullException").Resolve(); + MethodReference ctor_ArgumentNullException = MonoModRule.Modder.Module.ImportReference(t_ArgumentNullException.FindMethod("System.Void .ctor(System.String)")); + + ILCursor cursor = new ILCursor(context); + ILLabel label = cursor.DefineLabel(); + cursor.Emit(OpCodes.Ldarg_1); + cursor.Emit(OpCodes.Ldnull); + cursor.Emit(OpCodes.Ceq); + cursor.Emit(OpCodes.Brfalse_S, label); + cursor.Emit(OpCodes.Ldstr, "entity"); + cursor.Emit(OpCodes.Newobj, ctor_ArgumentNullException); + cursor.Emit(OpCodes.Throw); + cursor.MarkLabel(label); + } + } } From 5bf82ebef8abad24711198ee55865ada976efb74 Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Thu, 3 Aug 2023 17:30:25 +0800 Subject: [PATCH 09/28] Corrected modifiers from internal to public --- Celeste.Mod.mm/Patches/Monocle/EntityList.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/EntityList.cs b/Celeste.Mod.mm/Patches/Monocle/EntityList.cs index d9afad552..385c7fe6b 100644 --- a/Celeste.Mod.mm/Patches/Monocle/EntityList.cs +++ b/Celeste.Mod.mm/Patches/Monocle/EntityList.cs @@ -32,15 +32,15 @@ internal void ClearEntities() { [MonoModIgnore] [PatchEntityListUpdateLists] - internal extern void UpdateLists(); + public extern void UpdateLists(); [MonoModIgnore] [PatchEntityListAddAndRemove] - internal extern void Add(Entity entity); + public extern void Add(Entity entity); [MonoModIgnore] [PatchEntityListAddAndRemove] - internal extern void Remove(Entity entity); + public extern void Remove(Entity entity); } public static class EntityListExt { From 27cfed555fadc0fd0737a498b5c3a9a776e024b0 Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Mon, 7 Aug 2023 21:41:03 +0800 Subject: [PATCH 10/28] Clean the il code --- Celeste.Mod.mm/Patches/Monocle/EntityList.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/EntityList.cs b/Celeste.Mod.mm/Patches/Monocle/EntityList.cs index 385c7fe6b..edb5b42b9 100644 --- a/Celeste.Mod.mm/Patches/Monocle/EntityList.cs +++ b/Celeste.Mod.mm/Patches/Monocle/EntityList.cs @@ -140,14 +140,12 @@ public static void PatchEntityListAddAndRemove(ILContext context, CustomAttribut ILCursor cursor = new ILCursor(context); ILLabel label = cursor.DefineLabel(); - cursor.Emit(OpCodes.Ldarg_1); - cursor.Emit(OpCodes.Ldnull); - cursor.Emit(OpCodes.Ceq); - cursor.Emit(OpCodes.Brfalse_S, label); - cursor.Emit(OpCodes.Ldstr, "entity"); - cursor.Emit(OpCodes.Newobj, ctor_ArgumentNullException); - cursor.Emit(OpCodes.Throw); - cursor.MarkLabel(label); + cursor.Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Brtrue_S, label) + .Emit(OpCodes.Ldstr, "entity") + .Emit(OpCodes.Newobj, ctor_ArgumentNullException) + .Emit(OpCodes.Throw) + .MarkLabel(label); } } From 9b1acde11fd24fafce3d13e4a83982f60cd3ddd4 Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Thu, 7 Sep 2023 17:15:07 +0800 Subject: [PATCH 11/28] Fix game crashes when everest settings file is corrupted --- Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs | 7 ++++++- Celeste.Mod.mm/Mod/Everest/ButtonBinding.cs | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs b/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs index 2c7e9e802..83506f22b 100644 --- a/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs +++ b/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs @@ -296,8 +296,13 @@ public string CurrentBranch { set => _CurrentBranch = value is "dev" or "beta" or "stable" ? "updater_src_" + value : value; // branch names were changed at some point } + private Dictionary _LogLevels = new Dictionary(); + [SettingIgnore] - public Dictionary LogLevels { get; set; } = new Dictionary(); + public Dictionary LogLevels { + get => _LogLevels; + set => _LogLevels = value ?? new Dictionary(); + } [SettingSubHeader("MODOPTIONS_COREMODULE_MENUNAV_SUBHEADER")] [SettingInGame(false)] diff --git a/Celeste.Mod.mm/Mod/Everest/ButtonBinding.cs b/Celeste.Mod.mm/Mod/Everest/ButtonBinding.cs index c90687635..5ae91e93f 100644 --- a/Celeste.Mod.mm/Mod/Everest/ButtonBinding.cs +++ b/Celeste.Mod.mm/Mod/Everest/ButtonBinding.cs @@ -18,17 +18,17 @@ public class ButtonBinding { public List Buttons { get => Binding.Controller; - set => Binding.Controller = value; + set => Binding.Controller = value ?? new List(); } public List Keys { get => Binding.Keyboard; - set => Binding.Keyboard = value; + set => Binding.Keyboard = value ?? new List(); } public List MouseButtons { get => ((patch_Binding) Binding).Mouse; - set => ((patch_Binding) Binding).Mouse = value; + set => ((patch_Binding) Binding).Mouse = value ?? new List(); } private Binding _Binding; From 7ba9eaf1fe1555464c9f91fa519149a36742d97b Mon Sep 17 00:00:00 2001 From: l-Luna Date: Fri, 28 Apr 2023 23:27:33 +0100 Subject: [PATCH 12/28] work on statemachine --- Celeste.Mod.mm/Patches/StateMachine.cs | 67 ++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Celeste.Mod.mm/Patches/StateMachine.cs diff --git a/Celeste.Mod.mm/Patches/StateMachine.cs b/Celeste.Mod.mm/Patches/StateMachine.cs new file mode 100644 index 000000000..154ba145a --- /dev/null +++ b/Celeste.Mod.mm/Patches/StateMachine.cs @@ -0,0 +1,67 @@ +using Monocle; +using MonoMod; +using System; +using System.Collections; + +namespace Celeste { + public class patch_StateMachine : StateMachine { + + // We're effectively in StateMachine, but still need to "expose" private fields to our mod. + private Action[] begins; + private Func[] updates; + private Action[] ends; + private Func[] coroutines; + + // Keep track of state's names. + private string[] names; + + public extern void orig_ctor(int maxStates = 10); + [MonoModConstructor] + public void ctor(int maxStates = 10) { + orig_ctor(maxStates); + names = new string[maxStates]; + } + + private int Expand() { + int nextIdx = begins.Length; + + Array.Resize(ref begins, begins.Length + 1); + Array.Resize(ref updates, updates.Length + 1); + Array.Resize(ref ends, ends.Length + 1); + Array.Resize(ref coroutines, coroutines.Length + 1); + Array.Resize(ref names, names.Length + 1); + + return nextIdx; + } + + public extern void orig_SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null); + public void SetCallbacks( + int state, + Func onUpdate, + Func coroutine = null, + Action begin = null, + Action end = null) { + orig_SetCallbacks(state, onUpdate, coroutine, begin, end); + names[state] = state.ToString(); + } + + /// + /// Adds a state to this StateMachine. + /// + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + int nextIdx = Expand(); + SetCallbacks(nextIdx, () => onUpdate(Entity), + () => coroutine?.Invoke(Entity), + () => begin?.Invoke(Entity), + () => end?.Invoke(Entity)); + names[nextIdx] = name; + return nextIdx; + } + + public string GetState(int state) => names[state]; + public void SetStateName(int state, string name) => names[state] = name; + + public string GetCurrentStateName() => names[State]; + } +} \ No newline at end of file From a77dc2ff40fb9e6daed70feaaf42033c3a4556f5 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 12:26:10 +0100 Subject: [PATCH 13/28] now actually patches --- Celeste.Mod.mm/Patches/StateMachine.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Celeste.Mod.mm/Patches/StateMachine.cs b/Celeste.Mod.mm/Patches/StateMachine.cs index 154ba145a..e579e25cd 100644 --- a/Celeste.Mod.mm/Patches/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/StateMachine.cs @@ -51,10 +51,10 @@ public void SetCallbacks( /// The index of the new state. public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ int nextIdx = Expand(); - SetCallbacks(nextIdx, () => onUpdate(Entity), - () => coroutine?.Invoke(Entity), - () => begin?.Invoke(Entity), - () => end?.Invoke(Entity)); + SetCallbacks(nextIdx, Helper.WrapFunc(onUpdate, this), + Helper.WrapFunc(coroutine, this), + Helper.WrapAction(begin, this), + Helper.WrapAction(end, this)); names[nextIdx] = name; return nextIdx; } @@ -64,4 +64,9 @@ public int AddState(string name, Func onUpdate, Func names[State]; } + + internal static class Helper { + internal static Func WrapFunc(Func f, Component c) => f == null ? null : () => f(c.Entity); + internal static Action WrapAction(Action a, Component c) => a == null ? null : () => a(c.Entity); + } } \ No newline at end of file From 66f4e9fb1db706924bd830d29e60477afa2e2614 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 12:31:42 +0100 Subject: [PATCH 14/28] actually correct namespace --- .../Patches/{ => Monocle}/StateMachine.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) rename Celeste.Mod.mm/Patches/{ => Monocle}/StateMachine.cs (80%) diff --git a/Celeste.Mod.mm/Patches/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs similarity index 80% rename from Celeste.Mod.mm/Patches/StateMachine.cs rename to Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index e579e25cd..2dfd471ac 100644 --- a/Celeste.Mod.mm/Patches/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -1,9 +1,8 @@ -using Monocle; -using MonoMod; +using MonoMod; using System; using System.Collections; -namespace Celeste { +namespace Monocle { public class patch_StateMachine : StateMachine { // We're effectively in StateMachine, but still need to "expose" private fields to our mod. @@ -51,10 +50,10 @@ public void SetCallbacks( /// The index of the new state. public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ int nextIdx = Expand(); - SetCallbacks(nextIdx, Helper.WrapFunc(onUpdate, this), - Helper.WrapFunc(coroutine, this), - Helper.WrapAction(begin, this), - Helper.WrapAction(end, this)); + SetCallbacks(nextIdx, () => onUpdate(Entity), + coroutine == null ? null : () => coroutine(Entity), + begin == null ? null : () => begin(Entity), + end == null ? null : () => end(Entity)); names[nextIdx] = name; return nextIdx; } @@ -64,9 +63,4 @@ public int AddState(string name, Func onUpdate, Func names[State]; } - - internal static class Helper { - internal static Func WrapFunc(Func f, Component c) => f == null ? null : () => f(c.Entity); - internal static Action WrapAction(Action a, Component c) => a == null ? null : () => a(c.Entity); - } } \ No newline at end of file From 4e6cfb99271dcd2702230fef01ab43acbe5fd338 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 12:33:57 +0100 Subject: [PATCH 15/28] don't override names in `SetCallbacks` if they already exist --- Celeste.Mod.mm/Patches/Monocle/StateMachine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index 2dfd471ac..94723b0f0 100644 --- a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -41,7 +41,7 @@ public void SetCallbacks( Action begin = null, Action end = null) { orig_SetCallbacks(state, onUpdate, coroutine, begin, end); - names[state] = state.ToString(); + names[state] ??= state.ToString(); } /// From fe8bfff40f18aef8d174b5f862ced786c9b6832b Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 12:39:34 +0100 Subject: [PATCH 16/28] cleanup --- Celeste.Mod.mm/Patches/Monocle/StateMachine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index 94723b0f0..3a62b9b22 100644 --- a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -14,7 +14,7 @@ public class patch_StateMachine : StateMachine { // Keep track of state's names. private string[] names; - public extern void orig_ctor(int maxStates = 10); + private extern void orig_ctor(int maxStates = 10); [MonoModConstructor] public void ctor(int maxStates = 10) { orig_ctor(maxStates); @@ -33,7 +33,7 @@ private int Expand() { return nextIdx; } - public extern void orig_SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null); + private extern void orig_SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null); public void SetCallbacks( int state, Func onUpdate, @@ -58,7 +58,7 @@ public int AddState(string name, Func onUpdate, Func names[state]; + public string GetStateName(int state) => names[state]; public void SetStateName(int state, string name) => names[state] = name; public string GetCurrentStateName() => names[State]; From def087d1f20b3b347e2b792464a4709f890a7336 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 14:46:23 +0100 Subject: [PATCH 17/28] fix crashes, add type-safe methods for Player/Seeker/Oshiro states --- Celeste.Mod.mm/Patches/AngryOshiro.cs | 32 ++++++ .../Patches/Monocle/StateMachine.cs | 102 +++++++++++++----- Celeste.Mod.mm/Patches/Player.cs | 16 +++ Celeste.Mod.mm/Patches/Seeker.cs | 33 ++++++ 4 files changed, 157 insertions(+), 26 deletions(-) create mode 100644 Celeste.Mod.mm/Patches/AngryOshiro.cs create mode 100644 Celeste.Mod.mm/Patches/Seeker.cs diff --git a/Celeste.Mod.mm/Patches/AngryOshiro.cs b/Celeste.Mod.mm/Patches/AngryOshiro.cs new file mode 100644 index 000000000..7789ace31 --- /dev/null +++ b/Celeste.Mod.mm/Patches/AngryOshiro.cs @@ -0,0 +1,32 @@ +using Microsoft.Xna.Framework; +using Monocle; +using System; +using System.Collections; + +namespace Celeste.Patches { + public class patch_AngryOshiro : AngryOshiro { + + // We're effectively in AngryOshiro, but still need to "expose" private fields to our mod. + private StateMachine state; + + public patch_AngryOshiro(EntityData data, Vector2 offset) + : base(data, offset) { + // no-op. MonoMod ignores this - we only need this to make the compiler shut up. + } + + /// + /// Adds a new state to this oshiro with the given behaviour, and returns the index of the new state. + /// + /// States should always be added at the end of the AngryOshiro(Vector2, bool) constructor. + /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + return ((patch_StateMachine)state).AddState(name, onUpdate, coroutine, begin, end); + } + } +} \ No newline at end of file diff --git a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index 3a62b9b22..6fda97e5e 100644 --- a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -23,44 +23,94 @@ public void ctor(int maxStates = 10) { private int Expand() { int nextIdx = begins.Length; - - Array.Resize(ref begins, begins.Length + 1); - Array.Resize(ref updates, updates.Length + 1); - Array.Resize(ref ends, ends.Length + 1); - Array.Resize(ref coroutines, coroutines.Length + 1); - Array.Resize(ref names, names.Length + 1); + + int newLength = begins.Length + 1; + Array.Resize(ref begins, newLength); + Array.Resize(ref updates, newLength); + Array.Resize(ref ends, newLength); + Array.Resize(ref coroutines, newLength); + Array.Resize(ref names, newLength); return nextIdx; } - - private extern void orig_SetCallbacks(int state, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null); - public void SetCallbacks( - int state, - Func onUpdate, - Func coroutine = null, - Action begin = null, - Action end = null) { - orig_SetCallbacks(state, onUpdate, coroutine, begin, end); - names[state] ??= state.ToString(); + + /// + /// Adds a new state to this state machine with the given behaviour, and returns the index of the new state. + /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + int nextIdx = Expand(); + SetCallbacks(nextIdx, onUpdate, coroutine, begin, end); + names[nextIdx] = name; + return nextIdx; } /// - /// Adds a state to this StateMachine. + /// Adds a new state to this state machine with the given behaviour, providing access to the entity running this state machine. + /// + /// It's preferable to use the AddState methods provided in Player, AngryOshiro, and Seeker than to use this directly, as + /// they ensure the correct type is used. If the entity has the wrong type, then these functions are given null. /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The type of the entity that these functions run on. If the entity has the wrong type, the functions are given null. /// The index of the new state. - public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null) + where E : Entity + { int nextIdx = Expand(); - SetCallbacks(nextIdx, () => onUpdate(Entity), - coroutine == null ? null : () => coroutine(Entity), - begin == null ? null : () => begin(Entity), - end == null ? null : () => end(Entity)); + SetCallbacks(nextIdx, () => onUpdate(Entity as E), + coroutine == null ? null : () => coroutine(Entity as E), + begin == null ? null : () => begin(Entity as E), + end == null ? null : () => end(Entity as E)); names[nextIdx] = name; return nextIdx; } - public string GetStateName(int state) => names[state]; - public void SetStateName(int state, string name) => names[state] = name; - - public string GetCurrentStateName() => names[State]; + // Mods that expand the state machine manually won't increase the size of `names`, so we need to bounds-check + // accesses ourselves, both against `begins` ("is it an actual valid state") and `names` ("does it have a coherent name") + private void CheckBounds(int state) { + if (!(state < begins.Length && state >= 0)) + throw new IndexOutOfRangeException($"State {state} is out of range, maximum is {begins.Length}."); + } + + /// + /// Returns the name of the state with the given index, which defaults to the index in string form. + /// These names are for display purposes by mods only. + /// + /// The index of the state. + /// The display name of that state. + public string GetStateName(int state) { + CheckBounds(state); + return (state < names.Length ? names[state] : null) ?? state.ToString(); + } + + /// + /// Sets the name of the state with the given index. + /// These names are for display purposes by mods only. + /// + /// The index of the state. + /// The new display name it should use. + public void SetStateName(int state, string name) { + CheckBounds(state); + if (state >= names.Length) + Array.Resize(ref names, state + 1); + names[state] = name; + } + + /// + /// Returns the name of the current state, which defaults to the index in string form. + /// These names are for display purposes by mods only. + /// + /// The display name of the current state. + public string GetCurrentStateName() => GetStateName(State); } } \ No newline at end of file diff --git a/Celeste.Mod.mm/Patches/Player.cs b/Celeste.Mod.mm/Patches/Player.cs index be0380efa..eda0d245d 100644 --- a/Celeste.Mod.mm/Patches/Player.cs +++ b/Celeste.Mod.mm/Patches/Player.cs @@ -14,6 +14,7 @@ using MonoMod.Cil; using MonoMod.InlineRT; using MonoMod.Utils; +using System.Collections; namespace Celeste { class patch_Player : Player { @@ -179,6 +180,21 @@ private Color GetTrailColor(bool wasDashB) { return wasDashB ? NormalBadelineHairColor : UsedBadelineHairColor; return wasDashB ? NormalHairColor : UsedHairColor; } + + /// + /// Adds a new state to this player with the given behaviour, and returns the index of the new state. + /// + /// States should always be added at the end of the Player constructor. + /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + return ((patch_StateMachine)StateMachine).AddState(name, onUpdate, coroutine, begin, end); + } public Vector2 ExplodeLaunch(Vector2 from, bool snapUp = true) { return ExplodeLaunch(from, snapUp, false); diff --git a/Celeste.Mod.mm/Patches/Seeker.cs b/Celeste.Mod.mm/Patches/Seeker.cs new file mode 100644 index 000000000..916604908 --- /dev/null +++ b/Celeste.Mod.mm/Patches/Seeker.cs @@ -0,0 +1,33 @@ +using Microsoft.Xna.Framework; +using Monocle; +using System; +using System.Collections; + +namespace Celeste.Patches { + public class patch_Seeker : Seeker { + + // We're effectively in Seeker, but still need to "expose" private fields to our mod. + private StateMachine State; + + // no-op - only here to make + public patch_Seeker(EntityData data, Vector2 offset) + : base(data, offset) { + // no-op. MonoMod ignores this - we only need this to make the compiler shut up. + } + + /// + /// Adds a new state to this seeker with the given behaviour, and returns the index of the new state. + /// + /// States should always be added at the end of the Seeker(Vector2, Vector2[]) constructor. + /// + /// The name of this state, for display purposes by mods only. + /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. + /// A function that creates a coroutine to run when this state is switched to. + /// An action to run when this state is switched to. + /// An action to run when this state ends. + /// The index of the new state. + public int AddState(string name, Func onUpdate, Func coroutine = null, Action begin = null, Action end = null){ + return ((patch_StateMachine)State).AddState(name, onUpdate, coroutine, begin, end); + } + } +} \ No newline at end of file From 16f661fd112e34ef1d44bc730c2b7a0801cb571e Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sat, 29 Apr 2023 15:29:46 +0100 Subject: [PATCH 18/28] fix warnings --- Celeste.Mod.mm/Patches/AngryOshiro.cs | 4 +++- Celeste.Mod.mm/Patches/Monocle/StateMachine.cs | 4 +++- Celeste.Mod.mm/Patches/Seeker.cs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Celeste.Mod.mm/Patches/AngryOshiro.cs b/Celeste.Mod.mm/Patches/AngryOshiro.cs index 7789ace31..c9101a4e1 100644 --- a/Celeste.Mod.mm/Patches/AngryOshiro.cs +++ b/Celeste.Mod.mm/Patches/AngryOshiro.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null + +using Microsoft.Xna.Framework; using Monocle; using System; using System.Collections; diff --git a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs index 6fda97e5e..a472255e0 100644 --- a/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs +++ b/Celeste.Mod.mm/Patches/Monocle/StateMachine.cs @@ -1,4 +1,6 @@ -using MonoMod; +#pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it + +using MonoMod; using System; using System.Collections; diff --git a/Celeste.Mod.mm/Patches/Seeker.cs b/Celeste.Mod.mm/Patches/Seeker.cs index 916604908..3bf2893bf 100644 --- a/Celeste.Mod.mm/Patches/Seeker.cs +++ b/Celeste.Mod.mm/Patches/Seeker.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null + +using Microsoft.Xna.Framework; using Monocle; using System; using System.Collections; From f64508e969050bc798b477d343e9535c3443f978 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sun, 24 Sep 2023 15:27:26 +0100 Subject: [PATCH 19/28] implement vanilla state names + events for registering states --- Celeste.Mod.mm/Mod/Everest/Everest.Events.cs | 18 +++++++ Celeste.Mod.mm/Patches/AngryOshiro.cs | 23 ++++++++- Celeste.Mod.mm/Patches/Player.cs | 54 ++++++++++++++++++-- Celeste.Mod.mm/Patches/Seeker.cs | 29 +++++++++-- 4 files changed, 113 insertions(+), 11 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Events.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Events.cs index 7bdb55b2b..222574c99 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Events.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Events.cs @@ -7,6 +7,8 @@ using _OuiJournal = Celeste.OuiJournal; using _OuiMainMenu = Celeste.OuiMainMenu; using _Player = Celeste.Player; +using _Seeker = Celeste.Seeker; +using _AngryOshiro = Celeste.AngryOshiro; namespace Celeste.Mod { public static partial class Everest { @@ -168,6 +170,22 @@ internal static void Spawn(_Player player) public static event Action<_Player> OnDie; internal static void Die(_Player player) => OnDie?.Invoke(player); + + public static event Action<_Player> OnRegisterStates; + internal static void RegisterStates(_Player player) + => OnRegisterStates?.Invoke(player); + } + + public static class Seeker { + public static event Action<_Seeker> OnRegisterStates; + internal static void RegisterStates(_Seeker seeker) + => OnRegisterStates?.Invoke(seeker); + } + + public static class AngryOshiro { + public static event Action<_AngryOshiro> OnRegisterStates; + internal static void RegisterStates(_AngryOshiro oshiro) + => OnRegisterStates?.Invoke(oshiro); } public static class Input { diff --git a/Celeste.Mod.mm/Patches/AngryOshiro.cs b/Celeste.Mod.mm/Patches/AngryOshiro.cs index c9101a4e1..9feffa362 100644 --- a/Celeste.Mod.mm/Patches/AngryOshiro.cs +++ b/Celeste.Mod.mm/Patches/AngryOshiro.cs @@ -1,7 +1,10 @@ -#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null +#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 null +using Celeste.Mod; using Microsoft.Xna.Framework; using Monocle; +using MonoMod; using System; using System.Collections; @@ -15,11 +18,27 @@ public patch_AngryOshiro(EntityData data, Vector2 offset) : base(data, offset) { // no-op. MonoMod ignores this - we only need this to make the compiler shut up. } + + public extern void orig_ctor(Vector2 position, bool fromCutscene); + [MonoModConstructor] + public void ctor(Vector2 position, bool fromCutscene) { + orig_ctor(position, fromCutscene); + + // setup vanilla state names + ((patch_StateMachine) state).SetStateName(0, "Chase"); + ((patch_StateMachine) state).SetStateName(1, "ChargeUp"); + ((patch_StateMachine) state).SetStateName(2, "Attack"); + ((patch_StateMachine) state).SetStateName(3, "Dummy"); + ((patch_StateMachine) state).SetStateName(4, "Waiting"); + ((patch_StateMachine) state).SetStateName(5, "Hurt"); + // then allow mods to register new ones + Everest.Events.AngryOshiro.RegisterStates(this); + } /// /// Adds a new state to this oshiro with the given behaviour, and returns the index of the new state. /// - /// States should always be added at the end of the AngryOshiro(Vector2, bool) constructor. + /// States should always be added during the Events.AngryOshiro.OnRegisterStates event. /// /// The name of this state, for display purposes by mods only. /// A function to run every frame during this state, returning the index of the state that should be switched to next frame. diff --git a/Celeste.Mod.mm/Patches/Player.cs b/Celeste.Mod.mm/Patches/Player.cs index eda0d245d..5dc6a5106 100644 --- a/Celeste.Mod.mm/Patches/Player.cs +++ b/Celeste.Mod.mm/Patches/Player.cs @@ -55,9 +55,41 @@ public patch_Player(Vector2 position, PlayerSpriteMode spriteMode) [MonoModIgnore] [MonoModConstructor] - [PatchPlayerCtorOnFrameChange] + [PatchPlayerCtor] public extern void ctor(Vector2 position, PlayerSpriteMode spriteMode); + private void PostCtor() { + // setup vanilla state names + ((patch_StateMachine) StateMachine).SetStateName(StNormal, "Normal"); + ((patch_StateMachine) StateMachine).SetStateName(StClimb, "Climb"); + ((patch_StateMachine) StateMachine).SetStateName(StDash, "Dash"); + ((patch_StateMachine) StateMachine).SetStateName(StSwim, "Swim"); + ((patch_StateMachine) StateMachine).SetStateName(StBoost, "Boost"); + ((patch_StateMachine) StateMachine).SetStateName(StRedDash, "RedDash"); + ((patch_StateMachine) StateMachine).SetStateName(StHitSquash, "HitSquash"); + ((patch_StateMachine) StateMachine).SetStateName(StLaunch, "Launch"); + ((patch_StateMachine) StateMachine).SetStateName(StPickup, "Pickup"); + ((patch_StateMachine) StateMachine).SetStateName(StDreamDash, "DreamDash"); + ((patch_StateMachine) StateMachine).SetStateName(StSummitLaunch, "SummitLaunch"); + ((patch_StateMachine) StateMachine).SetStateName(StDummy, "Dummy"); + ((patch_StateMachine) StateMachine).SetStateName(StIntroWalk, "IntroWalk"); + ((patch_StateMachine) StateMachine).SetStateName(StIntroJump, "IntroJump"); + ((patch_StateMachine) StateMachine).SetStateName(StIntroRespawn, "IntroRespawn"); + ((patch_StateMachine) StateMachine).SetStateName(StIntroWakeUp, "IntroWakeUp"); + ((patch_StateMachine) StateMachine).SetStateName(StBirdDashTutorial, "BirdDashTutorial"); + ((patch_StateMachine) StateMachine).SetStateName(StFrozen, "Frozen"); + ((patch_StateMachine) StateMachine).SetStateName(StReflectionFall, "ReflectionFall"); + ((patch_StateMachine) StateMachine).SetStateName(StStarFly, "StarFly"); + ((patch_StateMachine) StateMachine).SetStateName(StTempleFall, "TempleFall"); + ((patch_StateMachine) StateMachine).SetStateName(StCassetteFly, "CassetteFly"); + ((patch_StateMachine) StateMachine).SetStateName(StAttract, "Attract"); + ((patch_StateMachine) StateMachine).SetStateName(StIntroMoonJump, "IntroMoonJump"); + ((patch_StateMachine) StateMachine).SetStateName(StFlingBird, "FlingBird"); + ((patch_StateMachine) StateMachine).SetStateName(StIntroThinkForABit, "IntroThinkForABit"); + // then allow mods to register new ones + Everest.Events.Player.RegisterStates(this); + } + public extern void orig_Added(Scene scene); public override void Added(Scene scene) { if (OverrideIntroType != null) { @@ -293,10 +325,11 @@ class PatchPlayerClimbBegin : Attribute { } class PatchPlayerOrigWallJumpAttribute : Attribute { } /// - /// Patches the method to un-hardcode the FMOD event string used to play the footstep and grab sound effect. + /// Patches the method to un-hardcode the FMOD event string used to play the footstep and grab sound effect, + /// and handle player state management. /// - [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchPlayerCtorOnFrameChange))] - class PatchPlayerCtorOnFrameChangeAttribute : Attribute { } + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchPlayerCtor))] + class PatchPlayerCtorAttribute : Attribute { } /// /// Patches the method to fix puffer boosts breaking on respawn. @@ -439,7 +472,18 @@ public static void PatchPlayerOrigWallJump(ILContext context, CustomAttribute at cursor.Remove(); } - public static void PatchPlayerCtorOnFrameChange(MethodDefinition method, CustomAttribute attrib) { + public static void PatchPlayerCtor(MethodDefinition method, CustomAttribute attrib) { + // We need to run player state management code just after the constructor, but can't use regular hooking + // as many mods IL hook the constructor already. + new ILContext(method).Invoke(il => { + MethodDefinition m_Player_PostCtor = il.Module.GetType("Celeste.Player").FindMethod("PostCtor"); + ILCursor cursor = new ILCursor(il); + cursor.Index = -1; + cursor.Emit(OpCodes.Ldarg_0); + cursor.Emit(OpCodes.Callvirt, m_Player_PostCtor); + }); + + // then hook another method given the context available. method = method.DeclaringType.FindMethod("<.ctor>b__280_1"); new ILContext(method).Invoke(il => { diff --git a/Celeste.Mod.mm/Patches/Seeker.cs b/Celeste.Mod.mm/Patches/Seeker.cs index 3bf2893bf..cdafd1f27 100644 --- a/Celeste.Mod.mm/Patches/Seeker.cs +++ b/Celeste.Mod.mm/Patches/Seeker.cs @@ -1,22 +1,43 @@ -#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null +#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 null +using Celeste.Mod; using Microsoft.Xna.Framework; using Monocle; +using MonoMod; using System; using System.Collections; namespace Celeste.Patches { public class patch_Seeker : Seeker { - + // We're effectively in Seeker, but still need to "expose" private fields to our mod. private StateMachine State; - + // no-op - only here to make public patch_Seeker(EntityData data, Vector2 offset) : base(data, offset) { // no-op. MonoMod ignores this - we only need this to make the compiler shut up. } - + + public extern void orig_ctor(Vector2 position, Vector2[] patrolPoints); + [MonoModConstructor] + public void ctor(Vector2 position, Vector2[] patrolPoints) { + orig_ctor(position, patrolPoints); + + // setup vanilla state names + ((patch_StateMachine) State).SetStateName(0, "Idle"); + ((patch_StateMachine) State).SetStateName(1, "Patrol"); + ((patch_StateMachine) State).SetStateName(2, "Spotted"); + ((patch_StateMachine) State).SetStateName(3, "Attack"); + ((patch_StateMachine) State).SetStateName(4, "Stunned"); + ((patch_StateMachine) State).SetStateName(5, "Skidding"); + ((patch_StateMachine) State).SetStateName(6, "Regenerate"); + ((patch_StateMachine) State).SetStateName(7, "Returned"); + // then allow mods to register new ones + Everest.Events.Seeker.RegisterStates(this); + } + /// /// Adds a new state to this seeker with the given behaviour, and returns the index of the new state. /// From 85827ea835660117e7d51bae20c9703c409f04a6 Mon Sep 17 00:00:00 2001 From: l-Luna Date: Sun, 24 Sep 2023 16:11:40 +0100 Subject: [PATCH 20/28] fix incorrect namespaces on patch classes --- Celeste.Mod.mm/Patches/AngryOshiro.cs | 2 +- Celeste.Mod.mm/Patches/Seeker.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Celeste.Mod.mm/Patches/AngryOshiro.cs b/Celeste.Mod.mm/Patches/AngryOshiro.cs index 9feffa362..9afb0125c 100644 --- a/Celeste.Mod.mm/Patches/AngryOshiro.cs +++ b/Celeste.Mod.mm/Patches/AngryOshiro.cs @@ -8,7 +8,7 @@ using System; using System.Collections; -namespace Celeste.Patches { +namespace Celeste { public class patch_AngryOshiro : AngryOshiro { // We're effectively in AngryOshiro, but still need to "expose" private fields to our mod. diff --git a/Celeste.Mod.mm/Patches/Seeker.cs b/Celeste.Mod.mm/Patches/Seeker.cs index cdafd1f27..84d802253 100644 --- a/Celeste.Mod.mm/Patches/Seeker.cs +++ b/Celeste.Mod.mm/Patches/Seeker.cs @@ -8,7 +8,7 @@ using System; using System.Collections; -namespace Celeste.Patches { +namespace Celeste { public class patch_Seeker : Seeker { // We're effectively in Seeker, but still need to "expose" private fields to our mod. From 3f537b58923cb0761094b60a673e70d46f7f9068 Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Mon, 16 Oct 2023 21:35:19 +0800 Subject: [PATCH 21/28] scene.Remove(null) should be allowed --- Celeste.Mod.mm/Patches/Monocle/EntityList.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Monocle/EntityList.cs b/Celeste.Mod.mm/Patches/Monocle/EntityList.cs index 0905516a8..ed5406084 100644 --- a/Celeste.Mod.mm/Patches/Monocle/EntityList.cs +++ b/Celeste.Mod.mm/Patches/Monocle/EntityList.cs @@ -35,12 +35,8 @@ internal void ClearEntities() { public extern void UpdateLists(); [MonoModIgnore] - [PatchEntityListAddAndRemove] + [PatchEntityListAdd] public extern void Add(Entity entity); - - [MonoModIgnore] - [PatchEntityListAddAndRemove] - public extern void Remove(Entity entity); } public static class EntityListExt { @@ -69,11 +65,11 @@ class PatchEntityListUpdateAttribute : Attribute { } class PatchEntityListUpdateListsAttribute : Attribute { } /// - /// Make Add(entity) and Remove(entity) methods to crash when entity is null + /// Make Add(entity) method to crash when entity is null /// so modders can catch bugs in time /// - [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchEntityListAddAndRemove))] - class PatchEntityListAddAndRemoveAttribute : Attribute { } + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchEntityListAdd))] + class PatchEntityListAddAttribute : Attribute { } static partial class MonoModRules { @@ -129,7 +125,7 @@ public static void PatchEntityListUpdateLists(ILContext context, CustomAttribute cursor.Next.Operand = hashRemoveOperand; } - public static void PatchEntityListAddAndRemove(ILContext context, CustomAttribute attrib) { + public static void PatchEntityListAdd(ILContext context, CustomAttribute attrib) { // insert the following code at the beginning of the method // if (entity == null) throw new ArgumentNullException("entity") From fda4a9caa09d03c41ebd05d7a9b02ce000942992 Mon Sep 17 00:00:00 2001 From: Kalobi <46748261+Kalobi@users.noreply.github.com> Date: Sun, 15 Oct 2023 03:51:43 +0200 Subject: [PATCH 22/28] Prevent pausing when closing console with Esc --- Celeste.Mod.mm/Patches/Monocle/Commands.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Celeste.Mod.mm/Patches/Monocle/Commands.cs b/Celeste.Mod.mm/Patches/Monocle/Commands.cs index 7d484a580..8b8f52802 100644 --- a/Celeste.Mod.mm/Patches/Monocle/Commands.cs +++ b/Celeste.Mod.mm/Patches/Monocle/Commands.cs @@ -210,6 +210,7 @@ internal void Render() { private void HandleKey(Keys key) { // this method handles all control characters, which go through the XNA Keys API if (key == Keys.Escape) { + MInput.Keyboard.CurrentState = new KeyboardState(Keys.Escape); Open = canOpen = false; return; } From d734c4ecbe821bc09dc4a6175502c1153fd58251 Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Wed, 18 Oct 2023 16:22:12 +0800 Subject: [PATCH 23/28] Add 'Toggle Debug Console' hotkey, '~' as default keys --- Celeste.Mod.mm/Content/Dialog/English.txt | 17 +++++++++-------- Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs | 6 +++++- Celeste.Mod.mm/Patches/KeyboardConfigUI.cs | 3 ++- Celeste.Mod.mm/Patches/Monocle/Commands.cs | 11 ++++++++--- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index 6c590dbe3..c544596f1 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -34,7 +34,7 @@ MENU_MAPLIST= Map List MENU_MODOPTIONS= Mod Options MENU_PAUSE_MODOPTIONS= Mod Options - + MENU_MODOPTIONS_UPDATE_FAILED= Failed to install Everest Update MENU_MODOPTIONS_ONE_MOD_FAILEDTOLOAD= 1 mod failed to load MENU_MODOPTIONS_MULTIPLE_MODS_FAILEDTOLOAD= {0} mods failed to load @@ -45,7 +45,7 @@ # Title Screen MENU_TITLESCREEN_RESTART_VANILLA= Restarting into orig/Celeste.exe - + # Extra Key Mapping KEY_CONFIG_ADDING= PRESS ADDITIONAL KEY FOR KEY_CONFIG_ADDITION_HINT= Press SHIFT + CONFIRM to add or remove additional keys @@ -86,7 +86,8 @@ MODOPTIONS_COREMODULE_MENUPAGEUP= Page Up in Menus MODOPTIONS_COREMODULE_MENUPAGEDOWN= Page Down in Menus MODOPTIONS_COREMODULE_DEBUGMODE_SUBHEADER= DEBUG MODE - MODOPTIONS_COREMODULE_DEBUGCONSOLE= Debug Console + MODOPTIONS_COREMODULE_TOGGLEDEBUGCONSOLE= Toggle Debug Console + MODOPTIONS_COREMODULE_OPENDEBUGCONSOLE= Open Debug Console MODOPTIONS_COREMODULE_DEBUGMAP= Debug Map MODOPTIONS_COREMODULE_MOUNTAINCAM_SUBHEADER= OVERWORLD MOUNTAIN CAMERA MODOPTIONS_COREMODULE_CAMERAFORWARD= Camera Forward @@ -101,7 +102,7 @@ MODOPTIONS_COREMODULE_SOUNDTEST= Sound Test MODOPTIONS_COREMODULE_OOBE= Redo Initial Setup MODOPTIONS_COREMODULE_TOGGLEMODS= Enable or Disable Mods - + MODOPTIONS_COREMODULE_DISCORDRICHPRESENCE= Discord Rich Presence MODOPTIONS_COREMODULE_DISCORDRICHPRESENCEOPTIONS= Rich Presence Options MODOPTIONS_COREMODULE_DISCORDSHOWICON= Show Icon @@ -138,7 +139,7 @@ MAPLIST_SEARCH= Search MAPLIST_SEARCH_MATCH= Quick Match MAPLIST_TYPE_EVERYTHING= Everything - MAPLIST_RESULTS_SINGULAR= {0} result found + MAPLIST_RESULTS_SINGULAR= {0} result found MAPLIST_RESULTS_PLURAL= {0} results found LEVELSET_CELESTE= Celeste @@ -149,7 +150,7 @@ UPDATER_VERSIONS_TITLE= CHANGE EVEREST VERSION UPDATER_VERSIONS_CURRENT= Installed: ((version)) UPDATER_VERSIONS_REQUESTING= Refreshing... - + UPDATER_VERSIONS_ERR_DOWNLOAD= Failed downloading version list. UPDATER_VERSIONS_ERR_FORMAT= Unknown format. @@ -162,7 +163,7 @@ UPDATER_SRC_RELEASE_GITHUB= Tagged releases (GitHub) UPDATER_SRC_BUILDBOT_AZURE= Automatic builds (Azure) - + # currently unused UPDATER_SRC_BUILDBOT= Automatic builds @@ -207,7 +208,7 @@ MODUPDATECHECKER_MENU_HEADER_RESTART= Choose an action MODUPDATECHECKER_SHUTDOWN= Exit MODUPDATECHECKER_RESTART= Restart - + # Auto Mod Updater AUTOUPDATECHECKER_CHECKING= Checking for mod updates... AUTOUPDATECHECKER_UPDATING= Auto-updating diff --git a/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs b/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs index 4221c31ed..136d0464b 100644 --- a/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs +++ b/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs @@ -320,9 +320,13 @@ public Dictionary LogLevels { public ButtonBinding MenuPageDown { get; set; } [SettingSubHeader("MODOPTIONS_COREMODULE_DEBUGMODE_SUBHEADER")] + [SettingInGame(false)] + [DefaultButtonBinding(0, Keys.OemTilde)] + public ButtonBinding ToggleDebugConsole { get; set; } + [SettingInGame(false)] [DefaultButtonBinding(0, Keys.OemPeriod)] - public ButtonBinding DebugConsole { get; set; } + public ButtonBinding OpenDebugConsole { get; set; } [SettingInGame(false)] [DefaultButtonBinding(0, Keys.F6)] diff --git a/Celeste.Mod.mm/Patches/KeyboardConfigUI.cs b/Celeste.Mod.mm/Patches/KeyboardConfigUI.cs index 28697e613..6e86ac27f 100644 --- a/Celeste.Mod.mm/Patches/KeyboardConfigUI.cs +++ b/Celeste.Mod.mm/Patches/KeyboardConfigUI.cs @@ -237,7 +237,8 @@ public void AddRemap(Keys key) { remappingBinding.Keyboard.RemoveAt(0); } Input.Initialize(); - CoreModule.Settings.DebugConsole.ConsumePress(); + CoreModule.Settings.ToggleDebugConsole.ConsumePress(); + CoreModule.Settings.OpenDebugConsole.ConsumePress(); CoreModule.Settings.ToggleMountainFreeCam.ConsumePress(); } diff --git a/Celeste.Mod.mm/Patches/Monocle/Commands.cs b/Celeste.Mod.mm/Patches/Monocle/Commands.cs index 8b8f52802..74edfc3e3 100644 --- a/Celeste.Mod.mm/Patches/Monocle/Commands.cs +++ b/Celeste.Mod.mm/Patches/Monocle/Commands.cs @@ -70,7 +70,7 @@ internal void UpdateClosed() { if (!canOpen) { canOpen = true; // Original code only checks OemTilde and Oem8, leaving QWERTZ users in the dark... - } else if (CoreModule.Settings.DebugConsole.Pressed) { + } else if (CoreModule.Settings.OpenDebugConsole.Pressed || CoreModule.Settings.ToggleDebugConsole.Pressed) { Open = true; currentState = Keyboard.GetState(); if (!installedListener) { @@ -209,8 +209,8 @@ internal void Render() { [MonoModReplace] // don't create an orig_ method private void HandleKey(Keys key) { // this method handles all control characters, which go through the XNA Keys API - if (key == Keys.Escape) { - MInput.Keyboard.CurrentState = new KeyboardState(Keys.Escape); + if (key == Keys.Escape || CoreModule.Settings.ToggleDebugConsole.Keys.Contains(key)) { + MInput.Keyboard.CurrentState = currentState; Open = canOpen = false; return; } @@ -375,6 +375,11 @@ private void HandleChar(char key) { return; } + KeyboardState keyboardState = Keyboard.GetState(); + if (CoreModule.Settings.ToggleDebugConsole.Keys.Any(k => keyboardState.IsKeyDown(k))) { + return; + } + currentText = currentText.Substring(0, charIndex) + key + currentText.Substring(charIndex); charIndex++; } From e3260db71b8ef715661420eedd772526f029287e Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Wed, 18 Oct 2023 21:54:53 +0800 Subject: [PATCH 24/28] Rename OpenDebugConsole back to DebugConsole --- Celeste.Mod.mm/Content/Dialog/English.txt | 2 +- Celeste.Mod.mm/Content/Dialog/Simplified Chinese.txt | 5 +++-- Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs | 2 +- Celeste.Mod.mm/Patches/KeyboardConfigUI.cs | 2 +- Celeste.Mod.mm/Patches/Monocle/Commands.cs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index c544596f1..6dff3d939 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -87,7 +87,7 @@ MODOPTIONS_COREMODULE_MENUPAGEDOWN= Page Down in Menus MODOPTIONS_COREMODULE_DEBUGMODE_SUBHEADER= DEBUG MODE MODOPTIONS_COREMODULE_TOGGLEDEBUGCONSOLE= Toggle Debug Console - MODOPTIONS_COREMODULE_OPENDEBUGCONSOLE= Open Debug Console + MODOPTIONS_COREMODULE_DEBUGCONSOLE= Open Debug Console MODOPTIONS_COREMODULE_DEBUGMAP= Debug Map MODOPTIONS_COREMODULE_MOUNTAINCAM_SUBHEADER= OVERWORLD MOUNTAIN CAMERA MODOPTIONS_COREMODULE_CAMERAFORWARD= Camera Forward diff --git a/Celeste.Mod.mm/Content/Dialog/Simplified Chinese.txt b/Celeste.Mod.mm/Content/Dialog/Simplified Chinese.txt index b4df57c70..ce089cea6 100644 --- a/Celeste.Mod.mm/Content/Dialog/Simplified Chinese.txt +++ b/Celeste.Mod.mm/Content/Dialog/Simplified Chinese.txt @@ -80,7 +80,8 @@ MODOPTIONS_COREMODULE_MENUPAGEUP= 向上翻页 MODOPTIONS_COREMODULE_MENUPAGEDOWN= 向下翻页 MODOPTIONS_COREMODULE_DEBUGMODE_SUBHEADER= 调试模式 - MODOPTIONS_COREMODULE_DEBUGCONSOLE= 调试控制台 + MODOPTIONS_COREMODULE_TOGGLEDEBUGCONSOLE= 开关调试控制台 + MODOPTIONS_COREMODULE_DEBUGCONSOLE= 打开调试控制台 MODOPTIONS_COREMODULE_DEBUGMAP= 调试地图 MODOPTIONS_COREMODULE_MOUNTAINCAM_SUBHEADER= 选关界面镜头 MODOPTIONS_COREMODULE_CAMERAFORWARD= 向前移动 @@ -133,7 +134,7 @@ UPDATER_VERSIONS_TITLE= 更改 Everest 版本 UPDATER_VERSIONS_CURRENT= 当前版本:((version)) UPDATER_VERSIONS_REQUESTING= 刷新中… - + UPDATER_VERSIONS_ERR_DOWNLOAD= 下载版本列表失败。 UPDATER_VERSIONS_ERR_FORMAT= 未知格式。 diff --git a/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs b/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs index 136d0464b..cdb294fee 100644 --- a/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs +++ b/Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs @@ -326,7 +326,7 @@ public Dictionary LogLevels { [SettingInGame(false)] [DefaultButtonBinding(0, Keys.OemPeriod)] - public ButtonBinding OpenDebugConsole { get; set; } + public ButtonBinding DebugConsole { get; set; } [SettingInGame(false)] [DefaultButtonBinding(0, Keys.F6)] diff --git a/Celeste.Mod.mm/Patches/KeyboardConfigUI.cs b/Celeste.Mod.mm/Patches/KeyboardConfigUI.cs index 6e86ac27f..12e549693 100644 --- a/Celeste.Mod.mm/Patches/KeyboardConfigUI.cs +++ b/Celeste.Mod.mm/Patches/KeyboardConfigUI.cs @@ -238,7 +238,7 @@ public void AddRemap(Keys key) { } Input.Initialize(); CoreModule.Settings.ToggleDebugConsole.ConsumePress(); - CoreModule.Settings.OpenDebugConsole.ConsumePress(); + CoreModule.Settings.DebugConsole.ConsumePress(); CoreModule.Settings.ToggleMountainFreeCam.ConsumePress(); } diff --git a/Celeste.Mod.mm/Patches/Monocle/Commands.cs b/Celeste.Mod.mm/Patches/Monocle/Commands.cs index 74edfc3e3..d2218062e 100644 --- a/Celeste.Mod.mm/Patches/Monocle/Commands.cs +++ b/Celeste.Mod.mm/Patches/Monocle/Commands.cs @@ -70,7 +70,7 @@ internal void UpdateClosed() { if (!canOpen) { canOpen = true; // Original code only checks OemTilde and Oem8, leaving QWERTZ users in the dark... - } else if (CoreModule.Settings.OpenDebugConsole.Pressed || CoreModule.Settings.ToggleDebugConsole.Pressed) { + } else if (CoreModule.Settings.DebugConsole.Pressed || CoreModule.Settings.ToggleDebugConsole.Pressed) { Open = true; currentState = Keyboard.GetState(); if (!installedListener) { From 388878997415963bb9ad2a6e9b77aa7fdba7d7ef Mon Sep 17 00:00:00 2001 From: Maddie <52103563+maddie480@users.noreply.github.com> Date: Thu, 19 Oct 2023 22:24:54 +0200 Subject: [PATCH 25/28] Revert "Fix non-even decal rendering (#624)" This reverts commit 275405c16fec4575eeec6fe3013321a9871779b2. --- Celeste.Mod.mm/Patches/Decal.cs | 227 +++----------------------------- 1 file changed, 17 insertions(+), 210 deletions(-) diff --git a/Celeste.Mod.mm/Patches/Decal.cs b/Celeste.Mod.mm/Patches/Decal.cs index f66190bf6..7ca68d7da 100644 --- a/Celeste.Mod.mm/Patches/Decal.cs +++ b/Celeste.Mod.mm/Patches/Decal.cs @@ -84,6 +84,7 @@ public patch_CoreSwapImage(MTexture hot, MTexture cold) : base(active: false, vi } [MonoModIgnore] + [PatchDecalImageRender] public extern override void Render(); } @@ -109,11 +110,8 @@ public override void Update() { } public override void Render() { - if (activeTextures.Count > 0) { - MTexture texture = activeTextures[(int) frame % activeTextures.Count]; - Vector2 offset = new Vector2(texture.Center.X * Decal.scale.X % 1, texture.Center.Y * Decal.scale.X % 1); - texture.DrawCentered(Decal.Position + offset, Decal.Color, Decal.scale, Decal.Rotation); - } + if (activeTextures.Count > 0) + activeTextures[(int) frame % activeTextures.Count].DrawCentered(Decal.Position, Decal.Color, Decal.scale, Decal.Rotation); } } @@ -154,15 +152,15 @@ public void ctor(string texture, Vector2 position, Vector2 scale, int depth, flo [MonoModIgnore] [MonoModPublic] - [PatchMirrorMaskRender] + [PatchMirrorMaskRotation] public extern void MakeMirror(string path, bool keepOffsetsClose); [MonoModIgnore] - [PatchMirrorMaskRender] + [PatchMirrorMaskRotation] private extern void MakeMirror(string path, Vector2 offset); [MonoModIgnore] - [PatchMirrorMaskRender] + [PatchMirrorMaskRotation] private extern void MakeMirrorSpecialCase(string path, Vector2 offset); [MonoModIgnore] @@ -352,16 +350,16 @@ namespace MonoMod { class PatchDecalUpdateAttribute : Attribute { } /// - /// Allow decal images to be rotated and correct rendering of images with non-even dimensions. + /// Allow decal images to be rotated. /// [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchDecalImageRender))] class PatchDecalImageRenderAttribute : Attribute { } /// - /// Allow mirror masks to be rotated and correct rendering of masks with non-even dimensions. + /// Allow mirror masks to be rotated. /// - [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchMirrorMaskRender))] - class PatchMirrorMaskRenderAttribute : Attribute { } + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchMirrorMaskRotation))] + class PatchMirrorMaskRotationAttribute : Attribute { } static partial class MonoModRules { @@ -384,120 +382,21 @@ public static void PatchDecalUpdate(ILContext context, CustomAttribute attrib) { public static void PatchDecalImageRender(ILContext context, CustomAttribute attrib) { TypeDefinition t_Decal = MonoModRule.Modder.FindType("Celeste.Decal").Resolve(); TypeDefinition t_MTexture = MonoModRule.Modder.FindType("Monocle.MTexture").Resolve(); - TypeDefinition t_Vector2 = MonoModRule.Modder.FindType("Microsoft.Xna.Framework.Vector2").Resolve(); - TypeDefinition t_Matrix = MonoModRule.Modder.FindType("Microsoft.Xna.Framework.Matrix").Resolve(); - // This is not allowed to be a TypeDefinition for ?? reasons - TypeReference t_float = MonoModRule.Modder.Module.ImportReference(MonoModRule.Modder.FindType("System.Single").Resolve()); - FieldReference f_Decal_Rotation = t_Decal.FindField("Rotation"); FieldReference f_Decal_Color = t_Decal.FindField("Color"); - FieldReference f_Decal_scale = t_Decal.FindField("scale"); - - MethodReference m_get_Center = t_MTexture.FindProperty("Center").GetMethod; - - MethodReference m_Vector2_ctor = MonoModRule.Modder.Module.ImportReference(t_Vector2.FindMethod("System.Void .ctor(System.Single,System.Single)")); - MethodReference m_Vector2_Transform = MonoModRule.Modder.Module.ImportReference(t_Vector2.FindMethod("Microsoft.Xna.Framework.Vector2 Transform(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Matrix)")); - FieldReference f_Vector2_X = MonoModRule.Modder.Module.ImportReference(t_Vector2.FindField("X")); - FieldReference f_Vector2_Y = MonoModRule.Modder.Module.ImportReference(t_Vector2.FindField("Y")); - - MethodReference m_Matrix_CreateRotationZ = MonoModRule.Modder.Module.ImportReference(t_Matrix.FindMethod("Microsoft.Xna.Framework.Matrix CreateRotationZ(System.Single)")); - - // These methods vary based on what class we're patching + MethodReference m_get_Decal = null; MethodReference m_Draw_old = null; - // Create some extra locals to make the math easier - VariableDefinition v_vector = new VariableDefinition(MonoModRule.Modder.Module.ImportReference(t_Vector2)); - VariableDefinition v_X = new VariableDefinition(t_float); - VariableDefinition v_Y = new VariableDefinition(t_float); - context.Body.Variables.Add(v_vector); - context.Body.Variables.Add(v_X); - context.Body.Variables.Add(v_Y); - ILCursor cursor = new ILCursor(context); - // Part one: fix rendering for decals with non-integer centers (i.e. non-even dimensions) - - // Jump to just after the decal texture is put on the stack. - // Because the various decal components have different methods of finding their texture, - // it's not possible to match the instruction that loads the texture itself. - // Instead, we can just look for the instructions that put the decal position on the stack, - // and put our cursor just before that, since the decal texture immediately precedes position - // in the arguments being made to MTexture.Draw(). - // Also, we collect the MethodReference for the Decal getter for this component type. - cursor.GotoNext(MoveType.Before, - instr => instr.MatchLdarg(0), - instr => instr.MatchCallvirt(out m_get_Decal), - instr => instr.MatchLdfld("Monocle.Entity", "Position") - ); - - // Duplicate the texture reference on the stack so we leave a copy to be used as an argument to Draw(). - // Then get MTexture.Center, multiply it by a rotation matrix based on decal rotation, and store it in our Vector2 local. - cursor.Emit(OpCodes.Dup); - cursor.Emit(OpCodes.Call, m_get_Center); - cursor.Emit(OpCodes.Ldarg_0); - cursor.Emit(OpCodes.Callvirt, m_get_Decal); - cursor.Emit(OpCodes.Ldfld, f_Decal_Rotation); - cursor.Emit(OpCodes.Ldc_R4, (float) Math.PI / 180); - cursor.Emit(OpCodes.Mul); - cursor.Emit(OpCodes.Call, m_Matrix_CreateRotationZ); - cursor.Emit(OpCodes.Call, m_Vector2_Transform); - cursor.Emit(OpCodes.Stloc_S, v_vector); - - // Get our stored Vector2 back, then get the X component as a float, and store the non-integer component - // of that float in our first float local. This will be used to offset the "real" position when the decal - // renders if the decal's Center is a non-integer. (If the Center is an integer, the offset will be 0.) - cursor.Emit(OpCodes.Ldloc_S, v_vector); - cursor.Emit(OpCodes.Ldfld, f_Vector2_X); - cursor.Emit(OpCodes.Ldarg_0); - cursor.Emit(OpCodes.Callvirt, m_get_Decal); - cursor.Emit(OpCodes.Ldfld, f_Decal_scale); - cursor.Emit(OpCodes.Ldfld, f_Vector2_X); - cursor.Emit(OpCodes.Mul); - cursor.Emit(OpCodes.Ldc_R4, 1f); - cursor.Emit(OpCodes.Rem); - cursor.Emit(OpCodes.Stloc_S, v_X); - - // Same thing but for the Y. - cursor.Emit(OpCodes.Ldloc_S, v_vector); - cursor.Emit(OpCodes.Ldfld, f_Vector2_Y); - cursor.Emit(OpCodes.Ldarg_0); - cursor.Emit(OpCodes.Callvirt, m_get_Decal); - cursor.Emit(OpCodes.Ldfld, f_Decal_scale); - cursor.Emit(OpCodes.Ldfld, f_Vector2_Y); - cursor.Emit(OpCodes.Mul); - cursor.Emit(OpCodes.Ldc_R4, 1f); - cursor.Emit(OpCodes.Rem); - cursor.Emit(OpCodes.Stloc_S, v_Y); - - // Now jump after the position has been put on the stack, so we can store and then modify it. - cursor.GotoNext(MoveType.After, - instr => instr.MatchLdfld("Monocle.Entity", "Position")); - cursor.Emit(OpCodes.Stloc_S, v_vector); - - // Get our stored position back, get the position's X component, and add it to our calculated offset. - cursor.Emit(OpCodes.Ldloc_S, v_vector); - cursor.Emit(OpCodes.Ldfld, f_Vector2_X); - cursor.Emit(OpCodes.Ldloc_S, v_X); - cursor.Emit(OpCodes.Add); - - // And again for Y. - cursor.Emit(OpCodes.Ldloc_S, v_vector); - cursor.Emit(OpCodes.Ldfld, f_Vector2_Y); - cursor.Emit(OpCodes.Ldloc_S, v_Y); - cursor.Emit(OpCodes.Add); - - // Use our offsetted position values to create a new "adjusted" position, which will be used instead. - cursor.Emit(OpCodes.Newobj, m_Vector2_ctor); - - // Part two: inject decal rotation - // move to just after the decal's scale is obtained, but just before the draw call. - // also get a reference to the draw call itself + // also get references to the Decal getter and to the draw call itself cursor.GotoNext(MoveType.After, - instr => instr.MatchLdfld("Celeste.Decal", "scale"), - instr => instr.MatchCallvirt(out m_Draw_old)); + instr => instr.MatchCallvirt(out m_get_Decal), + instr => instr.MatchLdfld("Celeste.Decal", "scale"), + instr => instr.MatchCallvirt(out m_Draw_old)); cursor.Index--; // find an appropriate draw method; it should have the same signature, but also take a float for the rotation as its last argument @@ -523,28 +422,10 @@ public static void PatchDecalImageRender(ILContext context, CustomAttribute attr cursor.Remove(); } - public static void PatchMirrorMaskRender(ILContext context, CustomAttribute attrib) { - TypeDefinition t_Decal = MonoModRule.Modder.FindType("Celeste.Decal").Resolve(); + public static void PatchMirrorMaskRotation(ILContext context, CustomAttribute attrib) { TypeDefinition t_MTexture = MonoModRule.Modder.FindType("Monocle.MTexture").Resolve(); - TypeDefinition t_Vector2 = MonoModRule.Modder.FindType("Microsoft.Xna.Framework.Vector2").Resolve(); - TypeDefinition t_Entity = MonoModRule.Modder.FindType("Monocle.Entity").Resolve(); - TypeDefinition t_Matrix = MonoModRule.Modder.FindType("Microsoft.Xna.Framework.Matrix").Resolve(); - - FieldReference f_Decal_Rotation = t_Decal.FindField("Rotation"); - FieldReference f_Decal_scale = t_Decal.FindField("scale"); - - FieldReference f_Entity_Position = t_Entity.FindField("Position"); - - MethodReference m_get_Center = t_MTexture.FindProperty("Center").GetMethod; + FieldDefinition f_Decal_Rotation = context.Method.DeclaringType.FindField("Rotation"); MethodDefinition m_DrawCentered = t_MTexture.FindMethod("System.Void DrawCentered(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Vector2,System.Single)"); - - MethodReference m_Vector2_ctor = MonoModRule.Modder.Module.ImportReference(t_Vector2.FindMethod("System.Void .ctor(System.Single,System.Single)")); - MethodReference m_Vector2_Transform = MonoModRule.Modder.Module.ImportReference(t_Vector2.FindMethod("Microsoft.Xna.Framework.Vector2 Transform(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Matrix)")); - FieldReference f_Vector2_X = MonoModRule.Modder.Module.ImportReference(t_Vector2.FindField("X")); - FieldReference f_Vector2_Y = MonoModRule.Modder.Module.ImportReference(t_Vector2.FindField("Y")); - - MethodReference m_Matrix_CreateRotationZ = MonoModRule.Modder.Module.ImportReference(t_Matrix.FindMethod("Microsoft.Xna.Framework.Matrix CreateRotationZ(System.Single)")); - MethodReference r_OnRender = null; ILCursor cursor = new ILCursor(context); @@ -557,82 +438,8 @@ public static void PatchMirrorMaskRender(ILContext context, CustomAttribute attr // Some of the patched methods use closure locals so search for those FieldDefinition f_locals = m_OnRender.DeclaringType.FindField("CS$<>8__locals1"); FieldReference f_this = null; - FieldReference f_mask = null; ILCursor cursor = new ILCursor(il); - - // Copies the IL strategy from PatchDecalImageRotationAndCentering, modified to avoid adding locals - - cursor.GotoNext(MoveType.After, - instr => instr.MatchLdfld(out f_mask), - instr => instr.MatchLdarg(0) - ); - - cursor.GotoNext(MoveType.After, - instr => instr.MatchLdfld(out f_this), - instr => instr.MatchLdfld("Monocle.Entity", "Position") - ); - - cursor.Emit(OpCodes.Ldfld, f_Vector2_X); - cursor.Emit(OpCodes.Ldarg_0); - if (f_locals != null) - cursor.Emit(OpCodes.Ldfld, f_locals); - cursor.Emit(OpCodes.Ldfld, f_mask); - cursor.Emit(OpCodes.Call, m_get_Center); - cursor.Emit(OpCodes.Ldarg_0); - if (f_locals != null) - cursor.Emit(OpCodes.Ldfld, f_locals); - cursor.Emit(OpCodes.Ldfld, f_this); - cursor.Emit(OpCodes.Ldfld, f_Decal_Rotation); - cursor.Emit(OpCodes.Ldc_R4, (float) Math.PI / 180); - cursor.Emit(OpCodes.Mul); - cursor.Emit(OpCodes.Call, m_Matrix_CreateRotationZ); - cursor.Emit(OpCodes.Call, m_Vector2_Transform); - cursor.Emit(OpCodes.Ldfld, f_Vector2_X); - cursor.Emit(OpCodes.Ldarg_0); - if (f_locals != null) - cursor.Emit(OpCodes.Ldfld, f_locals); - cursor.Emit(OpCodes.Ldfld, f_this); - cursor.Emit(OpCodes.Ldfld, f_Decal_scale); - cursor.Emit(OpCodes.Ldfld, f_Vector2_X); - cursor.Emit(OpCodes.Mul); - cursor.Emit(OpCodes.Ldc_R4, 1f); - cursor.Emit(OpCodes.Rem); - cursor.Emit(OpCodes.Add); - - cursor.Emit(OpCodes.Ldarg_0); - if (f_locals != null) - cursor.Emit(OpCodes.Ldfld, f_locals); - cursor.Emit(OpCodes.Ldfld, f_this); - cursor.Emit(OpCodes.Ldfld, f_Entity_Position); - cursor.Emit(OpCodes.Ldfld, f_Vector2_Y); - cursor.Emit(OpCodes.Ldarg_0); - if (f_locals != null) - cursor.Emit(OpCodes.Ldfld, f_locals); - cursor.Emit(OpCodes.Ldfld, f_mask); - cursor.Emit(OpCodes.Call, m_get_Center); - cursor.Emit(OpCodes.Ldarg_0); - if (f_locals != null) - cursor.Emit(OpCodes.Ldfld, f_locals); - cursor.Emit(OpCodes.Ldfld, f_this); - cursor.Emit(OpCodes.Ldfld, f_Decal_Rotation); - cursor.Emit(OpCodes.Ldc_R4, (float) Math.PI / 180); - cursor.Emit(OpCodes.Mul); - cursor.Emit(OpCodes.Call, m_Matrix_CreateRotationZ); - cursor.Emit(OpCodes.Call, m_Vector2_Transform); - cursor.Emit(OpCodes.Ldfld, f_Vector2_Y); - cursor.Emit(OpCodes.Ldarg_0); - if (f_locals != null) - cursor.Emit(OpCodes.Ldfld, f_locals); - cursor.Emit(OpCodes.Ldfld, f_this); - cursor.Emit(OpCodes.Ldfld, f_Decal_scale); - cursor.Emit(OpCodes.Ldfld, f_Vector2_X); - cursor.Emit(OpCodes.Mul); - cursor.Emit(OpCodes.Ldc_R4, 1f); - cursor.Emit(OpCodes.Rem); - cursor.Emit(OpCodes.Add); - - cursor.Emit(OpCodes.Newobj, m_Vector2_ctor); // Grab the function's decal reference and move to just before the draw call cursor.GotoNext(MoveType.After, From bb51087bea61b53664961dde6f3eb6e9ac570588 Mon Sep 17 00:00:00 2001 From: maddie480 <52103563+maddie480@users.noreply.github.com> Date: Thu, 19 Oct 2023 22:36:40 +0200 Subject: [PATCH 26/28] Add French translations for debug console keys --- Celeste.Mod.mm/Content/Dialog/French.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Celeste.Mod.mm/Content/Dialog/French.txt b/Celeste.Mod.mm/Content/Dialog/French.txt index b170e05ab..f2e919c45 100755 --- a/Celeste.Mod.mm/Content/Dialog/French.txt +++ b/Celeste.Mod.mm/Content/Dialog/French.txt @@ -82,8 +82,9 @@ MODOPTIONS_COREMODULE_MENUPAGEUP= Page précédente (menus) MODOPTIONS_COREMODULE_MENUPAGEDOWN= Page suivante (menus) MODOPTIONS_COREMODULE_DEBUGMODE_SUBHEADER= MODE DEBUG - MODOPTIONS_COREMODULE_DEBUGCONSOLE= Console de debug - MODOPTIONS_COREMODULE_DEBUGMAP= Carte de debug + MODOPTIONS_COREMODULE_TOGGLEDEBUGCONSOLE= Ouvrir/Fermer la console de debug + MODOPTIONS_COREMODULE_DEBUGCONSOLE= Ouvrir la console de debug + MODOPTIONS_COREMODULE_DEBUGMAP= Ouvrir la carte de debug MODOPTIONS_COREMODULE_MOUNTAINCAM_SUBHEADER= CAMERA DANS LE MENU PRINCIPAL MODOPTIONS_COREMODULE_CAMERAFORWARD= Avancer MODOPTIONS_COREMODULE_CAMERABACKWARD= Reculer From ca21adac401373ca445486aaba8cd81ba91d38f9 Mon Sep 17 00:00:00 2001 From: maddie480 <52103563+maddie480@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:24:24 +0200 Subject: [PATCH 27/28] Fix crash when updating mod after re-entering update scene (thanks Wartori!) --- Celeste.Mod.mm/Mod/UI/OuiModUpdateList.cs | 156 ++++++++-------------- 1 file changed, 52 insertions(+), 104 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModUpdateList.cs b/Celeste.Mod.mm/Mod/UI/OuiModUpdateList.cs index 7191ccdbe..47f8cdb4a 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModUpdateList.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModUpdateList.cs @@ -22,56 +22,27 @@ class OuiModUpdateList : Oui, OuiModOptions.ISubmenu { private float alpha = 0f; - private Task task; - - private Task renderButtonsTask; - private bool shouldRestart = false; - private bool restartMenuAdded = false; - private static List updatableMods = null; - - private static bool ongoingUpdateCancelled = false; + private Task updateTask = null; - private static bool isFetchingDone = false; + private List updatableMods = null; + private bool isFetchingDone = false; + private bool ongoingUpdateCancelled = false; private bool menuOnScreen = false; public override IEnumerator Enter(Oui from) { Everest.Loader.AutoLoadNewMods = false; - menu = new patch_TextMenu(); - // display the title and a dummy "Fetching" button - menu.Add(new TextMenu.Header(Dialog.Clean("MODUPDATECHECKER_MENU_TITLE"))); - - menu.Add(subHeader = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODUPDATECHECKER_MENU_HEADER"))); - - if (updatableMods == null) { - updatableMods = new List(); - task = new Task(() => {checkForUpdates(); isFetchingDone = true;}); - task.Start(); - fetchingButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_FETCHING")); - fetchingButton.Disabled = true; - menu.Add(fetchingButton); - } else if (isFetchingDone) { // mods have been already fetched - fetchingButton = null; - // if there are multiple updates... - if (updatableMods.Count > 1) { - // display an "update all" button at the top of the list - updateAllButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_UPDATE_ALL")); - updateAllButton.Pressed(() => downloadAllMods()); - menu.Add(updateAllButton); - } - foreach (ModUpdateHolder modHolder in updatableMods) { - menu.Add(modHolder.button); // buttons should get automatically generated - } - } else { // fetching button without task - fetchingButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_FETCHING")); - fetchingButton.Disabled = true; - menu.Add(fetchingButton); - } + + menu = new patch_TextMenu { + new TextMenu.Header(Dialog.Clean("MODUPDATECHECKER_MENU_TITLE")), + (subHeader = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODUPDATECHECKER_MENU_HEADER"))), + (fetchingButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_FETCHING")) { Disabled = true }) + }; Scene.Add(menu); @@ -110,8 +81,9 @@ public override IEnumerator Leave(Oui next) { updMod.RemoveButton(); } - task = null; - renderButtonsTask = null; + updateTask = null; + updatableMods = null; + isFetchingDone = false; } public override void Update() { @@ -120,11 +92,11 @@ public override void Update() { return; } - if (renderButtonsTask != null) { - renderButtonsTask.RunSynchronously(); - renderButtonsTask = null; + if (!isFetchingDone && ModUpdaterHelper.IsAsyncUpdateCheckingDone()) { + renderUpdateList(); + isFetchingDone = true; } - + // check if the "press Back to restart" message has to be toggled if (menu.Focused && shouldRestart) { subHeader.TextColor = Color.OrangeRed; @@ -137,8 +109,8 @@ public override void Update() { subHeader.TextColor = Color.Gray; subHeader.Title = Dialog.Clean("MODUPDATECHECKER_MENU_HEADER"); } - - if (Input.MenuCancel.Pressed && !menu.Focused && menuOnScreen) { + + if (Input.MenuCancel.Pressed && !menu.Focused && menuOnScreen) { // cancel any ongoing download (this has no effect if no download is ongoing anyway). ongoingUpdateCancelled = true; @@ -146,8 +118,6 @@ public override void Update() { // cancelling out during check for updates: go back to mod options instead Audio.Play(SFX.ui_main_button_back); Overworld.Goto(); - - renderButtonsTask = null; // make sure no leftover tasks are there } } else if (Input.MenuCancel.Pressed && menu.Focused && Selected) { if (shouldRestart && subRestartHeader != null) { @@ -171,14 +141,15 @@ public override void Render() { } private void addRestartButtons() { - if (restartMenuAdded) return; + if (restartMenuAdded) + return; menu.Add(subRestartHeader = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODUPDATECHECKER_MENU_HEADER_RESTART"))); TextMenu.Button shutdownButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_SHUTDOWN")); shutdownButton.Pressed(() => { new FadeWipe(base.Scene, false, delegate { - Engine.Scene = new Scene(); - Engine.Instance.Exit(); - }); + Engine.Scene = new Scene(); + Engine.Instance.Exit(); + }); }); TextMenu.Button restartButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_RESTART")); restartButton.Pressed(() => Everest.QuickFullRestart()); @@ -190,51 +161,30 @@ private void addRestartButtons() { } } - private void checkForUpdates() { - // 1. Download the mod updates database - Dictionary updateCatalog = null; - updateCatalog = ModUpdaterHelper.DownloadModUpdateList(); - - // 2. Find out what actually has been updated - SortedDictionary availableUpdatesCatalog = new SortedDictionary(); - if (updateCatalog != null) { - availableUpdatesCatalog = ModUpdaterHelper.ListAvailableUpdates(updateCatalog, excludeBlacklist: false); - } - + private void renderUpdateList() { + Logger.Log(LogLevel.Verbose, "OuiModUpdateList", "Rendering updates"); + // remove the "loading" button + menu.Remove(fetchingButton); + fetchingButton = null; - // 3. Render on screen - Logger.Log(LogLevel.Verbose, "OuiModUpdateList", "Rendering updates"); + SortedDictionary availableUpdates = ModUpdaterHelper.GetAsyncLoadedModUpdates(); - - if (updateCatalog == null) { + if (availableUpdates == null) { // display an error message - renderButtonsTask = new Task(() => { - menu.Remove(fetchingButton); - fetchingButton = null; - TextMenu.Button button = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_ERROR")); - button.Disabled = true; - menu.Add(button); - }); - isFetchingDone = false; - updatableMods = null; + menu.Add(new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_ERROR")) { Disabled = true }); return; - } else if (availableUpdatesCatalog.Count == 0) { + } else if (availableUpdates.Count == 0) { // display a dummy "no update available" button - renderButtonsTask = new Task(() => { - menu.Remove(fetchingButton); - fetchingButton = null; - TextMenu.Button button = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_NOUPDATE")); - button.Disabled = true; - menu.Add(button); - }); + menu.Add(new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_NOUPDATE")) { Disabled = true }); return; } List queuedItems = new List(); + updatableMods = new List(); // if there are multiple updates... - if (availableUpdatesCatalog.Count > 1) { + if (availableUpdates.Count > 1) { // display an "update all" button at the top of the list updateAllButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_UPDATE_ALL")); updateAllButton.Pressed(() => downloadAllMods()); @@ -243,15 +193,15 @@ private void checkForUpdates() { } // then, display one button per update - foreach (ModUpdateInfo update in availableUpdatesCatalog.Keys) { - EverestModuleMetadata metadata = availableUpdatesCatalog[update]; + foreach (ModUpdateInfo update in availableUpdates.Keys) { + EverestModuleMetadata metadata = availableUpdates[update]; string versionUpdate = metadata.VersionString; if (metadata.VersionString != update.Version) versionUpdate = $"{metadata.VersionString} > {update.Version}"; - + ModUpdateHolder holder = new ModUpdateHolder(update: update, metadata: metadata, buttonGenerator: () => null); - + Func buttonGenerator = new Func(() => { TextMenu.Button button = new TextMenu.Button( @@ -270,7 +220,7 @@ private void checkForUpdates() { // if there isn't, add it to the list of mods that can be updated via "update all" if (update.xxHash.Count > 1) { button.Disabled = true; - } + } return button; }); @@ -283,15 +233,12 @@ private void checkForUpdates() { } queuedItems.Add(holder.button); - + + } + + foreach (TextMenu.Button button in queuedItems) { + menu.Add(button); } - renderButtonsTask = new Task(() => { - foreach (TextMenu.Button button in queuedItems) { - menu.Remove(fetchingButton); - fetchingButton = null; - menu.Add(button); - } - }); } /// @@ -299,7 +246,7 @@ private void checkForUpdates() { /// /// The relevant info for the mod private void downloadModUpdate(ModUpdateHolder modHolder) { - task = new Task(() => { + updateTask = new Task(() => { bool updateSuccess = doDownloadModUpdate(modHolder.update, modHolder.metadata, modHolder.button); if (updateSuccess) { @@ -324,7 +271,7 @@ private void downloadModUpdate(ModUpdateHolder modHolder) { menu.Focused = true; }); - task.Start(); + updateTask.Start(); } /// @@ -389,7 +336,7 @@ private bool doDownloadModUpdate(ModUpdateInfo update, EverestModuleMetadata mod /// The update info coming from the update server /// The button for that mod shown on the interface /// The path to the zip the update will be downloaded to - private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { + private void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { Logger.Log(LogLevel.Verbose, "OuiModUpdateList", $"Downloading {update.URL} to {zipPath}"); Func progressCallback = (position, length, speed) => { @@ -418,7 +365,7 @@ private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, st /// Downloads all automatically updatable mods /// private void downloadAllMods() { - task = new Task(() => { + updateTask = new Task(() => { // make the menu non-interactive menu.Focused = false; updateAllButton.Disabled = true; @@ -463,7 +410,8 @@ private void downloadAllMods() { // give the menu control back to the player menu.Focused = true; }); - task.Start(); + + updateTask.Start(); } private void focusOn(TextMenu.Button button) { From a782688faf4f5b803259033cfbffba62eb4df358 Mon Sep 17 00:00:00 2001 From: maddie480 <52103563+maddie480@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:55:37 +0200 Subject: [PATCH 28/28] Update French translation --- Celeste.Mod.mm/Content/Dialog/French.txt | 49 +++++++++++++++++++++--- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/Celeste.Mod.mm/Content/Dialog/French.txt b/Celeste.Mod.mm/Content/Dialog/French.txt index 89dcdddd9..453803eb5 100755 --- a/Celeste.Mod.mm/Content/Dialog/French.txt +++ b/Celeste.Mod.mm/Content/Dialog/French.txt @@ -1,16 +1,23 @@ -# Check English.txt for up-to-date notes. +# Check English.txt for up-to-date notes. # Helper Postcards POSTCARD_LEVELGONE= {big}Merci ((player)) !{/big}{n}Mais {#ff1144}((sid)){#} est dans une autre montagne. POSTCARD_LEVELLOADFAILED= {big}Désolé !{/big}{n}Mais {#ff1144}((sid)){#}{n}n'a pas pu être chargé.{n}Envoyez votre {#44adf7}log.txt{#}{n}au créateur de la map. POSTCARD_LEVELNOSPAWN= {big}Oups !{/big}{n}Quelqu'un a mal placé{n}votre {#44adf7}point de départ !{#}{n}Vérifiez que votre map{n}contient une entité {#44adf7}Player{#}. + POSTCARD_BADTILEID= {big}Oups !{/big}{n}Une ((type))tile inexistante dont l'id est {#ff1144}((id)){#} est présente à la position{n}{#ff1144}(((x)), ((y))){#} dans la salle {#ff1144}((room)){#}{n}Vérifiez le fichier {#44adf7}log.txt{#} pour plus d'informations. + POSTCARD_DECALREGERROR= {big}Oups !{/big}{n}Il y a eu une erreur lors de l'application de la propriété{n}{#ff1144}((property)){#} à {#ff1144}((decal)){#}{n}Vous avez peut-être un attribut manquant ou invalide dans votre fichier {#44adf7}DecalRegistry.xml{#}. + POSTCARD_MISSINGTUTORIAL= {big}Oups !{/big}{n}Le tutorial {#ff1144}((tutorial)){#} est introuvable.{n}Vérifiez le fichier {#44adf7}log.txt{#} pour plus d'informations. + POSTCARD_TILEXMLERROR= {big}Oups !{/big}{n}Une erreur est survenue lors de la lecture d'un tileset dans {#ff1144}((path)){#}.{n}Vérifiez le fichier {#44adf7}log.txt{#} pour plus d'informations. + POSTCARD_XMLERROR= {big}Oups !{/big}{n}Il y a une erreur de syntaxe dans {#ff1144}((path)){#}.{n}Vérifiez le fichier {#44adf7}log.txt{#} pour plus d'informations. + POSTCARD_BOSSLASTNODEHIT= {big}Oups !{/big}{n}Le dernier noeud de l'entité {#ff1144}Badeline Boss{#} a été touché, veuillez ajouter un autre noeud en-dehors de la salle de façon à ce que le joueur ne puisse pas l'atteindre. # Main Menu MENU_TITLETOUCH= TOUCH MENU_MAPLIST= Liste des maps MENU_MODOPTIONS= Options des mods MENU_PAUSE_MODOPTIONS= Options des mods - + + MENU_MODOPTIONS_UPDATE_FAILED= L'installation de la mise à jour d'Everest a échoué MENU_MODOPTIONS_ONE_MOD_FAILEDTOLOAD= 1 mod n'a pas pu être chargé MENU_MODOPTIONS_MULTIPLE_MODS_FAILEDTOLOAD= {0} mods n'ont pas pu être chargés MENU_MODOPTIONS_EVEREST_YAML_ERRORS= Erreurs de chargement de everest.yaml @@ -19,7 +26,7 @@ MENU_MODOPTIONS_MOD_UPDATES_AVAILABLE= Mises à jour disponibles pour {0} mods # Title Screen - MENU_TITLESCREEN_RESTART_VANILLA= Lancement de orig/Celeste.exe + MENU_TITLESCREEN_RESTART_VANILLA= Lancement du jeu non modifié # Extra Key Mapping KEY_CONFIG_ADDING= APPUYEZ SUR LA TOUCHE SUPPLÉMENTAIRE POUR @@ -57,8 +64,23 @@ MODOPTIONS_COREMODULE_USEKEYBOARDFORTEXTINPUT= Utiliser le clavier pour saisir du texte MODOPTIONS_COREMODULE_WARNONEVERESTYAMLERRORS= Avertir en cas d'erreur sur everest.yaml MODOPTIONS_COREMODULE_WARNONEVERESTYAMLERRORS_DESC= Utile pour les créateurs de mods + MODOPTIONS_COREMODULE_MENUNAV_SUBHEADER= NAVIGATION DANS LES MENUS MODOPTIONS_COREMODULE_MENUPAGEUP= Page précédente (menus) MODOPTIONS_COREMODULE_MENUPAGEDOWN= Page suivante (menus) + MODOPTIONS_COREMODULE_DEBUGMODE_SUBHEADER= MODE DEBUG + MODOPTIONS_COREMODULE_TOGGLEDEBUGCONSOLE= Ouvrir/Fermer la console de debug + MODOPTIONS_COREMODULE_DEBUGCONSOLE= Ouvrir la console de debug + MODOPTIONS_COREMODULE_DEBUGMAP= Ouvrir la carte de debug + MODOPTIONS_COREMODULE_MOUNTAINCAM_SUBHEADER= CAMERA DANS LE MENU PRINCIPAL + MODOPTIONS_COREMODULE_CAMERAFORWARD= Avancer + MODOPTIONS_COREMODULE_CAMERABACKWARD= Reculer + MODOPTIONS_COREMODULE_CAMERARIGHT= Droite + MODOPTIONS_COREMODULE_CAMERALEFT= Gauche + MODOPTIONS_COREMODULE_CAMERAUP= Haut + MODOPTIONS_COREMODULE_CAMERADOWN= Bas + MODOPTIONS_COREMODULE_CAMERASLOW= Ralentir + MODOPTIONS_COREMODULE_CAMERAPRINT= Ecrire la position dans les logs + MODOPTIONS_COREMODULE_TOGGLEMOUNTAINFREECAM= Activer la Free Cam MODOPTIONS_COREMODULE_SOUNDTEST= Test sons MODOPTIONS_COREMODULE_OOBE= Ouvrir l'écran de premier démarrage @@ -115,8 +137,19 @@ UPDATER_VERSIONS_ERR_DOWNLOAD= Erreur de chargement de la liste des versions. UPDATER_VERSIONS_ERR_FORMAT= Format inconnu. - UPDATER_SRC_BUILDBOT= Constructions automatiques + UPDATER_CURRENT_BRANCH= Branche actuelle + + UPDATER_SRC_STABLE= STABLE + UPDATER_SRC_BETA= BETA + UPDATER_SRC_DEV= DEV + UPDATER_SRC_CORE= CORE + + UPDATER_SRC_RELEASE_GITHUB= Versions publiées sur GitHub + UPDATER_SRC_BUILDBOT_AZURE= Versions compilées automatiquement par Azure +# currently unused + UPDATER_SRC_BUILDBOT= Versions compilées automatiquement + # Everest Updater EVERESTUPDATER_NOTSUPPORTED= La mise à jour n'est pas supportée sur cette plateforme - annulation. EVERESTUPDATER_NOUPDATE= Pas de mise à jour à installer - annulation. @@ -132,6 +165,8 @@ EVERESTUPDATER_RESTARTING= Redémarrage EVERESTUPDATER_RESTARTINGIN= Redémarrage dans {0} EVERESTUPDATER_STARTINGFAILED= Le lancement de l'installateur a échoué. + EVERESTUPDATER_MISSINGRUNTIME_A= Vous devez avoir installé le runtime .NET 7.0 pour pouvoir installer la version .NET Core d'Everest. + EVERESTUPDATER_MISSINGRUNTIME_B= Vous pouvez télécharger le runtime ici : {0} EVERESTUPDATER_ERRORHINT1= Merci de créer une "issue" sur GitHub @ https://github.com/EverestAPI/Everest EVERESTUPDATER_ERRORHINT2= ou de rejoindre le salon #modding_help sur Discord (invitation sur le dépôt GitHub), EVERESTUPDATER_ERRORHINT3= et envoyez votre log.txt. @@ -152,7 +187,10 @@ MODUPDATECHECKER_UPDATE_ALL= Mettre à jour tous les mods MODUPDATECHECKER_UPDATE_ALL_INPROGRESS= Mise à jour des mods en cours... MODUPDATECHECKER_UPDATE_ALL_DONE= Tous les mods ont été mis à jour. - MODUPDATECHECKER_SHUTDOWN= Quitter + MODUPDATECHECKER_RESTARTNEEDED= Celeste doit redémarrer pour continuer + MODUPDATECHECKER_MENU_HEADER_RESTART= Choisissez une action + MODUPDATECHECKER_SHUTDOWN= Fermer + MODUPDATECHECKER_RESTART= Redémarrer # Auto Mod Updater AUTOUPDATECHECKER_CHECKING= Vérification des mises à jour des mods... @@ -173,6 +211,7 @@ DEPENDENCYDOWNLOADER_DONE= terminé. DEPENDENCYDOWNLOADER_DOWNLOAD_DATABASE_FAILED= ERREUR: Le téléchargement de la base de données a échoué. Vérifiez votre log.txt pour plus d'informations. DEPENDENCYDOWNLOADER_MUST_UPDATE_EVEREST= ATTENTION: Certains de vos mods demandent une version plus récente d'Everest. Installez-la depuis le menu "Changer de version d'Everest". + DEPENDENCYDOWNLOADER_NEEDS_CORE_EVEREST= ATTENTION: Certains de vos mods nécessite une version .NET Core d'Everest (en développement). Installez-la depuis le menu "Changer de version d'Everest". DEPENDENCYDOWNLOADER_EVEREST_UPDATE= Everest va être mis à jour vers la version {0} pour que certains mods puissent fonctionner. Appuyez sur Confirmer pour continuer. DEPENDENCYDOWNLOADER_UPDATE_CELESTE= ERREUR: Certains de vos mods ont besoin d'une version plus récente de Celeste pour fonctionner. Merci de mettre à jour votre jeu. DEPENDENCYDOWNLOADER_MOD_NOT_FOUND= ERREUR: {0} n'a pas été trouvé dans la base de données. Merci de l'installer manuellement.