diff --git a/.editorconfig b/.editorconfig
index 54585022d..79add473a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -118,12 +118,28 @@ csharp_style_prefer_readonly_struct = true
csharp_style_prefer_readonly_struct_member = true
# Code-block preferences
-csharp_prefer_braces = false:silent
+csharp_prefer_braces = true:suggestion
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = file_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_primary_constructors = true
csharp_style_prefer_top_level_statements = true
+resharper_braces_for_ifelse = required
+resharper_braces_for_dowhile = required
+resharper_braces_for_fixed = required
+resharper_braces_for_for = required
+resharper_braces_for_foreach = required
+resharper_braces_for_lock = required
+resharper_braces_for_using = required
+resharper_braces_for_while = required
+resharper_enforce_if_statement_braces_highlighting = suggestion
+resharper_enforce_dowhile_statement_braces_highlighting = required
+resharper_enforce_fixed_statement_braces_highlighting = required
+resharper_enforce_for_statement_braces_highlighting = required
+resharper_enforce_foreach_statement_braces_highlighting = required
+resharper_enforce_lock_statement_braces_highlighting = required
+resharper_enforce_using_statement_braces_highlighting = required
+resharper_enforce_while_statement_braces_highlighting = required
# Expression-level preferences
csharp_prefer_simple_default_expression = true
diff --git a/.github/workflows/Build.CelesteStudio.yml b/.github/workflows/Build.CelesteStudio.yml
index 2b9ae1882..0cfdad048 100644
--- a/.github/workflows/Build.CelesteStudio.yml
+++ b/.github/workflows/Build.CelesteStudio.yml
@@ -20,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0'
+ dotnet-version: '9.0'
- name: Set version suffix (release)
run: sed -i "s/-dev//" Studio/CelesteStudio/Studio.cs
@@ -69,7 +69,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0'
+ dotnet-version: '9.0'
- name: Set version suffix (release)
run: sed -i "s/-dev//" Studio/CelesteStudio/Studio.cs
@@ -118,7 +118,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0'
+ dotnet-version: '9.0'
- name: Install macOS workflow
run: dotnet workload install macos
- name: Switch XCode
diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml
index eae4fa267..f7621dbfd 100644
--- a/.github/workflows/Build.yml
+++ b/.github/workflows/Build.yml
@@ -39,7 +39,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0'
+ dotnet-version: '9.0'
- name: Download Studio builds
uses: actions/download-artifact@v4
diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml
index 01ea71030..9777e306d 100644
--- a/.github/workflows/Release.yml
+++ b/.github/workflows/Release.yml
@@ -74,7 +74,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0'
+ dotnet-version: '9.0'
- name: Setup Python
uses: actions/setup-python@v5.2.0
with:
diff --git a/CelesteTAS-EverestInterop/CelesteTAS-EverestInterop.csproj b/CelesteTAS-EverestInterop/CelesteTAS-EverestInterop.csproj
index 6d54d31e7..64dc74806 100644
--- a/CelesteTAS-EverestInterop/CelesteTAS-EverestInterop.csproj
+++ b/CelesteTAS-EverestInterop/CelesteTAS-EverestInterop.csproj
@@ -5,6 +5,7 @@
TAS
net7.0
latest
+ enable
../../..
lib-stripped
@@ -28,7 +29,7 @@
-
+
@@ -36,13 +37,16 @@
-
+
+
+
-
+
+
@@ -54,18 +58,22 @@
+
+
+
-
+
-
+
-
-
-
+
+
+
+
diff --git a/CelesteTAS-EverestInterop/Dialog/English.txt b/CelesteTAS-EverestInterop/Dialog/English.txt
index 52100c974..f2d3584be 100644
--- a/CelesteTAS-EverestInterop/Dialog/English.txt
+++ b/CelesteTAS-EverestInterop/Dialog/English.txt
@@ -61,10 +61,13 @@ TAS_INFO_CUSTOM= Custom Info
TAS_INFO_COPY_CUSTOM_TEMPLATE= Copy Custom Info Template to Clipboard
TAS_INFO_SET_CUSTOM_TEMPLATE= Set Custom Info Template from Clipboard
TAS_INFO_WATCH_ENTITY= Watch Entity
-TAS_INFO_WATCH_ENTITY_TYPE= Watch Entity Info
+TAS_INFO_WATCH_ENTITY_HUD_TYPE= Watch Entity Info (HUD)
+TAS_INFO_WATCH_ENTITY_STUDIO_TYPE= Watch Entity Info (Studio)
+TAS_INFO_WATCH_ENTITY_NONE= None
TAS_INFO_WATCH_ENTITY_POSITION= Position
TAS_INFO_WATCH_ENTITY_DECLARED_ONLY= Declared Only
TAS_INFO_WATCH_ENTITY_ALL= All
+TAS_INFO_WATCH_ENTITY_LOG_TO_CONSOLE= Log Entity Info to Console
TAS_INFO_TEXT_SIZE= Text Size
TAS_INFO_SUBPIXEL_INDICATOR_SIZE= Subpixel Indicator Size
TAS_INFO_OPACITY= Opacity
@@ -103,6 +106,11 @@ TAS_RESTORE_SETTINGS= Restore All Settings when TAS Stop
TAS_LAUNCH_STUDIO_AT_BOOT= Launch Studio at Boot
TAS_ATTEMPT_TO_CONNECT_TO_STUDIO= Attempt To Connect To Studio
TAS_SHOW_STUDIO_UPDATE_BANNER= Show Studio Update Banner
+TAS_OPEN_CONSOLE_IN_TAS= Allow Opening Console in TAS
+TAS_SCROLLABLE_HISTORY_LOG= Scrollable Console History Log
+TAS_BETTER_INVINCIBILITY= Better Invincibility
+TAS_BETTER_INVINCIBLE_DESCRIPTION= Running "Set Invincible true" will still prevent dying, but avoids gameplay changes like bouncing of the death plane,{n}
+ to avoid accidentally desyncing the TAS. Applies only while a TAS is active.
TAS_HIDE_FREEZE_FRAMES= Hide Freeze Frames during TAS
TAS_HIDE_FREEZE_FRAMES_DESCRIPTION_1=When a freeze frame is encountered, run it and continue to the next frame
TAS_HIDE_FREEZE_FRAMES_DESCRIPTION_2=before rendering. Applies only while a TAS is active.
diff --git a/CelesteTAS-EverestInterop/Dialog/Simplified Chinese.txt b/CelesteTAS-EverestInterop/Dialog/Simplified Chinese.txt
index 060326f3b..f3f3df446 100644
--- a/CelesteTAS-EverestInterop/Dialog/Simplified Chinese.txt
+++ b/CelesteTAS-EverestInterop/Dialog/Simplified Chinese.txt
@@ -65,6 +65,7 @@ TAS_INFO_WATCH_ENTITY_TYPE= 监视实体信息
TAS_INFO_WATCH_ENTITY_POSITION= 位置
TAS_INFO_WATCH_ENTITY_DECLARED_ONLY= 特有
TAS_INFO_WATCH_ENTITY_ALL= 所有
+TAS_INFO_WATCH_ENTITY_LOG_TO_COMMAND= 监视实体信息输出到控制台
TAS_INFO_TEXT_SIZE= 文字大小
TAS_INFO_SUBPIXEL_INDICATOR_SIZE= 亚像素指示器大小
TAS_INFO_OPACITY= 不透明度
@@ -101,6 +102,11 @@ TAS_CENTER_CAMERA_HORIZONTALLY_ONLY= 镜头只水平居中
TAS_RESTORE_SETTINGS= TAS 停止时恢复所有设置
TAS_LAUNCH_STUDIO_AT_BOOT= 启动游戏时打开 Studio
TAS_ATTEMPT_TO_CONNECT_TO_STUDIO= 尝试连接 Studio
+TAS_OPEN_CONSOLE_IN_TAS= 允许在 TAS 中打开控制台
+TAS_SCROLLABLE_HISTORY_LOG= 可滚动的控制台历史记录
+TAS_BETTER_INVINCIBILITY= 更好的无敌
+TAS_BETTER_INVINCIBLE_DESCRIPTION= 运行 "Set Invincible true" 既会阻止死亡, 同时也会避免例如在掉落致死时自动弹跳的游戏物理变化,{n}
+ 从而避免 TAS 意外 desync. 仅在 TAS 运行期间生效.
TAS_HIDE_FREEZE_FRAMES= TAS 时跳过冻结帧
TAS_HIDE_FREEZE_FRAMES_DESCRIPTION_1=运行到冻结帧时自动跳过进入下一帧
TAS_HIDE_FREEZE_FRAMES_DESCRIPTION_2=仅在 TAS 时启用
diff --git a/CelesteTAS-EverestInterop/Source/Communication/CommunicationAdapterCeleste.cs b/CelesteTAS-EverestInterop/Source/Communication/CommunicationAdapterCeleste.cs
index ea83e3725..7402efbb0 100644
--- a/CelesteTAS-EverestInterop/Source/Communication/CommunicationAdapterCeleste.cs
+++ b/CelesteTAS-EverestInterop/Source/Communication/CommunicationAdapterCeleste.cs
@@ -10,13 +10,13 @@
using StudioCommunication.Util;
using TAS.EverestInterop;
using TAS.EverestInterop.InfoHUD;
+using TAS.InfoHUD;
using TAS.Input;
using TAS.Input.Commands;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
-#nullable enable
-
namespace TAS.Communication;
public sealed class CommunicationAdapterCeleste() : CommunicationAdapterBase(Location.Celeste) {
@@ -28,7 +28,7 @@ protected override void FullReset() {
protected override void OnConnectionChanged() {
if (Connected) {
// Stall until input initialized to avoid sending invalid hotkey data
- while (Hotkeys.KeysDict == null) {
+ while (Hotkeys.AllHotkeys == null) {
Thread.Sleep(UpdateRate);
}
@@ -44,7 +44,7 @@ protected override void HandleMessage(MessageID messageId, BinaryReader reader)
string path = reader.ReadString();
LogVerbose($"Received message FilePath: '{path}'");
- InputController.StudioTasFilePath = path;
+ Manager.AddMainThreadAction(() => Manager.Controller.FilePath = path);
break;
case MessageID.Hotkey:
@@ -52,7 +52,7 @@ protected override void HandleMessage(MessageID messageId, BinaryReader reader)
bool released = reader.ReadBoolean();
LogVerbose($"Received message Hotkey: {hotkey} ({(released ? "released" : "pressed")})");
- Hotkeys.KeysDict[hotkey].OverrideCheck = !released;
+ Hotkeys.AllHotkeys[hotkey].OverrideCheck = !released;
break;
case MessageID.SetCustomInfoTemplate:
@@ -82,7 +82,6 @@ protected override void HandleMessage(MessageID messageId, BinaryReader reader)
object? arg = gameDataType switch {
GameDataType.ConsoleCommand => reader.ReadBoolean(),
GameDataType.SettingValue => reader.ReadString(),
- GameDataType.RawInfo => reader.ReadObject<(string, bool)>(),
GameDataType.CommandHash => reader.ReadObject<(string, string[], string, int)>(),
_ => null,
};
@@ -114,9 +113,6 @@ protected override void HandleMessage(MessageID messageId, BinaryReader reader)
case GameDataType.CustomInfoTemplate:
gameData = !string.IsNullOrWhiteSpace(TasSettings.InfoCustomTemplate) ? TasSettings.InfoCustomTemplate : string.Empty;
break;
- case GameDataType.RawInfo:
- gameData = InfoCustom.GetRawInfo(((string, bool))arg!);
- break;
case GameDataType.GameState:
gameData = GameData.GetGameState();
break;
@@ -158,10 +154,6 @@ protected override void HandleMessage(MessageID messageId, BinaryReader reader)
writer.Write((string?)gameData ?? string.Empty);
break;
- case GameDataType.RawInfo:
- writer.WriteObject(gameData);
- break;
-
case GameDataType.GameState:
writer.WriteObject((GameState?)gameData);
break;
@@ -169,7 +161,7 @@ protected override void HandleMessage(MessageID messageId, BinaryReader reader)
case GameDataType.CommandHash:
writer.Write((int)gameData!);
break;
-
+
case GameDataType.LevelInfo:
writer.WriteObject((LevelInfo)gameData!);
break;
@@ -317,42 +309,45 @@ public void WriteCommandList(CommandInfo[] commands) {
}
private void ProcessRecordTAS(string fileName) {
- if (!TASRecorderUtils.Installed) {
+ if (!TASRecorderInterop.Installed) {
WriteRecordingFailed(RecordingFailedReason.TASRecorderNotInstalled);
return;
}
- if (!TASRecorderUtils.FFmpegInstalled) {
+ if (!TASRecorderInterop.FFmpegInstalled) {
WriteRecordingFailed(RecordingFailedReason.FFmpegNotInstalled);
return;
}
- Manager.Controller.RefreshInputs(enableRun: true);
- if (RecordingCommand.RecordingTimes.IsNotEmpty()) {
- AbortTas("Can't use StartRecording/StopRecording with \"Record TAS\"");
- return;
- }
- Manager.NextStates |= States.Enable;
+ Manager.AddMainThreadAction(() => {
+ Manager.Controller.RefreshInputs();
+ if (RecordingCommand.RecordingTimes.IsNotEmpty()) {
+ AbortTas("Can't use StartRecording/StopRecording with \"Record TAS\"");
+ return;
+ }
+ Manager.EnableRun();
- int totalFrames = Manager.Controller.Inputs.Count;
- if (totalFrames <= 0) return;
+ int totalFrames = Manager.Controller.Inputs.Count;
+ if (totalFrames <= 0) return;
- TASRecorderUtils.StartRecording(fileName);
- TASRecorderUtils.SetDurationEstimate(totalFrames);
+ TASRecorderInterop.StartRecording(fileName);
+ TASRecorderInterop.SetDurationEstimate(totalFrames);
- if (!Manager.Controller.Commands.TryGetValue(0, out var commands)) {
- return;
- }
- bool startsWithConsoleLoad = commands.Any(c =>
- c.Is("Console") &&
- c.Args.Length >= 1 &&
- ConsoleCommand.LoadCommandRegex.Match(c.Args[0].ToLower()) is { Success: true });
-
- if (startsWithConsoleLoad) {
- // Restart the music when we enter the level
- Audio.SetMusic(null, startPlaying: false, allowFadeOut: false);
- Audio.SetAmbience(null, startPlaying: false);
- Audio.BusStopAll(Buses.GAMEPLAY, immediate: true);
- }
+ if (!Manager.Controller.Commands.TryGetValue(0, out var commands)) {
+ return;
+ }
+
+ bool startsWithConsoleLoad = commands.Any(c =>
+ c.Attribute.Name.Equals("Console", StringComparison.OrdinalIgnoreCase) &&
+ c.Args.Length >= 1 &&
+ ConsoleCommand.LoadCommandRegex.Match(c.Args[0].ToLower()) is {Success: true});
+
+ if (startsWithConsoleLoad) {
+ // Restart the music when we enter the level
+ Audio.SetMusic(null, startPlaying: false, allowFadeOut: false);
+ Audio.SetAmbience(null, startPlaying: false);
+ Audio.BusStopAll(Buses.GAMEPLAY, immediate: true);
+ }
+ });
}
protected override void LogInfo(string message) => Logger.Log(LogLevel.Info, "CelesteTAS/StudioCom", message);
diff --git a/CelesteTAS-EverestInterop/Source/Communication/CommunicationWrapper.cs b/CelesteTAS-EverestInterop/Source/Communication/CommunicationWrapper.cs
index a024478a2..4c1c7fc7c 100644
--- a/CelesteTAS-EverestInterop/Source/Communication/CommunicationWrapper.cs
+++ b/CelesteTAS-EverestInterop/Source/Communication/CommunicationWrapper.cs
@@ -71,7 +71,7 @@ public static void SendCurrentBindings() {
return;
}
- Dictionary> nativeBindings = Hotkeys.KeysInteractWithStudio.ToDictionary(pair => (int) pair.Key, pair => pair.Value.Cast().ToList());
+ Dictionary> nativeBindings = Hotkeys.StudioHotkeys.ToDictionary(pair => (int) pair.Key, pair => pair.Value.Cast().ToList());
comm.WriteCurrentBindings(nativeBindings);
}
public static void SendRecordingFailed(RecordingFailedReason reason) {
diff --git a/CelesteTAS-EverestInterop/Source/Entities/Toast.cs b/CelesteTAS-EverestInterop/Source/Entities/Toast.cs
index 60dd315ec..8945765f7 100644
--- a/CelesteTAS-EverestInterop/Source/Entities/Toast.cs
+++ b/CelesteTAS-EverestInterop/Source/Entities/Toast.cs
@@ -8,6 +8,11 @@
namespace TAS.Entities;
+public class DashCollision() : Component(active: false, visible: false) {
+ public Solid Hit = null!;
+ public bool IsBounceHit = false;
+}
+
[Tracked]
internal class Toast : Entity {
private const int Padding = 25;
@@ -75,4 +80,4 @@ public static void ShowAndLog(string message, float duration = DefaultDuration,
Show(message, duration);
message.Log(logLevel);
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/AreaCompleteInfo.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/AreaCompleteInfo.cs
index b97441531..bc566b3a9 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/AreaCompleteInfo.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/AreaCompleteInfo.cs
@@ -10,6 +10,7 @@
using StudioCommunication;
using TAS.Input;
using TAS.Input.Commands;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
using Comment = TAS.Input.Comment;
@@ -18,7 +19,7 @@ namespace TAS.EverestInterop;
public static class AreaCompleteInfo {
private class Meta : ITasCommandMeta {
- public string Insert => $"CompleteInfo{CommandInfo.Separator}[0;A 1]";
+ public string Insert => $"CompleteInfo{CommandInfo.Separator}[0;A{CommandInfo.Separator}1]";
public bool HasArguments => true;
}
@@ -176,25 +177,20 @@ private static void CompleteInfoCommand(CommandLine commandLine, int studioLine,
key = new AreaKey(id, mode).ToString();
}
- if (!completeInfos.TryGetValue(key, out StringBuilder info)) {
+ if (!completeInfos.TryGetValue(key, out var info)) {
completeInfos[key] = info = new StringBuilder();
}
info.Clear();
- if (Manager.Controller.Comments.TryGetValue(filePath, out List comments)) {
+
+ if (Manager.Controller.CurrentComments is { Count: > 0 } comments) {
bool firstComment = true;
- foreach (Comment comment in comments.Where(c => c.Line > fileLine)) {
- if (fileLine + 1 == comment.Line) {
- if (!firstComment) {
- info.AppendLine();
- }
-
- firstComment = false;
- info.Append($"{comment.Text}");
- fileLine++;
- } else {
- break;
+ foreach (var comment in comments) {
+ if (!firstComment) {
+ info.AppendLine();
}
+
+ info.AppendLine(comment.Text);
}
}
}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/AutoMute.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/AutoMute.cs
index 66a87d212..53ecb295b 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/AutoMute.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/AutoMute.cs
@@ -67,8 +67,8 @@ private static readonly IDictionary, int>
private static bool settingMusic;
private static bool hasMuted;
- private static bool ShouldBeMuted => Manager.FrameLoops >= 2 && !settingMusic;
- private static bool FrameStep => Manager.Running && (Manager.States & States.FrameStep) != 0;
+ private static bool ShouldBeMuted => Manager.PlaybackSpeed >= 2.0f && !settingMusic;
+ private static bool FrameStep => Manager.CurrState == Manager.State.Paused;
private static WeakReference dummy;
private static EventInstance DummyEventInstance {
@@ -197,4 +197,4 @@ private static void CelesteOnUpdate(On.Celeste.Celeste.orig_Update orig, Celeste
}
}
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Benchmark.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Benchmark.cs
index a10215de6..258d31cf3 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Benchmark.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Benchmark.cs
@@ -32,4 +32,4 @@ private static void EngineOnUpdate(On.Monocle.Engine.orig_Update orig, Engine se
lastRunning = Manager.Running;
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/CenterCamera.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/CenterCamera.cs
index cda0d71d4..1b93fb46d 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/CenterCamera.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/CenterCamera.cs
@@ -349,4 +349,4 @@ private static void ZoomCamera() {
viewportScale = Calc.Clamp(viewportScale + delta, 0.2f, MaximumViewportScale);
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/ConsoleEnhancements.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/ConsoleEnhancements.cs
index 52b6237f2..47e1157d9 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/ConsoleEnhancements.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/ConsoleEnhancements.cs
@@ -2,11 +2,13 @@
using System.Collections.Generic;
using Celeste;
using Celeste.Mod;
+using Microsoft.Xna.Framework.Input;
using Monocle;
using MonoMod.Cil;
-using TAS.EverestInterop.InfoHUD;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
+using TAS.InfoHUD;
namespace TAS.EverestInterop;
@@ -31,20 +33,67 @@ private static void InitializeHelperMethods() {
[Load]
private static void Load() {
- IL.Monocle.Commands.Render += Commands_Render;
+ IL.Monocle.Commands.Render += IL_Commands_Render;
}
[Unload]
private static void Unload() {
- IL.Monocle.Commands.Render -= Commands_Render;
+ IL.Monocle.Commands.Render -= IL_Commands_Render;
}
[EnableRun]
- private static void CloseCommand() {
+ private static void EnableRun() {
+ // Auto-close at start. Can be opened manually again
Engine.Commands.Open = false;
}
- private static void Commands_Render(ILContext il) {
+ internal static void UpdateMeta() {
+ if (!Manager.Running) {
+ return;
+ }
+
+ justClosed = false;
+ if (Engine.Commands.Open) {
+ Engine.Commands.UpdateOpen();
+ if (!Engine.Commands.Open) {
+ justClosed = true;
+ }
+ } else if (Engine.Commands.Enabled) {
+ Engine.Commands.UpdateClosed();
+ }
+ }
+
+ private static bool justClosed = false;
+ internal static void OpenConsole() {
+ if (!Manager.Running) {
+ return; // Only allow inside a TAS, since outside it's already handled
+ }
+ if (Engine.Commands.Open) {
+ return;
+ }
+ if (justClosed) {
+ // when commands open, hotkeys are not updated (in Hotkeys.UpdateMeta(), updateKey = false)
+ // so if without this extra check:
+ // Gameloop 1: Commands open
+ // Gameloop 2: CoreModule.(Toggle)DebugConsole gets pressed (note this is not our OpenConsole hotkey), and Commands get closed
+ // Gameloop 3: Hotkeys find Commands are closed and decide to update, and find that OpenConsole gets pressed, so it Opens Console again!
+ return;
+ }
+
+ // Copied from Commands.UpdateClosed
+ Engine.Commands.Open = true;
+ Engine.Commands.currentState = Keyboard.GetState();
+ if (!Engine.Commands.installedListener) {
+ Engine.Commands.installedListener = true;
+ TextInput.OnInput += Engine.Commands.HandleChar;
+ }
+ if (!Engine.Commands.printedInfoMessage) {
+ Engine.Commands.Log("Use the 'help' command for a list of debug commands. Press Esc or use the 'q' command to close the console.");
+ Engine.Commands.printedInfoMessage = true;
+ }
+ }
+
+ private static void IL_Commands_Render(ILContext il) {
// Hijack string.Format("\n level: {0}, {1}", xObj, yObj)
new ILCursor(il).FindNext(out ILCursor[] found,
i => i.MatchLdstr("\n level: {0}, {1}"),
@@ -106,4 +155,4 @@ public static string GetModName(Type type) {
return "Unknown";
}
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Core.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Core.cs
deleted file mode 100644
index 8f9b04cc6..000000000
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Core.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Celeste;
-using Microsoft.Xna.Framework;
-using Mono.Cecil.Cil;
-using Monocle;
-using MonoMod.Cil;
-using MonoMod.RuntimeDetour;
-using TAS.Input.Commands;
-using TAS.Module;
-using TAS.Utils;
-using GameInput = Celeste.Input;
-
-namespace TAS.EverestInterop;
-
-public static class Core {
- private static bool InUpdate;
-
- [Load]
- private static void Load() {
- using (new DetourContext {After = new List {"*"}}) {
- On.Celeste.Celeste.Update += Celeste_Update;
- IL.Monocle.Engine.Update += EngineOnUpdate;
- if (typeof(GameInput).GetMethod("UpdateGrab") is { } updateGrabMethod) {
- HookHelper.SkipMethod(typeof(Core), nameof(IsPause), updateGrabMethod);
- }
-
- // The original mod makes the MInput.Update call conditional and invokes UpdateInputs afterwards.
- On.Monocle.MInput.Update += MInput_Update;
- IL.Monocle.MInput.Update += MInputOnUpdate;
-
- // The original mod makes RunThread.Start run synchronously.
- On.Celeste.RunThread.Start += RunThread_Start;
-
- // Forced: Allow "rendering" entities without actually rendering them.
- IL.Monocle.Entity.Render += SkipRenderMethod;
- }
- }
-
- [Unload]
- private static void Unload() {
- On.Celeste.Celeste.Update -= Celeste_Update;
- IL.Monocle.Engine.Update -= EngineOnUpdate;
- On.Monocle.MInput.Update -= MInput_Update;
- IL.Monocle.MInput.Update -= MInputOnUpdate;
- On.Celeste.RunThread.Start -= RunThread_Start;
- IL.Monocle.Entity.Render -= SkipRenderMethod;
- }
-
- private static void Celeste_Update(On.Celeste.Celeste.orig_Update orig, Celeste.Celeste self, GameTime gameTime) {
- InUpdate = false;
-
- if (!TasSettings.Enabled || !Manager.Running) {
- orig(self, gameTime);
- return;
- }
-
- InUpdate = true;
-
- // The original patch doesn't store FrameLoops in a local variable, but it's only updated in UpdateInputs anyway.
- int loops = Manager.SlowForwarding ? 1 : (int) Manager.FrameLoops;
- for (int i = 0; i < loops; i++) {
- float oldFreezeTimer = Engine.FreezeTimer;
-
- // Anything happening early on runs in the MInput.Update hook.
- orig(self, gameTime);
- Manager.AdvanceThroughHiddenFrame = false;
-
- if (TasSettings.HideFreezeFrames && oldFreezeTimer > 0f && oldFreezeTimer > Engine.FreezeTimer) {
- Manager.AdvanceThroughHiddenFrame = true;
- loops += 1;
- } else if (RecordingCommand.StopFastForward) {
- break;
- }
- }
-
- InUpdate = false;
- }
-
- private static void EngineOnUpdate(ILContext il) {
- ILCursor ilCursor = new(il);
- if (ilCursor.TryGotoNext(MoveType.After, ins => ins.MatchCall(typeof(MInput), "Update"))) {
- ILLabel label = ilCursor.DefineLabel();
- ilCursor.EmitDelegate(IsPause);
- ilCursor.Emit(OpCodes.Brfalse, label)
- .Emit(OpCodes.Ret)
- .MarkLabel(label);
- }
- }
-
- private static bool IsPause() {
- return Manager.SkipFrame && !Manager.IsLoading();
- }
-
- private static void MInput_Update(On.Monocle.MInput.orig_Update orig) {
- if (!TasSettings.Enabled) {
- orig();
- return;
- }
-
- if (!Manager.Running) {
- orig();
- }
-
- Manager.Update();
- }
-
- // update controller even the game is lose focus
- private static void MInputOnUpdate(ILContext il) {
- ILCursor ilCursor = new(il);
- ilCursor.Goto(il.Instrs.Count - 1);
-
- if (ilCursor.TryGotoPrev(MoveType.After, i => i.MatchCallvirt("UpdateNull"))) {
- ilCursor.EmitDelegate(UpdateGamePads);
- }
-
- // skip the orig GamePads[j].UpdateNull();
- if (ilCursor.TryGotoNext(MoveType.After, i => i.MatchLdcI4(0))) {
- ilCursor.Emit(OpCodes.Ldc_I4_4).Emit(OpCodes.Add);
- }
- }
-
- private static void UpdateGamePads() {
- for (int i = 0; i < 4; i++) {
- if (MInput.Active) {
- MInput.GamePads[i].Update();
- } else {
- MInput.GamePads[i].UpdateNull();
- }
- }
- }
-
- private static void RunThread_Start(On.Celeste.RunThread.orig_Start orig, Action method, string name, bool highPriority) {
- if (Manager.Running && name != "USER_IO" && name != "MOD_IO") {
- RunThread.RunThreadWithLogging(method);
- return;
- }
-
- orig(method, name, highPriority);
- }
-
- private static void SkipRenderMethod(ILContext il) {
- ILCursor ilCursor = new(il);
- ILLabel startLabel = ilCursor.DefineLabel();
- ilCursor.Emit(OpCodes.Ldsfld, typeof(Core).GetFieldInfo(nameof(InUpdate)))
- .Emit(OpCodes.Brfalse, startLabel)
- .Emit(OpCodes.Ret)
- .MarkLabel(startLabel);
- }
-}
\ No newline at end of file
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/CriticalErrorHandlerFixer.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/CriticalErrorHandlerFixer.cs
index a38318724..1e49d54f0 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/CriticalErrorHandlerFixer.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/CriticalErrorHandlerFixer.cs
@@ -1,25 +1,19 @@
-using System;
-using MonoMod.Utils;
+using Celeste.Mod;
+using Celeste.Mod.UI;
using TAS.Module;
-using TAS.Utils;
namespace TAS.EverestInterop;
public static class CriticalErrorHandlerFixer {
- public static bool Handling => getCurrentHandler?.Invoke(null) != null;
- private static FastReflectionDelegate getCurrentHandler;
-
[Load]
private static void Load() {
- Type type = ModUtils.VanillaAssembly.GetType("Celeste.Mod.UI.CriticalErrorHandler");
- if (type != null) {
- type.GetMethod("Update")?.HookBefore(() => {
- if (Manager.Running) {
- Manager.DisableRun();
- }
- });
+ Everest.Events.OnCriticalError += HandleCriticalError;
+ }
- getCurrentHandler = type.GetProperty("CurrentHandler")?.GetGetMethod()?.GetFastDelegate();
- }
+ [Unload]
+ private static void Unload() {
+ Everest.Events.OnCriticalError -= HandleCriticalError;
}
-}
\ No newline at end of file
+
+ private static void HandleCriticalError(CriticalErrorHandler criticalErrorHandler) => Manager.DisableRun();
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/DebugRcPage.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/DebugRcPage.cs
index 74da0c8ad..47408db07 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/DebugRcPage.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/DebugRcPage.cs
@@ -24,8 +24,8 @@ public static class DebugRcPage {
StringBuilder builder = new();
Everest.DebugRC.WriteHTMLStart(c, builder);
WriteLine(builder, $"Running: {Manager.Running}");
- WriteLine(builder, $"State: {Manager.States}");
- WriteLine(builder, $"SaveState: {Savestates.IsSaved_Safe()}");
+ WriteLine(builder, $"State: {Manager.CurrState}");
+ WriteLine(builder, $"SaveState: {Savestates.IsSaved_Safe}");
WriteLine(builder, $"CurrentFrame: {Manager.Controller.CurrentFrameInTas}");
WriteLine(builder, $"TotalFrames: {Manager.Controller.Inputs.Count}");
WriteLine(builder, $"RoomName: {GameInfo.LevelName}");
@@ -49,31 +49,28 @@ void WriteIdErrorPage(string message) {
Everest.DebugRC.WriteHTMLStart(c, builder);
WriteLine(builder, $"ERROR: {message}
");
WriteLine(builder, "Example: /tas/sendhotkey?id=Start&action=press");
- WriteLine(builder, $"Available id: {string.Join(", ", Enum.GetNames(typeof(HotkeyID)).Select(id => $"{id}"))}");
+ WriteLine(builder, $"Available IDs: {string.Join(", ", Enum.GetNames(typeof(HotkeyID)).Select(id => $"{id}"))}");
WriteLine(builder, "Available action: press, release");
Everest.DebugRC.WriteHTMLEnd(c, builder);
Everest.DebugRC.Write(c, builder.ToString());
}
- NameValueCollection args = Everest.DebugRC.ParseQueryString(c.Request.RawUrl);
- string idValue = args["id"];
- string pressValue = args["action"];
+ var args = Everest.DebugRC.ParseQueryString(c.Request.RawUrl);
+ string? idValue = args["id"];
+ string? pressValue = args["action"];
- if (idValue.IsNullOrEmpty()) {
- WriteIdErrorPage("No id given.");
- } else {
- if (Enum.TryParse(idValue, true, out HotkeyID id) && (int) id < Enum.GetNames(typeof(HotkeyID)).Length) {
- if (Hotkeys.KeysDict.TryGetValue(id, out Hotkeys.Hotkey hotkey)) {
- bool press = !"release".Equals(pressValue, StringComparison.InvariantCultureIgnoreCase);
- hotkey.OverrideCheck = press;
- Everest.DebugRC.Write(c, "OK");
- } else {
- WriteIdErrorPage($"Hotkeys.KeysDict doesn't have id {id}, please report to the developer.");
- }
- } else {
- WriteIdErrorPage("Invalid id value.");
- }
+ if (string.IsNullOrEmpty(idValue)) {
+ WriteIdErrorPage("No ID given.");
+ return;
+ }
+ if (!Enum.TryParse(idValue, ignoreCase: true, out var id) || !Hotkeys.AllHotkeys.TryGetValue(id, out var hotkey)) {
+ WriteIdErrorPage("Invalid ID value.");
+ return;
}
+
+ bool press = !"release".Equals(pressValue, StringComparison.InvariantCultureIgnoreCase);
+ hotkey.OverrideCheck = press;
+ Everest.DebugRC.Write(c, "OK");
}
};
@@ -129,7 +126,7 @@ void WriteIdErrorPage(string message) {
} else {
WriteLine(builder, "OK");
Manager.AddMainThreadAction(() => {
- InputController.StudioTasFilePath = filePath;
+ Manager.Controller.FilePath = filePath;
Manager.EnableRun();
});
}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/DesyncFixer.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/DesyncFixer.cs
index 8a37dd4af..5905b83be 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/DesyncFixer.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/DesyncFixer.cs
@@ -8,6 +8,7 @@
using Monocle;
using MonoMod.Cil;
using MonoMod.Utils;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
@@ -48,7 +49,7 @@ private static void Initialize() {
}
if (ModUtils.GetModule("DeadzoneConfig")?.GetType() is { } deadzoneConfigModuleType) {
- HookHelper.SkipMethod(typeof(DesyncFixer), nameof(SkipDeadzoneConfig), deadzoneConfigModuleType.GetMethod("OnInputInitialize"));
+ deadzoneConfigModuleType.GetMethod("OnInputInitialize").SkipMethod(SkipDeadzoneConfig);
}
if (ModUtils.GetType("StrawberryJam2021", "Celeste.Mod.StrawberryJam2021.Entities.CustomAscendManager") is { } ascendManagerType) {
@@ -75,12 +76,6 @@ private static void Load() {
typeof(Entity).GetMethod("Update").HookAfter(AfterEntityUpdate);
typeof(AscendManager).GetMethodInfo("Routine").GetStateMachineTarget().IlHook(MakeRngConsistent);
- // https://github.com/EverestAPI/Everest/commit/b2a6f8e7c41ddafac4e6fde0e43a09ce1ac4f17e
- // Autosaving prevents opening the menu to skip cutscenes during fast forward before Everest v2865.
- if (Everest.Version < new Version(1, 2865)) {
- typeof(Level).GetProperty("CanPause").GetGetMethod().IlHook(AllowPauseDuringSaving);
- }
-
// System.IndexOutOfRangeException: Index was outside the bounds of the array.
// https://discord.com/channels/403698615446536203/1148931167983251466/1148931167983251466
On.Celeste.LightingRenderer.SetOccluder += IgnoreSetOccluderCrash;
@@ -188,16 +183,6 @@ private static bool SkipDeadzoneConfig() {
return Manager.Running;
}
- private static void AllowPauseDuringSaving(ILCursor ilCursor, ILContext ilContext) {
- if (ilCursor.TryGotoNext(MoveType.After, ins => ins.MatchCall(typeof(UserIO), "get_Saving"))) {
- ilCursor.EmitDelegate(IsSaving);
- }
- }
-
- private static bool IsSaving(bool saving) {
- return !Manager.Running && saving;
- }
-
private static void IgnoreSetOccluderCrash(On.Celeste.LightingRenderer.orig_SetOccluder orig, LightingRenderer self, Vector3 center, Color mask, Vector2 light, Vector2 edgeA, Vector2 edgeB) {
try {
orig(self, center, mask, light, edgeA, edgeB);
@@ -276,4 +261,4 @@ private static void AuraPopRandom() {
Calc.PopRandom();
}
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/DisableAchievements.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/DisableAchievements.cs
index 2fa0aaaf2..32549eb39 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/DisableAchievements.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/DisableAchievements.cs
@@ -50,4 +50,4 @@ private static void Stats_Increment(On.Celeste.Stats.orig_Increment orig, Stat s
orig(stat, increment);
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/DisableRumble.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/DisableRumble.cs
index 4a51f64b0..2351738a6 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/DisableRumble.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/DisableRumble.cs
@@ -7,7 +7,7 @@ namespace TAS.EverestInterop;
public static class DisableRumble {
[Load]
private static void Load() {
- HookHelper.SkipMethod(typeof(DisableRumble), nameof(IsDisableRumble), typeof(MInput.GamePadData).GetMethodInfo("Rumble"));
+ typeof(MInput.GamePadData).GetMethodInfo("Rumble").SkipMethod(IsDisableRumble);
}
private static bool IsDisableRumble() {
@@ -20,4 +20,4 @@ private static void EnableRun() {
gamePadData.StopRumble();
}
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/EntityDataHelper.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/EntityDataHelper.cs
index e0dad286f..de45fd11c 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/EntityDataHelper.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/EntityDataHelper.cs
@@ -8,6 +8,7 @@
using Mono.Cecil.Cil;
using Monocle;
using MonoMod.Cil;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
@@ -100,8 +101,8 @@ private static void SetEntityData(this Entity entity, EntityData data) {
}
}
- public static EntityData GetEntityData(this Entity entity) {
- return entity != null && CachedEntityData.TryGetValue(entity, out EntityData data) ? data : null;
+ public static EntityData? GetEntityData(this Entity? entity) {
+ return entity != null && CachedEntityData.TryGetValue(entity, out var data) ? data : null;
}
private static void LevelOnEnd(On.Celeste.Level.orig_End orig, Level self) {
@@ -226,35 +227,35 @@ private static void StrawberrySeedOnCtor(On.Celeste.StrawberrySeed.orig_ctor ori
private static void ModSpawnEntity(ILContext il) {
ILCursor cursor = new(il);
- if (cursor.TryGotoNext(
- i => i.OpCode == OpCodes.Callvirt && i.Operand.ToString() == "System.Void Monocle.Scene::Add(Monocle.Entity)")) {
- cursor.Emit(OpCodes.Dup).Emit(OpCodes.Ldarg_0);
+ if (cursor.TryGotoNext(ins => ins.OpCode == OpCodes.Callvirt && ins.Operand.ToString() == "System.Void Monocle.Scene::Add(Monocle.Entity)")) {
+ cursor.EmitDup();
+ cursor.EmitLdarg0();
+
+ // TODO: Better match
if (il.ToString().Contains("ldfld Celeste.SeekerStatue Celeste.SeekerStatue/<>c__DisplayClass3_0::<>4__this")
&& ModUtils.VanillaAssembly.GetType("Celeste.SeekerStatue+<>c__DisplayClass3_0")?.GetFieldInfo("<>4__this") is { } seekerStatue
) {
- cursor.Emit(OpCodes.Ldfld, seekerStatue);
- }
-
- cursor.EmitDelegate>(SetCustomEntityData);
- }
- }
-
- private static void SetCustomEntityData(Entity spawnedEntity, Entity entity) {
- if (entity.GetEntityData() is { } entityData) {
- EntityData clonedEntityData = entityData.ShallowClone();
- if (spawnedEntity is FireBall fireBall) {
- clonedEntityData.ID = clonedEntityData.ID * -100 - fireBall.index;
- } else if (entity is CS03_OshiroRooftop) {
- clonedEntityData.ID = 2;
- } else {
- clonedEntityData.ID *= -1;
+ cursor.EmitLdfld(seekerStatue);
}
- spawnedEntity.SetEntityData(clonedEntityData);
+ cursor.EmitStaticDelegate("SetCustomEntityData", static (Entity spawnedEntity, Entity entity) => {
+ if (entity.GetEntityData() is { } entityData) {
+ EntityData clonedEntityData = entityData.ShallowClone();
+ if (spawnedEntity is FireBall fireBall) {
+ clonedEntityData.ID = clonedEntityData.ID * -100 - fireBall.index;
+ } else if (entity is CS03_OshiroRooftop) {
+ clonedEntityData.ID = 2;
+ } else {
+ clonedEntityData.ID *= -1;
+ }
+
+ spawnedEntity.SetEntityData(clonedEntityData);
+ }
+ });
}
}
private static void CacheEntityData(Entity entity, EntityData data) {
entity.SetEntityData(data);
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/FastForwardBoost.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/FastForwardBoost.cs
index d06da9f27..593581250 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/FastForwardBoost.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/FastForwardBoost.cs
@@ -1,18 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using Celeste;
using Celeste.Mod;
using Mono.Cecil.Cil;
using Monocle;
using MonoMod.Cil;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
namespace TAS.EverestInterop;
public static class FastForwardBoost {
- private static bool UltraFastForwarding => Manager.UltraFastForwarding;
private static Type creditsType;
[Initialize]
@@ -140,7 +141,7 @@ private static void AddTypeToTracker(Type type, params Type[] subTypes) {
}
private static void BackdropRendererOnUpdate(On.Celeste.BackdropRenderer.orig_Update orig, BackdropRenderer self, Scene scene) {
- if (UltraFastForwarding && Engine.FrameCounter % 1000 > 0) {
+ if (Manager.FastForwarding && Engine.FrameCounter % 1000 > 0) {
return;
}
@@ -148,7 +149,7 @@ private static void BackdropRendererOnUpdate(On.Celeste.BackdropRenderer.orig_Up
}
private static void SoundEmitterOnUpdate(On.Celeste.SoundEmitter.orig_Update orig, SoundEmitter self) {
- if (UltraFastForwarding) {
+ if (Manager.FastForwarding) {
self.RemoveSelf();
} else {
orig(self);
@@ -158,7 +159,7 @@ private static void SoundEmitterOnUpdate(On.Celeste.SoundEmitter.orig_Update ori
private static void SkipUpdateMethod(ILContext il) {
ILCursor ilCursor = new(il);
Instruction start = ilCursor.Next;
- ilCursor.Emit(OpCodes.Call, typeof(Manager).GetProperty(nameof(Manager.UltraFastForwarding)).GetMethod);
+ ilCursor.Emit(OpCodes.Call, typeof(Manager).GetProperty(nameof(Manager.FastForwarding), BindingFlags.Public | BindingFlags.Static)!.GetMethod);
ilCursor.Emit(OpCodes.Brfalse, start).Emit(OpCodes.Ret);
}
@@ -170,16 +171,16 @@ private static void LightningRendererOnUpdate(ILContext il) {
}
private static bool IsSkipLightningRendererUpdate() {
- return Manager.UltraFastForwarding && Engine.FrameCounter % 30 > 0;
+ return Manager.FastForwarding && Engine.FrameCounter % 30 > 0;
}
private static void SeekerBarrierOnUpdate(ILContext il) {
ILCursor cursor = new(il);
if (!cursor.TryGotoNext(MoveType.AfterLabel,
- instr => instr.MatchLdarg(0),
- instr => instr.MatchLdfld("speeds"),
- instr => instr.MatchLdlen())
+ ins => ins.MatchLdarg(0),
+ ins => ins.MatchLdfld("speeds"),
+ ins => ins.MatchLdlen())
) {
return;
}
@@ -188,8 +189,8 @@ private static void SeekerBarrierOnUpdate(ILContext il) {
cursor.EmitDelegate>(IsSkipSeekerBarrierOverloadPart);
cursor.Emit(OpCodes.Brtrue, target);
- if (!cursor.TryGotoNext(instr => instr.MatchLdarg(0),
- instr => instr.MatchCall("Update"))
+ if (!cursor.TryGotoNext(ins => ins.MatchLdarg(0),
+ ins => ins.MatchCall("Update"))
) {
return;
}
@@ -198,7 +199,7 @@ private static void SeekerBarrierOnUpdate(ILContext il) {
}
private static bool IsSkipSeekerBarrierOverloadPart() {
- return UltraFastForwarding;
+ return Manager.FastForwarding;
}
private static void IgnoreGcCollect(ILContext il) {
@@ -212,10 +213,10 @@ private static void IgnoreGcCollect(ILContext il) {
}
private static bool IsIgnoreGcCollect() {
- return TasSettings.IgnoreGcCollect && UltraFastForwarding;
+ return TasSettings.IgnoreGcCollect && Manager.FastForwarding;
}
private static void InputOnOnInitialize() {
CelesteTasModule.Instance.OnInputDeregister();
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/HideGameplay.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/HideGameplay.cs
index b779a6ac3..7428be016 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/HideGameplay.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/HideGameplay.cs
@@ -47,4 +47,4 @@ private static void GameplayRenderer_Render(ILContext il) {
private static bool IsHideGamePlay() {
return !TasSettings.ShowGameplay;
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/ActualEntityCollideHitbox.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/ActualEntityCollideHitbox.cs
deleted file mode 100644
index d63f5acf3..000000000
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/ActualEntityCollideHitbox.cs
+++ /dev/null
@@ -1,176 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Celeste;
-using Microsoft.Xna.Framework;
-using Mono.Cecil.Cil;
-using Monocle;
-using MonoMod.Cil;
-using StudioCommunication;
-using TAS.Module;
-using TAS.Utils;
-
-namespace TAS.EverestInterop.Hitboxes;
-
-public static partial class ActualEntityCollideHitbox {
- private static readonly Dictionary LastPositions = new();
- private static readonly Dictionary LastColldables = new();
-
- private static bool playerUpdated;
- private static bool dontSaveLastPosition;
- private static bool colliderListRendering;
-
- [Initialize]
- private static void Initialize() {
- if (ModUtils.GetType("SpirialisHelper", "Celeste.Mod.Spirialis.TimeController")?.GetMethodInfo("CustomELUpdate") is { } customELUpdate) {
- customELUpdate.IlHook((cursor, _) => cursor.EmitDelegate(Clear));
- }
- }
-
- [Load]
- private static void Load() {
- typeof(Player).GetMethod("orig_Update").IlHook(ModPlayerOrigUpdateEntity);
- On.Celeste.Player.Update += PlayerOnUpdate;
- On.Monocle.Hitbox.Render += HitboxOnRenderEntity;
- On.Monocle.Circle.Render += CircleOnRender;
- On.Monocle.ColliderList.Render += ColliderListOnRender;
- On.Monocle.EntityList.Update += EntityListOnUpdate;
- On.Celeste.Level.End += LevelOnEnd;
- }
-
- [Unload]
- private static void Unload() {
- On.Celeste.Player.Update -= PlayerOnUpdate;
- On.Monocle.Hitbox.Render -= HitboxOnRenderEntity;
- On.Monocle.Circle.Render -= CircleOnRender;
- On.Monocle.ColliderList.Render -= ColliderListOnRender;
- On.Monocle.EntityList.Update -= EntityListOnUpdate;
- On.Celeste.Level.End -= LevelOnEnd;
- }
-
- private static void PlayerOnUpdate(On.Celeste.Player.orig_Update orig, Player self) {
- dontSaveLastPosition = Manager.UltraFastForwarding || !TasSettings.ShowHitboxes ||
- TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Off || playerUpdated;
- orig(self);
- playerUpdated = true;
- }
-
- private static void EntityListOnUpdate(On.Monocle.EntityList.orig_Update orig, EntityList self) {
- Clear();
- orig(self);
- }
-
- private static void LevelOnEnd(On.Celeste.Level.orig_End orig, Level self) {
- Clear();
- orig(self);
- }
-
- private static void Clear() {
- playerUpdated = false;
- LastPositions.Clear();
- LastColldables.Clear();
- }
-
- private static void ModPlayerOrigUpdateEntity(ILContext il) {
- ILCursor ilCursor = new(il);
- if (ilCursor.TryGotoNext(MoveType.After, ins => ins.MatchCastclass())) {
- ilCursor.Emit(OpCodes.Dup).EmitDelegate>(SaveEntityPosition);
- }
- }
-
- private static void SaveEntityPosition(PlayerCollider playerCollider) {
- Entity entity = playerCollider.Entity;
-
- if (dontSaveLastPosition || entity == null) {
- return;
- }
-
- entity.SaveActualCollidePosition();
- entity.SaveActualCollidable();
- }
-
- private static void CircleOnRender(On.Monocle.Circle.orig_Render orig, Circle self, Camera camera, Color color) {
- DrawLastFrameHitbox(self, color, hitboxColor => orig(self, camera, hitboxColor));
- }
-
- private static void HitboxOnRenderEntity(On.Monocle.Hitbox.orig_Render orig, Hitbox self, Camera camera, Color color) {
- DrawLastFrameHitbox(self, color, hitboxColor => orig(self, camera, hitboxColor));
- }
-
- private static void ColliderListOnRender(On.Monocle.ColliderList.orig_Render orig, ColliderList self, Camera camera, Color color) {
- colliderListRendering = true;
- DrawLastFrameHitbox(self, color, hitboxColor => orig(self, camera, hitboxColor));
- colliderListRendering = false;
- }
-
- private static void DrawLastFrameHitbox(Collider self, Color color, Action invokeOrig) {
- Entity entity = self.Entity;
-
- if (Manager.UltraFastForwarding
- || !TasSettings.ShowHitboxes
- || TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Off
- || colliderListRendering && self is not ColliderList
- || entity.Get() == null
- || entity.Scene?.Tracker.GetEntity() == null
- || entity.LoadActualCollidePosition() is not { } actualCollidePosition
- || TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Append && entity.Position == actualCollidePosition &&
- entity.Collidable == entity.LoadActualCollidable()
- ) {
- invokeOrig(color);
- return;
- }
-
- Color lastFrameColor =
- TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Append && entity.Position != actualCollidePosition
- ? color.Invert()
- : color;
-
- if (entity.Collidable && !entity.LoadActualCollidable()) {
- lastFrameColor *= HitboxColor.UnCollidableAlpha;
- } else if (!entity.Collidable && entity.LoadActualCollidable() & HitboxColor.UnCollidableAlpha > 0) {
- lastFrameColor *= 1 / HitboxColor.UnCollidableAlpha;
- }
-
- if (TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Append) {
- if (entity.Position == actualCollidePosition) {
- invokeOrig(lastFrameColor);
- return;
- }
-
- invokeOrig(color);
- }
-
- Vector2 currentPosition = entity.Position;
-
- IEnumerable playerColliders = entity.Components.GetAll().ToArray();
- if (playerColliders.All(playerCollider => playerCollider.Collider != null)) {
- if (playerColliders.Any(playerCollider => playerCollider.Collider == self)) {
- entity.Position = actualCollidePosition;
- invokeOrig(lastFrameColor);
- entity.Position = currentPosition;
- } else {
- invokeOrig(color);
- }
- } else {
- entity.Position = actualCollidePosition;
- invokeOrig(lastFrameColor);
- entity.Position = currentPosition;
- }
- }
-
- private static void SaveActualCollidePosition(this Entity entity) {
- LastPositions[entity] = entity.Position;
- }
-
- private static Vector2? LoadActualCollidePosition(this Entity entity) {
- return LastPositions.TryGetValue(entity, out Vector2 result) ? result : null;
- }
-
- private static void SaveActualCollidable(this Entity entity) {
- LastColldables[entity] = entity.Collidable;
- }
-
- private static bool LoadActualCollidable(this Entity entity) {
- return LastColldables.TryGetValue(entity, out bool result) && result;
- }
-}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/ActualPlayerCollideHitbox.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/ActualPlayerCollideHitbox.cs
deleted file mode 100644
index 86d09b692..000000000
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/ActualPlayerCollideHitbox.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System;
-using Celeste;
-using Microsoft.Xna.Framework;
-using Mono.Cecil.Cil;
-using Monocle;
-using MonoMod.Cil;
-using StudioCommunication;
-using TAS.Module;
-using TAS.Utils;
-
-namespace TAS.EverestInterop.Hitboxes;
-
-public static partial class ActualEntityCollideHitbox {
- private static readonly Color PlayerHitboxColor = Color.Red.Invert();
- private static readonly Color PlayerHurtboxColor = Color.Lime.Invert();
-
- [Load]
- private static void LoadPlayerHook() {
- On.Celeste.Player.DebugRender += PlayerOnDebugRender;
- typeof(Player).GetMethod("orig_Update").IlHook(ModPlayerOrigUpdate);
- }
-
- [Unload]
- private static void UnloadPlayerHook() {
- On.Celeste.Player.DebugRender -= PlayerOnDebugRender;
- }
-
- private static void ModPlayerOrigUpdate(ILContext il) {
- ILCursor ilCursor = new(il);
- if (ilCursor.TryGotoNext(MoveType.After,
- ins => ins.OpCode == OpCodes.Callvirt &&
- ins.Operand.ToString().Contains("Monocle.Tracker::GetComponents()"))) {
- ilCursor.Emit(OpCodes.Ldarg_0).EmitDelegate>(SavePlayerPosition);
- }
- }
-
- private static void SavePlayerPosition(Player player) {
- if (Manager.UltraFastForwarding || !TasSettings.ShowHitboxes || TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Off ||
- playerUpdated) {
- return;
- }
-
- player.SaveActualCollidePosition();
- }
-
- private static void PlayerOnDebugRender(On.Celeste.Player.orig_DebugRender orig, Player player, Camera camera) {
- if (Manager.UltraFastForwarding
- || !TasSettings.ShowHitboxes
- || TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Off
- || player.Scene is Level {Transitioning: true}
- || player.LoadActualCollidePosition() is not { } actualCollidePosition
- || actualCollidePosition == player.Position
- ) {
- orig(player, camera);
- return;
- }
-
- if (TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Override) {
- DrawAssistedHitbox(player, actualCollidePosition);
- }
-
- orig(player, camera);
- if (TasSettings.ShowActualCollideHitboxes == ActualCollideHitboxType.Append) {
- DrawAssistedHitbox(player, actualCollidePosition);
- }
- }
-
- private static void DrawAssistedHitbox(Player player, Vector2 hitboxPosition) {
- Collider origCollider = player.Collider;
- Collider hurtbox = player.hurtbox;
- Vector2 origPosition = player.Position;
-
- player.Position = hitboxPosition;
-
- Draw.HollowRect(origCollider, PlayerHitboxColor);
- player.Collider = hurtbox;
- Draw.HollowRect(hurtbox, PlayerHurtboxColor);
-
- player.Collider = origCollider;
- player.Position = origPosition;
- }
-}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/CycleHitboxColor.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/CycleHitboxColor.cs
index 170c61742..9f5b95a4d 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/CycleHitboxColor.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/CycleHitboxColor.cs
@@ -5,6 +5,7 @@
using Celeste.Mod.UI;
using Microsoft.Xna.Framework;
using Monocle;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
@@ -201,4 +202,4 @@ private static void CmdChangeOtherCyclesHitboxColor(string color) {
TasSettings.OtherCyclesHitboxColor = HitboxColor.HexToColor(color, DefaultOthersColor);
CelesteTasModule.Instance.SaveSettings();
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/FreeCameraHitbox.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/FreeCameraHitbox.cs
index 9f1ff502c..51016f34a 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/FreeCameraHitbox.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/FreeCameraHitbox.cs
@@ -4,6 +4,7 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Monocle;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
@@ -53,7 +54,7 @@ private static void SubHudRendererOnRenderContent(orig_SubHudRenderer_RenderCont
spriteEffects |= SpriteEffects.FlipHorizontally;
}
- if (ExtendedVariantsUtils.UpsideDown) {
+ if (ExtendedVariantsInterop.UpsideDown) {
spriteEffects |= SpriteEffects.FlipVertically;
}
@@ -66,4 +67,4 @@ private static void SubHudRendererOnRenderContent(orig_SubHudRenderer_RenderCont
orig(self, scene);
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxColor.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxColor.cs
index 9589d0a43..4c3e741c3 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxColor.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxColor.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Celeste;
@@ -7,6 +8,8 @@
using Mono.Cecil.Cil;
using Monocle;
using MonoMod.Cil;
+using StudioCommunication;
+using TAS.Gameplay.Hitboxes;
using TAS.Module;
namespace TAS.EverestInterop.Hitboxes;
@@ -16,6 +19,7 @@ public static class HitboxColor {
public static readonly Color DefaultTriggerColor = Color.MediumPurple;
public static readonly Color DefaultPlatformColor = Color.Coral;
public static readonly Color RespawnTriggerColor = Color.YellowGreen;
+ public static readonly Color CameraTriggerColor = Color.DarkGoldenrod;
public static readonly Color PufferHeightCheckColor = Color.WhiteSmoke;
public static readonly Color PufferPushRadiusColor = Color.DarkRed;
@@ -138,15 +142,21 @@ private static Color GetCustomColor(Color color, Entity entity) {
return color;
}
- Color customColor = entity switch {
- ChangeRespawnTrigger => RespawnTriggerColor,
- Trigger => TasSettings.TriggerHitboxColor,
+ Color customColor = Color.Red; // i hate warning CS0165
+ bool found = false;
+
+ customColor = entity switch {
+ Trigger => TriggerHitbox.GetHitboxColor(entity),
Platform => TasSettings.PlatformHitboxColor,
LookoutBlocker => Color.Green,
_ => TasSettings.EntityHitboxColor
};
- if (!entity.Collidable) {
+ if (TasSettings.ShowActualCollideHitboxes != ActualCollideHitboxType.Off && entity.LoadActualCollidable() is { } actualCollidable) {
+ if (!actualCollidable) {
+ customColor *= UnCollidableAlpha;
+ }
+ } else if (!entity.Collidable) {
customColor *= UnCollidableAlpha;
}
@@ -174,4 +184,4 @@ private static void CmdChangePlatformHitboxColor(string color) {
TasSettings.PlatformHitboxColor = HexToColor(color, DefaultPlatformColor);
CelesteTasModule.Instance.SaveSettings();
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxConquerorBeam.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxConquerorBeam.cs
index bd4bd8139..49828435c 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxConquerorBeam.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxConquerorBeam.cs
@@ -1,6 +1,7 @@
using System;
using Microsoft.Xna.Framework;
using Monocle;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
@@ -50,4 +51,4 @@ private static void ModHitbox(On.Monocle.Entity.orig_DebugRender orig, Entity se
Draw.Line(vector, vector2, HitboxColor.EntityColor);
}
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxFinalBoss.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxFinalBoss.cs
index 1780fc22e..c9e7cc122 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxFinalBoss.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxFinalBoss.cs
@@ -50,4 +50,4 @@ private static void HitboxOnRender(On.Monocle.Hitbox.orig_Render orig, Hitbox se
orig(self, camera, color);
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxFixer.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxFixer.cs
index 33299bc38..a077bf1ba 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxFixer.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxFixer.cs
@@ -147,4 +147,4 @@ private static void DrawLine(int x, int y0, int y1, bool interchangeXy, Color co
Draw.SpriteBatch.Draw(Draw.Pixel.Texture.Texture, rect, Draw.Pixel.ClipRect, color);
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxNpc.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxNpc.cs
index 59a50914d..3d1d88fb8 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxNpc.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxNpc.cs
@@ -112,4 +112,4 @@ private static void EntityOnDebugRender(On.Monocle.Entity.orig_DebugRender orig,
}
}
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxOptimized.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxOptimized.cs
index 574888e63..25a4fc48a 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxOptimized.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxOptimized.cs
@@ -7,6 +7,7 @@
using Monocle;
using MonoMod.Cil;
using MonoMod.RuntimeDetour;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
@@ -49,7 +50,7 @@ private static void Initialize() {
// but i have no good idea, so i put it aside
}
- using (new DetourContext {After = new List {"*"}}) {
+ using (new DetourConfigContext(new DetourConfig("CelesteTAS", before: ["*"])).Use()) {
On.Monocle.Entity.DebugRender += ModDebugRender;
}
}
@@ -201,7 +202,7 @@ private static void DrawPufferLaunchOrBounceIndicator(Puffer puffer) {
if (z > pc.Collider.Bottom + puffer.Y) {
return;
}
-
+
float top = Math.Max(pc.Collider.Top + puffer.Y, z);
Draw.HollowRect(puffer.X - 7f, top, 14f, pc.Collider.Bottom + puffer.Y - top,
puffer.Collidable ? HitboxColor.PufferHeightCheckColor : HitboxColor.PufferHeightCheckColor * HitboxColor.UnCollidableAlpha);
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxRoomBoundary.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxRoomBoundary.cs
index e8e2d95a4..03398ed6d 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxRoomBoundary.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxRoomBoundary.cs
@@ -27,4 +27,4 @@ private static void EntityListOnDebugRender(On.Monocle.EntityList.orig_DebugRend
Draw.HollowRect(bounds.X - 1, bounds.Y - topExtra, bounds.Width + 2, bounds.Height + topExtra + 1, HitboxColor.RespawnTriggerColor);
}
}
-}
\ No newline at end of file
+}
diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxSimplified.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxSimplified.cs
index d6de80c97..287a17c50 100644
--- a/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxSimplified.cs
+++ b/CelesteTAS-EverestInterop/Source/EverestInterop/Hitboxes/HitboxSimplified.cs
@@ -2,21 +2,25 @@
using System.Collections.Generic;
using System.Reflection;
using Celeste;
+using JetBrains.Annotations;
using Microsoft.Xna.Framework;
using Mono.Cecil.Cil;
using Monocle;
using MonoMod.Cil;
using TAS.EverestInterop.InfoHUD;
+using TAS.Gameplay.Hitboxes;
+using TAS.InfoHUD;
+using TAS.ModInterop;
using TAS.Module;
using TAS.Utils;
namespace TAS.EverestInterop.Hitboxes;
public static class HitboxSimplified {
- private static readonly Lazy> GeckoHostile = new(() =>
+ private static readonly Lazy?> GeckoHostile = new(() =>
ModUtils.GetType("JungleHelper", "Celeste.Mod.JungleHelper.Entities.Gecko")?.CreateGetDelegate