diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f06120a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,338 @@ +root = true + +[*.cs] +indent_style = space +indent_size = 4 + +# https://kent-boogaart.com/blog/editorconfig-reference-for-c-developers + +csharp_indent_block_contents = true +csharp_indent_case_contents = true +csharp_indent_labels = no_change +csharp_new_line_before_catch = false +csharp_new_line_before_else = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_open_brace = none +csharp_prefer_braces = true:suggestion +csharp_prefer_simple_default_expression = true:warning +csharp_preserve_single_line_statements = false +csharp_style_conditional_delegate_call = true:warning +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_inlined_variable_declaration = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_throw_expression = true:warning +csharp_style_var_elsewhere = false:suggestion +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:suggestion +dotnet_style_coalesce_expression = true:warning +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:silent +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion + +# CS1591: Missing XML comment for publicly visible type or member + +# ReSharper properties +resharper_accessor_owner_body = accessors_with_expression_body +resharper_autodetect_indent_settings = true +resharper_braces_redundant = true +resharper_cpp_max_line_length = 160 +resharper_csharp_empty_block_style = together_same_line +resharper_csharp_max_line_length = 150 +resharper_fsharp_max_line_length = 160 +resharper_html_max_line_length = 160 +resharper_resx_max_line_length = 160 +resharper_shaderlab_max_line_length = 160 +resharper_show_autodetect_configure_formatting_tip = false +resharper_use_indent_from_vs = false +resharper_vb_max_line_length = 160 +resharper_xmldoc_max_line_length = 160 +resharper_xml_max_line_length = 160 + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_enforce_do_while_statement_braces_highlighting = hint +resharper_enforce_fixed_statement_braces_highlighting = hint +resharper_enforce_foreach_statement_braces_highlighting = hint +resharper_enforce_for_statement_braces_highlighting = hint +resharper_enforce_if_statement_braces_highlighting = hint +resharper_enforce_lock_statement_braces_highlighting = hint +resharper_enforce_using_statement_braces_highlighting = hint +resharper_enforce_while_statement_braces_highlighting = hint +resharper_inconsistent_naming_highlighting = none +resharper_redundant_base_qualifier_highlighting = warning +resharper_remove_redundant_braces_highlighting = hint +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_unused_type_global_highlighting = warning +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning + +# Microsoft .NET properties +csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +dotnet_naming_rule.private_constants_rule.severity = none +dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_instance_fields_rule.severity = none +dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule.severity = none +dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.severity = none +dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +[*.{appxmanifest, asax, ascx, aspx, axaml, build, cg, cginc, compute, cs, cshtml, dtd, fs, fsi, fsscript, fsx, hlsl, hlsli, hlslinc, master, ml, mli, nuspec, paml, razor, resw, resx, shader, skin, usf, ush, vb, xaml, xamlx, xoml, xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[*] +charset = utf-8 +end_of_line = crlf +indent_style = space +indent_size = 4 + +# Standard properties +max_line_length = 160 + +# ReSharper properties +resharper_accessor_owner_body = expression_body +resharper_apply_auto_detected_rules = false +resharper_autodetect_indent_settings = true +resharper_braces_redundant = true +resharper_cpp_max_line_length = 160 +resharper_csharp_empty_block_style = together_same_line +resharper_csharp_max_line_length = 150 +resharper_csharp_naming_rule.constants = AaBb +resharper_csharp_naming_rule.event = AaBb +resharper_csharp_naming_rule.interfaces = I + AaBb +resharper_csharp_naming_rule.locals = aaBb +resharper_csharp_naming_rule.local_constants = aaBb +resharper_csharp_naming_rule.local_functions = AaBb +resharper_csharp_naming_rule.method = AaBb +resharper_csharp_naming_rule.parameters = aaBb +resharper_csharp_naming_rule.private_constants = AaBb +resharper_csharp_naming_rule.private_instance_fields = aaBb +resharper_csharp_naming_rule.private_static_fields = aaBb +resharper_csharp_naming_rule.private_static_readonly = AaBb +resharper_csharp_naming_rule.property = AaBb +resharper_csharp_naming_rule.public_fields = AaBb +resharper_csharp_naming_rule.static_readonly = AaBb +resharper_csharp_naming_rule.types_and_namespaces = AaBb +resharper_csharp_naming_rule.type_parameters = T + AaBb +resharper_fsharp_max_line_length = 160 +resharper_html_max_line_length = 160 +resharper_resx_max_line_length = 160 +resharper_resx_wrap_lines = false +resharper_shaderlab_max_line_length = 160 +resharper_show_autodetect_configure_formatting_tip = false +resharper_use_indent_from_vs = false +resharper_vb_max_line_length = 160 +resharper_wrap_lines = true +resharper_xmldoc_max_line_length = 160 +resharper_xml_max_line_length = 160 + +# Microsoft .NET properties +csharp_new_line_before_catch = false +csharp_new_line_before_else = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_open_brace = none +csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +csharp_prefer_braces = true:suggestion +csharp_style_var_elsewhere = false:suggestion +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:suggestion +dotnet_naming_rule.constants_rule.severity = warning +dotnet_naming_rule.constants_rule.style = upper_camel_case_style +dotnet_naming_rule.constants_rule.symbols = constants_symbols +dotnet_naming_rule.event_rule.severity = warning +dotnet_naming_rule.event_rule.style = upper_camel_case_style +dotnet_naming_rule.event_rule.symbols = event_symbols +dotnet_naming_rule.interfaces_rule.severity = warning +dotnet_naming_rule.interfaces_rule.style = i_upper_camel_case_style +dotnet_naming_rule.interfaces_rule.symbols = interfaces_symbols +dotnet_naming_rule.locals_rule.severity = warning +dotnet_naming_rule.locals_rule.style = lower_camel_case_style +dotnet_naming_rule.locals_rule.symbols = locals_symbols +dotnet_naming_rule.local_constants_rule.severity = warning +dotnet_naming_rule.local_constants_rule.style = lower_camel_case_style +dotnet_naming_rule.local_constants_rule.symbols = local_constants_symbols +dotnet_naming_rule.local_functions_rule.severity = warning +dotnet_naming_rule.local_functions_rule.style = upper_camel_case_style +dotnet_naming_rule.local_functions_rule.symbols = local_functions_symbols +dotnet_naming_rule.method_rule.severity = warning +dotnet_naming_rule.method_rule.style = upper_camel_case_style +dotnet_naming_rule.method_rule.symbols = method_symbols +dotnet_naming_rule.parameters_rule.severity = warning +dotnet_naming_rule.parameters_rule.style = lower_camel_case_style +dotnet_naming_rule.parameters_rule.symbols = parameters_symbols +dotnet_naming_rule.private_constants_rule.severity = warning +dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_instance_fields_rule.severity = warning +dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols +dotnet_naming_rule.private_instance_fields_rule_1.severity = none +dotnet_naming_rule.private_instance_fields_rule_1.style = lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule.severity = warning +dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols +dotnet_naming_rule.private_static_fields_rule_1.severity = none +dotnet_naming_rule.private_static_fields_rule_1.style = lower_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.severity = warning +dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_rule.private_static_readonly_rule_1.severity = none +dotnet_naming_rule.private_static_readonly_rule_1.style = lower_camel_case_style +dotnet_naming_rule.property_rule.severity = warning +dotnet_naming_rule.property_rule.style = upper_camel_case_style +dotnet_naming_rule.property_rule.symbols = property_symbols +dotnet_naming_rule.public_fields_rule.severity = none +dotnet_naming_rule.public_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.public_fields_rule_1.severity = warning +dotnet_naming_rule.public_fields_rule_1.style = upper_camel_case_style +dotnet_naming_rule.public_fields_rule_1.symbols = public_fields_symbols +dotnet_naming_rule.static_readonly_rule.severity = none +dotnet_naming_rule.static_readonly_rule.style = lower_camel_case_style +dotnet_naming_rule.static_readonly_rule_1.severity = warning +dotnet_naming_rule.static_readonly_rule_1.style = upper_camel_case_style +dotnet_naming_rule.static_readonly_rule_1.symbols = static_readonly_symbols +dotnet_naming_rule.types_and_namespaces_rule.severity = warning +dotnet_naming_rule.types_and_namespaces_rule.style = upper_camel_case_style +dotnet_naming_rule.types_and_namespaces_rule.symbols = types_and_namespaces_symbols +dotnet_naming_rule.type_parameters_rule.severity = warning +dotnet_naming_rule.type_parameters_rule.style = t_upper_camel_case_style +dotnet_naming_rule.type_parameters_rule.symbols = type_parameters_symbols +dotnet_naming_style.i_upper_camel_case_style.capitalization = pascal_case +dotnet_naming_style.i_upper_camel_case_style.required_prefix = I +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case +dotnet_naming_style.t_upper_camel_case_style.required_prefix = T +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.constants_symbols.applicable_kinds = field +dotnet_naming_symbols.constants_symbols.required_modifiers = const +dotnet_naming_symbols.event_symbols.applicable_accessibilities = * +dotnet_naming_symbols.event_symbols.applicable_kinds = event +dotnet_naming_symbols.interfaces_symbols.applicable_accessibilities = * +dotnet_naming_symbols.interfaces_symbols.applicable_kinds = interface +dotnet_naming_symbols.locals_symbols.applicable_accessibilities = * +dotnet_naming_symbols.locals_symbols.applicable_kinds = local +dotnet_naming_symbols.local_constants_symbols.applicable_accessibilities = * +dotnet_naming_symbols.local_constants_symbols.applicable_kinds = local +dotnet_naming_symbols.local_constants_symbols.required_modifiers = const +dotnet_naming_symbols.local_functions_symbols.applicable_accessibilities = * +dotnet_naming_symbols.local_functions_symbols.applicable_kinds = local_function +dotnet_naming_symbols.method_symbols.applicable_accessibilities = * +dotnet_naming_symbols.method_symbols.applicable_kinds = method +dotnet_naming_symbols.parameters_symbols.applicable_accessibilities = * +dotnet_naming_symbols.parameters_symbols.applicable_kinds = parameter +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.property_symbols.applicable_accessibilities = * +dotnet_naming_symbols.property_symbols.applicable_kinds = property +dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = * +dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, class, struct, enum, delegate +dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_convert_to_auto_property_with_private_setter_highlighting = none +resharper_enforce_do_while_statement_braces_highlighting = hint +resharper_enforce_fixed_statement_braces_highlighting = hint +resharper_enforce_foreach_statement_braces_highlighting = hint +resharper_enforce_for_statement_braces_highlighting = hint +resharper_enforce_if_statement_braces_highlighting = hint +resharper_enforce_lock_statement_braces_highlighting = hint +resharper_enforce_using_statement_braces_highlighting = hint +resharper_enforce_while_statement_braces_highlighting = hint +resharper_invert_if_highlighting = none +resharper_redundant_base_qualifier_highlighting = warning +resharper_redundant_if_else_block_highlighting = none +resharper_remove_redundant_braces_highlighting = hint +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_unused_type_global_highlighting = warning +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning +insert_final_newline = false + +[{*.yaml, *.yml}] +indent_style = space +indent_size = 2 + +[{*.yaml, *.yml}] +indent_style = space +indent_size = 2 + +[*.{appxmanifest, asax, ascx, aspx, axaml, build, cg, cginc, compute, cs, cshtml, dtd, fs, fsi, fsscript, fsx, hlsl, hlsli, hlslinc, master, ml, mli, nuspec, paml, razor, resw, resx, shader, skin, usf, ush, vb, xaml, xamlx, xoml, xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[{*.yaml, *.yml}] +indent_style = space +indent_size = 2 + +[*.{appxmanifest, asax, ascx, aspx, axaml, build, cg, cginc, compute, cs, cshtml, dtd, fs, fsi, fsscript, fsx, hlsl, hlsli, hlslinc, master, ml, mli, nuspec, paml, razor, resw, resx, shader, skin, usf, ush, vb, xaml, xamlx, xoml, xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/GhostMod/GhostCompareTime.cs b/GhostMod/GhostCompareTime.cs new file mode 100644 index 0000000..fd434a0 --- /dev/null +++ b/GhostMod/GhostCompareTime.cs @@ -0,0 +1,126 @@ +using System; +using System.Linq; +using Microsoft.Xna.Framework; +using Monocle; + +namespace Celeste.Mod.Ghost; + +internal static class GhostCompareTime { + public static long GhostTime; + public static long LastGhostTime; + public static long CurrentTime; + public static long LastCurrentTime; + + public static void Load() { + On.Celeste.Level.Render += LevelOnRender; + On.Celeste.Level.NextLevel += LevelOnNextLevel; + On.Celeste.Level.RegisterAreaComplete += LevelOnRegisterAreaComplete; + On.Celeste.LevelLoader.ctor += LevelLoaderOnCtor; + } + + public static void Unload() { + On.Celeste.Level.Render -= LevelOnRender; + On.Celeste.Level.NextLevel -= LevelOnNextLevel; + On.Celeste.Level.RegisterAreaComplete -= LevelOnRegisterAreaComplete; + On.Celeste.LevelLoader.ctor -= LevelLoaderOnCtor; + } + + private static void LevelOnNextLevel(On.Celeste.Level.orig_NextLevel orig, Level self, Vector2 at, Vector2 dir) { + orig(self, at, dir); + if (GhostModule.Instance.GhostManager?.Ghosts.FirstOrDefault()?.Data.Frames.LastOrDefault().Data.Time is { } time) { + LastGhostTime = GhostTime; + GhostTime = time; + LastCurrentTime = CurrentTime; + CurrentTime = self.Session.Time; + } + } + + private static void LevelOnRegisterAreaComplete(On.Celeste.Level.orig_RegisterAreaComplete orig, Level self) { + orig(self); + + if (GhostModule.Instance.GhostManager?.Ghosts.FirstOrDefault()?.Data.Frames.LastOrDefault().Data.Time is long time) { + LastGhostTime = GhostTime; + GhostTime = time; + LastCurrentTime = CurrentTime; + CurrentTime = self.Session.Time; + } + } + + private static void LevelOnRender(On.Celeste.Level.orig_Render orig, Level self) { + orig(self); + + if (GhostModule.ModuleSettings.Mode == GhostModuleMode.Play && GhostModule.ModuleSettings.ShowCompareTime) { + int viewWidth = Engine.ViewWidth; + int viewHeight = Engine.ViewHeight; + + float pixelScale = viewWidth / 320f; + float margin = 2 * pixelScale; + float padding = 2 * pixelScale; + float fontSize = 0.3f * pixelScale; + float alpha = 1f; + + if (GhostTime == 0) { + return; + } + + long diffRoomTime = CurrentTime - GhostTime - LastCurrentTime + LastGhostTime; + long diffTotalTime = CurrentTime - GhostTime; + string diffRoomTimeStr = (diffRoomTime > 0 ? "+" : string.Empty) + (diffRoomTime / 10000000D).ToString("0.000"); + string diffTotalTimeStr = (diffTotalTime > 0 ? "+" : string.Empty) + (diffTotalTime / 10000000D).ToString("0.000"); + string timeStr = $"last room: {diffRoomTimeStr}\ntotal : {diffTotalTimeStr}"; + + if (string.IsNullOrEmpty(timeStr)) { + return; + } + + Vector2 size = Draw.DefaultFont.MeasureString(timeStr) * fontSize; + + float x; + float y; + + x = margin; + y = margin; + + if (Settings.Instance.SpeedrunClock == SpeedrunType.Chapter) { + y += 16 * pixelScale; + } else if (Settings.Instance.SpeedrunClock == SpeedrunType.File) { + y += 20 * pixelScale; + } + + Rectangle bgRect = new Rectangle((int)x, (int)y, (int)(size.X + padding * 2), (int)(size.Y + padding * 2)); + + if (self.Entities.FindFirst() is Player player) { + Vector2 playerPosition = self.Camera.CameraToScreen(player.TopLeft) * pixelScale; + Rectangle playerRect = new Rectangle((int)playerPosition.X, (int)playerPosition.Y, (int)(8 * pixelScale), (int)(11 * pixelScale)); + Rectangle mirrorBgRect = bgRect; + if (SaveData.Instance?.Assists.MirrorMode == true) { + mirrorBgRect.X = (int)Math.Abs(x - viewWidth + size.X + padding * 2); + } + + if (self.Paused || playerRect.Intersects(mirrorBgRect)) { + alpha = 0.5f; + } + } + + Draw.SpriteBatch.Begin(); + + Draw.Rect(bgRect, Color.Black * 0.8f * alpha); + + Vector2 textPosition = new Vector2(x + padding, y + padding); + Vector2 scale = new Vector2(fontSize); + + Draw.Text(Draw.DefaultFont, timeStr, textPosition, Color.White * alpha, Vector2.Zero, scale, 0f); + + Draw.SpriteBatch.End(); + } + } + + private static void LevelLoaderOnCtor(On.Celeste.LevelLoader.orig_ctor orig, LevelLoader self, Session session, Vector2? startPosition) { + orig(self, session, startPosition); + + GhostTime = 0; + LastGhostTime = 0; + CurrentTime = 0; + LastCurrentTime = 0; + } +} \ No newline at end of file diff --git a/GhostMod/GhostMod.csproj b/GhostMod/GhostMod.csproj index bd44ac3..b956d79 100644 --- a/GhostMod/GhostMod.csproj +++ b/GhostMod/GhostMod.csproj @@ -44,6 +44,9 @@ lib-stripped\MMHOOK_Celeste.dll + + lib-stripped\SpeedrunTool.dll + diff --git a/GhostMod/GhostModule.cs b/GhostMod/GhostModule.cs index 6a75e4e..28e0e59 100644 --- a/GhostMod/GhostModule.cs +++ b/GhostMod/GhostModule.cs @@ -25,16 +25,14 @@ public class GhostModule : EverestModule { public Guid Run; - - private long ghostTime; - private long lastGhostTime; - private long currentTime; - private long lastCurrentTime; - public GhostModule() { Instance = this; } + public override void Initialize() { + SaveStateUtils.Initialize(); + } + public override void Load() { PathGhosts = Path.Combine(Everest.PathSettings, "Ghosts"); if (!Directory.Exists(PathGhosts)) { @@ -44,20 +42,15 @@ public override void Load() { On.Celeste.Level.LoadLevel += OnLoadLevel; Everest.Events.Level.OnExit += OnExit; On.Celeste.Player.Die += OnDie; - On.Celeste.Level.Render += LevelOnRender; - On.Celeste.Level.NextLevel += LevelOnNextLevel; - On.Celeste.Level.RegisterAreaComplete += LevelOnRegisterAreaComplete; - On.Celeste.LevelLoader.ctor += LevelLoaderOnCtor; + GhostCompareTime.Load(); } public override void Unload() { On.Celeste.Level.LoadLevel -= OnLoadLevel; Everest.Events.Level.OnExit -= OnExit; On.Celeste.Player.Die -= OnDie; - On.Celeste.Level.Render -= LevelOnRender; - On.Celeste.Level.NextLevel -= LevelOnNextLevel; - On.Celeste.Level.RegisterAreaComplete -= LevelOnRegisterAreaComplete; - On.Celeste.LevelLoader.ctor -= LevelLoaderOnCtor; + GhostCompareTime.Unload(); + SaveStateUtils.Unload(); } public void OnLoadLevel(On.Celeste.Level.orig_LoadLevel orig, Level level, Player.IntroTypes playerIntro, bool isFromLoader) { @@ -155,105 +148,6 @@ public PlayerDeadBody OnDie(On.Celeste.Player.orig_Die orig, Player player, Vect return corpse; } - private void LevelOnNextLevel(On.Celeste.Level.orig_NextLevel orig, Level self, Vector2 at, Vector2 dir) { - orig(self, at, dir); - if (GhostManager?.Ghosts.FirstOrDefault()?.Data.Frames.LastOrDefault().Data.Time is long time) { - lastGhostTime = ghostTime; - ghostTime = time; - lastCurrentTime = currentTime; - currentTime = self.Session.Time; - } - } - - private void LevelOnRegisterAreaComplete(On.Celeste.Level.orig_RegisterAreaComplete orig, Level self) { - orig(self); - - if (GhostManager?.Ghosts.FirstOrDefault()?.Data.Frames.LastOrDefault().Data.Time is long time) { - lastGhostTime = ghostTime; - ghostTime = time; - lastCurrentTime = currentTime; - currentTime = self.Session.Time; - } - } - - private void LevelOnRender(On.Celeste.Level.orig_Render orig, Level self) { - orig(self); - - if (ModuleSettings.Mode == GhostModuleMode.Play && ModuleSettings.ShowCompareTime) { - int viewWidth = Engine.ViewWidth; - int viewHeight = Engine.ViewHeight; - - float pixelScale = viewWidth / 320f; - float margin = 2 * pixelScale; - float padding = 2 * pixelScale; - float fontSize = 0.3f * pixelScale; - float alpha = 1f; - - if (ghostTime == 0) { - return; - } - - long diffRoomTime = currentTime - ghostTime - lastCurrentTime + lastGhostTime; - long diffTotalTime = currentTime - ghostTime; - string diffRoomTimeStr = (diffRoomTime > 0 ? "+" : string.Empty) + (diffRoomTime / 10000000D).ToString("0.000"); - string diffTotalTimeStr = (diffTotalTime > 0 ? "+" : string.Empty) + (diffTotalTime / 10000000D).ToString("0.000"); - string timeStr = $"last room: {diffRoomTimeStr}\ntotal : {diffTotalTimeStr}"; - - if (string.IsNullOrEmpty(timeStr)) { - return; - } - - Vector2 size = Draw.DefaultFont.MeasureString(timeStr) * fontSize; - - float x; - float y; - - x = margin; - y = margin; - - if (Settings.Instance.SpeedrunClock == SpeedrunType.Chapter) { - y += 16 * pixelScale; - } else if (Settings.Instance.SpeedrunClock == SpeedrunType.File) { - y += 20 * pixelScale; - } - - Rectangle bgRect = new Rectangle((int) x, (int) y, (int) (size.X + padding * 2), (int) (size.Y + padding * 2)); - - if (self.Entities.FindFirst() is Player player) { - Vector2 playerPosition = self.Camera.CameraToScreen(player.TopLeft) * pixelScale; - Rectangle playerRect = new Rectangle((int) playerPosition.X, (int) playerPosition.Y, (int) (8 * pixelScale), (int) (11 * pixelScale)); - Rectangle mirrorBgRect = bgRect; - if (SaveData.Instance?.Assists.MirrorMode == true) { - mirrorBgRect.X = (int) Math.Abs(x - viewWidth + size.X + padding * 2); - } - - if (self.Paused || playerRect.Intersects(mirrorBgRect)) { - alpha = 0.5f; - } - } - - Draw.SpriteBatch.Begin(); - - Draw.Rect(bgRect, Color.Black * 0.8f * alpha); - - Vector2 textPosition = new Vector2(x + padding, y + padding); - Vector2 scale = new Vector2(fontSize); - - Draw.Text(Draw.DefaultFont, timeStr, textPosition, Color.White * alpha, Vector2.Zero, scale, 0f); - - Draw.SpriteBatch.End(); - } - } - - private void LevelLoaderOnCtor(On.Celeste.LevelLoader.orig_ctor orig, LevelLoader self, Session session, Vector2? startPosition) { - orig(self, session, startPosition); - - ghostTime = 0; - lastGhostTime = 0; - currentTime = 0; - lastCurrentTime = 0; - } - public override void CreateModMenuSection(TextMenu menu, bool inGame, EventInstance snapshot) { if (SettingsOverridden && !ModuleSettings.AlwaysShowSettings) { menu.Add(new TextMenu.SubHeader(Dialog.Clean("modoptions_ghostmodule_overridden") + " | v." + Metadata.VersionString)); diff --git a/GhostMod/SaveStateUtils.cs b/GhostMod/SaveStateUtils.cs new file mode 100644 index 0000000..639bc60 --- /dev/null +++ b/GhostMod/SaveStateUtils.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using Celeste.Mod.SpeedrunTool.SaveLoad; + +namespace Celeste.Mod.Ghost; + +internal static class SaveStateUtils { + private static bool Installed => Everest.Modules.Any(module => module.Metadata?.Name == "SpeedrunTool"); + private static object action; + + private static Guid run; + private static long ghostTime; + private static long lastGhostTime; + private static long currentTime; + private static long lastCurrentTime; + + public static void Initialize() { + if (Installed) { + AddSaveLoadAction(); + } + } + + public static void Unload() { + if (Installed) { + RemoveSaveLoadAction(); + } + } + + private static void AddSaveLoadAction() { + action = SaveLoadAction.SafeAdd((_, _) => { + run = GhostModule.Instance.Run; + ghostTime = GhostCompareTime.GhostTime; + lastGhostTime = GhostCompareTime.LastGhostTime; + currentTime = GhostCompareTime.CurrentTime; + lastCurrentTime = GhostCompareTime.LastCurrentTime; + }, (_, _) => { + GhostModule.Instance.Run = run; + GhostCompareTime.GhostTime = ghostTime; + GhostCompareTime.LastGhostTime = lastGhostTime; + GhostCompareTime.CurrentTime = currentTime; + GhostCompareTime.LastCurrentTime = lastCurrentTime; + }); + } + + private static void RemoveSaveLoadAction() { + SaveLoadAction.Remove((SaveLoadAction)action); + } +} \ No newline at end of file diff --git a/GhostMod/lib-stripped/SpeedrunTool.dll b/GhostMod/lib-stripped/SpeedrunTool.dll new file mode 100644 index 0000000..c9bca42 Binary files /dev/null and b/GhostMod/lib-stripped/SpeedrunTool.dll differ