Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions Celeste.Mod.mm/Patches/Level.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,58 @@ public static void RegisterLoadOverride(Level level, LoadOverride loadOverride)
[PatchLevelUpdate] // ... except for manually manipulating the method via MonoModRules
public extern new void Update();

[MonoModIgnore] // don't put this in `Level` when we're done
internal static extern void base_FreezeFrameUpdate(); // dummy method, will be replaced with the actual `base.FreezeFrameUpdate` call in the IL patch
[PatchLevelFreezeFrameUpdate] // add the `virtual` flag to this method so it overrides the one in `patch_Scene` properly and calls `base.FreezeFrameUpdate`
public void FreezeFrameUpdate() {
Everest.Events.Level.BeforeFreezeFrameUpdate(this);

// same logic as in `Level.Update` so entities with `TagsExt.FreezeFrameUpdate` and other "special update tags" are handled correctly
if (FrozenOrPaused) {
bool disabled = MInput.Disabled;
MInput.Disabled = false;

if (!Paused) {
foreach (Entity entity in base[Tags.FrozenUpdate]) {
if (entity.Active && entity.TagCheck(TagsExt.FreezeFrameUpdate)) {
entity.Update();
}
}
}
foreach (Entity entity in base[Tags.PauseUpdate]) {
if (entity.Active && entity.TagCheck(TagsExt.FreezeFrameUpdate)) {
entity.Update();
}
}

MInput.Disabled = disabled;
} else if (!Transitioning) {
if (RetryPlayerCorpse == null) {
base_FreezeFrameUpdate();
} else {
foreach (Entity entity in base[Tags.PauseUpdate]) {
if (entity.Active && entity.TagCheck(TagsExt.FreezeFrameUpdate)) {
entity.Update();
}
}
}
} else {
foreach (Entity entity in base[Tags.TransitionUpdate]) {
if (entity.TagCheck(TagsExt.FreezeFrameUpdate)) {
entity.Update();
}
}
}

foreach (PostUpdateHook component in Tracker.GetComponents<PostUpdateHook>()) {
if (component.Entity.Active && component.Entity.TagCheck(TagsExt.FreezeFrameUpdate)) {
component.OnPostUpdate();
}
}

Everest.Events.Level.AfterFreezeFrameUpdate(this);
}
Comment thread
aonkeeper4 marked this conversation as resolved.

/// <summary>
/// Flash the screen a solid color. Respects the user's advanced photosensitivity settings.
/// </summary>
Expand Down Expand Up @@ -797,6 +849,20 @@ internal static void BeforeUpdate(_Level level)
public static event Action<_Level> OnAfterUpdate;
internal static void AfterUpdate(_Level level)
=> OnAfterUpdate?.Invoke(level);

/// <summary>
/// Called at the very beginning of <see cref="patch_Level.FreezeFrameUpdate"/>.
/// </summary>
public static event Action<_Level> OnBeforeFreezeFrameUpdate;
internal static void BeforeFreezeFrameUpdate(_Level level)
=> OnBeforeFreezeFrameUpdate?.Invoke(level);

/// <summary>
/// Called at the very end of <see cref="patch_Level.FreezeFrameUpdate"/>.
/// </summary>
public static event Action<_Level> OnAfterFreezeFrameUpdate;
internal static void AfterFreezeFrameUpdate(_Level level)
=> OnAfterFreezeFrameUpdate?.Invoke(level);
}
}
}
Expand All @@ -820,6 +886,12 @@ class PatchLevelLoaderDecalCreationAttribute : Attribute { }
/// </summary>
[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchLevelUpdate))]
class PatchLevelUpdateAttribute : Attribute { }

/// <summary>
/// Patch our <see cref="Celeste.patch_Level.FreezeFrameUpdate"/> to be marked as <c>virtual</c> so it overrides the base method, and to call the base method instead of the dummy.
/// </summary>
[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchLevelFreezeFrameUpdate))]
class PatchLevelFreezeFrameUpdateAttribute : Attribute { }

/// <summary>
/// Patch the Godzilla-sized level rendering method instead of reimplementing it in Everest.
Expand Down Expand Up @@ -1165,6 +1237,22 @@ ldc.i4.s 9
}
}

public static void PatchLevelFreezeFrameUpdate(ILContext context, CustomAttribute attrib) {
// mark the method as `virtual` so it registers as an override
context.Method.IsVirtual = true;

TypeDefinition t_Scene = context.Method.DeclaringType.BaseType.Resolve();
MethodReference m_Scene_FreezeFrameUpdate = t_Scene.FindMethod("FreezeFrameUpdate");

ILCursor cursor = new ILCursor(context);

// replace the dummy method with the actual call to `base.FreezeFrameUpdate`
cursor.GotoNext(MoveType.Before, instr => instr.MatchCall("Celeste.Level", "base_FreezeFrameUpdate"));
cursor.Remove();
cursor.EmitLdarg0();
cursor.EmitCall(m_Scene_FreezeFrameUpdate);
}

