Skip to content

Commit

Permalink
v2.0.7
Browse files Browse the repository at this point in the history
- Auto-Watch now supports CrumbleWallOnRumble
- Auto-Watch FallingBlock now works when HonlyHelper exists
  • Loading branch information
LozenChen committed Oct 4, 2024
1 parent da8f558 commit f2d6863
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 37 deletions.
1 change: 1 addition & 0 deletions Dialog/English.txt
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ TAS_HELPER_AUTO_WATCH_BADELINEORB= Badeline Orb
TAS_HELPER_AUTO_WATCH_BOOSTER= Booster
TAS_HELPER_AUTO_WATCH_BUMPER= Bumper
TAS_HELPER_AUTO_WATCH_CLOUD= Cloud
TAS_HELPER_AUTO_WATCH_CRUMBLEWALLONRUMBLE= Crumble Wall On Rumble
TAS_HELPER_AUTO_WATCH_FALLINGBLOCK= Falling Block
TAS_HELPER_AUTO_WATCH_FLINGBIRD= Fling Bird
TAS_HELPER_AUTO_WATCH_JELLY= Jelly
Expand Down
1 change: 1 addition & 0 deletions Dialog/Simplified Chinese.txt
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ TAS_HELPER_AUTO_WATCH_BADELINEORB= Badeline Orb
TAS_HELPER_AUTO_WATCH_BOOSTER= Booster
TAS_HELPER_AUTO_WATCH_BUMPER= Bumper
TAS_HELPER_AUTO_WATCH_CLOUD= Cloud
TAS_HELPER_AUTO_WATCH_CRUMBLEWALLONRUMBLE= Crumble Wall On Rumble
TAS_HELPER_AUTO_WATCH_FALLINGBLOCK= Falling Block
TAS_HELPER_AUTO_WATCH_FLINGBIRD= Fling Bird
TAS_HELPER_AUTO_WATCH_JELLY= Jelly
Expand Down
4 changes: 2 additions & 2 deletions Source/Gameplay/AutoWatchEntity/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ internal static class Config {

public static Mode Cloud => TasHelperSettings.AutoWatch_Cloud;

public static Mode CrumbleWallOnRumble => TasHelperSettings.AutoWatch_CrumbleWallOnRumble;

public static Mode CrushBlock => TasHelperSettings.AutoWatch_Kevin;

public static Mode CutsceneEntity => TasHelperSettings.AutoWatch_Cutscene;
Expand Down Expand Up @@ -77,8 +79,6 @@ internal static class TODO {

public static Mode Triggers = Mode.Always; // take care of compatibility with simplified triggers , camera trigger in particular

public static Mode RumbleTrigger = Mode.Always; // show its RumbleRoutine

public static Mode CrumblePlatform = Mode.Always; // disappear and respawn

public static Mode Seeker = Mode.Always; // more info
Expand Down
10 changes: 10 additions & 0 deletions Source/Gameplay/AutoWatchEntity/CoreLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,15 @@ internal class AutoWatchRenderer : Component {
public bool PreActive;

public bool PostActive;

public new bool Active {
get {
throw new Exception("Use Pre/PostActive Instead!");
}
set {
throw new Exception("Use Pre/PostActive Instead!");
}
}
public AutoWatchRenderer(RenderMode mode, bool hasUpdate = true, bool hasPreUpdate = false) : base(false, visible: true) {
// the component itself doesn't update (so active = false), but pass its "update" to entity.Pre/PostUpdate (to avoid some OoO issue)
this.mode = mode;
Expand Down Expand Up @@ -193,6 +202,7 @@ public override void Added(Entity entity) {
entity.PostUpdate += this.UpdateWrapper;
// move it here so we don't need to worry about OoO
// updates even if the entity itself is not active
// though this will not be called when level is transitioning, frozen, or paused
}
if (hasPreUpdate) {
entity.PreUpdate += this.PreUpdateWrapper;
Expand Down
214 changes: 214 additions & 0 deletions Source/Gameplay/AutoWatchEntity/Entity/CrumbleWallOnRumble.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using Celeste.Mod.TASHelper.Utils;
using Monocle;

namespace Celeste.Mod.TASHelper.Gameplay.AutoWatchEntity;


internal class CrumbleWallOnRumbleRenderer : AutoWatchTextRenderer {

public CrumbleWallOnRumble crumble;

public bool Initalized = false;

public Dictionary<RumbleTrigger, int> rumbleTriggers = new Dictionary<RumbleTrigger, int>();

public RumbleTrigger currentActiveTrigger = null;

public int listCurrentIndex;

public int listTargetIndex;

public float localTimer;
public CrumbleWallOnRumbleRenderer(RenderMode mode) : base(mode, active: true) { }

public override void Added(Entity entity) {
base.Added(entity);
crumble = entity as CrumbleWallOnRumble;
}

[Initialize]
private static void Initialize() {
LevelExtensions.AddToTracker(typeof(RumbleTrigger));
}

private const string expectedIEnumeratorName = "<RumbleRoutine>d__13";

public void DelayedInitialize() {
// if a (persistent) rumble trigger remove the CrumbleBlock, then we need to do nothing
if (!crumble.Collidable || crumble.Scene is null) {
SleepForever(); // we don't remove it, so when users click on the crumble wall, nothing happens
return;
}

text.Position = crumble.Center;
rumbleTriggers.Clear();
foreach (RumbleTrigger trigger in crumble.Scene.Tracker.GetEntities<RumbleTrigger>()) {
int index = trigger.crumbles.IndexOf(crumble);
if (index != -1) {
rumbleTriggers.Add(trigger, index);
}
}
if (rumbleTriggers.IsEmpty()) {
SleepForever();
return;
}

Initalized = true;
}

public void SleepForever() {
Visible = PostActive = hasUpdate = false;
if (text is not null) {
HiresLevelRenderer.Remove(text);
}
}

public override void UpdateImpl() {
if (!Initalized) {
DelayedInitialize();
if (!Initalized) {
return;
}
}
if (!crumble.Collidable || crumble.Scene is null) {
SleepForever();
return;
}
int fastestCurrentIndex = -1;
float fastestWaitTimer = 9999f;
bool triggerChanged = false;
if (rumbleTriggers.Count(x => x.Key.started) == 1) {
if (currentActiveTrigger is null || !currentActiveTrigger.started) { // how can it be not started
currentActiveTrigger = rumbleTriggers.First(x => x.Key.started).Key;
foreach (Component c in currentActiveTrigger.Components) {
if (c is not Coroutine cor) {
continue;
}
if (cor.Current?.GetType()?.Name?.Equals(expectedIEnumeratorName) ?? false) {
// when communal helper exists (due to SwapImmediately), this won't be shown in the first frame
// when vanilla, for some reason we also don't render its first frame
int state = cor.Current.GetFieldValue<int>("<>1__state");
switch (state) {
case 1: {
fastestCurrentIndex = -1;
break;
}
case 2: {
List<CrumbleWallOnRumble>.Enumerator enumerator = cor.Current.GetFieldValue<List<CrumbleWallOnRumble>.Enumerator>("<>7__wrap1");
fastestCurrentIndex = currentActiveTrigger.crumbles.IndexOf(enumerator.Current);
break;
}
default: {
continue;
}
}
fastestWaitTimer = cor.waitTimer;
triggerChanged = true;
}
}
if (!triggerChanged) {
currentActiveTrigger = null;
}
}
}
else {
List<RumbleTrigger> slowerTriggers = new List<RumbleTrigger>();
RumbleTrigger lastFastestTrigger = null;
int fastestRecord = int.MaxValue;
foreach (KeyValuePair<RumbleTrigger, int> pair in rumbleTriggers) {
if (pair.Key is RumbleTrigger trigger && trigger.started) {
foreach (Component c in trigger.Components) {
if (c is not Coroutine cor) {
continue;
}
if (cor.Current?.GetType()?.Name?.Equals(expectedIEnumeratorName) ?? false) {
// when communal helper exists (due to SwapImmediately), this won't be shown in the first frame
// when vanilla, for some reason we also don't render its first frame
int state = cor.Current.GetFieldValue<int>("<>1__state");
int currentBreakingBlock = -1;
switch (state) {
case 1: {
currentBreakingBlock = -1;
break;
}
case 2: {
List<CrumbleWallOnRumble>.Enumerator enumerator = cor.Current.GetFieldValue<List<CrumbleWallOnRumble>.Enumerator>("<>7__wrap1");
currentBreakingBlock = trigger.crumbles.IndexOf(enumerator.Current);
break;
}
default: {
continue;
}
}
int predictTime = PredictTime(currentBreakingBlock, pair.Value, cor.waitTimer);
if (lastFastestTrigger is null) {
lastFastestTrigger = trigger;
fastestRecord = predictTime;
fastestCurrentIndex = currentBreakingBlock;
fastestWaitTimer = cor.waitTimer;
}
else if (predictTime >= fastestRecord) {
slowerTriggers.Add(trigger);
}
else {
slowerTriggers.Add(lastFastestTrigger);
lastFastestTrigger = trigger;
fastestRecord = predictTime;
fastestCurrentIndex = currentBreakingBlock;
fastestWaitTimer = cor.waitTimer;
}
break;
}
}
}
}
if (currentActiveTrigger != lastFastestTrigger && lastFastestTrigger is not null) {
currentActiveTrigger = lastFastestTrigger;
triggerChanged = true;
}
foreach (RumbleTrigger trigger in slowerTriggers) {
rumbleTriggers.Remove(trigger);
}
}
if (currentActiveTrigger is null) {
Visible = false;
return;
}
if (triggerChanged) {
localTimer = fastestWaitTimer;
listCurrentIndex = fastestCurrentIndex;
listTargetIndex = rumbleTriggers[currentActiveTrigger];
}
else {
if (localTimer > 0f) {
localTimer -= Engine.DeltaTime;
}
else {
listCurrentIndex++;
localTimer = 0.05f;
}
}
text.content = PredictTime(listCurrentIndex, listTargetIndex, localTimer).ToFrame();
Visible = true;
}

public static int PredictTime(int currentIndex, int targetIndex, float waitTimer) {
// if there are multiple different manual triggered RumbleTrigger with different delay, this can be a bit wrong when TimeRate changes
return (targetIndex - currentIndex - 1) * ((0.05f).ToFrameData() + 1) + waitTimer.ToFrameData();
}
}

internal class CrumbleWallOnRumbleFactory : IRendererFactory {
public Type GetTargetType() => typeof(CrumbleWallOnRumble);

public bool Inherited() => true;
public RenderMode Mode() => Config.CrumbleWallOnRumble;
public void AddComponent(Entity entity) {
entity.Add(new CrumbleWallOnRumbleRenderer(Mode()).SleepWhenUltraFastforward());
}
}





4 changes: 4 additions & 0 deletions Source/Gameplay/AutoWatchEntity/Entity/CutsceneEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public override void Added(Entity entity) {
}

public override void UpdateImpl() {
if (!cs.Running) {
Visible = false;
return;
}
if (waitingForCoroutine) { // CS06_Campfire adds its coroutine when OnBegin is called
bool found = false;
foreach (Component c in cs.Components) {
Expand Down
29 changes: 20 additions & 9 deletions Source/Gameplay/AutoWatchEntity/Entity/FallingBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,37 @@ internal class FallingBlockRenderer : AutoWatchTextRenderer {
public Vector2 lastPos;

public Vector2 pos;

public bool Initialized = false;
public FallingBlockRenderer(RenderMode mode) : base(mode, active: true) { }

public override void Added(Entity entity) {
base.Added(entity);
lastPos = pos = entity.Position;
block = entity as FallingBlock;
if (entity.FindCoroutineComponent("Celeste.FallingBlock+<Sequence>d__21", out Tuple<Coroutine, IEnumerator> tuple)) {
coroutine = tuple.Item1;
sequence = tuple.Item2;
}
else {
coroutine = null;
sequence = null;
}

}

public override void UpdateImpl() {
text.Position = block.Center;
if (!Initialized) {
Initialized = true;
if (block.FindCoroutineComponent("Celeste.FallingBlock+<Sequence>d__21", out Tuple<Coroutine, IEnumerator> tuple)) {
coroutine = tuple.Item1;
sequence = tuple.Item2;
}
else if (block.FindCoroutineComponent("Celeste.Mod.HonlyHelper.RisingBlock+<FallingBlock_Sequence>d__5", out Tuple<Coroutine, IEnumerator> tuple2)
&& tuple2.Item2.GetFieldValue("<origEnum>5__8") is IEnumerator enumrator) {
coroutine = tuple2.Item1;
sequence = enumrator;
}
else {
coroutine = null;
sequence = null;
}
}
if (state == 3) {
text.content = coroutine.waitTimer.ToFrame();
text.content = coroutine.waitTimer.ToFrameAllowZero();
Visible = true;
}
else if (state == 4) {
Expand Down
25 changes: 5 additions & 20 deletions Source/Gameplay/AutoWatchEntity/HelperClasses.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#define Test_Mod_Compatibility
using Celeste.Mod.TASHelper.Entities;
using Celeste.Mod.TASHelper.Entities;
using Celeste.Mod.TASHelper.Utils;
using Microsoft.Xna.Framework;
using Monocle;
Expand Down Expand Up @@ -340,10 +339,9 @@ internal static class CoroutineFinder {
// note that if it's hooked, then the name will change
// like Celeste.FallingBlock+<Sequence>d__21 -> HonlyHelper.RisingBlock+<FallingBlock_Sequence>d__5
// even if the block itself is a FallingBlock instead of a RisingBlock
public static bool FindCoroutineComponent(this Entity entity, string compiler_generated_class_name, out Tuple<Coroutine, System.Collections.IEnumerator> pair) {
public static bool FindCoroutineComponent(this Entity entity, string compiler_generated_class_name, out Tuple<Coroutine, System.Collections.IEnumerator> pair, bool logError = false) {
// e.g. compiler_generated_class_name = Celeste.FallingBlock+<Sequence>d__21

#if Test_Mod_Compatibility
foreach (Component c in entity.Components) {
if (c is not Coroutine coroutine) {
continue;
Expand All @@ -352,26 +350,13 @@ public static bool FindCoroutineComponent(this Entity entity, string compiler_ge
pair = Tuple.Create(coroutine, func);
return true;
}
//Logger.Log(LogLevel.Debug, "TASHelper", string.Join(" + ", coroutine.enumerators.Select(functionCall => functionCall.GetType().FullName)));
}
// throw new Exception($"AutoWatchEntity: can't find {compiler_generated_class_name} of {entity.GetEntityId()}");
Logger.Log(LogLevel.Error, "TASHelper", $"AutoWatchEntity: can't find {compiler_generated_class_name} of {entity.GetEntityId()}");
pair = null;
return false;

#else
foreach (Component c in entity.Components) {
if (c is not Coroutine coroutine) {
continue;
}
if (coroutine.enumerators.FirstOrDefault(functioncall => functioncall.GetType().FullName == compiler_generated_class_name) is System.Collections.IEnumerator func) {
pair = Tuple.Create(coroutine, func);
return true;
}
if (logError) {
Logger.Log(LogLevel.Error, "TASHelper", $"AutoWatchEntity: can't find {compiler_generated_class_name} of {entity.GetEntityId()}");
}
pair = null;
return false;
#endif

}

public static System.Collections.IEnumerator FindIEnumrator(this Coroutine coroutine, string compiler_generated_class_name) {
Expand Down
3 changes: 3 additions & 0 deletions Source/Module/Menu/AutoWatchMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public static class AutoWatchMenu {
page.Add(new EnumerableSliderExt<RenderMode>("Auto Watch Cloud".ToDialogText(),
CreateOptions(), TasHelperSettings.AutoWatch_Cloud).Change(value => TasHelperSettings.AutoWatch_Cloud = value));

page.Add(new EnumerableSliderExt<RenderMode>("Auto Watch CrumbleWallOnRumble".ToDialogText(),
CreateOptions(), TasHelperSettings.AutoWatch_CrumbleWallOnRumble).Change(value => TasHelperSettings.AutoWatch_CrumbleWallOnRumble = value));

page.Add(new EnumerableSliderExt<RenderMode>("Auto Watch FallingBlock".ToDialogText(),
CreateOptions(), TasHelperSettings.AutoWatch_FallingBlock).Change(value => TasHelperSettings.AutoWatch_FallingBlock = value));

Expand Down
2 changes: 2 additions & 0 deletions Source/Module/TASHelperSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,8 @@ private void AutoWatchInitialize() {

public Mode AutoWatch_Cloud = Mode.Always;

public Mode AutoWatch_CrumbleWallOnRumble = Mode.Always;

public Mode AutoWatch_FallingBlock = Mode.Always;

public Mode AutoWatch_FlingBird = Mode.Always;
Expand Down
Loading

0 comments on commit f2d6863

Please sign in to comment.