Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flag Dream Block Support #849

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Celeste.Mod.mm/Mod/Entities/ActivateDreamBlocksTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,25 @@ public override void OnEnter(Player player) {
Level level = Scene as Level;
if (activate && !level.Session.Inventory.DreamDash) {
level.Session.Inventory.DreamDash = true;
foreach (DreamBlock dreamBlock in level.Tracker.GetEntities<DreamBlock>()) {
foreach (patch_DreamBlock dreamBlock in level.Tracker.GetEntities<DreamBlock>()) {
if (rumble) {
if (fastAnimation)
dreamBlock.Add(new Coroutine(((patch_DreamBlock) dreamBlock).FastActivate(), true));
dreamBlock.Add(new Coroutine(dreamBlock.UpdateFastRoutine(), true));
else
dreamBlock.Add(new Coroutine(dreamBlock.Activate(), true));
dreamBlock.Add(new Coroutine(dreamBlock.UpdateRoutine(), true));
} else
dreamBlock.ActivateNoRoutine();
dreamBlock.UpdateNoRoutine();
}
} else if (!activate && level.Session.Inventory.DreamDash) {
level.Session.Inventory.DreamDash = false;
foreach (DreamBlock dreamBlock in level.Tracker.GetEntities<DreamBlock>()) {
if (rumble) {
if (fastAnimation)
dreamBlock.Add(new Coroutine(((patch_DreamBlock) dreamBlock).FastDeactivate(), true));
dreamBlock.Add(new Coroutine(((patch_DreamBlock) dreamBlock).UpdateFastRoutine(), true));
else
dreamBlock.Add(new Coroutine(((patch_DreamBlock) dreamBlock).Deactivate(), true));
dreamBlock.Add(new Coroutine(((patch_DreamBlock) dreamBlock).UpdateRoutine(), true));
} else
((patch_DreamBlock) dreamBlock).DeactivateNoRoutine();
((patch_DreamBlock) dreamBlock).UpdateNoRoutine();
}
}
}
Expand Down
209 changes: 206 additions & 3 deletions Celeste.Mod.mm/Patches/DreamBlock.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
#pragma warning disable CS0626 // extern
using Microsoft.Xna.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Monocle;
Expand All @@ -8,21 +9,93 @@
using System.Collections;
using System.Runtime.CompilerServices;
using System.Linq;
using MonoMod.InlineRT;
using MonoMod.Utils;

namespace Celeste {
class patch_DreamBlock : DreamBlock {
static object DreamBlockPatch;

internal Vector2 movementCounter {
[MonoModLinkTo("Celeste.Platform", "get__movementCounter")] get;
}


private bool flagState;
private bool playerHasDreamDash;
private LightOcclude occlude;
private float whiteHeight;
private float whiteFill;
private Shaker shaker;
private Vector2 shake;
private int randomSeed = Calc.Random.Next();
private string? flag;

public bool DeactivatedIsSolid { get; set; }

public string? Flag {
get => flag;
set {
if (Scene is not null) {
if (value is null && flag is not null) {
SceneAs<patch_Level>().NewDreamBlockCounter--;
}
if (value is not null && flag is null) {
SceneAs<patch_Level>().NewDreamBlockCounter++;
}
}
flag = value;
if (Scene is not null) {
CheckFlags();
UpdateNoRoutine();
}
}
}

/// <summary>
/// determine if a dream block is activated.
/// you can add your custom state here by hooking it.
/// also change <see cref="patch_Level.NewDreamBlockCounter"/>,
/// or it may not run correctly.
/// <br /><br />
/// this will not update visual state automatically.
/// if your custom state is changed, update it manually.
/// <br /><br />
/// anyone can add their own state,
/// so better to have a thing similar to <see cref="flagState"/> to determine if you should update visual.
/// <br /><br />
/// as a reference, see how <see cref="Flag"/> was implemented.
/// </summary>
public bool Activated {
[MethodImpl(MethodImplOptions.NoInlining)]
get => Flag is null ? SceneAs<patch_Level>().Session.Inventory.DreamDash : flagState;
}

/// <summary>
/// determine if a dream block can be dash through.
/// mainly used for some temp state that should not change visual state.
/// for example, for Tera Helper, if DreamBlock is Fairy type and Madeline is Dragon type, there will be no effect.
/// then this property returns false.
/// </summary>
public bool ActivatedPlus {
[MethodImpl(MethodImplOptions.NoInlining)]
get => Activated;
}

public override void Removed(Scene scene) {
if (Flag is not null) {
SceneAs<patch_Level>().NewDreamBlockCounter--;
}
base.Removed(scene);
}

[MonoModIgnore]
[PatchDreamBlockAdded]
public override extern void Added(Scene scene);

[MonoModIgnore]
[PatchDreamBlockUpdate]
public override extern void Update();

public patch_DreamBlock(EntityData data, Vector2 offset)
: base(data, offset) {
Expand All @@ -38,10 +111,92 @@ public void ctor(Vector2 position, float width, float height, Vector2? node, boo
ctor(position, width, height, node, fastMoving, oneUse, false);
}

public extern void orig_ctor(EntityData data, Vector2 offset);

[MonoModConstructor]
public void ctor(EntityData data, Vector2 offset) {
orig_ctor(data, offset);
Flag = data.Attr("flag", null);
DeactivatedIsSolid = data.Bool("deactivatedIsSolid", false);
}
public void CheckFlags() {
bool fs = SceneAs<patch_Level>().Session.GetFlag(Flag);
if (flagState != fs) {
flagState = fs;
UpdateNoRoutine();
}
}

/// <summary>
/// Aims to patch <see cref="Added"/>. <see cref="Added"/> has been ilhooked, so we can only patch it in this way.
/// </summary>
internal static bool Init(bool _, patch_DreamBlock self) {
if (self.Flag is not null) {
self.SceneAs<patch_Level>().NewDreamBlockCounter++;
self.flagState = self.SceneAs<patch_Level>().Session.GetFlag(self.Flag);
}
return self.Activated;
}

public void UpdateVisual(bool routine, bool fast) {
if (routine) {
if (fast) {
Add(new Coroutine(UpdateFastRoutine()));
} else {
Add(new Coroutine(UpdateRoutine()));
}
} else {
UpdateRoutine();
}
}

#pragma warning disable CS0618 // obsolete
private static IEnumerator Empty() {
yield break;
}
public void UpdateNoRoutine() {
bool activated = Activated;
if (playerHasDreamDash != activated) {
if (activated) {
ActivateNoRoutine();
} else {
DeactivateNoRoutine();
}
}
}
public IEnumerator UpdateRoutine() {
bool activated = Activated;
if (playerHasDreamDash != activated) {
if (activated) {
return Activate();
} else {
return Deactivate();
}
}
return Empty();
}
public IEnumerator UpdateFastRoutine() {
bool activated = Activated;
if (playerHasDreamDash != activated) {
if (activated) {
return Activate();
} else {
return Deactivate();
}
}
return Empty();
}
#pragma warning restore CS0618

[MonoModIgnore]
[PatchDreamBlockSetup]
public new extern void Setup();

[PatchDreamBlockAddObsolete($"Use {nameof(UpdateNoRoutine)} instead")]
[MonoModIgnore]
public new extern void ActivateNoRoutine();

[Obsolete($"Use {nameof(UpdateNoRoutine)} instead")]
public void DeactivateNoRoutine() {
if (playerHasDreamDash) {
playerHasDreamDash = false;
Expand All @@ -64,6 +219,11 @@ public void DeactivateNoRoutine() {
}
}

[PatchDreamBlockAddObsolete($"Use {nameof(UpdateRoutine)} instead")]
[MonoModIgnore]
public new extern IEnumerator Activate();

[Obsolete($"Use {nameof(UpdateRoutine)} instead")]
public IEnumerator Deactivate() {
Level level = SceneAs<Level>();
yield return 1f;
Expand Down Expand Up @@ -107,6 +267,7 @@ public IEnumerator Deactivate() {
}
}

[Obsolete($"Use {nameof(UpdateFastRoutine)} instead")]
public IEnumerator FastDeactivate() {
Level level = SceneAs<Level>();
yield return null;
Expand Down Expand Up @@ -145,6 +306,7 @@ public IEnumerator FastDeactivate() {
}
}

[Obsolete($"Use {nameof(UpdateFastRoutine)} instead")]
public IEnumerator FastActivate() {
Level level = SceneAs<Level>();
yield return null;
Expand Down Expand Up @@ -203,7 +365,7 @@ private Vector2 PutInside(Vector2 pos) {
// Patch XNA/FNA jank in Tween.OnUpdate lambda
[MonoModPatch("<>c__DisplayClass22_0")]
class patch_AddedLambdas {

[MonoModPatch("<>4__this")]
private patch_DreamBlock _this = default;
private Vector2 start = default, end = default;
Expand Down Expand Up @@ -243,10 +405,51 @@ namespace MonoMod {
/// same results). This fixes issue #556.
/// </summary>
[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchDreamBlockSetup))]
class PatchDreamBlockSetupAttribute : Attribute {}
class PatchDreamBlockSetupAttribute : Attribute { }

[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchDreamBlockAdded))]
class PatchDreamBlockAddedAttribute : Attribute { }

/// <summary>
/// BetterFreezeFrames is il hooking it
/// </summary>
[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchDreamBlockUpdate))]
class PatchDreamBlockUpdateAttribute : Attribute { }

[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchDreamBlockAddObsolete))]
class PatchDreamBlockAddObsolete : Attribute {
public PatchDreamBlockAddObsolete(string v) {
Info = v;
}

public string Info { get; set; }
}

static partial class MonoModRules {

public static void PatchDreamBlockUpdate(ILContext context, CustomAttribute attrib) {
MethodDefinition m_patch_DreamBlock_UpdateHasDreamDash = MonoModRule.Modder.Module.GetType("Celeste.DreamBlock").FindMethod(nameof(Celeste.patch_DreamBlock.CheckFlags));
ILCursor cursor = new(context);
cursor.EmitLdarg0();
cursor.EmitCallvirt(m_patch_DreamBlock_UpdateHasDreamDash);
}
public static void PatchDreamBlockAddObsolete(ILContext context, CustomAttribute attrib) {

var attr = new CustomAttribute(context.Import(typeof(ObsoleteAttribute).GetConstructor(new Type[] { typeof(string) })));

attr.ConstructorArguments.Add(attrib.ConstructorArguments[0]);
context.Method.CustomAttributes.Add(attr);
}
public static void PatchDreamBlockAdded(ILContext context, CustomAttribute attrib) {
ILCursor cursor = new(context);
TypeDefinition t_patch_DreamBlock = MonoModRule.Modder.Module.GetType("Celeste.DreamBlock");
MethodDefinition m_patch_DreamBlock_Init = t_patch_DreamBlock.FindMethod(nameof(Celeste.patch_DreamBlock.Init));
// this.playerHasDreamDash = base.SceneAs<Level>().Session.Inventory.DreamDash;
cursor.GotoNext(MoveType.AfterLabel, i => i.MatchStfld("Celeste.DreamBlock", "playerHasDreamDash"));
cursor.EmitLdarg0();
cursor.EmitCall(m_patch_DreamBlock_Init);
}

public static void PatchDreamBlockSetup(ILContext context, CustomAttribute attrib) {
// Patch instructions before the 'conv.i4' cast to use doubles instead of floats
for (int i = 0; i < context.Instrs.Count; i++) {
Expand Down
25 changes: 14 additions & 11 deletions Celeste.Mod.mm/Patches/Level.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
namespace Celeste {
class patch_Level : Level {

public int NewDreamBlockCounter;

public bool HasNewDreamBlock => NewDreamBlockCounter > 0;
// We're effectively in GameLoader, but still need to "expose" private fields to our mod.
private static EventInstance PauseSnapshot;
public static EventInstance _PauseSnapshot => PauseSnapshot;
Expand Down Expand Up @@ -177,19 +180,19 @@ void Unpause() {
}

Everest.Events.Level.Pause(this, startIndex, minimal, quickReset);
}
}

/// <summary>
/// Forcefully close the pause menu; resume from paused.
/// </summary>
/// </summary>
public void Unpause() {
if (Paused) {
PauseMainMenuOpen = false;
if (Entities.FindFirst<TextMenu>() is patch_TextMenu menu)
menu.CloseAndRun(Everest.SaveSettings(), null);
Paused = false;
Audio.Play("event:/ui/game/unpause");
unpauseTimer = 0.15f;
if (Paused) {
PauseMainMenuOpen = false;
if (Entities.FindFirst<TextMenu>() is patch_TextMenu menu)
menu.CloseAndRun(Everest.SaveSettings(), null);
Paused = false;
Audio.Play("event:/ui/game/unpause");
unpauseTimer = 0.15f;
}
}

Expand Down Expand Up @@ -823,7 +826,7 @@ public static void PatchLevelLoaderDecalCreation(ILContext context, CustomAttrib

FieldDefinition f_DecalData_Rotation = t_DecalData.FindField("Rotation");
FieldDefinition f_DecalData_ColorHex = t_DecalData.FindField("ColorHex");
FieldDefinition f_DecalData_Depth = t_DecalData.FindField("Depth");
FieldDefinition f_DecalData_Depth = t_DecalData.FindField("Depth");

FieldDefinition f_Decal_DepthSetByPlacement = t_Decal.FindField("DepthSetByPlacement");

Expand Down
Loading