public static void PatchLevelRender(ILContext context, CustomAttribute attrib) {
FieldDefinition f_SubHudRenderer = context.Method.DeclaringType.FindField("SubHudRenderer");

Expand Down
15 changes: 15 additions & 0 deletions Celeste.Mod.mm/Patches/Monocle/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Mono.Cecil.Cil;
using MonoMod;
using MonoMod.Cil;
using MonoMod.InlineRT;
using MonoMod.Utils;
using System;
using System.Linq;
Expand Down Expand Up @@ -201,11 +202,13 @@ class PatchEngineCctorAttribute : Attribute { }
static partial class MonoModRules {
public static void PatchEngineUpdate(ILContext context, CustomAttribute attrib) {
TypeDefinition t_Engine = context.Method.DeclaringType;
TypeDefinition t_Scene = MonoModRule.Modder.FindType("Monocle.Scene").Resolve();
FieldReference f_scene = t_Engine.FindField("scene");
FieldReference sf_TimeRate = t_Engine.FindField("TimeRate");
FieldReference sf_TimeRateB = t_Engine.FindField("TimeRateB");
FieldReference sf_EffectiveTimeRate = t_Engine.FindField("EffectiveTimeRate");
MethodReference m_GetTimeRateComponentMultiplier = t_Engine.FindMethod("GetTimeRateComponentMultiplier");
MethodReference m_Scene_FreezeFrameUpdate = t_Scene.FindMethod("FreezeFrameUpdate");
VariableDefinition v_componentTimeRate = new(context.Import(typeof(float)));
context.Method.Body.Variables.Add(v_componentTimeRate);

Expand Down Expand Up @@ -234,6 +237,18 @@ public static void PatchEngineUpdate(ILContext context, CustomAttribute attrib)
instr => instr.MatchMul());
cursor.EmitLdloc(v_componentTimeRate);
cursor.EmitMul();

// call the current scene's `FreezeFrameUpdate` before `FreezeTimer` is updated
cursor.GotoNext(MoveType.Before,
instr => instr.MatchLdsfld("Monocle.Engine", "FreezeTimer"),
instr => instr.MatchCall("Monocle.Engine", "get_RawDeltaTime"),
instr => instr.MatchSub(),
instr => instr.MatchLdcR4(0f),
instr => instr.MatchCall("System.Math", "Max"),
instr => instr.MatchStsfld("Monocle.Engine", "FreezeTimer"));
cursor.EmitLdarg0();
cursor.EmitLdfld(f_scene);
cursor.EmitCallvirt(m_Scene_FreezeFrameUpdate);
Comment thread
aonkeeper4 marked this conversation as resolved.
}

public static void PatchEngineCctor(ILContext context, CustomAttribute attrib) {
Expand Down
19 changes: 16 additions & 3 deletions Celeste.Mod.mm/Patches/Monocle/Scene.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Celeste.Mod.Registry;
using Celeste;
using Celeste.Mod.Registry;
using MonoMod;
using System;
using System.Collections.Generic;
Expand All @@ -23,8 +24,7 @@ class patch_Scene : Scene {
public new bool OnInterval(float interval, float offset) {
return Math.Floor(((double) TimeActive - offset - Engine.DeltaTime) / interval) < Math.Floor(((double) TimeActive - offset) / interval);
}



/// <summary>
/// Finds all entities created from an EntityData with the specified SID, using the Tracker if possible.
/// </summary>
Expand All @@ -48,5 +48,18 @@ public IEnumerable<Entity> FindEntitiesWithSid(string sid) {
public new virtual void AfterUpdate() {
Interlocked.Exchange(ref OnEndOfFrame, null)?.Invoke();
}

/// <summary>
/// Called during freeze frames instead of the normal <see cref="Scene.Update"/>.
/// </summary>
public virtual void FreezeFrameUpdate() {
if (!Paused) {
foreach (patch_Entity entity in this[TagsExt.FreezeFrameUpdate]) {
if (entity.Active) {
entity.Update();
}
}
}
}
}
}
8 changes: 8 additions & 0 deletions Celeste.Mod.mm/Patches/Tags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class patch_Tags {
public static void Initialize() {
orig_Initialize();
TagsExt.SubHUD = new BitTag("subHUD");
TagsExt.FreezeFrameUpdate = new BitTag("freezeFrameUpdate");
}

}
Expand All @@ -20,5 +21,12 @@ public static class TagsExt {
/// </summary>
public static BitTag SubHUD;

/// <summary>
/// Tag to be used for entities that should update during freeze frames.<br/>
/// If in a <see cref="Level"/>, <see cref="Tags.PauseUpdate"/> is also required for this entity to update during freeze frames when the level is <c>Paused</c>,
/// <see cref="Tags.TransitionUpdate"/> is required to update during a transition, and <see cref="Tags.FrozenUpdate"/> is required to update when the level is <c>Frozen</c>.
/// </summary>
public static BitTag FreezeFrameUpdate;

}
}
Loading