From 0ae71f71a3cbc9e8fc5bba4664eed4e63f325b2e Mon Sep 17 00:00:00 2001 From: Frame Date: Tue, 30 Jun 2026 19:40:41 +0200 Subject: [PATCH 01/31] rotate Discord invite link --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cf9286ea7..f4c65b52b 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,13 @@ This refactor is ongoing and has diverged severely from the main repository in o Features from the main branch are partially missing or work in different ways. -I hope to eventually get STROOP back onto a single track again, whether that be the main branch or this fork remains to be seen however. If you are capable and willing to take a heavy load, I'd invite you to discuss details with me on how that should happen on this barebones [Discord](https://discord.gg/QdcwCgXn) server (will adjust as the needs arise). +I hope to eventually get STROOP back onto a single track again, whether that be the main branch or this fork remains to be seen however. If you are capable and willing to take a heavy load, I'd invite you to discuss details with me on how that should happen on this barebones [Discord] server (will adjust as the needs arise). ## Contributing I'd love to develop this version of STROOP into something that everyone involved with SM64 TASing and beyond can get great value from.
If you are a user of this STROOP version in any capacity, all of your suggestions for improvements will be appreciated (and ideally eventually implemented).
-You may submit your feedback either via an [issue](../../issues) or just speak your mind in this barebones [Discord](https://discord.gg/YHgau6tg2d) server. +You may submit your feedback either via an [issue](../../issues) or just speak your mind in this barebones [Discord] server. If you choose to contribute code via a [pull request](../../pulls), please make an effort to keep your changes free of noise, especially regarding code formatting.
While I do not enforce any specific style and do not intend to do so, the result of `dotnet format` should be taken as a baseline. @@ -60,3 +60,5 @@ While I do not enforce any specific style and do not intend to do so, the result These line breaks usually don't facilitate readability at all, and the never-ending war about the "optimal maximum line length" makes it so the noise from added or removed line breaks remains constant in every pull request when different developers with different settings work on the same files.
Particularly, JetBrains Rider has a setting to `Wrap long lines` in the `Editor->Code Style->C#` section, **which I have turned off to prevent it from introducing meaningless line breaks.**
If you feel the need to break long lines for better readability, please do so manually with intent. + +[Discord]: https://discord.gg/FAEACwrqEr From 5f3021bc2c5c21ec92ab424c9b5a852b5da2c6a2 Mon Sep 17 00:00:00 2001 From: Ramos Hugo <132284948+hugou74130@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:15:37 +0200 Subject: [PATCH 02/31] Merge pull request #54 from hugou74130/fix/map-popout-gl-rendering fix: map popout rendering by presenting through its own GL context --- STROOP/Tabs/MapTab/MapGraphics.cs | 74 +++++++++++++++++++++++++++---- STROOP/Tabs/MapTab/MapPopout.cs | 13 ++++-- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/STROOP/Tabs/MapTab/MapGraphics.cs b/STROOP/Tabs/MapTab/MapGraphics.cs index 898933596..1a233767f 100644 --- a/STROOP/Tabs/MapTab/MapGraphics.cs +++ b/STROOP/Tabs/MapTab/MapGraphics.cs @@ -6,6 +6,7 @@ using System.Drawing; using OpenTK.GLControl; using OpenTK.Mathematics; +using OpenTK.Windowing.Common; using STROOP.Controls; using STROOP.Core; using STROOP.Extensions; @@ -76,6 +77,10 @@ public bool Hover3D(Vector3 position, float radius) int mainFrameBuffer, mainColorBuffer, mainDepthBuffer; + // Only used when rendering into a foreign (shared) context - see OnPaint. This FBO lives in + // THIS control's own context and wraps the shared color texture so we can blit it to our window. + int presentFrameBuffer; + public class CachedCollisionStructure { readonly TriangleClassification filter; @@ -212,9 +217,19 @@ public Vector2 mousePosition2D public readonly KeyboardControls keyboardControls; - Func getContext; + Func getContext; + + /// + /// The OpenGL context that hosts all resources necessary to render a complete map image, + /// and is capable of rendering to the main window's Map tab. + /// + /// Popout windows' instances will share with this context, + /// but blit to their own framebuffer before presenting. + /// + /// + IGraphicsContext hostGlContext => getContext != null ? getContext() : glControl.Context; - public MapGraphics(MapTab mapTab, GLControl glControl, Func getContext = null) + public MapGraphics(MapTab mapTab, GLControl glControl, Func getContext = null) { this.mapTab = mapTab; this.glControl = glControl; @@ -290,9 +305,12 @@ public void Load(Func getRenderers) if (glControl.Width * glControl.Height > 0) using (new AccessScope(mapTab)) { + // These surfaces must live in the host context, recreate them there. + hostGlContext.MakeCurrent(); DeleteMainSurfaces(); transparencyRenderer.SetDimensions(glControl.Width, glControl.Height); InitMainSurfaces(); + InitOrUpdatePresentFrameBuffer(); } }; @@ -306,6 +324,7 @@ public void Load(Func getRenderers) GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest); InitMainSurfaces(); + InitOrUpdatePresentFrameBuffer(); }); rendererCollection = getRenderers(); @@ -350,14 +369,38 @@ void InitMainSurfaces() GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); } + void InitOrUpdatePresentFrameBuffer() + { + // Only needed for Map popouts + if (getContext == null) return; + + glControl.MakeCurrent(); + if (presentFrameBuffer == 0) + presentFrameBuffer = GL.GenFramebuffer(); + GL.BindFramebuffer(FramebufferTarget.Framebuffer, presentFrameBuffer); + GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, mainColorBuffer, 0); + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); + } + public void CleanUp() { + if (getContext != null) + { + // The presentFrameBuffer is created specifically and exclusively in the "popout" context of the glControl this instance shall render to. + // Temporarily switch contexts to free the name of the framebuffer as early as possible. + glControl.Context!.MakeCurrent(); + GL.DeleteFramebuffer(presentFrameBuffer); + getContext().MakeCurrent(); + } transparencyRenderer.CleanUp(); DeleteMainSurfaces(); } private void OnPaint() { + hostGlContext.MakeCurrent(); + PerformGLInit(); if (Config.Stream == null || rendererCollection == null) @@ -370,7 +413,6 @@ private void OnPaint() if (glControl.Cursor != cursor) glControl.Cursor = cursor; - (getContext != null ? getContext() : glControl.Context).MakeCurrent(); UpdateMapView(); GL.BindFramebuffer(FramebufferTarget.Framebuffer, mainFrameBuffer); @@ -409,11 +451,27 @@ private void OnPaint() foreach (var action in layer) action.Invoke(); - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, mainFrameBuffer); - GL.BlitFramebuffer(0, 0, glControl.Width, glControl.Height, 0, 0, glControl.Width, glControl.Height, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest); - - glControl.SwapBuffers(); + if (getContext == null) + { + // Main map: render context == our own context. Blit our FBO to our window directly. + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, mainFrameBuffer); + GL.BlitFramebuffer(0, 0, glControl.Width, glControl.Height, 0, 0, glControl.Width, glControl.Height, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest); + glControl.SwapBuffers(); + } + else + { + // Popout: We rendered in the host context. Present into OUR own context/window + // by blitting the shared color texture (valid via GLControl.SharedContext) through a + // present-FBO that lives in our context. This is the fix for issue #39: the old code + // blitted into the main window and swapped our never-rendered buffer. + GL.Flush(); + glControl.MakeCurrent(); + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, presentFrameBuffer); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); + GL.BlitFramebuffer(0, 0, glControl.Width, glControl.Height, 0, 0, glControl.Width, glControl.Height, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest); + glControl.SwapBuffers(); + } } } diff --git a/STROOP/Tabs/MapTab/MapPopout.cs b/STROOP/Tabs/MapTab/MapPopout.cs index b9e3d94c6..5375bac8c 100644 --- a/STROOP/Tabs/MapTab/MapPopout.cs +++ b/STROOP/Tabs/MapTab/MapPopout.cs @@ -1,9 +1,7 @@ -using OpenTK; -using System; +using System; using System.Windows.Forms; using OpenTK.GLControl; using STROOP.Core; -using STROOP.Utilities; namespace STROOP.Tabs.MapTab { @@ -16,10 +14,17 @@ public MapPopout(MapTab tab) { InitializeComponent(); ClientSize = tab.graphics.glControl.ClientRectangle.Size; - glControl = new GLControl(); + // Own GL context, but sharing resources with the main map's context so we can present the + // shared color texture the main context renders into. See issue #39. + glControl = new GLControl() + { + APIVersion = new Version(3, 3), + SharedContext = tab.graphics.glControl, + }; glControl.Bounds = ClientRectangle; glControl.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom; Controls.Add(glControl); + // Render in the main map's (shared) context; present into our own context (handled in MapGraphics). graphics = new MapGraphics(tab, glControl, () => tab.graphics.glControl.Context); graphics.MapViewAngleValue = tab.graphics.MapViewAngleValue; graphics.MapViewScaleValue = tab.graphics.MapViewScaleValue; From c216f8e436b7e2ff07441b14aed329f997f846e5 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Sat, 18 Jul 2026 09:52:53 +0200 Subject: [PATCH 03/31] fix null value treatment in VariableSelectionUtilities.GetNumberValue --- STROOP/Utilities/VariableSelectionUtilities.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/STROOP/Utilities/VariableSelectionUtilities.cs b/STROOP/Utilities/VariableSelectionUtilities.cs index 1bf7e6407..da7f48168 100644 --- a/STROOP/Utilities/VariableSelectionUtilities.cs +++ b/STROOP/Utilities/VariableSelectionUtilities.cs @@ -27,7 +27,10 @@ static IEnumerable FilterNumberVariables(IEnumerable cells.OfType().Where(x => x is not IVariableCellData); static double GetNumberValue(this INumberVariableCell cell) - => (double)(Convert.ChangeType(cell.CombineValues().value, TypeCode.Double) ?? double.NaN); + { + var cellValue = cell.CombineValues().value; + return cellValue == null ? double.NaN : (double)(Convert.ChangeType(cellValue, TypeCode.Double)); + } static bool SetValue(this INumberVariableCell cell, double value) => cell.TrySetValue(value.ToString(CultureInfo.InvariantCulture)); From 154e5fe2c3a278b4eebd239833bf266ecf59630a Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Sat, 18 Jul 2026 09:53:10 +0200 Subject: [PATCH 04/31] make VariableAngleCell implement INumberVariableCell --- STROOP/Controls/VariablePanel/Cells/VariableAngleCell.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/STROOP/Controls/VariablePanel/Cells/VariableAngleCell.cs b/STROOP/Controls/VariablePanel/Cells/VariableAngleCell.cs index 7ed6ef8ab..dab3d7115 100644 --- a/STROOP/Controls/VariablePanel/Cells/VariableAngleCell.cs +++ b/STROOP/Controls/VariablePanel/Cells/VariableAngleCell.cs @@ -6,6 +6,7 @@ namespace STROOP.Controls.VariablePanel.Cells; public class VariableAngleCell(VariableNumberCell baseCell) : VariableAngleCell(baseCell) + , INumberVariableCell where TNumber : struct, IConvertible { protected override bool DisplayAsUnsigned() => SavedSettingsConfig.DisplayYawAnglesAsUnsigned; From 0790099db1022f4fb9d74cfb82471d1be71405bb Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Sat, 18 Jul 2026 09:53:24 +0200 Subject: [PATCH 05/31] implement CreateDummyVariable --- .../Controls/VariablePanel/VariablePanel.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/STROOP/Controls/VariablePanel/VariablePanel.cs b/STROOP/Controls/VariablePanel/VariablePanel.cs index c11bf7d5a..e07910e8d 100644 --- a/STROOP/Controls/VariablePanel/VariablePanel.cs +++ b/STROOP/Controls/VariablePanel/VariablePanel.cs @@ -411,19 +411,17 @@ private void AddToVarHackTab(List cells) private static CustomVariable CreateDummyVariable() where T : struct, IConvertible { - throw new NotImplementedException(); - // T capturedValue = default(T); - // - // return new CustomVariableView(VariableUtilities.GetWrapperType(typeof(T))) - // { - // Name = $"Dummy {++numDummies} {StringUtilities.Capitalize(typeof(T).Name)}", - // _getterFunction = () => capturedValue.Yield(), - // _setterFunction = (T value) => - // { - // capturedValue = value; - // return true.Yield(); - // } - // }; + T capturedValue = default(T); + + var variable = new CustomVariable("Number"); + variable.getter = () => capturedValue.Yield(); + variable.setter = value => + { + capturedValue = value; + return true.Yield(); + }; + + return variable; } private ToolStripMenuItem CreateFilterItem(string varGroup) From 9ea1db00caf6a2c530c1957a415afdbee44d23e1 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Sat, 18 Jul 2026 11:47:47 +0200 Subject: [PATCH 06/31] remove unused variable --- STROOP/Controls/VariablePanel/VariablePanel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/STROOP/Controls/VariablePanel/VariablePanel.cs b/STROOP/Controls/VariablePanel/VariablePanel.cs index e07910e8d..2a6cc2ba1 100644 --- a/STROOP/Controls/VariablePanel/VariablePanel.cs +++ b/STROOP/Controls/VariablePanel/VariablePanel.cs @@ -34,8 +34,6 @@ static void ViewInMemoryTab(DescribedMemoryState memoryDescriptor) tab.UpdateHexDisplay(); } - private static int numDummies = 0; - public readonly Func> GetSelectedVars; public Func> getSpecialFuncVariables = null; From fa4e6761b5e84414d62433a2be0b30783944a5ab Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:03:56 +0200 Subject: [PATCH 07/31] fix geo_switch_mario_cap_effect.asm --- .../Ghosts/geo_switch_mario_cap_effect.asm | 25 ++++++++++--------- STROOP/Resources/Hacks/GhostHackJP.hck | 2 +- STROOP/Resources/Hacks/GhostHackUS.hck | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/HackSources/Ghosts/geo_switch_mario_cap_effect.asm b/HackSources/Ghosts/geo_switch_mario_cap_effect.asm index 2d69133d3..269a8f393 100644 --- a/HackSources/Ghosts/geo_switch_mario_cap_effect.asm +++ b/HackSources/Ghosts/geo_switch_mario_cap_effect.asm @@ -1,21 +1,22 @@ .n64 - -addiu SP, SP, 0xFFF8 -lui t9, gBodyStatesAddrHi -addiu t9, t9, gBodyStatesAddrLo lui t0, gCurGraphNodeObjectHi lw t0, gCurGraphNodeObjectLo (t0) -lb t2, 0x61 (t0) -beq t2, r0, @@RETURN -lb t1, 0x8 (t8) -sh t1, 0x1E (a1) lui at, MarioObjectAddrHi lw at, MarioObjectAddrLo (at) -beq t0, at, @@RETURN -nop +bne t0, at, @@IsGhost + +; render Mario as usual +lui t9, gBodyStatesAddrHi +lh t2, gBodyStatesAddrLo + 0x8 (t9) +srl t2, t2, 0x8 +beq r0, r0, @@RETURN +sh t2, 0x1e (a1) + +@@IsGhost: +; apply the chosen effect from the ghost node +lb t2, 0x61 (t0) sh t2, 0x1e (a1) @@RETURN: -or v0, r0, r0 jr ra -addiu SP, SP, 0x0008 \ No newline at end of file +or v0, r0, r0 \ No newline at end of file diff --git a/STROOP/Resources/Hacks/GhostHackJP.hck b/STROOP/Resources/Hacks/GhostHackJP.hck index df2629e62..f83067165 100644 --- a/STROOP/Resources/Hacks/GhostHackJP.hck +++ b/STROOP/Resources/Hacks/GhostHackJP.hck @@ -10,4 +10,4 @@ 80276AF4: 27 BD FF D0 AF BF 00 14 24 01 00 01 14 81 00 1A 00 00 10 25 00 A0 20 25 3C 08 80 34 25 08 A0 40 8C B8 00 18 00 18 C8 80 03 38 C8 21 00 19 C8 C0 03 28 48 21 3C 08 80 33 8D 08 CF A0 3C 01 80 36 8C 21 FD E8 15 01 00 06 85 2C 00 08 31 8D 01 00 11 A0 00 07 34 05 00 FF 10 00 00 05 31 85 00 FF 81 18 00 61 13 00 00 02 34 05 00 FF 34 05 00 7F 0C 09 DA 78 00 00 00 00 8F BF 00 14 03 E0 00 08 27 BD 00 30 -80277128: 27 BD FF F8 3C 19 80 34 27 39 A0 40 3C 08 80 33 8D 08 CF A0 81 0A 00 61 11 40 00 07 83 09 00 08 A4 A9 00 1E 3C 01 80 36 8C 21 FD E8 11 01 00 02 00 00 00 00 A4 AA 00 1E 00 00 10 25 03 E0 00 08 27 BD 00 08 +80277128: 3C 08 80 33 8D 08 CF A0 3C 01 80 36 8C 21 FD E8 15 01 00 05 3C 19 80 34 87 2A A0 48 00 0A 52 02 10 00 00 03 A4 AA 00 1E 81 0A 00 61 A4 AA 00 1E 03 E0 00 08 00 00 10 25 \ No newline at end of file diff --git a/STROOP/Resources/Hacks/GhostHackUS.hck b/STROOP/Resources/Hacks/GhostHackUS.hck index d0067db58..aef9fd76a 100644 --- a/STROOP/Resources/Hacks/GhostHackUS.hck +++ b/STROOP/Resources/Hacks/GhostHackUS.hck @@ -10,4 +10,4 @@ 802770A4: 27 BD FF D0 AF BF 00 14 24 01 00 01 14 81 00 1A 00 00 10 25 00 A0 20 25 3C 08 80 34 25 08 B3 B0 8C B8 00 18 00 18 C8 80 03 38 C8 21 00 19 C8 C0 03 28 48 21 3C 08 80 33 8D 08 DF 00 3C 01 80 36 8C 21 11 58 15 01 00 06 85 2C 00 08 31 8D 01 00 11 A0 00 07 34 05 00 FF 10 00 00 05 31 85 00 FF 81 18 00 61 13 00 00 02 34 05 00 FF 34 05 00 7F 0C 09 DB E4 00 00 00 00 8F BF 00 14 03 E0 00 08 27 BD 00 30 -802776D8: 27 BD FF F8 3C 19 80 34 27 39 B3 B0 3C 08 80 33 8D 08 DF 00 81 0A 00 61 11 40 00 07 83 09 00 08 A4 A9 00 1E 3C 01 80 36 8C 21 11 58 11 01 00 02 00 00 00 00 A4 AA 00 1E 00 00 10 25 03 E0 00 08 27 BD 00 08 +802776D8: 3C 08 80 33 8D 08 DF 00 3C 01 80 36 8C 21 11 58 15 01 00 05 3C 19 80 34 87 2A B3 B8 00 0A 52 02 10 00 00 03 A4 AA 00 1E 81 0A 00 61 A4 AA 00 1E 03 E0 00 08 00 00 10 25 From 13595b175854e321b11ca328ff2f834ac27b5f13 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:47:38 +0200 Subject: [PATCH 08/31] make it moveable --- HackSources/Ghosts/additional hacks.txt | 2 +- .../Ghosts/export_moving_code_references.asm | 8 +++ .../Ghosts/gfx_generate_colored_hats.asm | 14 ++-- HackSources/Ghosts/ghost_hack_JP.asm | 4 +- HackSources/Ghosts/ghost_hack_US.asm | 4 +- HackSources/Ghosts/ghost_loop.asm | 49 +++++++------ STROOP/Structs/RomHack.cs | 9 +-- STROOP/Tabs/GhostTab/ColoredHats.cs | 13 ++-- STROOP/Tabs/GhostTab/GhostTab.cs | 68 +++++++++++++++---- 9 files changed, 118 insertions(+), 53 deletions(-) create mode 100644 HackSources/Ghosts/export_moving_code_references.asm diff --git a/HackSources/Ghosts/additional hacks.txt b/HackSources/Ghosts/additional hacks.txt index 41aa8165e..8364a9bed 100644 --- a/HackSources/Ghosts/additional hacks.txt +++ b/HackSources/Ghosts/additional hacks.txt @@ -1,5 +1,5 @@ <--- Hooks the ghost update function into area_update_objects ---> -US: +US: 8027B188: 0C 10 20 00 JP: diff --git a/HackSources/Ghosts/export_moving_code_references.asm b/HackSources/Ghosts/export_moving_code_references.asm new file mode 100644 index 000000000..466e7c1aa --- /dev/null +++ b/HackSources/Ghosts/export_moving_code_references.asm @@ -0,0 +1,8 @@ +.create "./build/DynamicOffsts.bin", 0x00000000 +.word orga(FirstAnimationBufferAddrHi_LUI_1) +.word orga(GhostBaseHi_LUI_PLUS_1) +.word orga(GhostBaseHi_LUI_1) +.word orga(GhostBaseHi_LUI_2) +.word orga(GhostBaseHi_LUI_3) +.word orga(GhostBaseHi_LUI_4) +.close diff --git a/HackSources/Ghosts/gfx_generate_colored_hats.asm b/HackSources/Ghosts/gfx_generate_colored_hats.asm index e0b4642ed..b295447e8 100644 --- a/HackSources/Ghosts/gfx_generate_colored_hats.asm +++ b/HackSources/Ghosts/gfx_generate_colored_hats.asm @@ -9,18 +9,18 @@ lui at, MarioObjectAddrHi lw at, MarioObjectAddrLo (at) lui t8, gCurGraphNodeObjectHi lw t8, gCurGraphNodeObjectLo (t8) -beq t8, at, @@SkipGhostRead +beq t8, at, @SkipGhostRead or t2, r0, r0 lb t2, 0x60 (t8) -@@SkipGhostRead: +@SkipGhostRead: ori t1, t1, 0x1978 sw t0, 0x18 (sp) sw t1, 0x1C (sp) sw t2, 0x20 (sp) addiu at, r0, 0x1 -bne at, a0, @@FinishTheJob +bne at, a0, @FinishTheJob ori a0, r0, 0x38 jal alloc_display_list nop @@ -32,7 +32,9 @@ lui at, 0x0388 ori at, at, 0x0010 sw at, 0x18 (v0) sw at, 0x0 (v0) -lui t0, 0x8040 + +GhostBaseHi_LUI_1: +lui t0, GhostBaseHi ; defined in ghost_loop.asm (included first); with the ori below, must match COLORED_HATS_LIGHTS_ADDR in ColoredHats.cs ori t0, t0, 0x8300 lw t1, 0x20 (sp) sll t1, t1, 0x5 @@ -49,10 +51,10 @@ sw t3, 0xC (v0) sw t4, 0x28 (v0) lw t0, 0x1C (sp) -@@FinishTheJob: +@FinishTheJob: sw t0, 0x2C (v0) lui at, 0xB800 sw at, 0x30 (v0) lw ra, 0x14 (sp) jr ra -addiu sp, sp, 0x40 \ No newline at end of file +addiu sp, sp, 0x40 diff --git a/HackSources/Ghosts/ghost_hack_JP.asm b/HackSources/Ghosts/ghost_hack_JP.asm index c5dd45219..289e82e0e 100644 --- a/HackSources/Ghosts/ghost_hack_JP.asm +++ b/HackSources/Ghosts/ghost_hack_JP.asm @@ -39,4 +39,6 @@ alloc_display_list equ 0x8027897c .create "./build/JP_80408200_gfx_generate_colored_hats.bin", 0x00000000 .include "./gfx_generate_colored_hats.asm" -.close \ No newline at end of file +.close + +.include "./export_moving_code_references.asm" diff --git a/HackSources/Ghosts/ghost_hack_US.asm b/HackSources/Ghosts/ghost_hack_US.asm index 3d58cee6b..84c787cc4 100644 --- a/HackSources/Ghosts/ghost_hack_US.asm +++ b/HackSources/Ghosts/ghost_hack_US.asm @@ -39,4 +39,6 @@ alloc_display_list equ 0x80278f2c .create "./build/US_80408200_gfx_generate_colored_hats.bin", 0x00000000 .include "./gfx_generate_colored_hats.asm" -.close \ No newline at end of file +.close + +.include "./export_moving_code_references.asm" diff --git a/HackSources/Ghosts/ghost_loop.asm b/HackSources/Ghosts/ghost_loop.asm index 6566928f7..7d213cd96 100644 --- a/HackSources/Ghosts/ghost_loop.asm +++ b/HackSources/Ghosts/ghost_loop.asm @@ -8,7 +8,7 @@ RegPointerToCurrentGhost equ s1 ; Pointer to the currently processed ghost node RegProcessedGhostCount equ s0 ; Iteration counter for loops that process all ghosts ; hardcoded offsets -ExtendedRAMStartHi equ 0x8040 ; The Hi part of the address pointing to the start of extended RAM +GhostBaseHi equ 0x8040 ; The Hi part of the address pointing to the start of extended RAM NumRequestedGhosts equ 0x7FFF ; Offset from extended RAM start to the byte indicating the number of ghosts to display. This value is written by STROOP. PointerToFirstGhost equ 0x7FF8 ; Offset from extended RAM start to the 4 byte pointer to the first ghost node NegativeGhostStructSize equ 0xFF98 ; The negative size of a single ghost node, used to iterate ghosts like a reversed array @@ -22,7 +22,7 @@ addiu SP, SP, 0xFFC0 ; return early if there's no Mario object lui t0, MarioObjectAddrHi lw t0, MarioObjectAddrLo (t0) -beq r0, t0, @@EARLY_RETURN +beq r0, t0, @EARLY_RETURN ; push static registers to stack sw ra, 0x34 (SP) @@ -36,20 +36,22 @@ sw RegProcessedGhostCount, 0x20 (SP) or RegMarioObject, r0, t0 lh t0, 0x2 (RegMarioObject) andi t1, t0, InitializedGhostsFlag -bnez t1, @@SKIP_INIT +bnez t1, @SKIP_INIT ; set initialized flag on Mario object ori t1, t0, InitializedGhostsFlag sh t1, 0x2 (RegMarioObject) -beq r0, r0, @@RETURN +beq r0, r0, @RETURN ; clean up (this can cause failure?) -lui RegPointerToCurrentGhost, ExtendedRAMStartHi -beq r0, r0, @@CLEAN_UP_EARLY +GhostBaseHi_LUI_2: +lui RegPointerToCurrentGhost, GhostBaseHi +beq r0, r0, @CLEAN_UP_EARLY ori RegPointerToCurrentGhost, RegPointerToCurrentGhost, PointerToFirstGhost -@@SKIP_INIT: +@SKIP_INIT: ; set up dummy Mario struct +FirstAnimationBufferAddrHi_LUI_1: lui RegAnimationBuffer, FirstAnimationBufferAddrHi lui t8, 0x8037 ori at, r0, 0xBD @@ -61,17 +63,18 @@ sw at, 0x5B8 (t8) ; clean up if no ghosts are requested or RegProcessedGhostCount, r0, r0 -lui at, ExtendedRAMStartHi +GhostBaseHi_LUI_3: +lui at, GhostBaseHi ori RegPointerToCurrentGhost, at, PointerToFirstGhost lb at, NumRequestedGhosts (at) -beq r0, at, @@CLEAN_UP_EARLY +beq r0, at, @CLEAN_UP_EARLY nop -@@ITERATE_GHOSTS: +@ITERATE_GHOSTS: ; skip initialization if ghost already exists lw t0, 0x0 (RegPointerToCurrentGhost) -bnez t0, @@GHOST_EXISTS +bnez t0, @GHOST_EXISTS ; create a new ghost object graph node or a0, r0, r0 @@ -91,7 +94,7 @@ lw a0, 0xC (RegMarioObject) jal 0x8037C044 or a1, v0, r0 -@@GHOST_EXISTS: +@GHOST_EXISTS: ; copy Mario's area and animation ID into the ghost node lw RegCurrentGhost, 0x0 (RegPointerToCurrentGhost) @@ -107,7 +110,9 @@ andi t0, t0, 0x7F sll t0, t0, 0x5 sll t1, RegProcessedGhostCount, 0xC addu t0, t0, t1 -lui at, 0x8041 + +GhostBaseHi_LUI_PLUS_1: +lui at, GhostBaseHi + 1 addu t0, t0, at addiu t0, t0, 0x9B00 @@ -147,25 +152,27 @@ sh t1, 0x40 (RegCurrentGhost) addiu RegAnimationBuffer, RegAnimationBuffer, AnimationBufferSize addiu RegPointerToCurrentGhost, RegPointerToCurrentGhost, NegativeGhostStructSize addiu RegProcessedGhostCount, RegProcessedGhostCount, 0x1 -lui at, ExtendedRAMStartHi + +GhostBaseHi_LUI_4: +lui at, GhostBaseHi lb at, NumRequestedGhosts (at) sltu t0, RegProcessedGhostCount, at -bnez t0, @@ITERATE_GHOSTS +bnez t0, @ITERATE_GHOSTS sb RegProcessedGhostCount, 0x60 (RegCurrentGhost) ; delete leftover ghosts lw RegCurrentGhost, 0x0 (RegPointerToCurrentGhost) -beq RegCurrentGhost, r0, @@RETURN +beq RegCurrentGhost, r0, @RETURN or a0, r0, RegCurrentGhost -@@CLEAN_UP_LOOP: +@CLEAN_UP_LOOP: jal 0x8037C0BC sw r0, 0x0 (RegPointerToCurrentGhost) -@@CLEAN_UP_EARLY: +@CLEAN_UP_EARLY: lw a0, 0x0 (RegPointerToCurrentGhost) -bnez a0, @@CLEAN_UP_LOOP +bnez a0, @CLEAN_UP_LOOP addiu RegPointerToCurrentGhost, RegPointerToCurrentGhost, NegativeGhostStructSize -@@RETURN: +@RETURN: ; pop static registers from stack lw ra, 0x34 (SP) @@ -175,6 +182,6 @@ lw RegCurrentGhost, 0x28 (SP) lw RegPointerToCurrentGhost, 0x24 (SP) lw RegProcessedGhostCount, 0x20 (SP) -@@EARLY_RETURN: +@EARLY_RETURN: jr ra addiu SP, SP, 0x40 diff --git a/STROOP/Structs/RomHack.cs b/STROOP/Structs/RomHack.cs index 7575a111e..1e912cc62 100644 --- a/STROOP/Structs/RomHack.cs +++ b/STROOP/Structs/RomHack.cs @@ -76,7 +76,7 @@ void LoadHackFromFile(string hackFileName) } while (nextEnd != -1); } - public void LoadPayload() + public void LoadPayload(Dictionary destinationRemap = null) { bool success = true; @@ -85,10 +85,11 @@ public void LoadPayload() using (Config.Stream.Suspend()) { - foreach (var (address, data) in _payload) + foreach (var (originalAddress, data) in _payload) { - // Hacks are entered as big endian; we need to swap the address endianess before writing - var fixedAddress = EndiannessUtilities.SwapAddressEndianness(address, data.Length); + var effectiveAddress = destinationRemap?.GetValueOrDefault(originalAddress, originalAddress) ?? originalAddress; + // Hacks are entered as big endian; we need to swap the address endianess before writing + var fixedAddress = EndiannessUtilities.SwapAddressEndianness(effectiveAddress, data.Length); // Read original memory before replacing _originalMemory.Add(new Tuple(fixedAddress, Config.Stream.ReadRam((UIntPtr)fixedAddress, data.Length, EndiannessType.Big))); diff --git a/STROOP/Tabs/GhostTab/ColoredHats.cs b/STROOP/Tabs/GhostTab/ColoredHats.cs index ea56fd809..e8f6b83c9 100644 --- a/STROOP/Tabs/GhostTab/ColoredHats.cs +++ b/STROOP/Tabs/GhostTab/ColoredHats.cs @@ -15,6 +15,8 @@ partial class GhostTab { // <--- static utility and information ---> + const uint COLORED_HATS_CODE_OFFSET = 0x8200u; + private static int defaultGhostColorCounter = 1; private static readonly Vector4[] DefaultGhostColors = new[] @@ -29,17 +31,16 @@ partial class GhostTab new Vector4(0.2f, 0.2f, 0.2f, 1), }; - Vector4 marioHatColor = new Vector4(1, 0, 0, 1); - const uint VANILLA_BANK_04_OFFSET_US = 0x0007EC20; const uint VANILLA_BANK_04_OFFSET_JP = 0x0007BDC0; const uint S_SEGMENT_TABLE_OFFSET_JP = 0x8033a090; const uint S_SEGMENT_TABLE_OFFSET_US = 0x8033b400; - const uint COLORED_HATS_CODE_TARGET_ADDR = 0x80408200; - const uint COLORED_HATS_LIGHTS_ADDR = 0x80408300; + Vector4 marioHatColor = new Vector4(1, 0, 0, 1); + + uint COLORED_HATS_LIGHTS_ADDR => GHOST_REGION_BASE + 0x8300u; - private static void EnableColoredHats() + private void EnableColoredHats() { using (Config.Stream.Suspend()) { @@ -68,7 +69,7 @@ private static void EnableColoredHats() var foundPointer = Config.Stream.GetUInt32(addr + 0x14); if (Array.IndexOf(originalDisplayListPointers, foundPointer) != -1) { - Config.Stream.SetValue(COLORED_HATS_CODE_TARGET_ADDR, addr + 0x14); + Config.Stream.SetValue(GHOST_REGION_BASE + COLORED_HATS_CODE_OFFSET, addr + 0x14); Config.Stream.SetValue((ushort)0x12A, addr); } } diff --git a/STROOP/Tabs/GhostTab/GhostTab.cs b/STROOP/Tabs/GhostTab/GhostTab.cs index a571e2f25..ea33af01f 100644 --- a/STROOP/Tabs/GhostTab/GhostTab.cs +++ b/STROOP/Tabs/GhostTab/GhostTab.cs @@ -18,7 +18,28 @@ namespace STROOP.Tabs.GhostTab { public partial class GhostTab : STROOPTab { - const uint bufferBaseAddress = 0x80409B00; + /// The variable part to move the ghost loop and colored hats code with. + ushort EXTENDED_RAM_UPPER_PART = 0x8045; // originally 0x8040 + + const uint GHOST_LOOP_CODE_OFFSET = 0x8000u; + const uint HACK_FILE_BASE_OFFSET = 0x80400000; + + // Base of the ghost hack's extended-RAM region. Must match GhostBaseHi in ghost_loop.asm + // and the inject addresses + hook bytes in Resources/Hacks/GhostHack*.hck. + uint GHOST_REGION_BASE => (uint)EXTENDED_RAM_UPPER_PART << 0x10; + + // These numbers are the 4 byte words, in order, as exported into "DynamicOffsets.bin". + const uint FirstAnimationBufferAddrHi_LUI_1 = 0x50; + const uint GhostBaseHi_LUI_PLUS_1 = 0xF8; + const uint COLORED_HATS_GhostBaseHi_LUI = 0x70; + static readonly uint[] GhostBaseHi_LUI = [0x44, 0x78, 0x170]; + + // These offsets mirror NumRequestedGhosts / PointerToFirstGhost in ghost_loop.asm. + uint NUM_GHOSTS_ADDR => GHOST_REGION_BASE + 0x7FFFu; + uint FIRST_GHOST_POINTER_ADDR => GHOST_REGION_BASE + 0x7FF8u; + uint DISABLE_REQUEST_ADDR => GHOST_REGION_BASE + 0x7FFCu; + + uint bufferBaseAddress => GHOST_REGION_BASE + 0x9B00u; static IEnumerable GetActiveGhostIndices() { @@ -49,7 +70,6 @@ static void AddSpecialVariables() int lastGlobalTimer; Ghost selectedGhost => listBoxGhosts.SelectedItem as Ghost; - GhostFrame lastValidPlaybackFrame => selectedGhost?.lastValidPlaybackFrame ?? default(GhostFrame); public GhostTab() { @@ -85,7 +105,7 @@ public override void Update(bool active) int numGhosts = Math.Max(1, ghostArr.Length); if (updateGhostData) { - Config.Stream.SetValue((byte)numGhosts, 0x80407FFF); + Config.Stream.SetValue((byte)numGhosts, NUM_GHOSTS_ADDR); WriteMarioColorToStream(); } @@ -165,7 +185,7 @@ public override void Update(bool active) WriteGhostColorToStream(ghostIndex, ghostArr); - var ptr = Config.Stream.GetUInt32((uint)(0x80407ff8 - ghostIndex * 0x68)); + var ptr = Config.Stream.GetUInt32((uint)(FIRST_GHOST_POINTER_ADDR - ghostIndex * 0x68)); Config.Stream.SetValue((byte)(ghostTransparent ? 1 : 0), ptr + 0x61); lastGlobalTimer = globalTimer; } @@ -248,16 +268,16 @@ bool UpdateHackStatus() if (ghostHack?.Name != expectedHackName) ghostHack = new RomHack($"Resources/Hacks/GhostHack{RomVersionConfig.Version}.hck", expectedHackName); - var ghostPointer = Config.Stream.GetInt32(0x80407FF8); + var ghostPointer = Config.Stream.GetInt32(FIRST_GHOST_POINTER_ADDR); bool ghostsActive = (ghostPointer & 0xFF000000) == 0x80000000; - bool shouldDisable = Config.Stream.GetByte(0x80407FFC) == 0xFF; + bool shouldDisable = Config.Stream.GetByte(DISABLE_REQUEST_ADDR) == 0xFF; if (shouldDisable) { labelHackActiveState.Text = "Disabling Ghost hack...\nInside a level, frame advance\nthen save state and load state.\nNot doing so will crash.\n(Not on Pure Interpreter)"; if (!ghostsActive) { ghostHack.ClearPayload(); - Config.Stream.SetValue((byte)0, 0x80407FFC); + Config.Stream.SetValue((byte)0, DISABLE_REQUEST_ADDR); } else return true; @@ -344,14 +364,36 @@ private void buttonEnableGhostHack_Click(object sender, EventArgs e) { if (Config.Stream.GetInt32(0x80000000) != 0) { - ghostHack.LoadPayload(); - Config.Stream.WriteRam(new byte[4], 0x80407FFC, EndiannessType.Little); - Config.Stream.WriteRam(new byte[0x70], 0x80407F90, EndiannessType.Little); + ghostHack.LoadPayload(new() + { + [HACK_FILE_BASE_OFFSET + GHOST_LOOP_CODE_OFFSET] = GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET, + [HACK_FILE_BASE_OFFSET + COLORED_HATS_CODE_OFFSET] = GHOST_REGION_BASE + COLORED_HATS_CODE_OFFSET, + }); + Config.Stream.WriteRam(new byte[4], DISABLE_REQUEST_ADDR, EndiannessType.Little); + Config.Stream.WriteRam(new byte[0x70], GHOST_REGION_BASE + 0x7F90u, EndiannessType.Little); EnableColoredHats(); //Tell ROM Hacks to suck it and get rid of the 01010101 pattern - Config.Stream.WriteRam(new byte[0x1000], 0x80408000 - 0x1000, EndiannessType.Big); + Config.Stream.WriteRam(new byte[0x1000], HACK_FILE_BASE_OFFSET + GHOST_LOOP_CODE_OFFSET - 0x1000u, EndiannessType.Big); + + // Modify code for moving parts + ushort luiGhostBaseValue = EXTENDED_RAM_UPPER_PART; + ushort luiFirstAnimationValue = (ushort)(luiGhostBaseValue + 0x10); + + ApplyLui((ushort)(luiGhostBaseValue + 1), GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET + GhostBaseHi_LUI_PLUS_1); + foreach (var offset in GhostBaseHi_LUI) + ApplyLui(luiGhostBaseValue, GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET + offset); + ApplyLui(luiFirstAnimationValue, GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET + FirstAnimationBufferAddrHi_LUI_1); + + ApplyLui(luiGhostBaseValue, GHOST_REGION_BASE + COLORED_HATS_CODE_OFFSET + COLORED_HATS_GhostBaseHi_LUI); + + var jalTarget = 0x00FFFFFF & (GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET); + var hookPoint = RomVersionConfig.Version == RomVersion.JP ? 0x8027ABD8 : 0x8027B188; + Config.Stream.SetValue((uint)((0x0C << 0x18) | (jalTarget / 4)), hookPoint); + + void ApplyLui(ushort value, uint address) + => Config.Stream.SetValue(value, address + 2); } } @@ -366,8 +408,8 @@ It is recommended that you load a savestate without the hack enabled instead. Are you sure you want to continue?"; if (MessageBox.Show(txt, "You should not have to do this.", MessageBoxButtons.YesNo) == DialogResult.Yes) { - Config.Stream.SetValue((byte)0, 0x80407FFF); - Config.Stream.SetValue((byte)0xFF, 0x80407FFC); + Config.Stream.SetValue((byte)0, NUM_GHOSTS_ADDR); + Config.Stream.SetValue((byte)0xFF, DISABLE_REQUEST_ADDR); } } From 0a3d745dadcf8aff02812008ae495bfefdc63a85 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:57:52 +0200 Subject: [PATCH 09/31] create simple ghost base UI (screw the WinForms designers) --- STROOP/Tabs/GhostTab/GhostTab.Designer.cs | 844 ++++++++++++---------- STROOP/Tabs/GhostTab/GhostTab.cs | 8 +- 2 files changed, 463 insertions(+), 389 deletions(-) diff --git a/STROOP/Tabs/GhostTab/GhostTab.Designer.cs b/STROOP/Tabs/GhostTab/GhostTab.Designer.cs index 84281af1e..4226058b4 100644 --- a/STROOP/Tabs/GhostTab/GhostTab.Designer.cs +++ b/STROOP/Tabs/GhostTab/GhostTab.Designer.cs @@ -22,513 +22,581 @@ protected override void Dispose(bool disposing) #region Vom Komponenten-Designer generierter Code - /// - /// Erforderliche Methode für die Designerunterstützung. - /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. /// private void InitializeComponent() { - this.groupBoxGhosts = new System.Windows.Forms.GroupBox(); - this.listBoxGhosts = new System.Windows.Forms.ListBox(); - this.buttonMarioColor = new System.Windows.Forms.Button(); - this.buttonWatchGhostFile = new System.Windows.Forms.Button(); - this.buttonLoadGhost = new System.Windows.Forms.Button(); - this.groupBoxGhostInfo = new System.Windows.Forms.GroupBox(); - this.checkTransparentGhosts = new System.Windows.Forms.CheckBox(); - this.buttonGhostColor = new System.Windows.Forms.Button(); - this.textBoxGhostName = new System.Windows.Forms.TextBox(); - this.labelNumFrames = new System.Windows.Forms.Label(); - this.labelName = new System.Windows.Forms.Label(); - this.labelGhostFile = new System.Windows.Forms.Label(); - this.labelGhostPlaybackStart = new System.Windows.Forms.Label(); - this.lblPlaybackOffset = new System.Windows.Forms.Label(); - this.numericUpDownPlaybackOffset = new System.Windows.Forms.NumericUpDown(); - this.labelBaseGlobalTimer = new System.Windows.Forms.Label(); - this.numericUpDownStartOfPlayback = new System.Windows.Forms.NumericUpDown(); - this.buttonSaveGhost = new System.Windows.Forms.Button(); - this.buttonTutorialRecord = new System.Windows.Forms.Button(); - this.groupGhostHack = new System.Windows.Forms.GroupBox(); - this.labelHackActiveState = new System.Windows.Forms.Label(); - this.buttonDisableGhostHack = new System.Windows.Forms.Button(); - this.buttonEnableGhostHack = new System.Windows.Forms.Button(); - this._variablePanelGhost = new STROOP.Controls.VariablePanel.VariablePanel(); - this.groupBoxVariables = new System.Windows.Forms.GroupBox(); - this.groupBoxHelp = new System.Windows.Forms.GroupBox(); - this.buttonTutorialFileWatch = new System.Windows.Forms.Button(); - this.buttonHelpGfxPool = new System.Windows.Forms.Button(); - this.buttonTutorialPlayback = new System.Windows.Forms.Button(); - this.buttonTutorialNotes = new System.Windows.Forms.Button(); - this.groupBoxGfxPool = new System.Windows.Forms.GroupBox(); - this.textBoxPoolSize = new System.Windows.Forms.TextBox(); - this.textBoxPoolAddr2 = new System.Windows.Forms.TextBox(); - this.textBoxPoolAddr1 = new System.Windows.Forms.TextBox(); - this.labelPoolSize = new System.Windows.Forms.Label(); - this.label1 = new System.Windows.Forms.Label(); - this.labelPool1Address = new System.Windows.Forms.Label(); - this.buttonMoveGfxPool = new System.Windows.Forms.Button(); - this.groupBoxGhosts.SuspendLayout(); - this.groupBoxGhostInfo.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDownPlaybackOffset)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDownStartOfPlayback)).BeginInit(); - this.groupGhostHack.SuspendLayout(); - this.groupBoxVariables.SuspendLayout(); - this.groupBoxHelp.SuspendLayout(); - this.groupBoxGfxPool.SuspendLayout(); - this.SuspendLayout(); + groupBoxGhosts = new System.Windows.Forms.GroupBox(); + listBoxGhosts = new System.Windows.Forms.ListBox(); + buttonMarioColor = new System.Windows.Forms.Button(); + buttonWatchGhostFile = new System.Windows.Forms.Button(); + buttonLoadGhost = new System.Windows.Forms.Button(); + groupBoxGhostInfo = new System.Windows.Forms.GroupBox(); + checkTransparentGhosts = new System.Windows.Forms.CheckBox(); + buttonGhostColor = new System.Windows.Forms.Button(); + textBoxGhostName = new System.Windows.Forms.TextBox(); + labelNumFrames = new System.Windows.Forms.Label(); + labelName = new System.Windows.Forms.Label(); + labelGhostFile = new System.Windows.Forms.Label(); + labelGhostPlaybackStart = new System.Windows.Forms.Label(); + lblPlaybackOffset = new System.Windows.Forms.Label(); + numericUpDownPlaybackOffset = new System.Windows.Forms.NumericUpDown(); + labelBaseGlobalTimer = new System.Windows.Forms.Label(); + numericUpDownStartOfPlayback = new System.Windows.Forms.NumericUpDown(); + buttonSaveGhost = new System.Windows.Forms.Button(); + buttonTutorialRecord = new System.Windows.Forms.Button(); + groupGhostHack = new System.Windows.Forms.GroupBox(); + labelHackActiveState = new System.Windows.Forms.Label(); + buttonDisableGhostHack = new System.Windows.Forms.Button(); + buttonEnableGhostHack = new System.Windows.Forms.Button(); + _variablePanelGhost = new STROOP.Controls.VariablePanel.VariablePanel(); + groupBoxVariables = new System.Windows.Forms.GroupBox(); + groupBoxHelp = new System.Windows.Forms.GroupBox(); + buttonTutorialFileWatch = new System.Windows.Forms.Button(); + buttonHelpGfxPool = new System.Windows.Forms.Button(); + buttonTutorialPlayback = new System.Windows.Forms.Button(); + buttonTutorialNotes = new System.Windows.Forms.Button(); + groupBoxGfxPool = new System.Windows.Forms.GroupBox(); + textBoxPoolSize = new System.Windows.Forms.TextBox(); + textBoxPoolAddr2 = new System.Windows.Forms.TextBox(); + textBoxPoolAddr1 = new System.Windows.Forms.TextBox(); + labelPoolSize = new System.Windows.Forms.Label(); + label1 = new System.Windows.Forms.Label(); + labelPool1Address = new System.Windows.Forms.Label(); + buttonMoveGfxPool = new System.Windows.Forms.Button(); + lblRAMOffsetBase = new System.Windows.Forms.Label(); + txtRAMOffsetBase = new System.Windows.Forms.TextBox(); + groupBoxGhosts.SuspendLayout(); + groupBoxGhostInfo.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)numericUpDownPlaybackOffset).BeginInit(); + ((System.ComponentModel.ISupportInitialize)numericUpDownStartOfPlayback).BeginInit(); + groupGhostHack.SuspendLayout(); + groupBoxVariables.SuspendLayout(); + groupBoxHelp.SuspendLayout(); + groupBoxGfxPool.SuspendLayout(); + SuspendLayout(); // // groupBoxGhosts // - this.groupBoxGhosts.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxGhosts.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.groupBoxGhosts.Controls.Add(this.listBoxGhosts); - this.groupBoxGhosts.Controls.Add(this.buttonMarioColor); - this.groupBoxGhosts.Controls.Add(this.buttonWatchGhostFile); - this.groupBoxGhosts.Controls.Add(this.buttonLoadGhost); - this.groupBoxGhosts.Controls.Add(this.groupBoxGhostInfo); - this.groupBoxGhosts.Controls.Add(this.buttonSaveGhost); - this.groupBoxGhosts.Location = new System.Drawing.Point(170, 3); - this.groupBoxGhosts.Name = "groupBoxGhosts"; - this.groupBoxGhosts.Size = new System.Drawing.Size(515, 457); - this.groupBoxGhosts.TabIndex = 2; - this.groupBoxGhosts.TabStop = false; - this.groupBoxGhosts.Text = "Ghosts"; + groupBoxGhosts.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)); + groupBoxGhosts.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + groupBoxGhosts.Controls.Add(listBoxGhosts); + groupBoxGhosts.Controls.Add(buttonMarioColor); + groupBoxGhosts.Controls.Add(buttonWatchGhostFile); + groupBoxGhosts.Controls.Add(buttonLoadGhost); + groupBoxGhosts.Controls.Add(groupBoxGhostInfo); + groupBoxGhosts.Controls.Add(buttonSaveGhost); + groupBoxGhosts.Location = new System.Drawing.Point(198, 3); + groupBoxGhosts.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxGhosts.Name = "groupBoxGhosts"; + groupBoxGhosts.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxGhosts.Size = new System.Drawing.Size(601, 527); + groupBoxGhosts.TabIndex = 2; + groupBoxGhosts.TabStop = false; + groupBoxGhosts.Text = "Ghosts"; // // listBoxGhosts // - this.listBoxGhosts.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.listBoxGhosts.FormattingEnabled = true; - this.listBoxGhosts.Location = new System.Drawing.Point(9, 12); - this.listBoxGhosts.Name = "listBoxGhosts"; - this.listBoxGhosts.Size = new System.Drawing.Size(350, 316); - this.listBoxGhosts.TabIndex = 5; - this.listBoxGhosts.SelectedIndexChanged += new System.EventHandler(this.listBoxGhosts_SelectedIndexChanged); + listBoxGhosts.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)); + listBoxGhosts.FormattingEnabled = true; + listBoxGhosts.ItemHeight = 15; + listBoxGhosts.Location = new System.Drawing.Point(10, 14); + listBoxGhosts.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + listBoxGhosts.Name = "listBoxGhosts"; + listBoxGhosts.Size = new System.Drawing.Size(408, 364); + listBoxGhosts.TabIndex = 5; + listBoxGhosts.SelectedIndexChanged += listBoxGhosts_SelectedIndexChanged; // // buttonMarioColor // - this.buttonMarioColor.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonMarioColor.BackColor = System.Drawing.Color.Red; - this.buttonMarioColor.Location = new System.Drawing.Point(365, 118); - this.buttonMarioColor.Name = "buttonMarioColor"; - this.buttonMarioColor.Size = new System.Drawing.Size(115, 23); - this.buttonMarioColor.TabIndex = 5; - this.buttonMarioColor.Text = "Main Mario Color"; - this.buttonMarioColor.UseVisualStyleBackColor = false; - this.buttonMarioColor.Click += new System.EventHandler(this.buttonMarioColor_Click); + buttonMarioColor.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)); + buttonMarioColor.BackColor = System.Drawing.Color.Red; + buttonMarioColor.Location = new System.Drawing.Point(426, 136); + buttonMarioColor.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonMarioColor.Name = "buttonMarioColor"; + buttonMarioColor.Size = new System.Drawing.Size(134, 27); + buttonMarioColor.TabIndex = 5; + buttonMarioColor.Text = "Main Mario Color"; + buttonMarioColor.UseVisualStyleBackColor = false; + buttonMarioColor.Click += buttonMarioColor_Click; // // buttonWatchGhostFile // - this.buttonWatchGhostFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonWatchGhostFile.Location = new System.Drawing.Point(365, 12); - this.buttonWatchGhostFile.Name = "buttonWatchGhostFile"; - this.buttonWatchGhostFile.Size = new System.Drawing.Size(143, 23); - this.buttonWatchGhostFile.TabIndex = 0; - this.buttonWatchGhostFile.Text = "Edit File Watch List"; - this.buttonWatchGhostFile.UseVisualStyleBackColor = true; - this.buttonWatchGhostFile.Click += new System.EventHandler(this.buttonWatchGhostFile_Click); + buttonWatchGhostFile.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)); + buttonWatchGhostFile.Location = new System.Drawing.Point(426, 14); + buttonWatchGhostFile.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonWatchGhostFile.Name = "buttonWatchGhostFile"; + buttonWatchGhostFile.Size = new System.Drawing.Size(167, 27); + buttonWatchGhostFile.TabIndex = 0; + buttonWatchGhostFile.Text = "Edit File Watch List"; + buttonWatchGhostFile.UseVisualStyleBackColor = true; + buttonWatchGhostFile.Click += buttonWatchGhostFile_Click; // // buttonLoadGhost // - this.buttonLoadGhost.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonLoadGhost.Location = new System.Drawing.Point(365, 60); - this.buttonLoadGhost.Name = "buttonLoadGhost"; - this.buttonLoadGhost.Size = new System.Drawing.Size(143, 23); - this.buttonLoadGhost.TabIndex = 0; - this.buttonLoadGhost.Text = "Load Ghost"; - this.buttonLoadGhost.UseVisualStyleBackColor = true; - this.buttonLoadGhost.Click += new System.EventHandler(this.buttonLoadGhost_Click); + buttonLoadGhost.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)); + buttonLoadGhost.Location = new System.Drawing.Point(426, 69); + buttonLoadGhost.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonLoadGhost.Name = "buttonLoadGhost"; + buttonLoadGhost.Size = new System.Drawing.Size(167, 27); + buttonLoadGhost.TabIndex = 0; + buttonLoadGhost.Text = "Load Ghost"; + buttonLoadGhost.UseVisualStyleBackColor = true; + buttonLoadGhost.Click += buttonLoadGhost_Click; // // groupBoxGhostInfo // - this.groupBoxGhostInfo.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxGhostInfo.Controls.Add(this.checkTransparentGhosts); - this.groupBoxGhostInfo.Controls.Add(this.buttonGhostColor); - this.groupBoxGhostInfo.Controls.Add(this.textBoxGhostName); - this.groupBoxGhostInfo.Controls.Add(this.labelNumFrames); - this.groupBoxGhostInfo.Controls.Add(this.labelName); - this.groupBoxGhostInfo.Controls.Add(this.labelGhostFile); - this.groupBoxGhostInfo.Controls.Add(this.labelGhostPlaybackStart); - this.groupBoxGhostInfo.Controls.Add(this.lblPlaybackOffset); - this.groupBoxGhostInfo.Controls.Add(this.numericUpDownPlaybackOffset); - this.groupBoxGhostInfo.Controls.Add(this.labelBaseGlobalTimer); - this.groupBoxGhostInfo.Controls.Add(this.numericUpDownStartOfPlayback); - this.groupBoxGhostInfo.Location = new System.Drawing.Point(9, 343); - this.groupBoxGhostInfo.Name = "groupBoxGhostInfo"; - this.groupBoxGhostInfo.Size = new System.Drawing.Size(499, 108); - this.groupBoxGhostInfo.TabIndex = 1; - this.groupBoxGhostInfo.TabStop = false; - this.groupBoxGhostInfo.Text = "Ghost Info"; + groupBoxGhostInfo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)); + groupBoxGhostInfo.Controls.Add(checkTransparentGhosts); + groupBoxGhostInfo.Controls.Add(buttonGhostColor); + groupBoxGhostInfo.Controls.Add(textBoxGhostName); + groupBoxGhostInfo.Controls.Add(labelNumFrames); + groupBoxGhostInfo.Controls.Add(labelName); + groupBoxGhostInfo.Controls.Add(labelGhostFile); + groupBoxGhostInfo.Controls.Add(labelGhostPlaybackStart); + groupBoxGhostInfo.Controls.Add(lblPlaybackOffset); + groupBoxGhostInfo.Controls.Add(numericUpDownPlaybackOffset); + groupBoxGhostInfo.Controls.Add(labelBaseGlobalTimer); + groupBoxGhostInfo.Controls.Add(numericUpDownStartOfPlayback); + groupBoxGhostInfo.Location = new System.Drawing.Point(10, 396); + groupBoxGhostInfo.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxGhostInfo.Name = "groupBoxGhostInfo"; + groupBoxGhostInfo.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxGhostInfo.Size = new System.Drawing.Size(582, 125); + groupBoxGhostInfo.TabIndex = 1; + groupBoxGhostInfo.TabStop = false; + groupBoxGhostInfo.Text = "Ghost Info"; // // checkTransparentGhosts // - this.checkTransparentGhosts.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.checkTransparentGhosts.AutoSize = true; - this.checkTransparentGhosts.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; - this.checkTransparentGhosts.Checked = true; - this.checkTransparentGhosts.CheckState = System.Windows.Forms.CheckState.Checked; - this.checkTransparentGhosts.Location = new System.Drawing.Point(374, 15); - this.checkTransparentGhosts.Name = "checkTransparentGhosts"; - this.checkTransparentGhosts.Size = new System.Drawing.Size(119, 17); - this.checkTransparentGhosts.TabIndex = 6; - this.checkTransparentGhosts.Text = "Transparent Ghosts"; - this.checkTransparentGhosts.UseVisualStyleBackColor = true; - this.checkTransparentGhosts.CheckedChanged += new System.EventHandler(this.checkTransparentGhosts_CheckedChanged); + checkTransparentGhosts.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)); + checkTransparentGhosts.AutoSize = true; + checkTransparentGhosts.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + checkTransparentGhosts.Checked = true; + checkTransparentGhosts.CheckState = System.Windows.Forms.CheckState.Checked; + checkTransparentGhosts.Location = new System.Drawing.Point(449, 17); + checkTransparentGhosts.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + checkTransparentGhosts.Name = "checkTransparentGhosts"; + checkTransparentGhosts.Size = new System.Drawing.Size(126, 19); + checkTransparentGhosts.TabIndex = 6; + checkTransparentGhosts.Text = "Transparent Ghosts"; + checkTransparentGhosts.UseVisualStyleBackColor = true; + checkTransparentGhosts.CheckedChanged += checkTransparentGhosts_CheckedChanged; // // buttonGhostColor // - this.buttonGhostColor.Enabled = false; - this.buttonGhostColor.Location = new System.Drawing.Point(156, 11); - this.buttonGhostColor.Name = "buttonGhostColor"; - this.buttonGhostColor.Size = new System.Drawing.Size(49, 23); - this.buttonGhostColor.TabIndex = 5; - this.buttonGhostColor.Text = "Color"; - this.buttonGhostColor.UseVisualStyleBackColor = true; - this.buttonGhostColor.Click += new System.EventHandler(this.buttonGhostColor_Click); + buttonGhostColor.Enabled = false; + buttonGhostColor.Location = new System.Drawing.Point(182, 13); + buttonGhostColor.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonGhostColor.Name = "buttonGhostColor"; + buttonGhostColor.Size = new System.Drawing.Size(57, 27); + buttonGhostColor.TabIndex = 5; + buttonGhostColor.Text = "Color"; + buttonGhostColor.UseVisualStyleBackColor = true; + buttonGhostColor.Click += buttonGhostColor_Click; // // textBoxGhostName // - this.textBoxGhostName.Location = new System.Drawing.Point(50, 13); - this.textBoxGhostName.Name = "textBoxGhostName"; - this.textBoxGhostName.Size = new System.Drawing.Size(100, 20); - this.textBoxGhostName.TabIndex = 4; - this.textBoxGhostName.TextChanged += new System.EventHandler(this.textBoxGhostName_TextChanged); + textBoxGhostName.Location = new System.Drawing.Point(58, 15); + textBoxGhostName.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + textBoxGhostName.Name = "textBoxGhostName"; + textBoxGhostName.Size = new System.Drawing.Size(116, 23); + textBoxGhostName.TabIndex = 4; + textBoxGhostName.TextChanged += textBoxGhostName_TextChanged; // // labelNumFrames // - this.labelNumFrames.AutoSize = true; - this.labelNumFrames.Location = new System.Drawing.Point(6, 53); - this.labelNumFrames.Name = "labelNumFrames"; - this.labelNumFrames.Size = new System.Drawing.Size(93, 13); - this.labelNumFrames.TabIndex = 3; - this.labelNumFrames.Text = "Number of frames:"; + labelNumFrames.AutoSize = true; + labelNumFrames.Location = new System.Drawing.Point(7, 61); + labelNumFrames.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + labelNumFrames.Name = "labelNumFrames"; + labelNumFrames.Size = new System.Drawing.Size(107, 15); + labelNumFrames.TabIndex = 3; + labelNumFrames.Text = "Number of frames:"; // // labelName // - this.labelName.AutoSize = true; - this.labelName.Location = new System.Drawing.Point(6, 16); - this.labelName.Name = "labelName"; - this.labelName.Size = new System.Drawing.Size(38, 13); - this.labelName.TabIndex = 3; - this.labelName.Text = "Name:"; + labelName.AutoSize = true; + labelName.Location = new System.Drawing.Point(7, 18); + labelName.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + labelName.Name = "labelName"; + labelName.Size = new System.Drawing.Size(42, 15); + labelName.TabIndex = 3; + labelName.Text = "Name:"; // // labelGhostFile // - this.labelGhostFile.AutoSize = true; - this.labelGhostFile.Location = new System.Drawing.Point(6, 40); - this.labelGhostFile.Name = "labelGhostFile"; - this.labelGhostFile.Size = new System.Drawing.Size(26, 13); - this.labelGhostFile.TabIndex = 3; - this.labelGhostFile.Text = "File:"; + labelGhostFile.AutoSize = true; + labelGhostFile.Location = new System.Drawing.Point(7, 46); + labelGhostFile.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + labelGhostFile.Name = "labelGhostFile"; + labelGhostFile.Size = new System.Drawing.Size(28, 15); + labelGhostFile.TabIndex = 3; + labelGhostFile.Text = "File:"; // // labelGhostPlaybackStart // - this.labelGhostPlaybackStart.AutoSize = true; - this.labelGhostPlaybackStart.Location = new System.Drawing.Point(6, 66); - this.labelGhostPlaybackStart.Name = "labelGhostPlaybackStart"; - this.labelGhostPlaybackStart.Size = new System.Drawing.Size(117, 13); - this.labelGhostPlaybackStart.TabIndex = 3; - this.labelGhostPlaybackStart.Text = "Original Playback Start:"; + labelGhostPlaybackStart.AutoSize = true; + labelGhostPlaybackStart.Location = new System.Drawing.Point(7, 76); + labelGhostPlaybackStart.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + labelGhostPlaybackStart.Name = "labelGhostPlaybackStart"; + labelGhostPlaybackStart.Size = new System.Drawing.Size(129, 15); + labelGhostPlaybackStart.TabIndex = 3; + labelGhostPlaybackStart.Text = "Original Playback Start:"; // // lblPlaybackOffset // - this.lblPlaybackOffset.AutoSize = true; - this.lblPlaybackOffset.Location = new System.Drawing.Point(291, 88); - this.lblPlaybackOffset.Name = "lblPlaybackOffset"; - this.lblPlaybackOffset.Size = new System.Drawing.Size(83, 13); - this.lblPlaybackOffset.TabIndex = 3; - this.lblPlaybackOffset.Text = "Playback offset:"; + lblPlaybackOffset.AutoSize = true; + lblPlaybackOffset.Location = new System.Drawing.Point(340, 102); + lblPlaybackOffset.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblPlaybackOffset.Name = "lblPlaybackOffset"; + lblPlaybackOffset.Size = new System.Drawing.Size(90, 15); + lblPlaybackOffset.TabIndex = 3; + lblPlaybackOffset.Text = "Playback offset:"; // // numericUpDownPlaybackOffset // - this.numericUpDownPlaybackOffset.Location = new System.Drawing.Point(379, 86); - this.numericUpDownPlaybackOffset.Maximum = new decimal(new int[] { - -1, - 0, - 0, - 0}); - this.numericUpDownPlaybackOffset.Minimum = new decimal(new int[] { - -1, - 0, - 0, - -2147483648}); - this.numericUpDownPlaybackOffset.Name = "numericUpDownPlaybackOffset"; - this.numericUpDownPlaybackOffset.Size = new System.Drawing.Size(92, 20); - this.numericUpDownPlaybackOffset.TabIndex = 2; - this.numericUpDownPlaybackOffset.ValueChanged += new System.EventHandler(this.numericUpDownPlaybakcOffset_ValueChanged); + numericUpDownPlaybackOffset.Location = new System.Drawing.Point(442, 99); + numericUpDownPlaybackOffset.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + numericUpDownPlaybackOffset.Maximum = new decimal(new int[] + { + -1, + 0, + 0, + 0 + }); + numericUpDownPlaybackOffset.Minimum = new decimal(new int[] + { + -1, + 0, + 0, + -2147483648 + }); + numericUpDownPlaybackOffset.Name = "numericUpDownPlaybackOffset"; + numericUpDownPlaybackOffset.Size = new System.Drawing.Size(107, 23); + numericUpDownPlaybackOffset.TabIndex = 2; + numericUpDownPlaybackOffset.ValueChanged += numericUpDownPlaybakcOffset_ValueChanged; // // labelBaseGlobalTimer // - this.labelBaseGlobalTimer.AutoSize = true; - this.labelBaseGlobalTimer.Location = new System.Drawing.Point(6, 88); - this.labelBaseGlobalTimer.Name = "labelBaseGlobalTimer"; - this.labelBaseGlobalTimer.Size = new System.Drawing.Size(153, 13); - this.labelBaseGlobalTimer.TabIndex = 3; - this.labelBaseGlobalTimer.Text = "Start Playback at Global Timer:"; + labelBaseGlobalTimer.AutoSize = true; + labelBaseGlobalTimer.Location = new System.Drawing.Point(7, 102); + labelBaseGlobalTimer.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + labelBaseGlobalTimer.Name = "labelBaseGlobalTimer"; + labelBaseGlobalTimer.Size = new System.Drawing.Size(167, 15); + labelBaseGlobalTimer.TabIndex = 3; + labelBaseGlobalTimer.Text = "Start Playback at Global Timer:"; // // numericUpDownStartOfPlayback // - this.numericUpDownStartOfPlayback.Location = new System.Drawing.Point(165, 86); - this.numericUpDownStartOfPlayback.Maximum = new decimal(new int[] { - -1, - 0, - 0, - 0}); - this.numericUpDownStartOfPlayback.Name = "numericUpDownStartOfPlayback"; - this.numericUpDownStartOfPlayback.Size = new System.Drawing.Size(120, 20); - this.numericUpDownStartOfPlayback.TabIndex = 2; - this.numericUpDownStartOfPlayback.ValueChanged += new System.EventHandler(this.numericUpDownStartOfPlayback_ValueChanged); + numericUpDownStartOfPlayback.Location = new System.Drawing.Point(192, 99); + numericUpDownStartOfPlayback.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + numericUpDownStartOfPlayback.Maximum = new decimal(new int[] + { + -1, + 0, + 0, + 0 + }); + numericUpDownStartOfPlayback.Name = "numericUpDownStartOfPlayback"; + numericUpDownStartOfPlayback.Size = new System.Drawing.Size(140, 23); + numericUpDownStartOfPlayback.TabIndex = 2; + numericUpDownStartOfPlayback.ValueChanged += numericUpDownStartOfPlayback_ValueChanged; // // buttonSaveGhost // - this.buttonSaveGhost.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonSaveGhost.Location = new System.Drawing.Point(365, 89); - this.buttonSaveGhost.Name = "buttonSaveGhost"; - this.buttonSaveGhost.Size = new System.Drawing.Size(143, 23); - this.buttonSaveGhost.TabIndex = 0; - this.buttonSaveGhost.Text = "Save Selected"; - this.buttonSaveGhost.UseVisualStyleBackColor = true; - this.buttonSaveGhost.Click += new System.EventHandler(this.buttonSaveGhost_Click); + buttonSaveGhost.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)); + buttonSaveGhost.Location = new System.Drawing.Point(426, 103); + buttonSaveGhost.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonSaveGhost.Name = "buttonSaveGhost"; + buttonSaveGhost.Size = new System.Drawing.Size(167, 27); + buttonSaveGhost.TabIndex = 0; + buttonSaveGhost.Text = "Save Selected"; + buttonSaveGhost.UseVisualStyleBackColor = true; + buttonSaveGhost.Click += buttonSaveGhost_Click; // // buttonTutorialRecord // - this.buttonTutorialRecord.Location = new System.Drawing.Point(9, 52); - this.buttonTutorialRecord.Name = "buttonTutorialRecord"; - this.buttonTutorialRecord.Size = new System.Drawing.Size(146, 23); - this.buttonTutorialRecord.TabIndex = 7; - this.buttonTutorialRecord.Text = "Recording Ghosts"; - this.buttonTutorialRecord.UseVisualStyleBackColor = true; - this.buttonTutorialRecord.Click += new System.EventHandler(this.buttonTutorialRecord_Click); + buttonTutorialRecord.Location = new System.Drawing.Point(10, 60); + buttonTutorialRecord.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonTutorialRecord.Name = "buttonTutorialRecord"; + buttonTutorialRecord.Size = new System.Drawing.Size(170, 27); + buttonTutorialRecord.TabIndex = 7; + buttonTutorialRecord.Text = "Recording Ghosts"; + buttonTutorialRecord.UseVisualStyleBackColor = true; + buttonTutorialRecord.Click += buttonTutorialRecord_Click; // // groupGhostHack // - this.groupGhostHack.Controls.Add(this.labelHackActiveState); - this.groupGhostHack.Controls.Add(this.buttonDisableGhostHack); - this.groupGhostHack.Controls.Add(this.buttonEnableGhostHack); - this.groupGhostHack.Location = new System.Drawing.Point(3, 3); - this.groupGhostHack.Name = "groupGhostHack"; - this.groupGhostHack.Size = new System.Drawing.Size(161, 164); - this.groupGhostHack.TabIndex = 3; - this.groupGhostHack.TabStop = false; - this.groupGhostHack.Text = "Ghost Hack"; + groupGhostHack.Controls.Add(txtRAMOffsetBase); + groupGhostHack.Controls.Add(lblRAMOffsetBase); + groupGhostHack.Controls.Add(labelHackActiveState); + groupGhostHack.Controls.Add(buttonDisableGhostHack); + groupGhostHack.Controls.Add(buttonEnableGhostHack); + groupGhostHack.Location = new System.Drawing.Point(4, 3); + groupGhostHack.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupGhostHack.Name = "groupGhostHack"; + groupGhostHack.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupGhostHack.Size = new System.Drawing.Size(188, 189); + groupGhostHack.TabIndex = 3; + groupGhostHack.TabStop = false; + groupGhostHack.Text = "Ghost Hack"; // // labelHackActiveState // - this.labelHackActiveState.AutoSize = true; - this.labelHackActiveState.Location = new System.Drawing.Point(6, 82); - this.labelHackActiveState.Name = "labelHackActiveState"; - this.labelHackActiveState.Size = new System.Drawing.Size(131, 13); - this.labelHackActiveState.TabIndex = 3; - this.labelHackActiveState.Text = "Ghost hack is not enabled"; + labelHackActiveState.AutoSize = true; + labelHackActiveState.Location = new System.Drawing.Point(7, 95); + labelHackActiveState.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + labelHackActiveState.Name = "labelHackActiveState"; + labelHackActiveState.Size = new System.Drawing.Size(143, 15); + labelHackActiveState.TabIndex = 3; + labelHackActiveState.Text = "Ghost hack is not enabled"; // // buttonDisableGhostHack // - this.buttonDisableGhostHack.Location = new System.Drawing.Point(6, 48); - this.buttonDisableGhostHack.Name = "buttonDisableGhostHack"; - this.buttonDisableGhostHack.Size = new System.Drawing.Size(143, 23); - this.buttonDisableGhostHack.TabIndex = 0; - this.buttonDisableGhostHack.Text = "Disable Ghost Hack"; - this.buttonDisableGhostHack.UseVisualStyleBackColor = true; - this.buttonDisableGhostHack.Click += new System.EventHandler(this.buttonDisableGhostHack_Click); + buttonDisableGhostHack.Location = new System.Drawing.Point(7, 55); + buttonDisableGhostHack.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonDisableGhostHack.Name = "buttonDisableGhostHack"; + buttonDisableGhostHack.Size = new System.Drawing.Size(167, 27); + buttonDisableGhostHack.TabIndex = 0; + buttonDisableGhostHack.Text = "Disable Ghost Hack"; + buttonDisableGhostHack.UseVisualStyleBackColor = true; + buttonDisableGhostHack.Click += buttonDisableGhostHack_Click; // // buttonEnableGhostHack // - this.buttonEnableGhostHack.Location = new System.Drawing.Point(6, 19); - this.buttonEnableGhostHack.Name = "buttonEnableGhostHack"; - this.buttonEnableGhostHack.Size = new System.Drawing.Size(143, 23); - this.buttonEnableGhostHack.TabIndex = 0; - this.buttonEnableGhostHack.Text = "Enable Ghost Hack"; - this.buttonEnableGhostHack.UseVisualStyleBackColor = true; - this.buttonEnableGhostHack.Click += new System.EventHandler(this.buttonEnableGhostHack_Click); - // - // watchVariablePanelGhost - // - this._variablePanelGhost.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this._variablePanelGhost.AutoScroll = true; - this._variablePanelGhost.DataPath = "Config/GhostData.xml"; - this._variablePanelGhost.elementNameWidth = null; - this._variablePanelGhost.elementValueWidth = null; - this._variablePanelGhost.Location = new System.Drawing.Point(6, 19); - this._variablePanelGhost.Name = "_variablePanelGhost"; - this._variablePanelGhost.Size = new System.Drawing.Size(209, 432); - this._variablePanelGhost.TabIndex = 4; + buttonEnableGhostHack.Location = new System.Drawing.Point(7, 22); + buttonEnableGhostHack.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonEnableGhostHack.Name = "buttonEnableGhostHack"; + buttonEnableGhostHack.Size = new System.Drawing.Size(167, 27); + buttonEnableGhostHack.TabIndex = 0; + buttonEnableGhostHack.Text = "Enable Ghost Hack"; + buttonEnableGhostHack.UseVisualStyleBackColor = true; + buttonEnableGhostHack.Click += buttonEnableGhostHack_Click; + // + // _variablePanelGhost + // + _variablePanelGhost.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)); + _variablePanelGhost.AutoScroll = true; + _variablePanelGhost.DataPath = "Config/GhostData.xml"; + _variablePanelGhost.elementNameWidth = null; + _variablePanelGhost.elementValueWidth = null; + _variablePanelGhost.Location = new System.Drawing.Point(7, 22); + _variablePanelGhost.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + _variablePanelGhost.Name = "_variablePanelGhost"; + _variablePanelGhost.Size = new System.Drawing.Size(244, 498); + _variablePanelGhost.TabIndex = 4; // // groupBoxVariables // - this.groupBoxVariables.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxVariables.Controls.Add(this._variablePanelGhost); - this.groupBoxVariables.Location = new System.Drawing.Point(691, 3); - this.groupBoxVariables.Name = "groupBoxVariables"; - this.groupBoxVariables.Size = new System.Drawing.Size(221, 457); - this.groupBoxVariables.TabIndex = 5; - this.groupBoxVariables.TabStop = false; - this.groupBoxVariables.Text = "Variables"; + groupBoxVariables.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right)); + groupBoxVariables.Controls.Add(_variablePanelGhost); + groupBoxVariables.Location = new System.Drawing.Point(806, 3); + groupBoxVariables.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxVariables.Name = "groupBoxVariables"; + groupBoxVariables.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxVariables.Size = new System.Drawing.Size(258, 527); + groupBoxVariables.TabIndex = 5; + groupBoxVariables.TabStop = false; + groupBoxVariables.Text = "Variables"; // // groupBoxHelp // - this.groupBoxHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.groupBoxHelp.Controls.Add(this.buttonTutorialFileWatch); - this.groupBoxHelp.Controls.Add(this.buttonHelpGfxPool); - this.groupBoxHelp.Controls.Add(this.buttonTutorialPlayback); - this.groupBoxHelp.Controls.Add(this.buttonTutorialNotes); - this.groupBoxHelp.Controls.Add(this.buttonTutorialRecord); - this.groupBoxHelp.Location = new System.Drawing.Point(3, 288); - this.groupBoxHelp.Name = "groupBoxHelp"; - this.groupBoxHelp.Size = new System.Drawing.Size(161, 172); - this.groupBoxHelp.TabIndex = 8; - this.groupBoxHelp.TabStop = false; - this.groupBoxHelp.Text = "Help"; + groupBoxHelp.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)); + groupBoxHelp.Controls.Add(buttonTutorialFileWatch); + groupBoxHelp.Controls.Add(buttonHelpGfxPool); + groupBoxHelp.Controls.Add(buttonTutorialPlayback); + groupBoxHelp.Controls.Add(buttonTutorialNotes); + groupBoxHelp.Controls.Add(buttonTutorialRecord); + groupBoxHelp.Location = new System.Drawing.Point(4, 332); + groupBoxHelp.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxHelp.Name = "groupBoxHelp"; + groupBoxHelp.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxHelp.Size = new System.Drawing.Size(188, 198); + groupBoxHelp.TabIndex = 8; + groupBoxHelp.TabStop = false; + groupBoxHelp.Text = "Help"; // // buttonTutorialFileWatch // - this.buttonTutorialFileWatch.Location = new System.Drawing.Point(9, 139); - this.buttonTutorialFileWatch.Name = "buttonTutorialFileWatch"; - this.buttonTutorialFileWatch.Size = new System.Drawing.Size(146, 23); - this.buttonTutorialFileWatch.TabIndex = 7; - this.buttonTutorialFileWatch.Text = "Using File Watchers"; - this.buttonTutorialFileWatch.UseVisualStyleBackColor = true; - this.buttonTutorialFileWatch.Click += new System.EventHandler(this.buttonTutorialFileWatch_Click); + buttonTutorialFileWatch.Location = new System.Drawing.Point(10, 160); + buttonTutorialFileWatch.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonTutorialFileWatch.Name = "buttonTutorialFileWatch"; + buttonTutorialFileWatch.Size = new System.Drawing.Size(170, 27); + buttonTutorialFileWatch.TabIndex = 7; + buttonTutorialFileWatch.Text = "Using File Watchers"; + buttonTutorialFileWatch.UseVisualStyleBackColor = true; + buttonTutorialFileWatch.Click += buttonTutorialFileWatch_Click; // // buttonHelpGfxPool // - this.buttonHelpGfxPool.Location = new System.Drawing.Point(9, 110); - this.buttonHelpGfxPool.Name = "buttonHelpGfxPool"; - this.buttonHelpGfxPool.Size = new System.Drawing.Size(146, 23); - this.buttonHelpGfxPool.TabIndex = 7; - this.buttonHelpGfxPool.Text = "Moving the GFX Pool"; - this.buttonHelpGfxPool.UseVisualStyleBackColor = true; - this.buttonHelpGfxPool.Click += new System.EventHandler(this.buttonHelpGfxPool_Click); + buttonHelpGfxPool.Location = new System.Drawing.Point(10, 127); + buttonHelpGfxPool.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonHelpGfxPool.Name = "buttonHelpGfxPool"; + buttonHelpGfxPool.Size = new System.Drawing.Size(170, 27); + buttonHelpGfxPool.TabIndex = 7; + buttonHelpGfxPool.Text = "Moving the GFX Pool"; + buttonHelpGfxPool.UseVisualStyleBackColor = true; + buttonHelpGfxPool.Click += buttonHelpGfxPool_Click; // // buttonTutorialPlayback // - this.buttonTutorialPlayback.Location = new System.Drawing.Point(9, 81); - this.buttonTutorialPlayback.Name = "buttonTutorialPlayback"; - this.buttonTutorialPlayback.Size = new System.Drawing.Size(146, 23); - this.buttonTutorialPlayback.TabIndex = 7; - this.buttonTutorialPlayback.Text = "Playing Ghosts back"; - this.buttonTutorialPlayback.UseVisualStyleBackColor = true; - this.buttonTutorialPlayback.Click += new System.EventHandler(this.buttonTutorialPlayback_Click); + buttonTutorialPlayback.Location = new System.Drawing.Point(10, 93); + buttonTutorialPlayback.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonTutorialPlayback.Name = "buttonTutorialPlayback"; + buttonTutorialPlayback.Size = new System.Drawing.Size(170, 27); + buttonTutorialPlayback.TabIndex = 7; + buttonTutorialPlayback.Text = "Playing Ghosts back"; + buttonTutorialPlayback.UseVisualStyleBackColor = true; + buttonTutorialPlayback.Click += buttonTutorialPlayback_Click; // // buttonTutorialNotes // - this.buttonTutorialNotes.Location = new System.Drawing.Point(9, 19); - this.buttonTutorialNotes.Name = "buttonTutorialNotes"; - this.buttonTutorialNotes.Size = new System.Drawing.Size(146, 23); - this.buttonTutorialNotes.TabIndex = 7; - this.buttonTutorialNotes.Text = "General Notes"; - this.buttonTutorialNotes.UseVisualStyleBackColor = true; - this.buttonTutorialNotes.Click += new System.EventHandler(this.buttonTutorialNotes_Click); + buttonTutorialNotes.Location = new System.Drawing.Point(10, 22); + buttonTutorialNotes.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonTutorialNotes.Name = "buttonTutorialNotes"; + buttonTutorialNotes.Size = new System.Drawing.Size(170, 27); + buttonTutorialNotes.TabIndex = 7; + buttonTutorialNotes.Text = "General Notes"; + buttonTutorialNotes.UseVisualStyleBackColor = true; + buttonTutorialNotes.Click += buttonTutorialNotes_Click; // // groupBoxGfxPool // - this.groupBoxGfxPool.Controls.Add(this.textBoxPoolSize); - this.groupBoxGfxPool.Controls.Add(this.textBoxPoolAddr2); - this.groupBoxGfxPool.Controls.Add(this.textBoxPoolAddr1); - this.groupBoxGfxPool.Controls.Add(this.labelPoolSize); - this.groupBoxGfxPool.Controls.Add(this.label1); - this.groupBoxGfxPool.Controls.Add(this.labelPool1Address); - this.groupBoxGfxPool.Controls.Add(this.buttonMoveGfxPool); - this.groupBoxGfxPool.Location = new System.Drawing.Point(3, 171); - this.groupBoxGfxPool.Name = "groupBoxGfxPool"; - this.groupBoxGfxPool.Size = new System.Drawing.Size(155, 111); - this.groupBoxGfxPool.TabIndex = 9; - this.groupBoxGfxPool.TabStop = false; - this.groupBoxGfxPool.Text = "Gfx Pool"; + groupBoxGfxPool.Controls.Add(textBoxPoolSize); + groupBoxGfxPool.Controls.Add(textBoxPoolAddr2); + groupBoxGfxPool.Controls.Add(textBoxPoolAddr1); + groupBoxGfxPool.Controls.Add(labelPoolSize); + groupBoxGfxPool.Controls.Add(label1); + groupBoxGfxPool.Controls.Add(labelPool1Address); + groupBoxGfxPool.Controls.Add(buttonMoveGfxPool); + groupBoxGfxPool.Location = new System.Drawing.Point(4, 197); + groupBoxGfxPool.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxGfxPool.Name = "groupBoxGfxPool"; + groupBoxGfxPool.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxGfxPool.Size = new System.Drawing.Size(181, 128); + groupBoxGfxPool.TabIndex = 9; + groupBoxGfxPool.TabStop = false; + groupBoxGfxPool.Text = "Gfx Pool"; // // textBoxPoolSize // - this.textBoxPoolSize.Location = new System.Drawing.Point(80, 57); - this.textBoxPoolSize.Name = "textBoxPoolSize"; - this.textBoxPoolSize.Size = new System.Drawing.Size(69, 20); - this.textBoxPoolSize.TabIndex = 2; - this.textBoxPoolSize.Text = "FFF00"; + textBoxPoolSize.Location = new System.Drawing.Point(93, 66); + textBoxPoolSize.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + textBoxPoolSize.Name = "textBoxPoolSize"; + textBoxPoolSize.Size = new System.Drawing.Size(80, 23); + textBoxPoolSize.TabIndex = 2; + textBoxPoolSize.Text = "FFF00"; // // textBoxPoolAddr2 // - this.textBoxPoolAddr2.Location = new System.Drawing.Point(80, 35); - this.textBoxPoolAddr2.Name = "textBoxPoolAddr2"; - this.textBoxPoolAddr2.Size = new System.Drawing.Size(69, 20); - this.textBoxPoolAddr2.TabIndex = 2; - this.textBoxPoolAddr2.Text = "80700000"; + textBoxPoolAddr2.Location = new System.Drawing.Point(93, 40); + textBoxPoolAddr2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + textBoxPoolAddr2.Name = "textBoxPoolAddr2"; + textBoxPoolAddr2.Size = new System.Drawing.Size(80, 23); + textBoxPoolAddr2.TabIndex = 2; + textBoxPoolAddr2.Text = "80700000"; // // textBoxPoolAddr1 // - this.textBoxPoolAddr1.Location = new System.Drawing.Point(80, 13); - this.textBoxPoolAddr1.Name = "textBoxPoolAddr1"; - this.textBoxPoolAddr1.Size = new System.Drawing.Size(69, 20); - this.textBoxPoolAddr1.TabIndex = 2; - this.textBoxPoolAddr1.Text = "80600000"; + textBoxPoolAddr1.Location = new System.Drawing.Point(93, 15); + textBoxPoolAddr1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + textBoxPoolAddr1.Name = "textBoxPoolAddr1"; + textBoxPoolAddr1.Size = new System.Drawing.Size(80, 23); + textBoxPoolAddr1.TabIndex = 2; + textBoxPoolAddr1.Text = "80600000"; // // labelPoolSize // - this.labelPoolSize.AutoSize = true; - this.labelPoolSize.Location = new System.Drawing.Point(23, 60); - this.labelPoolSize.Name = "labelPoolSize"; - this.labelPoolSize.Size = new System.Drawing.Size(51, 13); - this.labelPoolSize.TabIndex = 1; - this.labelPoolSize.Text = "Pool Size"; - this.labelPoolSize.TextAlign = System.Drawing.ContentAlignment.TopRight; + labelPoolSize.AutoSize = true; + labelPoolSize.Location = new System.Drawing.Point(27, 69); + labelPoolSize.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + labelPoolSize.Name = "labelPoolSize"; + labelPoolSize.Size = new System.Drawing.Size(54, 15); + labelPoolSize.TabIndex = 1; + labelPoolSize.Text = "Pool Size"; + labelPoolSize.TextAlign = System.Drawing.ContentAlignment.TopRight; // // label1 // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(6, 38); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(68, 13); - this.label1.TabIndex = 1; - this.label1.Text = "Pool Addr. 2:"; + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(7, 44); + label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(75, 15); + label1.TabIndex = 1; + label1.Text = "Pool Addr. 2:"; // // labelPool1Address // - this.labelPool1Address.AutoSize = true; - this.labelPool1Address.Location = new System.Drawing.Point(6, 16); - this.labelPool1Address.Name = "labelPool1Address"; - this.labelPool1Address.Size = new System.Drawing.Size(68, 13); - this.labelPool1Address.TabIndex = 1; - this.labelPool1Address.Text = "Pool Addr. 1:"; + labelPool1Address.AutoSize = true; + labelPool1Address.Location = new System.Drawing.Point(7, 18); + labelPool1Address.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + labelPool1Address.Name = "labelPool1Address"; + labelPool1Address.Size = new System.Drawing.Size(75, 15); + labelPool1Address.TabIndex = 1; + labelPool1Address.Text = "Pool Addr. 1:"; // // buttonMoveGfxPool // - this.buttonMoveGfxPool.Location = new System.Drawing.Point(52, 82); - this.buttonMoveGfxPool.Name = "buttonMoveGfxPool"; - this.buttonMoveGfxPool.Size = new System.Drawing.Size(97, 23); - this.buttonMoveGfxPool.TabIndex = 0; - this.buttonMoveGfxPool.Text = "Move GFX Pool"; - this.buttonMoveGfxPool.UseVisualStyleBackColor = true; - this.buttonMoveGfxPool.Click += new System.EventHandler(this.buttonMoveGfxPool_Click); + buttonMoveGfxPool.Location = new System.Drawing.Point(61, 95); + buttonMoveGfxPool.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + buttonMoveGfxPool.Name = "buttonMoveGfxPool"; + buttonMoveGfxPool.Size = new System.Drawing.Size(113, 27); + buttonMoveGfxPool.TabIndex = 0; + buttonMoveGfxPool.Text = "Move GFX Pool"; + buttonMoveGfxPool.UseVisualStyleBackColor = true; + buttonMoveGfxPool.Click += buttonMoveGfxPool_Click; + // + // lblRAMOffsetBase + // + lblRAMOffsetBase.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)); + lblRAMOffsetBase.Location = new System.Drawing.Point(11, 160); + lblRAMOffsetBase.Name = "lblRAMOffsetBase"; + lblRAMOffsetBase.Size = new System.Drawing.Size(126, 23); + lblRAMOffsetBase.TabIndex = 4; + lblRAMOffsetBase.Text = "RAM offset base:"; + lblRAMOffsetBase.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // txtRAMOffsetBase + // + txtRAMOffsetBase.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)); + txtRAMOffsetBase.Location = new System.Drawing.Point(143, 160); + txtRAMOffsetBase.Name = "txtRAMOffsetBase"; + txtRAMOffsetBase.Size = new System.Drawing.Size(38, 23); + txtRAMOffsetBase.TabIndex = 5; + txtRAMOffsetBase.Text = "8040"; // // GhostTab // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.Controls.Add(this.groupBoxGfxPool); - this.Controls.Add(this.groupBoxHelp); - this.Controls.Add(this.groupBoxVariables); - this.Controls.Add(this.groupBoxGhosts); - this.Controls.Add(this.groupGhostHack); - this.Name = "GhostTab"; - this.groupBoxGhosts.ResumeLayout(false); - this.groupBoxGhostInfo.ResumeLayout(false); - this.groupBoxGhostInfo.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDownPlaybackOffset)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDownStartOfPlayback)).EndInit(); - this.groupGhostHack.ResumeLayout(false); - this.groupGhostHack.PerformLayout(); - this.groupBoxVariables.ResumeLayout(false); - this.groupBoxHelp.ResumeLayout(false); - this.groupBoxGfxPool.ResumeLayout(false); - this.groupBoxGfxPool.PerformLayout(); - this.ResumeLayout(false); - + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + Controls.Add(groupBoxGfxPool); + Controls.Add(groupBoxHelp); + Controls.Add(groupBoxVariables); + Controls.Add(groupBoxGhosts); + Controls.Add(groupGhostHack); + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBoxGhosts.ResumeLayout(false); + groupBoxGhostInfo.ResumeLayout(false); + groupBoxGhostInfo.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)numericUpDownPlaybackOffset).EndInit(); + ((System.ComponentModel.ISupportInitialize)numericUpDownStartOfPlayback).EndInit(); + groupGhostHack.ResumeLayout(false); + groupGhostHack.PerformLayout(); + groupBoxVariables.ResumeLayout(false); + groupBoxHelp.ResumeLayout(false); + groupBoxGfxPool.ResumeLayout(false); + groupBoxGfxPool.PerformLayout(); + ResumeLayout(false); } + private System.Windows.Forms.Label lblRAMOffsetBase; + private System.Windows.Forms.TextBox txtRAMOffsetBase; + #endregion private System.Windows.Forms.GroupBox groupBoxGhosts; diff --git a/STROOP/Tabs/GhostTab/GhostTab.cs b/STROOP/Tabs/GhostTab/GhostTab.cs index ea33af01f..b1fca592d 100644 --- a/STROOP/Tabs/GhostTab/GhostTab.cs +++ b/STROOP/Tabs/GhostTab/GhostTab.cs @@ -13,13 +13,17 @@ using STROOP.Variables; using STROOP.Variables.SM64MemoryLayout; using STROOP.Variables.Utilities; +using System.Globalization; namespace STROOP.Tabs.GhostTab { public partial class GhostTab : STROOPTab { /// The variable part to move the ghost loop and colored hats code with. - ushort EXTENDED_RAM_UPPER_PART = 0x8045; // originally 0x8040 + ushort EXTENDED_RAM_UPPER_PART => + ushort.TryParse(txtRAMOffsetBase.Text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed) + ? parsed + : (ushort)0x8040; const uint GHOST_LOOP_CODE_OFFSET = 0x8000u; const uint HACK_FILE_BASE_OFFSET = 0x80400000; @@ -287,6 +291,8 @@ bool UpdateHackStatus() bool enabled = ghostHack.Status != RomHack.EnabledStatus.Disabled; labelHackActiveState.Text = (ghostsActive && enabled) ? "Ghost hack is enabled." : (enabled ? "Ghost hack is enabled\nbut not running.\nInside a level,\nsave state and load state,\nthen frame advance." : "Ghost hack is disabled."); buttonDisableGhostHack.Enabled = enabled; + lblRAMOffsetBase.Visible = !enabled; + txtRAMOffsetBase.Visible = !enabled; return true; } From 3fc8891275de0a84b046c90e6d55918c3a1a832c Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:09:25 +0200 Subject: [PATCH 10/31] adjust property casing --- STROOP/Tabs/GhostTab/ColoredHats.cs | 4 +- STROOP/Tabs/GhostTab/GhostTab.cs | 59 ++++++++++++++--------------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/STROOP/Tabs/GhostTab/ColoredHats.cs b/STROOP/Tabs/GhostTab/ColoredHats.cs index e8f6b83c9..14c01c9fd 100644 --- a/STROOP/Tabs/GhostTab/ColoredHats.cs +++ b/STROOP/Tabs/GhostTab/ColoredHats.cs @@ -38,7 +38,7 @@ partial class GhostTab Vector4 marioHatColor = new Vector4(1, 0, 0, 1); - uint COLORED_HATS_LIGHTS_ADDR => GHOST_REGION_BASE + 0x8300u; + uint COLORED_HATS_LIGHTS_ADDR => ghostRegionBase + 0x8300u; private void EnableColoredHats() { @@ -69,7 +69,7 @@ private void EnableColoredHats() var foundPointer = Config.Stream.GetUInt32(addr + 0x14); if (Array.IndexOf(originalDisplayListPointers, foundPointer) != -1) { - Config.Stream.SetValue(GHOST_REGION_BASE + COLORED_HATS_CODE_OFFSET, addr + 0x14); + Config.Stream.SetValue(ghostRegionBase + COLORED_HATS_CODE_OFFSET, addr + 0x14); Config.Stream.SetValue((ushort)0x12A, addr); } } diff --git a/STROOP/Tabs/GhostTab/GhostTab.cs b/STROOP/Tabs/GhostTab/GhostTab.cs index b1fca592d..0eb3eb340 100644 --- a/STROOP/Tabs/GhostTab/GhostTab.cs +++ b/STROOP/Tabs/GhostTab/GhostTab.cs @@ -19,31 +19,30 @@ namespace STROOP.Tabs.GhostTab { public partial class GhostTab : STROOPTab { - /// The variable part to move the ghost loop and colored hats code with. - ushort EXTENDED_RAM_UPPER_PART => - ushort.TryParse(txtRAMOffsetBase.Text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed) - ? parsed - : (ushort)0x8040; const uint GHOST_LOOP_CODE_OFFSET = 0x8000u; const uint HACK_FILE_BASE_OFFSET = 0x80400000; - // Base of the ghost hack's extended-RAM region. Must match GhostBaseHi in ghost_loop.asm - // and the inject addresses + hook bytes in Resources/Hacks/GhostHack*.hck. - uint GHOST_REGION_BASE => (uint)EXTENDED_RAM_UPPER_PART << 0x10; - // These numbers are the 4 byte words, in order, as exported into "DynamicOffsets.bin". const uint FirstAnimationBufferAddrHi_LUI_1 = 0x50; const uint GhostBaseHi_LUI_PLUS_1 = 0xF8; const uint COLORED_HATS_GhostBaseHi_LUI = 0x70; static readonly uint[] GhostBaseHi_LUI = [0x44, 0x78, 0x170]; + /// The variable part to move the ghost loop and colored hats code with. + ushort ghostHackBaseHi => + ushort.TryParse(txtRAMOffsetBase.Text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed) + ? parsed + : (ushort)0x8040; + + uint ghostRegionBase => (uint)ghostHackBaseHi << 0x10; + // These offsets mirror NumRequestedGhosts / PointerToFirstGhost in ghost_loop.asm. - uint NUM_GHOSTS_ADDR => GHOST_REGION_BASE + 0x7FFFu; - uint FIRST_GHOST_POINTER_ADDR => GHOST_REGION_BASE + 0x7FF8u; - uint DISABLE_REQUEST_ADDR => GHOST_REGION_BASE + 0x7FFCu; + uint numGhostsAddr => ghostRegionBase + 0x7FFFu; + uint firstGhostPointerAddr => ghostRegionBase + 0x7FF8u; + uint disableRequestAddr => ghostRegionBase + 0x7FFCu; - uint bufferBaseAddress => GHOST_REGION_BASE + 0x9B00u; + uint bufferBaseAddress => ghostRegionBase + 0x9B00u; static IEnumerable GetActiveGhostIndices() { @@ -109,7 +108,7 @@ public override void Update(bool active) int numGhosts = Math.Max(1, ghostArr.Length); if (updateGhostData) { - Config.Stream.SetValue((byte)numGhosts, NUM_GHOSTS_ADDR); + Config.Stream.SetValue((byte)numGhosts, numGhostsAddr); WriteMarioColorToStream(); } @@ -189,7 +188,7 @@ public override void Update(bool active) WriteGhostColorToStream(ghostIndex, ghostArr); - var ptr = Config.Stream.GetUInt32((uint)(FIRST_GHOST_POINTER_ADDR - ghostIndex * 0x68)); + var ptr = Config.Stream.GetUInt32((uint)(firstGhostPointerAddr - ghostIndex * 0x68)); Config.Stream.SetValue((byte)(ghostTransparent ? 1 : 0), ptr + 0x61); lastGlobalTimer = globalTimer; } @@ -272,16 +271,16 @@ bool UpdateHackStatus() if (ghostHack?.Name != expectedHackName) ghostHack = new RomHack($"Resources/Hacks/GhostHack{RomVersionConfig.Version}.hck", expectedHackName); - var ghostPointer = Config.Stream.GetInt32(FIRST_GHOST_POINTER_ADDR); + var ghostPointer = Config.Stream.GetInt32(firstGhostPointerAddr); bool ghostsActive = (ghostPointer & 0xFF000000) == 0x80000000; - bool shouldDisable = Config.Stream.GetByte(DISABLE_REQUEST_ADDR) == 0xFF; + bool shouldDisable = Config.Stream.GetByte(disableRequestAddr) == 0xFF; if (shouldDisable) { labelHackActiveState.Text = "Disabling Ghost hack...\nInside a level, frame advance\nthen save state and load state.\nNot doing so will crash.\n(Not on Pure Interpreter)"; if (!ghostsActive) { ghostHack.ClearPayload(); - Config.Stream.SetValue((byte)0, DISABLE_REQUEST_ADDR); + Config.Stream.SetValue((byte)0, disableRequestAddr); } else return true; @@ -372,11 +371,11 @@ private void buttonEnableGhostHack_Click(object sender, EventArgs e) { ghostHack.LoadPayload(new() { - [HACK_FILE_BASE_OFFSET + GHOST_LOOP_CODE_OFFSET] = GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET, - [HACK_FILE_BASE_OFFSET + COLORED_HATS_CODE_OFFSET] = GHOST_REGION_BASE + COLORED_HATS_CODE_OFFSET, + [HACK_FILE_BASE_OFFSET + GHOST_LOOP_CODE_OFFSET] = ghostRegionBase + GHOST_LOOP_CODE_OFFSET, + [HACK_FILE_BASE_OFFSET + COLORED_HATS_CODE_OFFSET] = ghostRegionBase + COLORED_HATS_CODE_OFFSET, }); - Config.Stream.WriteRam(new byte[4], DISABLE_REQUEST_ADDR, EndiannessType.Little); - Config.Stream.WriteRam(new byte[0x70], GHOST_REGION_BASE + 0x7F90u, EndiannessType.Little); + Config.Stream.WriteRam(new byte[4], disableRequestAddr, EndiannessType.Little); + Config.Stream.WriteRam(new byte[0x70], ghostRegionBase + 0x7F90u, EndiannessType.Little); EnableColoredHats(); @@ -384,17 +383,17 @@ private void buttonEnableGhostHack_Click(object sender, EventArgs e) Config.Stream.WriteRam(new byte[0x1000], HACK_FILE_BASE_OFFSET + GHOST_LOOP_CODE_OFFSET - 0x1000u, EndiannessType.Big); // Modify code for moving parts - ushort luiGhostBaseValue = EXTENDED_RAM_UPPER_PART; + ushort luiGhostBaseValue = ghostHackBaseHi; ushort luiFirstAnimationValue = (ushort)(luiGhostBaseValue + 0x10); - ApplyLui((ushort)(luiGhostBaseValue + 1), GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET + GhostBaseHi_LUI_PLUS_1); + ApplyLui((ushort)(luiGhostBaseValue + 1), ghostRegionBase + GHOST_LOOP_CODE_OFFSET + GhostBaseHi_LUI_PLUS_1); foreach (var offset in GhostBaseHi_LUI) - ApplyLui(luiGhostBaseValue, GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET + offset); - ApplyLui(luiFirstAnimationValue, GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET + FirstAnimationBufferAddrHi_LUI_1); + ApplyLui(luiGhostBaseValue, ghostRegionBase + GHOST_LOOP_CODE_OFFSET + offset); + ApplyLui(luiFirstAnimationValue, ghostRegionBase + GHOST_LOOP_CODE_OFFSET + FirstAnimationBufferAddrHi_LUI_1); - ApplyLui(luiGhostBaseValue, GHOST_REGION_BASE + COLORED_HATS_CODE_OFFSET + COLORED_HATS_GhostBaseHi_LUI); + ApplyLui(luiGhostBaseValue, ghostRegionBase + COLORED_HATS_CODE_OFFSET + COLORED_HATS_GhostBaseHi_LUI); - var jalTarget = 0x00FFFFFF & (GHOST_REGION_BASE + GHOST_LOOP_CODE_OFFSET); + var jalTarget = 0x00FFFFFF & (ghostRegionBase + GHOST_LOOP_CODE_OFFSET); var hookPoint = RomVersionConfig.Version == RomVersion.JP ? 0x8027ABD8 : 0x8027B188; Config.Stream.SetValue((uint)((0x0C << 0x18) | (jalTarget / 4)), hookPoint); @@ -414,8 +413,8 @@ It is recommended that you load a savestate without the hack enabled instead. Are you sure you want to continue?"; if (MessageBox.Show(txt, "You should not have to do this.", MessageBoxButtons.YesNo) == DialogResult.Yes) { - Config.Stream.SetValue((byte)0, NUM_GHOSTS_ADDR); - Config.Stream.SetValue((byte)0xFF, DISABLE_REQUEST_ADDR); + Config.Stream.SetValue((byte)0, numGhostsAddr); + Config.Stream.SetValue((byte)0xFF, disableRequestAddr); } } From 25a59a86fbf19e46fc3665023f591c0199896e19 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:18:39 +0200 Subject: [PATCH 11/31] expand 'Ghost Help' 'Notes' page for Usamune use-case --- STROOP/Tabs/GhostTab/GhostTabHelp.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/STROOP/Tabs/GhostTab/GhostTabHelp.cs b/STROOP/Tabs/GhostTab/GhostTabHelp.cs index dca138664..20ada9275 100644 --- a/STROOP/Tabs/GhostTab/GhostTabHelp.cs +++ b/STROOP/Tabs/GhostTab/GhostTabHelp.cs @@ -47,7 +47,7 @@ Always make sure your runs work from a clean savestate. private void buttonTutorialNotes_Click(object sender, EventArgs e) { Forms.InfoForm frm = new Forms.InfoForm(); - frm.Size = new System.Drawing.Size(900, 400); + frm.Size = new System.Drawing.Size(900, 570); frm.SetText("Ghost Help", "Notes", @"The ghost hack works on the US and JP versions of Super Mario 64 (and therefore also on numerous ROM hacks). @@ -59,6 +59,9 @@ Always verify that your runs work without the hack. Rendering too many ghosts at once can cause a game crash. This number is usually somewhere around 15 to 18, but can be increased by moving the game's Gfx pool. See ""Moving the GFX pool"" for details. +For Usamune ROMs (and perhaps other ROMs as well), you will need to change the ""RAM offset base"" to a higher value (e.g. 8060) before enabling the hack, in order to not conflict with the ROM hack's own memory allocations. +(Reasonable values are between 8040 and 8060 for this hack due to its own memory requirements.) + Disabling the ghost hack via the 'Disable Ghost hack' button may have unintended side effects, including crashing the game. Try to keep a savestate around that doesn't have the hack enabled instead. From 15f72cc8ff6a0d0f3426af1aa977686e4ef9ebfe Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Wed, 26 Aug 2026 00:46:10 +0200 Subject: [PATCH 12/31] fix misplaced address for clearing memory --- STROOP/Tabs/GhostTab/GhostTab.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STROOP/Tabs/GhostTab/GhostTab.cs b/STROOP/Tabs/GhostTab/GhostTab.cs index 0eb3eb340..962c50140 100644 --- a/STROOP/Tabs/GhostTab/GhostTab.cs +++ b/STROOP/Tabs/GhostTab/GhostTab.cs @@ -380,7 +380,7 @@ private void buttonEnableGhostHack_Click(object sender, EventArgs e) EnableColoredHats(); //Tell ROM Hacks to suck it and get rid of the 01010101 pattern - Config.Stream.WriteRam(new byte[0x1000], HACK_FILE_BASE_OFFSET + GHOST_LOOP_CODE_OFFSET - 0x1000u, EndiannessType.Big); + Config.Stream.WriteRam(new byte[0x1000], ghostRegionBase + GHOST_LOOP_CODE_OFFSET - 0x1000u, EndiannessType.Big); // Modify code for moving parts ushort luiGhostBaseValue = ghostHackBaseHi; From 68c69b12a536ae3b59ce444d7815fce059713ca0 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Wed, 26 Aug 2026 00:46:16 +0200 Subject: [PATCH 13/31] fix typo --- HackSources/Ghosts/export_moving_code_references.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HackSources/Ghosts/export_moving_code_references.asm b/HackSources/Ghosts/export_moving_code_references.asm index 466e7c1aa..30e144b92 100644 --- a/HackSources/Ghosts/export_moving_code_references.asm +++ b/HackSources/Ghosts/export_moving_code_references.asm @@ -1,4 +1,4 @@ -.create "./build/DynamicOffsts.bin", 0x00000000 +.create "./build/DynamicOffsets.bin", 0x00000000 .word orga(FirstAnimationBufferAddrHi_LUI_1) .word orga(GhostBaseHi_LUI_PLUS_1) .word orga(GhostBaseHi_LUI_1) From 66a4d68202be838136cdace9314a35baa179295e Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Sat, 1 Aug 2026 10:28:32 +0200 Subject: [PATCH 14/31] stabilize core loop timing --- STROOP.Core/CoreLoop.cs | 66 +++++++++++++++++++++++----------- STROOP.Win32/NativeMethods.txt | 3 ++ 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/STROOP.Core/CoreLoop.cs b/STROOP.Core/CoreLoop.cs index 993babbb3..adc7e5915 100644 --- a/STROOP.Core/CoreLoop.cs +++ b/STROOP.Core/CoreLoop.cs @@ -1,23 +1,36 @@ using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.Win32; namespace STROOP.Core; public class CoreLoop { - private List _fpsTimes = new List(); + private Queue _frameTimes = new Queue(); private byte[] _ram; private object _mStreamProcess = new object(); - public double FpsInPractice => _fpsTimes.Count == 0 ? 0 : 1 / _fpsTimes.Average(); - public double lastFrameTime => _fpsTimes.Count == 0 ? double.NaN : _fpsTimes.Last(); + public double FpsInPractice => _frameTimes.Count == 0 ? 0 : Stopwatch.Frequency / _frameTimes.Average(); + public double lastFrameTime => _frameTimes.Count == 0 ? double.NaN : _frameTimes.Last() / (double)Stopwatch.Frequency; - public void Run(CancellationToken cancellationToken, Action handleEvents, Func getTargetedFps) + public void Run(CancellationToken cancellationToken, Action handleEvents, Func getTargetedRefreshRate) { - Stopwatch frameStopwatch = Stopwatch.StartNew(); + using var _ = new HighResTimer(1); // request ~1ms resolution + + // since computation of the time to wait for takes time itself, compensate with a few ticks + const int BUFFER_TICKS = 1000; + + long ticksPerTwoMs = 2 * Stopwatch.Frequency / 1000; + + Stopwatch frameStopwatch = new Stopwatch(); + Queue extraTime = new Queue(); + extraTime.Enqueue(0); while (!cancellationToken.IsCancellationRequested) { - double timeToWait; + long ticksPerFrame = (long)(Stopwatch.Frequency * getTargetedRefreshRate()); + + frameStopwatch.Restart(); lock (_mStreamProcess) { ProcessStream.Instance.RefreshRam(); @@ -26,23 +39,36 @@ public void Run(CancellationToken cancellationToken, Action handleEvents, Func= 10) - _fpsTimes.RemoveAt(0); - _fpsTimes.Add(timePassed + timeToWait); - - frameStopwatch.Restart(); + while (_frameTimes.Count() >= 10) + _frameTimes.Dequeue(); + _frameTimes.Enqueue(frameStopwatch.ElapsedTicks); - if (timeToWait > 0) - Thread.Sleep(new TimeSpan((long)(timeToWait * 10000000))); - else - Thread.Yield(); + while (extraTime.Count() >= 10) + extraTime.Dequeue(); + extraTime.Enqueue(frameStopwatch.ElapsedTicks - frameTicks + BUFFER_TICKS); } } } +file class HighResTimer : IDisposable +{ + readonly uint _period; + + public HighResTimer(uint periodMs) + { + _period = periodMs; + PInvoke.timeBeginPeriod(_period); + } + + public void Dispose() + { + PInvoke.timeEndPeriod(_period); + } +} diff --git a/STROOP.Win32/NativeMethods.txt b/STROOP.Win32/NativeMethods.txt index 7087d42e1..2428bfdb1 100644 --- a/STROOP.Win32/NativeMethods.txt +++ b/STROOP.Win32/NativeMethods.txt @@ -24,3 +24,6 @@ SendMessage GetAsyncKeyState GetSystemMetrics SYSTEM_METRICS_INDEX + +timeBeginPeriod +timeEndPeriod From ff5b0453b68a17453848f3943aa85a91019cebfb Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:32:02 +0200 Subject: [PATCH 15/31] fix 2D Distance crash --- STROOP/Utilities/VariableSelectionUtilities.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/STROOP/Utilities/VariableSelectionUtilities.cs b/STROOP/Utilities/VariableSelectionUtilities.cs index da7f48168..add91f6b2 100644 --- a/STROOP/Utilities/VariableSelectionUtilities.cs +++ b/STROOP/Utilities/VariableSelectionUtilities.cs @@ -241,8 +241,8 @@ void createDistanceMathOperationVariable(bool use3D) { var x1 = values[0]; var y1 = values[1]; - var x2 = values[3]; - var y2 = values[4]; + var x2 = values[2]; + var y2 = values[3]; var min = values.Min(x => x.Length); var result = new List(min); for (int i = 0; i < min; i++) From 8798b8809a0580ba3409dfd37c10fa75e274a9a0 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Mon, 31 Aug 2026 21:55:39 +0200 Subject: [PATCH 16/31] render all tape measure diffs in all view modes --- .../MapTab/MapObjects/MapTapeMeasureObject.cs | 44 +++++++++++++------ STROOP/Tabs/MapTab/Renderers/TextRenderer.cs | 4 +- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs index 1c54a087a..492b7255a 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs @@ -16,7 +16,7 @@ public interface IPositionCalculatorProvider } [ObjectDescription("Tape Measure", "Custom")] - public class MapTapeMeasureObject : MapLineObject + public class MapTapeMeasureObject : MapObject { class TapeHoverData : IHoverData { @@ -104,6 +104,19 @@ public void AddContextMenuItems(MapTab tab, ContextMenuStrip menu) } } + // display options for the following in order: x, y, xy, z, xz, yz, xyz + static (Color color, bool[] farAlignment)[] textDisplay = + [ + (Color.FromArgb(255, 100, 100), [false, false, false]), + (Color.LightGreen, [false, true, false]), + (Color.Yellow, [true, false, false]), + (Color.LightBlue, [false, false, false]), + (Color.Pink, [false, true, false]), + (Color.Cyan, [true, true, false]), + (Color.LightGray, [true, true, false]), + ]; + + Vector3 a, b; Func aProvider, bProvider; @@ -136,13 +149,11 @@ protected override ContextMenuStrip GetContextMenuStrip(MapTracker targetTracker public override string GetName() => "Tape Measure"; - protected override List GetVertices(MapGraphics graphics) => - new List(new[] { aProvider?.Invoke() ?? a, bProvider?.Invoke() ?? b }); - - protected override void Draw3D(MapGraphics graphics) + protected override void DrawTopDown(MapGraphics graphics) { graphics.drawLayers[(int)MapGraphics.DrawLayers.FillBuffers].Add(() => { + var verticalTextAlignmentIndex = (int)graphics.viewMode; Vector3 _a = aProvider?.Invoke() ?? a; Vector3 _b = bProvider?.Invoke() ?? b; List ends = new List(); @@ -158,20 +169,19 @@ protected override void Draw3D(MapGraphics graphics) new Vector3(1, float.NaN, float.NaN), new Vector3(1, float.NaN, 1), }); - Color[] colors = new[] { Color.FromArgb(255, 100, 100), Color.LightGreen, Color.Yellow, Color.LightBlue, Color.Pink, Color.Cyan, Color.LightGray }; foreach (var end in ends) { string nameString = ""; var p1 = _a; var p2 = _b; - int colorIndex = 0; + int displayIndex = 0; if (!float.IsNaN(end.X)) p1.X = p2.X = end.X == 0 ? p1.X : p2.X; else { nameString += "x"; - colorIndex |= 1; + displayIndex |= 1; } if (!float.IsNaN(end.Y)) @@ -179,7 +189,7 @@ protected override void Draw3D(MapGraphics graphics) else { nameString += "y"; - colorIndex |= 2; + displayIndex |= 2; } if (!float.IsNaN(end.Z)) @@ -187,16 +197,24 @@ protected override void Draw3D(MapGraphics graphics) else { nameString += "z"; - colorIndex |= 4; + displayIndex |= 4; } - var lineColor = colors[colorIndex - 1]; - graphics.lineRenderer.Add(p1, p2, OpenTKUtilities.ColorToVec4(lineColor), OutlineWidth); - graphics.textRenderer.AddText($"{nameString}: {(p1 - p2).Length}", (p1 + p2) * 0.5f, lineColor, StringAlignment.Far); + var t = textDisplay[displayIndex - 1]; + graphics.lineRenderer.Add(p1, p2, OpenTKUtilities.ColorToVec4(t.color), OutlineWidth); + graphics.textRenderer.AddText( + $"{nameString}: {(p1 - p2).Length}", + (p1 + p2) * 0.5f, t.color, + StringAlignment.Far, + lineAlignment: t.farAlignment[verticalTextAlignmentIndex] ? StringAlignment.Far : StringAlignment.Near + ); } }); } + protected override void DrawOrthogonal(MapGraphics graphics) + => DrawTopDown(graphics); + public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 position) { float magicConst = 15; diff --git a/STROOP/Tabs/MapTab/Renderers/TextRenderer.cs b/STROOP/Tabs/MapTab/Renderers/TextRenderer.cs index b278fd38e..75265724d 100644 --- a/STROOP/Tabs/MapTab/Renderers/TextRenderer.cs +++ b/STROOP/Tabs/MapTab/Renderers/TextRenderer.cs @@ -127,7 +127,7 @@ public override void SetDrawCalls(MapGraphics graphics) }); } - public void AddText(string text, Vector3 position, Color color, StringAlignment alignment, Font font = null) + public void AddText(string text, Vector3 position, Color color, StringAlignment alignment, Font font = null, StringAlignment lineAlignment = StringAlignment.Near) { var graphics = AccessScope.content.graphics; var ssp = Vector4.TransformRow(new Vector4(position.X, position.Y, position.Z, 1.0f), graphics.ViewMatrix); @@ -142,7 +142,7 @@ public void AddText(string text, Vector3 position, Color color, StringAlignment value = text, brush = GetBrush(color), font = font ?? Fonts.medium, - format = new StringFormat() { Alignment = alignment }, + format = new StringFormat() { Alignment = alignment, LineAlignment = lineAlignment }, position = new PointF((screenspacePoint.X + 1) * targetImage.Width / 2f, (-screenspacePoint.Y + 1) * targetImage.Height / 2f), }); } From 414aff0c1a6075a8fe96f50bdbac04a45674722a Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:21:30 +0200 Subject: [PATCH 17/31] add UI toggles for shown tape measure dimensions --- .../MapTab/MapObjects/MapTapeMeasureObject.cs | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs index 492b7255a..ffcd176af 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs @@ -40,13 +40,9 @@ public void DragTo(Vector3 newPosition, bool setY) parent.targetTracker.textBoxSize.Text = (parent.Size = (parent.a - parent.b).Length).ToString(); } - public void SetLookAt(Vector3 lookAt) - { - } + public void SetLookAt(Vector3 lookAt) { } - public void LeftClick(Vector3 position) - { - } + public void LeftClick(Vector3 position) { } public void RightClick(Vector3 position) { @@ -116,6 +112,7 @@ public void AddContextMenuItems(MapTab tab, ContextMenuStrip menu) (Color.LightGray, [true, true, false]), ]; + ToolStripMenuItem[] itemsShownMeasurements = new ToolStripMenuItem[8]; Vector3 a, b; @@ -131,6 +128,14 @@ public MapTapeMeasureObject() a = new Vector3(currentMapTab.graphics.view.position.X - 50, 0, currentMapTab.graphics.view.position.Z); b = new Vector3(currentMapTab.graphics.view.position.X + 50, 0, currentMapTab.graphics.view.position.Z); hoverData = new TapeHoverData(this); + for (int mask = 1; mask <= 8; mask++) + { + var item = new ToolStripMenuItem($"Show {((mask & 1) != 0 ? "x" : "")}{((mask & 2) != 0 ? "y" : "")}{((mask & 4) != 0 ? "z" : "")}"); + item.Click += (_, __) => item.Checked = !item.Checked; + itemsShownMeasurements[mask - 1] = item; + } + foreach (int index in new [] { 0, 1, 3, 4 }) + itemsShownMeasurements[index].Checked = true; } MapTracker targetTracker; @@ -142,6 +147,8 @@ protected override ContextMenuStrip GetContextMenuStrip(MapTracker targetTracker var _contextMenuStrip = base.GetContextMenuStrip(targetTracker); _contextMenuStrip.Items.Cast().FirstOrDefault(x => x.Text == "Enable dragging")?.PerformClick(); + _contextMenuStrip.Items.Add(new ToolStripSeparator()); + _contextMenuStrip.Items.AddRange(itemsShownMeasurements); return _contextMenuStrip; } @@ -200,14 +207,17 @@ protected override void DrawTopDown(MapGraphics graphics) displayIndex |= 4; } - var t = textDisplay[displayIndex - 1]; - graphics.lineRenderer.Add(p1, p2, OpenTKUtilities.ColorToVec4(t.color), OutlineWidth); - graphics.textRenderer.AddText( - $"{nameString}: {(p1 - p2).Length}", - (p1 + p2) * 0.5f, t.color, - StringAlignment.Far, - lineAlignment: t.farAlignment[verticalTextAlignmentIndex] ? StringAlignment.Far : StringAlignment.Near - ); + if (itemsShownMeasurements[--displayIndex].Checked) + { + var t = textDisplay[displayIndex]; + graphics.lineRenderer.Add(p1, p2, OpenTKUtilities.ColorToVec4(t.color), OutlineWidth); + graphics.textRenderer.AddText( + $"{nameString}: {(p1 - p2).Length}", + (p1 + p2) * 0.5f, t.color, + StringAlignment.Far, + lineAlignment: t.farAlignment[verticalTextAlignmentIndex] ? StringAlignment.Far : StringAlignment.Near + ); + } } }); } From faaee66787e41807a43db912d048b9845f7e8c8f Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:55:42 +0200 Subject: [PATCH 18/31] Decouple map tab view modes --- STROOP/Tabs/MapTab/DataUtil/HoverDatas.cs | 26 +-- .../MapTab/MapGraphics.LegacyControlScheme.cs | 34 +-- STROOP/Tabs/MapTab/MapGraphics.cs | 219 ++++++++++-------- .../MapObjects/MapBruteforceTriangles.cs | 2 +- .../Tabs/MapTab/MapObjects/MapCircleObject.cs | 2 +- .../MapTab/MapObjects/MapCustomCameraPath.cs | 14 +- .../MapTab/MapObjects/MapCustomIconPoints.cs | 6 +- .../MapTab/MapObjects/MapCylinderObject.cs | 2 +- .../Tabs/MapTab/MapObjects/MapGhostObject.cs | 2 +- .../MapTab/MapObjects/MapGridlinesObject.cs | 6 +- .../MapObjects/MapHorizontalTriangleObject.cs | 4 +- .../MapTab/MapObjects/MapIconPointObject.cs | 10 +- .../MapTab/MapObjects/MapIwerlipsesObject.cs | 2 +- .../MapObjects/MapMultipleObjectsObject.cs | 2 +- .../Tabs/MapTab/MapObjects/MapNearbyUnits.cs | 6 +- .../MapObjects/MapNextPositionsObject.cs | 2 +- STROOP/Tabs/MapTab/MapObjects/MapObject.cs | 14 +- .../MapObjects/MapPreviousPositionsObject.cs | 2 +- .../Tabs/MapTab/MapObjects/MapQuadObject.cs | 2 +- .../MapTab/MapObjects/MapTapeMeasureObject.cs | 10 +- .../MapTab/MapObjects/MapTriangleObject.cs | 8 +- .../Tabs/MapTab/MapObjects/MapWallObject.cs | 2 +- STROOP/Tabs/MapTab/MapPopout.cs | 2 +- STROOP/Tabs/MapTab/MapTab.cs | 59 +++-- STROOP/Tabs/MapTab/MapView.cs | 50 ---- .../Tabs/MapTab/Renderers/GeometryRenderer.cs | 2 +- .../MapTab/Renderers/TransparencyRenderer.cs | 2 +- .../Tabs/MapTab/Renderers/TriangleRenderer.cs | 2 +- STROOP/Tabs/MapTab/Views/View3D.cs | 32 +++ STROOP/Tabs/MapTab/Views/ViewBase.cs | 27 +++ STROOP/Tabs/MapTab/Views/ViewOrthogonal.cs | 15 ++ STROOP/Tabs/MapTab/Views/ViewTopDown.cs | 8 + 32 files changed, 309 insertions(+), 267 deletions(-) delete mode 100644 STROOP/Tabs/MapTab/MapView.cs create mode 100644 STROOP/Tabs/MapTab/Views/View3D.cs create mode 100644 STROOP/Tabs/MapTab/Views/ViewBase.cs create mode 100644 STROOP/Tabs/MapTab/Views/ViewOrthogonal.cs create mode 100644 STROOP/Tabs/MapTab/Views/ViewTopDown.cs diff --git a/STROOP/Tabs/MapTab/DataUtil/HoverDatas.cs b/STROOP/Tabs/MapTab/DataUtil/HoverDatas.cs index e88f5c3ef..a911bbc6e 100644 --- a/STROOP/Tabs/MapTab/DataUtil/HoverDatas.cs +++ b/STROOP/Tabs/MapTab/DataUtil/HoverDatas.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using OpenTK.Mathematics; using STROOP.Core; +using STROOP.Tabs.MapTab.Views; namespace STROOP.Tabs.MapTab.MapObjects { @@ -22,13 +23,9 @@ public PointHoverData(MapObject parent) this.parent = parent; } - public virtual void LeftClick(Vector3 position) - { - } + public virtual void LeftClick(Vector3 position) { } - public virtual void RightClick(Vector3 position) - { - } + public virtual void RightClick(Vector3 position) { } public virtual DragMask CanDrag() => parent.dragMask; @@ -45,11 +42,8 @@ public void DragTo(Vector3 newPosition, bool setY) SetPosition(newPosition); } - public virtual void Pivot(MapTab tab) - { - tab.graphics.view.camera3DMode = MapView.Camera3DMode.FocusOnPositionAngle; - tab.graphics.view.focusPositionAngle = PositionAngle.Custom(GetPosition(), 0); - } + protected virtual void Pivot(MapTab tab) + => (tab.graphics.currentView as PivotingView)?.Pivot(PositionAngle.Custom(GetPosition(), 0)); public virtual void AddContextMenuItems(MapTab tab, ContextMenuStrip menu) { @@ -114,7 +108,7 @@ public virtual void AddContextMenuItems(MapTab tab, ContextMenuStrip menu) ); myItem.DropDownItems.Add(makeReferencePointItem); - if (tab.graphics.view.mode != MapView.ViewMode.TopDown) + if (tab.graphics.viewMode != MapGraphics.ViewMode.TopDown) { var pivotItem = new ToolStripMenuItem("Make Pivot Point"); pivotItem.Click += (_, __) => Pivot(tab); @@ -129,9 +123,7 @@ protected class MapObjectHoverData : PointHoverData, IPositionCalculatorProvider { public PositionAngle currentPositionAngle; - public MapObjectHoverData(MapObject parent) : base(parent) - { - } + public MapObjectHoverData(MapObject parent) : base(parent) { } protected override void SetPosition(Vector3 position) { @@ -144,11 +136,11 @@ protected override void SetPosition(Vector3 position) protected override Vector3 GetPosition() => currentPositionAngle?.position ?? Vector3.Zero; - public override void Pivot(MapTab tab) + protected override void Pivot(MapTab tab) { if (currentPositionAngle == null) return; - tab.graphics.view.Pivot(currentPositionAngle); + (tab.graphics.currentView as PivotingView)?.Pivot(currentPositionAngle); } public override string ToString() => currentPositionAngle.ToString(); diff --git a/STROOP/Tabs/MapTab/MapGraphics.LegacyControlScheme.cs b/STROOP/Tabs/MapTab/MapGraphics.LegacyControlScheme.cs index 24b390123..b2671bc4d 100644 --- a/STROOP/Tabs/MapTab/MapGraphics.LegacyControlScheme.cs +++ b/STROOP/Tabs/MapTab/MapGraphics.LegacyControlScheme.cs @@ -77,7 +77,7 @@ private void UpdateCenter() if (!isMainMap) return; - if (view.mode == MapView.ViewMode.ThreeDimensional) + if (viewMode == ViewMode.ThreeDimensional) return; if (mapTab.radioButtonMapControllersCenterBestFit.Checked) @@ -93,14 +93,14 @@ private void UpdateCenter() { case MapCenter.BestFit: RectangleF rectangle = MapViewScaleWasCourseDefault ? mapTab.GetMapLayout().Coordinates : MAX_COURSE_SIZE; - view.position.X = rectangle.X + rectangle.Width / 2; - view.position.Z = rectangle.Y + rectangle.Height / 2; + currentView.position.X = rectangle.X + rectangle.Width / 2; + currentView.position.Z = rectangle.Y + rectangle.Height / 2; break; case MapCenter.Origin: - view.position = new Vector3(0.5f); + currentView.position = new Vector3(0.5f); break; case MapCenter.Mario: - view.position = new Vector3(Config.Stream.GetSingle(MarioConfig.StructAddress + MarioConfig.XOffset), + currentView.position = new Vector3(Config.Stream.GetSingle(MarioConfig.StructAddress + MarioConfig.XOffset), Config.Stream.GetSingle(MarioConfig.StructAddress + MarioConfig.YOffset), Config.Stream.GetSingle(MarioConfig.StructAddress + MarioConfig.ZOffset)); break; @@ -109,7 +109,7 @@ private void UpdateCenter() mapTab.textBoxMapControllersCenterCustom.LastSubmittedText); if (posAngle != null) { - view.position = posAngle.position; + currentView.position = posAngle.position; break; } @@ -117,28 +117,28 @@ private void UpdateCenter() mapTab.textBoxMapControllersCenterCustom.LastSubmittedText, replaceComma: false); if (stringValues.Count >= 3) { - view.position.X = ParsingUtilities.ParseFloatNullable(stringValues[0]) ?? 0; - view.position.Y = ParsingUtilities.ParseFloatNullable(stringValues[1]) ?? 0; - view.position.Z = ParsingUtilities.ParseFloatNullable(stringValues[2]) ?? 0; + currentView.position.X = ParsingUtilities.ParseFloatNullable(stringValues[0]) ?? 0; + currentView.position.Y = ParsingUtilities.ParseFloatNullable(stringValues[1]) ?? 0; + currentView.position.Z = ParsingUtilities.ParseFloatNullable(stringValues[2]) ?? 0; } else if (stringValues.Count >= 2) { - view.position.X = ParsingUtilities.ParseFloatNullable(stringValues[0]) ?? 0; - view.position.Z = ParsingUtilities.ParseFloatNullable(stringValues[1]) ?? 0; + currentView.position.X = ParsingUtilities.ParseFloatNullable(stringValues[0]) ?? 0; + currentView.position.Z = ParsingUtilities.ParseFloatNullable(stringValues[1]) ?? 0; } else if (stringValues.Count == 1) { - view.position = new Vector3(ParsingUtilities.ParseFloatNullable(stringValues[0]) ?? 0); + currentView.position = new Vector3(ParsingUtilities.ParseFloatNullable(stringValues[0]) ?? 0); } else - view.position = new Vector3(); + currentView.position = new Vector3(); break; } if (MapViewCenter != MapCenter.Custom) { - mapTab.textBoxMapControllersCenterCustom.SubmitTextLoosely($"{view.position.X}; {view.position.Y}; {view.position.Z}"); + mapTab.textBoxMapControllersCenterCustom.SubmitTextLoosely($"{currentView.position.X}; {currentView.position.Y}; {currentView.position.Z}"); } } @@ -243,9 +243,9 @@ public void ChangeCenter(int xSign, int zSign, object value) (float xOffsetRotated, float zOffsetRotated) = ((float, float))MoreMath.RotatePointAboutPointAnAngularDistance( xOffset, zOffset, 0, 0, MapViewAngleValue); float multiplier = MapViewCenterChangeByPixels ? 1 / MapViewScaleValue : 1; - float newCenterXValue = view.position.X + xOffsetRotated * multiplier; - float newCenterZValue = view.position.Z + zOffsetRotated * multiplier; - mapTab.textBoxMapControllersCenterCustom.SubmitText($"{newCenterXValue}; {view.position.Y}; {newCenterZValue}"); + float newCenterXValue = currentView.position.X + xOffsetRotated * multiplier; + float newCenterZValue = currentView.position.Z + zOffsetRotated * multiplier; + mapTab.textBoxMapControllersCenterCustom.SubmitText($"{newCenterXValue}; {currentView.position.Y}; {newCenterZValue}"); } public void ChangeAngle(int sign, object value) diff --git a/STROOP/Tabs/MapTab/MapGraphics.cs b/STROOP/Tabs/MapTab/MapGraphics.cs index 1a233767f..3046817ec 100644 --- a/STROOP/Tabs/MapTab/MapGraphics.cs +++ b/STROOP/Tabs/MapTab/MapGraphics.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using OpenTK; using OpenTK.Graphics.OpenGL; using System.Windows.Forms; using System.Drawing; @@ -12,6 +11,7 @@ using STROOP.Extensions; using STROOP.Structs; using STROOP.Structs.Configurations; +using STROOP.Tabs.MapTab.Views; using STROOP.Utilities; namespace STROOP.Tabs.MapTab @@ -31,6 +31,13 @@ public enum DrawLayers Overlay, } + public enum ViewMode + { + TopDown, + Orthogonal, + ThreeDimensional + } + static Vector3 ProjectOnLineSegment(Vector3 p, Vector3 A, Vector3 B) { Vector3 d = B - A; @@ -49,14 +56,6 @@ public bool HoverOrthogonal(Vector3 position, float radius) return (projectedPos.Xy - mousePosition2D).LengthSquared < (radius * radius); } - public bool Hover3D(Vector3 position, float radius) - { - var lineEnd = cursorOnMap - ? mapCursorPosition - : view.position + Vector3.Normalize(mapCursorPosition - view.position) * 10000; - return ((ProjectOnLineSegment(position, view.position, lineEnd) - position).Length < radius); - } - public readonly List[] drawLayers; public Renderers.RendererCollection rendererCollection { get; private set; } @@ -155,28 +154,42 @@ private enum MapAngle public readonly GLControl glControl; public readonly MapTab mapTab; - public readonly MapView view; + + public ViewMode viewMode = ViewMode.TopDown; + + public ViewBase currentView => viewMode switch + { + ViewMode.TopDown => viewTopDown, + ViewMode.Orthogonal => viewOrthogonal, + ViewMode.ThreeDimensional => view3D, + }; + + public readonly ViewTopDown viewTopDown = new(); + public readonly ViewOrthogonal viewOrthogonal = new(); + public readonly View3D view3D = new(); public float MapViewRadius => (float)MoreMath.GetHypotenuse(glControl.Width / 2, glControl.Height / 2) / MapViewScaleValue; + public bool drawCylinderOutlines = false; + public float MapViewXMin { - get => view.position.X - MapViewRadius * glControl.AspectRatio; + get => currentView.position.X - MapViewRadius * glControl.AspectRatio; } public float MapViewXMax { - get => view.position.X + MapViewRadius * glControl.AspectRatio; + get => currentView.position.X + MapViewRadius * glControl.AspectRatio; } public float MapViewZMin { - get => view.position.Z - MapViewRadius; + get => currentView.position.Z - MapViewRadius; } public float MapViewZMax { - get => view.position.Z + MapViewRadius; + get => currentView.position.Z + MapViewRadius; } public static readonly int MAX_COURSE_SIZE_X_MIN = -8191; @@ -204,7 +217,7 @@ public Vector2 mousePosition2D public bool cursorOnMap = false; Vector3 normalAtCursor; public float cursorViewPlaneDist = 1000; - public bool fixCursorPlane => view.mode == MapView.ViewMode.ThreeDimensional && keyboardControls.IsShiftDown(); + public bool fixCursorPlane => viewMode == ViewMode.ThreeDimensional && keyboardControls.IsShiftDown(); public float nearClip { get; private set; } public float farClip { get; private set; } @@ -237,7 +250,6 @@ public MapGraphics(MapTab mapTab, GLControl glControl, Func ge glControl.MouseDown += (_, _) => glControl.Focus(); keyboardControls = new(glControl); - view = new MapView(); drawLayers = new List[Enum.GetNames(typeof(DrawLayers)).Length]; for (int i = 0; i < drawLayers.Length; i++) drawLayers[i] = new List(); @@ -393,6 +405,7 @@ public void CleanUp() GL.DeleteFramebuffer(presentFrameBuffer); getContext().MakeCurrent(); } + transparencyRenderer.CleanUp(); DeleteMainSurfaces(); } @@ -431,12 +444,12 @@ private void OnPaint() if (levelTrianglesFor3DMap == null || mapTab.NeedsGeometryRefresh()) levelTrianglesFor3DMap = TriangleUtilities.GetLevelTriangles(); - if (view.mode == MapView.ViewMode.ThreeDimensional) + if (currentView == view3D) { GL.ClearDepth(1); GL.Clear(ClearBufferMask.DepthBufferBit); - if (view.display3DLevelGeometry) + if (view3D.display3DLevelGeometry) drawLayers[(int)DrawLayers.FillBuffers].Insert(0, () => { foreach (var t in levelTrianglesFor3DMap) @@ -490,63 +503,63 @@ private void UpdateMapView() ); - float zFar = view.mode == MapView.ViewMode.TopDown || float.IsNaN(view.orthoRelativeFarPlane) ? 100000 : view.orthoRelativeFarPlane; - float zNear = view.mode == MapView.ViewMode.TopDown || float.IsNaN(view.orthoRelativeNearPlane) ? -100000 : view.orthoRelativeNearPlane; + float zFar = viewMode == ViewMode.TopDown || float.IsNaN(viewOrthogonal.orthoRelativeFarPlane) ? 100000 : viewOrthogonal.orthoRelativeFarPlane; + float zNear = viewMode == ViewMode.TopDown || float.IsNaN(viewOrthogonal.orthoRelativeNearPlane) ? -100000 : viewOrthogonal.orthoRelativeNearPlane; zFar = Math.Max(zNear + 0.0001f, zFar); Matrix4 othoDepth = Matrix4.CreateOrthographic(2, 2, zNear, zFar); - switch (view.mode) + switch (viewMode) { - case MapView.ViewMode.TopDown: + case ViewMode.TopDown: BillboardMatrix = swapYZ; - ViewMatrix = Matrix4.CreateTranslation(new Vector3(-view.position.X, 0, -view.position.Z)) + ViewMatrix = Matrix4.CreateTranslation(new Vector3(-currentView.position.X, 0, -currentView.position.Z)) * swapYZ * Matrix4.CreateRotationZ((float)(Math.PI + MoreMath.AngleUnitsToRadians(MapViewAngleValue))) * Matrix4.CreateScale(scale / glControl.AspectRatio, -scale, 1) * othoDepth; break; - case MapView.ViewMode.Orthogonal: + case ViewMode.Orthogonal: float cool = (float)MoreMath.AngleUnitsToRadians(MapViewAngleValue); BillboardMatrix = Matrix4.CreateRotationY(cool); - float d = -Vector3.Dot(-BillboardMatrix.Row2.Xyz, view.focusPositionAngle.position); + float d = -Vector3.Dot(-BillboardMatrix.Row2.Xyz, viewOrthogonal.focusPositionAngle.position); orthographicZero = (-BillboardMatrix.Row2.Xyz, d); - worldspaceNearPlane = (-BillboardMatrix.Row2.Xyz, d + view.orthoRelativeNearPlane); - worldspaceFarPlane = (BillboardMatrix.Row2.Xyz, d - view.orthoRelativeFarPlane); + worldspaceNearPlane = (-BillboardMatrix.Row2.Xyz, d + viewOrthogonal.orthoRelativeNearPlane); + worldspaceFarPlane = (BillboardMatrix.Row2.Xyz, d - viewOrthogonal.orthoRelativeFarPlane); ViewMatrix = - Matrix4.CreateTranslation(-view.focusPositionAngle.position) + Matrix4.CreateTranslation(-viewOrthogonal.focusPositionAngle.position) * Matrix4.CreateRotationY(-cool) - * Matrix4.CreateTranslation(-view.orthoOffset.X, -view.orthoOffset.Y, 0) + * Matrix4.CreateTranslation(-viewOrthogonal.orthoOffset.X, -viewOrthogonal.orthoOffset.Y, 0) * Matrix4.CreateScale(scale / glControl.AspectRatio, scale, 1) * othoDepth; break; - case MapView.ViewMode.ThreeDimensional: - Vector3 target = view.focusPositionAngle.position; - Vector3 viewDirection = view.ComputeViewDirection(); + case ViewMode.ThreeDimensional: + Vector3 target = view3D.focusPositionAngle.position; + Vector3 viewDirection = currentView.ComputeViewDirection(); if (float.IsNaN(viewDirection.X)) viewDirection = new Vector3(0, 0, 1); - switch (view.camera3DMode) + switch (view3D.camera3DMode) { - case MapView.Camera3DMode.InGame: - view.position = new Vector3(Models.DataModels.Camera.X, Models.DataModels.Camera.Y, Models.DataModels.Camera.Z); - view.yaw = (float)MoreMath.AngleUnitsToRadians(Models.DataModels.Camera.FacingYaw); - view.pitch = (float)MoreMath.AngleUnitsToRadians(-Models.DataModels.Camera.FacingPitch); - target = view.position + viewDirection; + case View3D.Camera3DMode.InGame: + view3D.position = new Vector3(Models.DataModels.Camera.X, Models.DataModels.Camera.Y, Models.DataModels.Camera.Z); + view3D.yaw = (float)MoreMath.AngleUnitsToRadians(Models.DataModels.Camera.FacingYaw); + view3D.pitch = (float)MoreMath.AngleUnitsToRadians(-Models.DataModels.Camera.FacingPitch); + target = currentView.position + viewDirection; break; - case MapView.Camera3DMode.FocusOnPositionAngle: - view.position = target - viewDirection / (float)Math.Exp(-view.camera3DDistanceController * 0.1f); + case View3D.Camera3DMode.FocusOnPositionAngle: + currentView.position = target - viewDirection / (float)Math.Exp(-view3D.camera3DDistanceController * 0.1f); break; - case MapView.Camera3DMode.Free: - target = view.position + viewDirection * (mapCursorPosition - view.position).Length; + case View3D.Camera3DMode.Free: + target = currentView.position + viewDirection * (mapCursorPosition - currentView.position).Length; break; } - nearClip = Math.Max(1, Math.Min(50, (target - view.position).Length / 100)); + nearClip = Math.Max(1, Math.Min(50, (target - currentView.position).Length / 100)); farClip = nearClip * 5000; - ViewMatrix = Matrix4.LookAt(view.position, target, new Vector3(0, 1, 0)); + ViewMatrix = Matrix4.LookAt(currentView.position, target, new Vector3(0, 1, 0)); var mat = Matrix4.Invert(ViewMatrix); mat.Row3 = new Vector4(0, 0, 0, 1); BillboardMatrix = mat; @@ -567,7 +580,7 @@ bool FindClosestIntersection(Vector3 rayOrigin, Vector3 rayDirection, out Vector float closestDistance = float.PositiveInfinity, newDistance; foreach (var t in levelTrianglesFor3DMap) if (t.Intersect(rayOrigin, viewDirection, out Vector3 newIntersection, out Vector3 newNormal) - && (newDistance = (newIntersection - view.position).LengthSquared) < closestDistance) + && (newDistance = (newIntersection - currentView.position).LengthSquared) < closestDistance) { closestDistance = newDistance; intersection = newIntersection; @@ -579,7 +592,7 @@ bool FindClosestIntersection(Vector3 rayOrigin, Vector3 rayDirection, out Vector public void UpdateCursor() { - if (view.mode != MapView.ViewMode.ThreeDimensional) + if (viewMode != ViewMode.ThreeDimensional) { var e = glControl.PointToClient(Cursor.Position); mapCursorPosition = Vector3.TransformPosition(new Vector3(2.0f * e.X / glControl.Width - 1, 1 - 2.0f * e.Y / glControl.Height, 0), Matrix4.Invert(ViewMatrix)); @@ -596,18 +609,26 @@ public void UpdateCursor() if (float.IsNaN(dir.X)) dir = new Vector3(0, 0, 1); if (!fixCursorPlane - && (cursorOnMap = FindClosestIntersection(view.position + dir, dir, out Vector3 closestIntersection, out hoverTriangle))) + && (cursorOnMap = FindClosestIntersection(currentView.position + dir, dir, out Vector3 closestIntersection, out hoverTriangle))) { normalAtCursor = new Vector3(hoverTriangle.NormX, hoverTriangle.NormY, hoverTriangle.NormZ); mapCursorPosition = closestIntersection; - cursorViewPlaneDist = Vector3.Dot(mapCursorPosition - view.position, -BillboardMatrix.Row2.Xyz); + cursorViewPlaneDist = Vector3.Dot(mapCursorPosition - currentView.position, -BillboardMatrix.Row2.Xyz); } else - mapCursorPosition = view.position + dir * cursorViewPlaneDist; + mapCursorPosition = currentView.position + dir * cursorViewPlaneDist; } } } + public bool Hover3D(Vector3 position, float radius) + { + var lineEnd = cursorOnMap + ? mapCursorPosition + : currentView.position + Vector3.Normalize(mapCursorPosition - currentView.position) * 10000; + return ((ProjectOnLineSegment(position, currentView.position, lineEnd) - position).Length < radius); + } + private int _dragStartMouseX = 0; private int _dragStartMouseY = 0; private Vector3 _translateStartCenter = new Vector3(0); @@ -627,15 +648,15 @@ private void OnMouseDown(object sender, MouseEventArgs e) _rotateStartAngle = MapViewAngleValue; _dragStartMouseX = e.X; _dragStartMouseY = e.Y; - _translateStartCenter = view.position; - _translateStartOrthoOffset = view.orthoOffset; - _dragStartYaw = view.yaw; - _dragStartPitch = view.pitch; + _translateStartCenter = currentView.position; + _translateStartOrthoOffset = viewOrthogonal.orthoOffset; + _dragStartYaw = currentView.yaw; + _dragStartPitch = currentView.pitch; _rotatePivot = mapCursorPosition; - Matrix4 viewOrientation = view.ComputeViewOrientation(); - _rotateDiff = Vector3.TransformPosition(view.position - mapCursorPosition, Matrix4.Invert(viewOrientation)); + Matrix4 viewOrientation = currentView.ComputeViewOrientation(); + _rotateDiff = Vector3.TransformPosition(currentView.position - mapCursorPosition, Matrix4.Invert(viewOrientation)); - view.movementSpeed = (mapCursorPosition - view.position).Length * 0.5f; + currentView.movementSpeed = (mapCursorPosition - currentView.position).Length * 0.5f; break; case MouseButtons.Right: mouseDown[1] = true; @@ -644,8 +665,8 @@ private void OnMouseDown(object sender, MouseEventArgs e) mouseDown[2] = true; _dragStartMouseX = e.X; _dragStartMouseY = e.Y; - _dragStartYaw = view.yaw; - _dragStartPitch = view.pitch; + _dragStartYaw = currentView.yaw; + _dragStartPitch = currentView.pitch; break; } @@ -681,7 +702,7 @@ private void OnMouseMove(object sender, MouseEventArgs e) for (int i = 0; i < mouseDown.Length; i++) mouseDown[i] &= MouseUtility.IsMouseDown(i); - if (view.mode != MapView.ViewMode.ThreeDimensional) + if (viewMode != ViewMode.ThreeDimensional) mapCursorPosition = Vector3.TransformPosition(new Vector3(2.0f * e.X / glControl.Width - 1, 1 - 2.0f * e.Y / glControl.Height, 0), Matrix4.Invert(ViewMatrix)); using (new AccessScope(mapTab)) @@ -699,7 +720,7 @@ private void OnMouseMove(object sender, MouseEventArgs e) } else if (hover.CanDrag() != DragMask.None) { - hover.DragTo(mapCursorPosition, view.mode != MapView.ViewMode.TopDown); + hover.DragTo(mapCursorPosition, viewMode != ViewMode.TopDown); return; } } @@ -707,24 +728,24 @@ private void OnMouseMove(object sender, MouseEventArgs e) if (mouseDown[2]) { - if (view.mode == MapView.ViewMode.ThreeDimensional) + if (viewMode == ViewMode.ThreeDimensional) { int pixelDiffX = e.X - _dragStartMouseX; int pixelDiffY = e.Y - _dragStartMouseY; - float mul = 10.0f / (float)Math.Log((view.position - _rotatePivot).Length); + float mul = 10.0f / (float)Math.Log((currentView.position - _rotatePivot).Length); float diffX = pixelDiffX / (float)glControl.Width * 2 * mul; float diffY = pixelDiffY / (float)glControl.Height * 2 * mul; if (float.IsNaN(diffX) || float.IsNaN(diffY)) throw null; - if (view.camera3DMode == MapView.Camera3DMode.Free) + if (view3D.camera3DMode == View3D.Camera3DMode.Free) { - view.yaw = _dragStartYaw - diffX; - view.pitch = Math.Max(-(float)Math.PI * 0.499f, Math.Min((float)Math.PI * 0.499f, _dragStartPitch + diffY)); + view3D.yaw = _dragStartYaw - diffX; + view3D.pitch = Math.Max(-(float)Math.PI * 0.499f, Math.Min((float)Math.PI * 0.499f, _dragStartPitch + diffY)); } - else if (view.camera3DMode == MapView.Camera3DMode.FocusOnPositionAngle) + else if (view3D.camera3DMode == View3D.Camera3DMode.FocusOnPositionAngle) { - view.yaw = _dragStartYaw + diffX; - view.pitch = Math.Max(-(float)Math.PI * 0.499f, Math.Min((float)Math.PI * 0.499f, _dragStartPitch - diffY)); + view3D.yaw = _dragStartYaw + diffX; + view3D.pitch = Math.Max(-(float)Math.PI * 0.499f, Math.Min((float)Math.PI * 0.499f, _dragStartPitch - diffY)); } } } @@ -739,38 +760,38 @@ private void OnMouseMove(object sender, MouseEventArgs e) pixelDiffY = mapTab.MaybeReverse(pixelDiffY); float unitDiffX = pixelDiffX / MapViewScaleValue; float unitDiffY = pixelDiffY / MapViewScaleValue; - switch (view.mode) + switch (viewMode) { - case MapView.ViewMode.TopDown: + case ViewMode.TopDown: { (float rotatedX, float rotatedY) = ((float, float)) MoreMath.RotatePointAboutPointAnAngularDistance( unitDiffX, unitDiffY, 0, 0, MapViewAngleValue); - view.position.X = _translateStartCenter.X - rotatedX; - view.position.Z = _translateStartCenter.Z - rotatedY; - SetCustomCenter($"{view.position.X}; {view.position.Y}; {view.position.Z}"); + currentView.position.X = _translateStartCenter.X - rotatedX; + currentView.position.Z = _translateStartCenter.Z - rotatedY; + SetCustomCenter($"{currentView.position.X}; {currentView.position.Y}; {currentView.position.Z}"); break; } - case MapView.ViewMode.Orthogonal: + case ViewMode.Orthogonal: { - view.orthoOffset = _translateStartOrthoOffset + new Vector2(-unitDiffX, unitDiffY); + viewOrthogonal.orthoOffset = _translateStartOrthoOffset + new Vector2(-unitDiffX, unitDiffY); break; } - case MapView.ViewMode.ThreeDimensional: - if (view.camera3DMode != MapView.Camera3DMode.InGame) + case ViewMode.ThreeDimensional: + if (view3D.camera3DMode != View3D.Camera3DMode.InGame) { - float mul = 10.0f / (float)Math.Log((view.position - _rotatePivot).Length); + float mul = 10.0f / (float)Math.Log((currentView.position - _rotatePivot).Length); float diffX = pixelDiffX / (float)glControl.Width * 2 * mul; float diffY = pixelDiffY / (float)glControl.Height * 2 * mul; if (float.IsNaN(diffX) || float.IsNaN(diffY)) throw null; - view.yaw = _dragStartYaw + diffX; - view.pitch = Math.Max(-(float)Math.PI * 0.499f, Math.Min((float)Math.PI * 0.499f, _dragStartPitch - diffY)); + view3D.yaw = _dragStartYaw + diffX; + view3D.pitch = Math.Max(-(float)Math.PI * 0.499f, Math.Min((float)Math.PI * 0.499f, _dragStartPitch - diffY)); - if (view.camera3DMode == MapView.Camera3DMode.Free) + if (view3D.camera3DMode == View3D.Camera3DMode.Free) { - var dir = Vector3.TransformPosition(_rotateDiff, view.ComputeViewOrientation()); - view.position = _rotatePivot + dir; + var dir = Vector3.TransformPosition(_rotateDiff, currentView.ComputeViewOrientation()); + currentView.position = _rotatePivot + dir; } } @@ -779,9 +800,9 @@ private void OnMouseMove(object sender, MouseEventArgs e) } else { - switch (view.mode) + switch (viewMode) { - case MapView.ViewMode.TopDown: + case ViewMode.TopDown: { double oldAngle = Math.Atan2(glControl.Height / 2 - _dragStartMouseY, _dragStartMouseX - glControl.Width / 2); double thingAngle = Math.Atan2(glControl.Height / 2 - e.Y, e.X - glControl.Width / 2); @@ -790,7 +811,7 @@ private void OnMouseMove(object sender, MouseEventArgs e) SetCustomAngle(MapViewAngleValue); break; } - case MapView.ViewMode.Orthogonal: + case ViewMode.Orthogonal: { float newAngle = _rotateStartAngle - (e.X - _dragStartMouseX) * 128; newAngle %= 0x10000; @@ -804,12 +825,12 @@ private void OnMouseMove(object sender, MouseEventArgs e) SetCustomAngle(MapViewAngleValue); break; } - case MapView.ViewMode.ThreeDimensional: + case ViewMode.ThreeDimensional: { - view.camera3DMode = MapView.Camera3DMode.Free; - float dx = -(float)(e.X - _dragStartMouseX) / glControl.Height * view.movementSpeed; - float dy = (float)(e.Y - _dragStartMouseY) / glControl.Height * view.movementSpeed; - view.position = _translateStartCenter + BillboardMatrix.Row0.Xyz * dx + BillboardMatrix.Row1.Xyz * dy; + view3D.camera3DMode = View3D.Camera3DMode.Free; + float dx = -(float)(e.X - _dragStartMouseX) / glControl.Height * currentView.movementSpeed; + float dy = (float)(e.Y - _dragStartMouseY) / glControl.Height * currentView.movementSpeed; + currentView.position = _translateStartCenter + BillboardMatrix.Row0.Xyz * dx + BillboardMatrix.Row1.Xyz * dy; break; } } @@ -820,16 +841,16 @@ private void OnMouseMove(object sender, MouseEventArgs e) private void OnScroll(object sender, MouseEventArgs e) { int delta = e.Delta > 0 ? 1 : -1; - if (view.mode == MapView.ViewMode.ThreeDimensional) + if (viewMode == ViewMode.ThreeDimensional) { - if (view.camera3DMode == MapView.Camera3DMode.FocusOnPositionAngle) - view.camera3DDistanceController = Math.Max(0.0f, Math.Min(100, view.camera3DDistanceController - delta)); - else if (view.camera3DMode == MapView.Camera3DMode.Free) + if (view3D.camera3DMode == View3D.Camera3DMode.FocusOnPositionAngle) + view3D.camera3DDistanceController = Math.Max(0.0f, Math.Min(100, view3D.camera3DDistanceController - delta)); + else if (view3D.camera3DMode == View3D.Camera3DMode.Free) { - var diff = mapCursorPosition - view.position; + var diff = mapCursorPosition - currentView.position; if (Vector3.Dot(diff, normalAtCursor) < 0) - view.movementSpeed = diff.Length * 0.5f; - view.position += Vector3.Normalize(mapCursorPosition - view.position) * delta * view.movementSpeed / 5; + currentView.movementSpeed = diff.Length * 0.5f; + currentView.position += Vector3.Normalize(mapCursorPosition - currentView.position) * delta * currentView.movementSpeed / 5; } } else @@ -860,7 +881,7 @@ public void UpdateFlyingControls(double frameTime) { relativeMovement.Normalize(); float movement = (float)frameTime * (keyboardControls.IsShiftDown() ? 100 : 2000); - view.position += (right * relativeMovement.X + up * relativeMovement.Y + forwards * relativeMovement.Z) * movement; + currentView.position += (right * relativeMovement.X + up * relativeMovement.Y + forwards * relativeMovement.Z) * movement; } } } diff --git a/STROOP/Tabs/MapTab/MapObjects/MapBruteforceTriangles.cs b/STROOP/Tabs/MapTab/MapObjects/MapBruteforceTriangles.cs index 88ca57dad..3e9b75d02 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapBruteforceTriangles.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapBruteforceTriangles.cs @@ -143,7 +143,7 @@ protected override void DrawTopDown(MapGraphics graphics) new Vector4(Color.R / 255f, Color.G / 255f, Color.B / 255f, OpacityByte / 255f), new Vector4(OutlineColor.R / 255f, OutlineColor.G / 255f, OutlineColor.B / 255f, OutlineColor.A / 255f), OutlineWidth, - graphics.view.mode != MapView.ViewMode.TopDown); + graphics.viewMode != MapGraphics.ViewMode.TopDown); } }); } diff --git a/STROOP/Tabs/MapTab/MapObjects/MapCircleObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapCircleObject.cs index e65a41164..53c1c80eb 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapCircleObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapCircleObject.cs @@ -26,7 +26,7 @@ protected override void DrawTopDown(MapGraphics graphics) { var transform = graphics.BillboardMatrix * Matrix4.CreateScale(dim.radius) * Matrix4.CreateTranslation(dim.centerX, 0, dim.centerZ); graphics.circleRenderer.AddInstance( - graphics.view.mode != MapView.ViewMode.TopDown, + graphics.viewMode != MapGraphics.ViewMode.TopDown, transform, OutlineWidth, color, diff --git a/STROOP/Tabs/MapTab/MapObjects/MapCustomCameraPath.cs b/STROOP/Tabs/MapTab/MapObjects/MapCustomCameraPath.cs index 2385ab27d..47df8e707 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapCustomCameraPath.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapCustomCameraPath.cs @@ -57,8 +57,8 @@ public override void AddContextMenuItems(MapTab tab, ContextMenuStrip menu) var alignPositionItem = new ToolStripMenuItem("Align with view"); alignPositionItem.Click += (_, __) => { - currentKeyFrame.position = tab.graphics.view.position; - currentKeyFrame.targetPoint.position = tab.graphics.view.position + tab.graphics.view.ComputeViewDirection() * 400; + currentKeyFrame.position = tab.graphics.currentView.position; + currentKeyFrame.targetPoint.position = tab.graphics.currentView.position + tab.graphics.currentView.ComputeViewDirection() * 400; }; var waitForItem = new ToolStripMenuItem("Wait for... (adjust timings)"); @@ -211,18 +211,18 @@ protected override void DrawTopDown(MapGraphics graphics) foreach (var a in keyFrames) { DrawIcon(graphics, - graphics.view.mode == MapView.ViewMode.ThreeDimensional, + graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional, (float)a.X, (float)a.Y, (float)a.Z, Rotates ? (float)a.Angle : 0x8000 - graphics.MapViewAngleValue, GetInternalImage()?.Value, new Vector4(1, 1, 1, actualHoverData.currentKeyFrame == a ? ObjectUtilities.HoverAlpha() : 1)); float desiredDiameter = Size * 2; - if (graphics.view.mode == MapView.ViewMode.ThreeDimensional) + if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) desiredDiameter *= Get3DIconScale(graphics, (float)a.targetPoint.X, (float)a.targetPoint.Y, (float)a.targetPoint.Z); graphics.circleRenderer.AddInstance( - graphics.view.mode == MapView.ViewMode.ThreeDimensional, + graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional, graphics.BillboardMatrix * Matrix4.CreateScale(desiredDiameter) * Matrix4.CreateTranslation(a.targetPoint.position), 1, new Vector4(0.5f, 0.5f, 0.5f, 0.5f), @@ -245,8 +245,8 @@ protected override ContextMenuStrip GetContextMenuStrip(MapTracker targetTracker itemAddKeyframe.Click += (_, __) => { var f = new KeyFrame(); - f.position = targetTracker.mapTab.graphics.view.position; - f.targetPoint.position = targetTracker.mapTab.graphics.view.position + targetTracker.mapTab.graphics.view.ComputeViewDirection() * 400; + f.position = targetTracker.mapTab.graphics.currentView.position; + f.targetPoint.position = targetTracker.mapTab.graphics.currentView.position + targetTracker.mapTab.graphics.currentView.ComputeViewDirection() * 400; keyFrames.Add(f); }; _contextMenuStrip.Items.Add(itemAddKeyframe); diff --git a/STROOP/Tabs/MapTab/MapObjects/MapCustomIconPoints.cs b/STROOP/Tabs/MapTab/MapObjects/MapCustomIconPoints.cs index 84e725012..ce96d2222 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapCustomIconPoints.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapCustomIconPoints.cs @@ -64,9 +64,9 @@ void CreateNewPoint(MapTab mapTab) { if (mapTab == null) return; - var newPointPos = mapTab.graphics.view.position; - if (mapTab.graphics.view.mode == MapView.ViewMode.ThreeDimensional) - newPointPos += mapTab.graphics.view.ComputeViewDirection() * 50; + var newPointPos = mapTab.graphics.currentView.position; + if (mapTab.graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) + newPointPos += mapTab.graphics.currentView.ComputeViewDirection() * 50; positionAngles.Add(PositionAngle.Custom(newPointPos)); } diff --git a/STROOP/Tabs/MapTab/MapObjects/MapCylinderObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapCylinderObject.cs index f677287a8..efdf43a09 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapCylinderObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapCylinderObject.cs @@ -27,7 +27,7 @@ protected override void DrawOrthogonal(MapGraphics graphics) var color = new Vector4(Color.R / 255.0f, Color.G / 255.0f, Color.B / 255.0f, (float)Opacity); foreach (var dim in Get3DDimensions()) { - var dist = (graphics.view.focusPositionAngle.position.Xz - new Vector2(dim.centerX, dim.centerZ)).Length; + var dist = (graphics.viewOrthogonal.focusPositionAngle.position.Xz - new Vector2(dim.centerX, dim.centerZ)).Length; dist /= dim.radius; var scale = System.Math.Sqrt(1 - dist * dist); if (!double.IsNaN(scale)) diff --git a/STROOP/Tabs/MapTab/MapObjects/MapGhostObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapGhostObject.cs index cfba3ed99..952f15da5 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapGhostObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapGhostObject.cs @@ -26,7 +26,7 @@ protected override void DrawTopDown(MapGraphics graphics) foreach (var pa in positionAngleProvider()) if (pa is GhostTab.Ghost.GhostPositionAngle a) { - var transparent = graphics.view.mode == MapView.ViewMode.ThreeDimensional; + var transparent = graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional; var alpha = hoverData.currentPositionAngle == a ? ObjectUtilities.HoverAlpha() : 1; var angle = Rotates ? (float)a.Angle : 0x8000 - graphics.MapViewAngleValue; diff --git a/STROOP/Tabs/MapTab/MapObjects/MapGridlinesObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapGridlinesObject.cs index 857f615c5..b2818b25c 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapGridlinesObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapGridlinesObject.cs @@ -22,7 +22,7 @@ protected override Vector4 GetColor(MapGraphics graphics) { var c = base.GetColor(graphics); float maxSize = 4 * OutlineWidth; - if (graphics.view.mode == MapView.ViewMode.TopDown && graphics.pixelsPerUnit.Y < maxSize / Size) + if (graphics.viewMode == MapGraphics.ViewMode.TopDown && graphics.pixelsPerUnit.Y < maxSize / Size) c.W *= (graphics.pixelsPerUnit.Y * Size - 2) / (maxSize - 2); return c; } @@ -88,7 +88,7 @@ protected override List GetVertices(MapGraphics graphics) graphics.mapCursorPosition, _hExpanse, _vExpanse, - graphics.view.mode == MapView.ViewMode.ThreeDimensional ? 1 : float.NaN, + graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional ? 1 : float.NaN, _verticalLineDistance); return vertices; } @@ -111,7 +111,7 @@ protected void AddVerticesToPositionAngle( float hExpanse = horizontalExpanse * Size; float vExpanse = verticalExpanse * Size; - if (graphics.view.mode == MapView.ViewMode.TopDown) + if (graphics.viewMode == MapGraphics.ViewMode.TopDown) { if (graphics.pixelsPerUnit.X < 2 / Size || graphics.pixelsPerUnit.Y < 2 / Size) return; diff --git a/STROOP/Tabs/MapTab/MapObjects/MapHorizontalTriangleObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapHorizontalTriangleObject.cs index b2fdab302..af22e3c10 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapHorizontalTriangleObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapHorizontalTriangleObject.cs @@ -31,7 +31,7 @@ protected MapHorizontalTriangleObject(ObjectCreateParams creationParameters) public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 position) { - if (graphics.view.mode == MapView.ViewMode.TopDown) + if (graphics.viewMode == MapGraphics.ViewMode.TopDown) foreach (var tri in GetTrianglesWithinDist()) { if (tri.GetTruncatedHeightOnTriangleIfInsideTriangle(graphics.mapCursorPosition.X, graphics.mapCursorPosition.Z) != null) @@ -82,7 +82,7 @@ protected override void DrawTopDown(MapGraphics graphics) new Vector4(Color.R / 255f, Color.G / 255f, Color.B / 255f, OpacityByte / 255f), new Vector4(OutlineColor.R / 255f, OutlineColor.G / 255f, OutlineColor.B / 255f, OutlineColor.A / 255f), OutlineWidth, - graphics.view.mode != MapView.ViewMode.TopDown); + graphics.viewMode != MapGraphics.ViewMode.TopDown); } }); } diff --git a/STROOP/Tabs/MapTab/MapObjects/MapIconPointObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapIconPointObject.cs index 1dc2c01f6..8b34d10e3 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapIconPointObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapIconPointObject.cs @@ -17,7 +17,7 @@ protected override void DrawTopDown(MapGraphics graphics) { foreach (var a in positionAngleProvider()) DrawIcon(graphics, - graphics.view.mode == MapView.ViewMode.ThreeDimensional, + graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional, (float)a.X, (float)a.Y, (float)a.Z, Rotates ? (float)a.Angle : 0x8000 - graphics.MapViewAngleValue, GetInternalImage()?.Value, @@ -38,7 +38,7 @@ public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 positi { hoverData.currentPositionAngle = null; foreach (var a in positionAngleProvider()) - if (graphics.view.mode == MapView.ViewMode.TopDown) + if (graphics.viewMode == MapGraphics.ViewMode.TopDown) { if (graphics.HoverTopDown(new Vector3((float)a.X, cursorPos.Y, (float)a.Z), radius)) { @@ -46,7 +46,7 @@ public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 positi break; } } - else if (graphics.view.mode == MapView.ViewMode.Orthogonal) + else if (graphics.viewMode == MapGraphics.ViewMode.Orthogonal) { if (graphics.HoverOrthogonal(a.position, radius)) { @@ -54,12 +54,12 @@ public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 positi break; } } - else if (graphics.view.mode == MapView.ViewMode.ThreeDimensional) + else if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) { var rad = Size * Get3DIconScale(graphics, (float)a.X, (float)a.Y, (float)a.Z); if (graphics.Hover3D(a.position, rad)) { - var newDist = (a.position - graphics.view.position).LengthSquared; + var newDist = (a.position - graphics.currentView.position).LengthSquared; if (closestDist > newDist) { hoverData.currentPositionAngle = a; diff --git a/STROOP/Tabs/MapTab/MapObjects/MapIwerlipsesObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapIwerlipsesObject.cs index cf00ffc25..cb7bed783 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapIwerlipsesObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapIwerlipsesObject.cs @@ -64,7 +64,7 @@ protected override void DrawTopDown(MapGraphics graphics) var outlineColor = OpenTKUtilities.ColorToVec4(OutlineColor); foreach (var transform in _ellipseTransforms) graphics.circleRenderer.AddInstance( - graphics.view.mode != MapView.ViewMode.TopDown, + graphics.viewMode != MapGraphics.ViewMode.TopDown, transform, OutlineWidth, color, diff --git a/STROOP/Tabs/MapTab/MapObjects/MapMultipleObjectsObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapMultipleObjectsObject.cs index 7dcf6f659..f6d20e0f0 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapMultipleObjectsObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapMultipleObjectsObject.cs @@ -83,7 +83,7 @@ protected override void DrawTopDown(MapGraphics graphics) List<(float x, float y, float z, float angle, Lazy tex, float alpha)> data = GetData(); data.Reverse(); foreach (var d in data) - DrawIcon(graphics, graphics.view.mode != MapView.ViewMode.TopDown, d.x, d.y, d.z, d.angle, d.tex.Value, new Vector4(1, 1, 1, d.alpha)); + DrawIcon(graphics, graphics.viewMode != MapGraphics.ViewMode.TopDown, d.x, d.y, d.z, d.angle, d.tex.Value, new Vector4(1, 1, 1, d.alpha)); }); } diff --git a/STROOP/Tabs/MapTab/MapObjects/MapNearbyUnits.cs b/STROOP/Tabs/MapTab/MapObjects/MapNearbyUnits.cs index f9d1cc8a3..c21268ab0 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapNearbyUnits.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapNearbyUnits.cs @@ -106,7 +106,7 @@ void DrawHorizontalPieces(MapGraphics graphics, (int x, int z) offset, float[,] * Matrix4.CreateScale(0.5f) * Matrix4.CreateTranslation(x + offset.x + 0.5f, vs[x, z], z + offset.z + 0.5f); graphics.circleRenderer.AddInstance( - graphics.view.mode != MapView.ViewMode.TopDown, + graphics.viewMode != MapGraphics.ViewMode.TopDown, transform, OutlineWidth, color, @@ -148,7 +148,7 @@ void DrawVerticalPieces(MapGraphics graphics, (int x, int z) offset, float[,] vs * Matrix4.CreateTranslation(x + offset.x + 1, (high + low) * 0.5f, z + offset.z + 0.5f); if (low != high) graphics.circleRenderer.AddInstance( - graphics.view.mode != MapView.ViewMode.TopDown, + graphics.viewMode != MapGraphics.ViewMode.TopDown, transform, OutlineWidth, colorX, @@ -163,7 +163,7 @@ void DrawVerticalPieces(MapGraphics graphics, (int x, int z) offset, float[,] vs * Matrix4.CreateTranslation(x + offset.x + 0.5f, (high + low) * 0.5f, z + offset.z + 1); if (low != high) graphics.circleRenderer.AddInstance( - graphics.view.mode != MapView.ViewMode.TopDown, + graphics.viewMode != MapGraphics.ViewMode.TopDown, transform, OutlineWidth, colorX, diff --git a/STROOP/Tabs/MapTab/MapObjects/MapNextPositionsObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapNextPositionsObject.cs index 182098627..29f1c1a72 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapNextPositionsObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapNextPositionsObject.cs @@ -39,7 +39,7 @@ protected override void DrawTopDown(MapGraphics graphics) List<(float x, float y, float z, float angle, Lazy tex)> data = GetData(); data.Reverse(); foreach (var dataPoint in data) - DrawIcon(graphics, graphics.view.mode == MapView.ViewMode.ThreeDimensional, dataPoint.x, dataPoint.y, dataPoint.z, dataPoint.angle, dataPoint.tex?.Value, new Vector4(1)); + DrawIcon(graphics, graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional, dataPoint.x, dataPoint.y, dataPoint.z, dataPoint.angle, dataPoint.tex?.Value, new Vector4(1)); }); } diff --git a/STROOP/Tabs/MapTab/MapObjects/MapObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapObject.cs index a3b23efd5..64ff05e98 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapObject.cs @@ -187,7 +187,7 @@ protected MapObject(ObjectCreateParams creationParameters) this.creationParameters = creationParameters; } - public static float Get3DIconScale(MapGraphics graphics, float x, float y, float z) => (0.5f * (float)Math.Tan(1) * (new Vector3(x, y, z) - graphics.view.position).Length) / graphics.glControl.Height; + public static float Get3DIconScale(MapGraphics graphics, float x, float y, float z) => (0.5f * (float)Math.Tan(1) * (new Vector3(x, y, z) - graphics.currentView.position).Length) / graphics.glControl.Height; public void DrawIcon( MapGraphics graphics, @@ -198,7 +198,7 @@ public void DrawIcon( DrawIcon( graphics, sortTransparent, - x, y, z, graphics.view.mode != MapView.ViewMode.TopDown ? 0x8000 : angle, + x, y, z, graphics.viewMode != MapGraphics.ViewMode.TopDown ? 0x8000 : angle, Size, image, color); @@ -214,7 +214,7 @@ public static void DrawIcon( if (image == null) return; float desiredDiameter = size * 2; - if (graphics.view.mode == MapView.ViewMode.ThreeDimensional) + if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) desiredDiameter *= Get3DIconScale(graphics, x, y, z); else if (!graphics.MapViewScaleIconSizes) desiredDiameter /= graphics.MapViewScaleValue; @@ -234,15 +234,15 @@ public static void DrawIcon( public void Draw(MapGraphics graphics) { - switch (graphics.view.mode) + switch (graphics.viewMode) { - case MapView.ViewMode.TopDown: + case MapGraphics.ViewMode.TopDown: DrawTopDown(graphics); break; - case MapView.ViewMode.Orthogonal: + case MapGraphics.ViewMode.Orthogonal: DrawOrthogonal(graphics); break; - case MapView.ViewMode.ThreeDimensional: + case MapGraphics.ViewMode.ThreeDimensional: Draw3D(graphics); break; } diff --git a/STROOP/Tabs/MapTab/MapObjects/MapPreviousPositionsObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapPreviousPositionsObject.cs index e4c61f4d2..9f95a386c 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapPreviousPositionsObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapPreviousPositionsObject.cs @@ -75,7 +75,7 @@ protected override void DrawTopDown(MapGraphics graphics) foreach (var dataPoint in data) DrawIcon( graphics, - graphics.view.mode == MapView.ViewMode.ThreeDimensional, + graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional, dataPoint.x, dataPoint.y, dataPoint.z, dataPoint.angle, dataPoint.tex.Value, new Vector4(1)); diff --git a/STROOP/Tabs/MapTab/MapObjects/MapQuadObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapQuadObject.cs index 85dd661a3..58ac398d5 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapQuadObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapQuadObject.cs @@ -24,7 +24,7 @@ protected override void DrawTopDown(MapGraphics graphics) * Matrix4.CreateScale((quad.xMax - quad.xMin) * 0.5f, 1, (quad.zMax - quad.zMin) * 0.5f) * Matrix4.CreateTranslation((quad.xMin + quad.xMax) * 0.5f, quad.y, (quad.zMin + quad.zMax) * 0.5f); graphics.circleRenderer.AddInstance( - graphics.view.mode != MapView.ViewMode.TopDown, + graphics.viewMode != MapGraphics.ViewMode.TopDown, transform, OutlineWidth, color, diff --git a/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs index ffcd176af..e95fd6c6b 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapTapeMeasureObject.cs @@ -125,8 +125,8 @@ public MapTapeMeasureObject() { OutlineColor = Color.Orange; OutlineWidth = 3; - a = new Vector3(currentMapTab.graphics.view.position.X - 50, 0, currentMapTab.graphics.view.position.Z); - b = new Vector3(currentMapTab.graphics.view.position.X + 50, 0, currentMapTab.graphics.view.position.Z); + a = new Vector3(currentMapTab.graphics.currentView.position.X - 50, 0, currentMapTab.graphics.currentView.position.Z); + b = new Vector3(currentMapTab.graphics.currentView.position.X + 50, 0, currentMapTab.graphics.currentView.position.Z); hoverData = new TapeHoverData(this); for (int mask = 1; mask <= 8; mask++) { @@ -230,7 +230,7 @@ public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 positi float magicConst = 15; Vector3 _a = aProvider?.Invoke() ?? a; Vector3 _b = bProvider?.Invoke() ?? b; - if (graphics.view.mode == MapView.ViewMode.TopDown) + if (graphics.viewMode == MapGraphics.ViewMode.TopDown) { var rad = (magicConst / graphics.MapViewScaleValue); if (graphics.HoverTopDown(_a, rad)) @@ -246,9 +246,9 @@ public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 positi return hoverData; } } - else if (graphics.view.mode == MapView.ViewMode.ThreeDimensional) + else if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) { - bool prioritizeA = (_a - graphics.view.position).LengthSquared < (_b - graphics.view.position).LengthSquared; + bool prioritizeA = (_a - graphics.currentView.position).LengthSquared < (_b - graphics.currentView.position).LengthSquared; bool hoverA = graphics.Hover3D(_a, magicConst * Get3DIconScale(graphics, _a.X, _a.Y, _a.Z)); bool hoverB = graphics.Hover3D(_b, magicConst * Get3DIconScale(graphics, _b.X, _b.Y, _b.Z)); if (hoverA && (!hoverB || prioritizeA)) diff --git a/STROOP/Tabs/MapTab/MapObjects/MapTriangleObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapTriangleObject.cs index 6796bcc48..790a71484 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapTriangleObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapTriangleObject.cs @@ -61,7 +61,7 @@ public void AddContextMenuItems(MapTab tab, ContextMenuStrip menu) { if (triangle != null) { - if (tab.graphics.view.mode == MapView.ViewMode.TopDown) + if (tab.graphics.viewMode == MapGraphics.ViewMode.TopDown) { float y = triangle.IsWall() ? mapCursorOnRightClick.Y : (float)triangle.GetHeightOnTriangle(mapCursorOnRightClick.X, mapCursorOnRightClick.Z); CopyUtilities.CopyPosition(new Vector3(mapCursorOnRightClick.X, y, mapCursorOnRightClick.Z)); @@ -164,7 +164,7 @@ public override void Update() public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 position) { - if (graphics.view.mode == MapView.ViewMode.ThreeDimensional) + if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) { if (graphics.hoverTriangle != null && _bufferedTris.Any(_ => _.Address == graphics.hoverTriangle.Address)) { @@ -231,7 +231,7 @@ protected override void DrawOrthogonal(MapGraphics graphics) var baseColor = new Vector4(Color.R / 255f, Color.G / 255f, Color.B / 255f, OpacityByte / 255f); foreach (var tri in GetTrianglesWithinDist()) { - if (graphics.view.displayOrthoLevelGeometry) + if (graphics.viewOrthogonal.displayOrthoLevelGeometry) graphics.triangleRenderer.Add( tri.p1, tri.p2, @@ -265,7 +265,7 @@ protected override void Draw3D(MapGraphics graphics) baseColor.W = OpacityByte / 255f; var projectionColor = new Vector4(baseColor.Xyz, _projectionAlphaMultiplier * baseColor.W); - if (!graphics.view.display3DLevelGeometry) + if (!graphics.view3D.display3DLevelGeometry) graphics.triangleRenderer.Add( tri.p1, tri.p2, diff --git a/STROOP/Tabs/MapTab/MapObjects/MapWallObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapWallObject.cs index a7415ec4d..8be83bd2f 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapWallObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapWallObject.cs @@ -46,7 +46,7 @@ protected override ContextMenuStrip GetContextMenuStrip(MapTracker targetTracker public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 position) { - if (graphics.view.mode == MapView.ViewMode.TopDown) + if (graphics.viewMode == MapGraphics.ViewMode.TopDown) foreach (var tri in GetTrianglesWithinDist()) { var dat = MapUtilities.Get2DWallDataFromTri(tri); diff --git a/STROOP/Tabs/MapTab/MapPopout.cs b/STROOP/Tabs/MapTab/MapPopout.cs index 5375bac8c..c9e087620 100644 --- a/STROOP/Tabs/MapTab/MapPopout.cs +++ b/STROOP/Tabs/MapTab/MapPopout.cs @@ -28,7 +28,7 @@ public MapPopout(MapTab tab) graphics = new MapGraphics(tab, glControl, () => tab.graphics.glControl.Context); graphics.MapViewAngleValue = tab.graphics.MapViewAngleValue; graphics.MapViewScaleValue = tab.graphics.MapViewScaleValue; - graphics.view.position = tab.graphics.view.position; + graphics.currentView.position = tab.graphics.currentView.position; Shown += (_, __) => { using (new AccessScope(tab)) diff --git a/STROOP/Tabs/MapTab/MapTab.cs b/STROOP/Tabs/MapTab/MapTab.cs index 10dd5b85a..1078fef57 100644 --- a/STROOP/Tabs/MapTab/MapTab.cs +++ b/STROOP/Tabs/MapTab/MapTab.cs @@ -15,6 +15,7 @@ using OpenTK.Mathematics; using STROOP.Core; using STROOP.Core.Utilities; +using STROOP.Tabs.MapTab.Views; using STROOP.Variables.SM64MemoryLayout; using STROOP.Variables.Utilities; @@ -506,14 +507,10 @@ void ShowRightClickMenu() contextMenu.Items.Add(copyPositionItem); contextMenu.Items.Add(new ToolStripSeparator()); - if (graphics.view.mode == MapView.ViewMode.ThreeDimensional) + if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) { var pivotPositionItem = new ToolStripMenuItem("Pivot This Position"); - pivotPositionItem.Click += (e, args) => - { - graphics.view.camera3DMode = MapView.Camera3DMode.FocusOnPositionAngle; - graphics.view.focusPositionAngle = PositionAngle.Custom(onClickPosition); - }; + pivotPositionItem.Click += (_, _) => (graphics.currentView as PivotingView)?.Pivot(PositionAngle.Custom(onClickPosition)); contextMenu.Items.Add(pivotPositionItem); contextMenu.Items.Add(new ToolStripSeparator()); } @@ -584,18 +581,18 @@ void AddViewModeContextMenu() itemRefreshLevelGeometry.Click += (__, ___) => RequireGeometryUpdate(); ctx.Items.Add(itemRefreshLevelGeometry); - if (graphics.view.mode == MapView.ViewMode.ThreeDimensional) + if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) { ctx.Items.Add(new ToolStripSeparator()); var itemDisplayLevelGeometry = new ToolStripMenuItem("Display Level Geometry"); - itemDisplayLevelGeometry.Checked = graphics.view.display3DLevelGeometry; - itemDisplayLevelGeometry.Click += (__, ___) => itemDisplayLevelGeometry.Checked = graphics.view.display3DLevelGeometry = !graphics.view.display3DLevelGeometry; + itemDisplayLevelGeometry.Checked = graphics.view3D.display3DLevelGeometry; + itemDisplayLevelGeometry.Click += (__, ___) => itemDisplayLevelGeometry.Checked = graphics.view3D.display3DLevelGeometry = !graphics.view3D.display3DLevelGeometry; ctx.Items.Add(itemDisplayLevelGeometry); var itemDisplayCylinderOutlines = new ToolStripMenuItem("Draw Cylinder Outlines"); - itemDisplayCylinderOutlines.Checked = graphics.view.drawCylinderOutlines; - itemDisplayCylinderOutlines.Click += (__, ___) => itemDisplayCylinderOutlines.Checked = graphics.view.drawCylinderOutlines = !graphics.view.drawCylinderOutlines; + itemDisplayCylinderOutlines.Checked = graphics.drawCylinderOutlines; + itemDisplayCylinderOutlines.Click += (__, ___) => itemDisplayCylinderOutlines.Checked = graphics.drawCylinderOutlines = !graphics.drawCylinderOutlines; ctx.Items.Add(itemDisplayCylinderOutlines); ctx.Items.Add(new ToolStripSeparator()); @@ -603,23 +600,23 @@ void AddViewModeContextMenu() var itemCameraModeInGame = new ToolStripMenuItem("In-Game View"); var itemCameraModePivot = new ToolStripMenuItem("Pivot"); var itemCameraModeFree = new ToolStripMenuItem("Free"); - itemCameraModeInGame.Checked = graphics.view.camera3DMode == MapView.Camera3DMode.InGame; - itemCameraModePivot.Checked = graphics.view.camera3DMode == MapView.Camera3DMode.FocusOnPositionAngle; - itemCameraModeFree.Checked = graphics.view.camera3DMode == MapView.Camera3DMode.Free; + itemCameraModeInGame.Checked = graphics.view3D.camera3DMode == View3D.Camera3DMode.InGame; + itemCameraModePivot.Checked = graphics.view3D.camera3DMode == View3D.Camera3DMode.FocusOnPositionAngle; + itemCameraModeFree.Checked = graphics.view3D.camera3DMode == View3D.Camera3DMode.Free; itemCameraModeInGame.Click += (__, ___) => { - graphics.view.camera3DMode = MapView.Camera3DMode.InGame; + graphics.view3D.camera3DMode = View3D.Camera3DMode.InGame; itemCameraModePivot.Checked = itemCameraModeFree.Checked = !(itemCameraModeInGame.Checked = true); }; itemCameraModePivot.Click += (__, ___) => { - graphics.view.camera3DMode = MapView.Camera3DMode.FocusOnPositionAngle; + graphics.view3D.camera3DMode = View3D.Camera3DMode.FocusOnPositionAngle; itemCameraModeInGame.Checked = itemCameraModeFree.Checked = !(itemCameraModePivot.Checked = true); }; itemCameraModeFree.Click += (__, ___) => { - graphics.view.camera3DMode = MapView.Camera3DMode.Free; + graphics.view3D.camera3DMode = View3D.Camera3DMode.Free; itemCameraModeInGame.Checked = itemCameraModePivot.Checked = !(itemCameraModeInGame.Checked = true); }; ctx.Items.Add(itemCameraModeInGame); @@ -634,31 +631,31 @@ void AddViewModeContextMenu() ctx.Items.Add(itemFollowInGame); } - if (graphics.view.mode == MapView.ViewMode.Orthogonal) + if (graphics.viewMode == MapGraphics.ViewMode.Orthogonal) { ctx.Items.Add(new ToolStripSeparator()); var itemDisplayLevelGeometry = new ToolStripMenuItem("Display Triangle Tracker Geometry"); - itemDisplayLevelGeometry.Checked = graphics.view.displayOrthoLevelGeometry; - itemDisplayLevelGeometry.Click += (__, ___) => itemDisplayLevelGeometry.Checked = graphics.view.displayOrthoLevelGeometry = !graphics.view.displayOrthoLevelGeometry; + itemDisplayLevelGeometry.Checked = graphics.viewOrthogonal.displayOrthoLevelGeometry; + itemDisplayLevelGeometry.Click += (__, ___) => itemDisplayLevelGeometry.Checked = graphics.viewOrthogonal.displayOrthoLevelGeometry = !graphics.viewOrthogonal.displayOrthoLevelGeometry; ctx.Items.Add(itemDisplayLevelGeometry); var itemSetRelativeNearPlane = new ToolStripMenuItem("Set Relative Near Plane"); itemSetRelativeNearPlane.Click += (__, ___) => - graphics.view.orthoRelativeNearPlane = (float)DialogUtilities.GetDoubleFromDialog(0, labelText: "Enter relative near plane value."); + graphics.viewOrthogonal.orthoRelativeNearPlane = (float)DialogUtilities.GetDoubleFromDialog(0, labelText: "Enter relative near plane value."); ctx.Items.Add(itemSetRelativeNearPlane); var itemClearRelativeNearPlane = new ToolStripMenuItem("Clear Relative Near Plane"); - itemClearRelativeNearPlane.Click += (__, ___) => graphics.view.orthoRelativeNearPlane = float.NaN; + itemClearRelativeNearPlane.Click += (__, ___) => graphics.viewOrthogonal.orthoRelativeNearPlane = float.NaN; ctx.Items.Add(itemClearRelativeNearPlane); var itemSetRelativeFarPlane = new ToolStripMenuItem("Set Relative Far Plane"); itemSetRelativeFarPlane.Click += (__, ___) => - graphics.view.orthoRelativeFarPlane = (float)DialogUtilities.GetDoubleFromDialog(0, labelText: "Enter relative far plane value."); + graphics.viewOrthogonal.orthoRelativeFarPlane = (float)DialogUtilities.GetDoubleFromDialog(0, labelText: "Enter relative far plane value."); ctx.Items.Add(itemSetRelativeFarPlane); var itemClearRelativeFarPlane = new ToolStripMenuItem("Clear Relative Far Plane"); - itemClearRelativeFarPlane.Click += (__, ___) => graphics.view.orthoRelativeFarPlane = float.NaN; + itemClearRelativeFarPlane.Click += (__, ___) => graphics.viewOrthogonal.orthoRelativeFarPlane = float.NaN; ctx.Items.Add(itemClearRelativeFarPlane); } @@ -683,7 +680,7 @@ public void UpdateHover() var newHover = tracker.mapObject.GetHoverData(graphics, ref newCursor); if (graphics.fixCursorPlane) { - graphics.cursorViewPlaneDist = Vector3.Dot(graphics.view.ComputeViewDirection(), (newCursor - graphics.view.position)); + graphics.cursorViewPlaneDist = Vector3.Dot(graphics.currentView.ComputeViewDirection(), (newCursor - graphics.currentView.position)); graphics.UpdateCursor(); } @@ -742,13 +739,13 @@ public override void Update(bool active) } } - if (graphics.view.mode == MapView.ViewMode.ThreeDimensional && makeInGameCameraFollow) + if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional && makeInGameCameraFollow) { Config.Stream.SetValue(3, CamHackConfig.StructAddress + CamHackConfig.CameraModeOffset); - Config.Stream.SetValue(graphics.view.position.X, CamHackConfig.StructAddress + CamHackConfig.CameraXOffset); - Config.Stream.SetValue(graphics.view.position.Y, CamHackConfig.StructAddress + CamHackConfig.CameraYOffset); - Config.Stream.SetValue(graphics.view.position.Z, CamHackConfig.StructAddress + CamHackConfig.CameraZOffset); - var target = graphics.view.position + graphics.view.ComputeViewDirection(); + Config.Stream.SetValue(graphics.currentView.position.X, CamHackConfig.StructAddress + CamHackConfig.CameraXOffset); + Config.Stream.SetValue(graphics.currentView.position.Y, CamHackConfig.StructAddress + CamHackConfig.CameraYOffset); + Config.Stream.SetValue(graphics.currentView.position.Z, CamHackConfig.StructAddress + CamHackConfig.CameraZOffset); + var target = graphics.currentView.position + graphics.currentView.ComputeViewDirection(); Config.Stream.SetValue(target.X, CamHackConfig.StructAddress + CamHackConfig.FocusXOffset); Config.Stream.SetValue(target.Y, CamHackConfig.StructAddress + CamHackConfig.FocusYOffset); Config.Stream.SetValue(target.Z, CamHackConfig.StructAddress + CamHackConfig.FocusZOffset); @@ -979,7 +976,7 @@ void SaveTrackerConfig(string targetFileName) private void comboBoxViewMode_SelectedIndexChanged(object sender, EventArgs e) { - graphics.view.mode = (MapView.ViewMode)comboBoxViewMode.SelectedIndex; + graphics.viewMode = (MapGraphics.ViewMode)comboBoxViewMode.SelectedIndex; } } } diff --git a/STROOP/Tabs/MapTab/MapView.cs b/STROOP/Tabs/MapTab/MapView.cs deleted file mode 100644 index 4c27d90b0..000000000 --- a/STROOP/Tabs/MapTab/MapView.cs +++ /dev/null @@ -1,50 +0,0 @@ -using STROOP.Utilities; -using OpenTK; -using OpenTK.Mathematics; - -namespace STROOP.Tabs.MapTab -{ - public class MapView - { - public enum ViewMode - { - TopDown, - Orthogonal, - ThreeDimensional - } - - public enum Camera3DMode - { - InGame, - FocusOnPositionAngle, - Free, - } - - public MapGraphics MapGraphics; - public ViewMode mode = ViewMode.TopDown; - public Camera3DMode camera3DMode = Camera3DMode.FocusOnPositionAngle; - public PositionAngle focusPositionAngle = PositionAngle.Mario; - public Vector2 orthoOffset = Vector2.Zero; - public float orthoRelativeNearPlane = float.NaN, orthoRelativeFarPlane = float.NaN; - public bool displayOrthoLevelGeometry = true; - public bool display3DLevelGeometry = true; - public bool drawCylinderOutlines = false; - - public Vector3 position; - public float yaw, pitch, camera3DDistanceController = 50; - public float movementSpeed = 2000.0f; - - public Matrix4 ComputeViewOrientation() => Matrix4.CreateRotationX(pitch) * Matrix4.CreateRotationY(yaw); - public Vector3 ComputeViewDirection() => Vector3.TransformPosition(new Vector3(0, 0, 1), ComputeViewOrientation()); - - public void Pivot(PositionAngle pivotPoint) - { - camera3DMode = Camera3DMode.FocusOnPositionAngle; - focusPositionAngle = pivotPoint; - var d = focusPositionAngle.position - position; - yaw = (float)(System.Math.PI / 2 - System.Math.Atan2(d.Z, d.X)); - pitch = (float)-System.Math.Atan2(d.Y, System.Math.Sqrt(d.X * d.X + d.Z * d.Z)); - camera3DDistanceController = 10 * (float)(System.Math.Log(d.Length)); - } - } -} diff --git a/STROOP/Tabs/MapTab/Renderers/GeometryRenderer.cs b/STROOP/Tabs/MapTab/Renderers/GeometryRenderer.cs index 415bf7477..d7039837b 100644 --- a/STROOP/Tabs/MapTab/Renderers/GeometryRenderer.cs +++ b/STROOP/Tabs/MapTab/Renderers/GeometryRenderer.cs @@ -227,7 +227,7 @@ void DrawGeometry() public override void SetDrawCalls(MapGraphics graphics) { instances.Clear(); - if (graphics.view.drawCylinderOutlines) + if (graphics.drawCylinderOutlines) graphics.drawLayers[(int)MapGraphics.DrawLayers.FillBuffersRedirect].Add(() => { foreach (var instance in instances) diff --git a/STROOP/Tabs/MapTab/Renderers/TransparencyRenderer.cs b/STROOP/Tabs/MapTab/Renderers/TransparencyRenderer.cs index fc17cb56c..f38e78546 100644 --- a/STROOP/Tabs/MapTab/Renderers/TransparencyRenderer.cs +++ b/STROOP/Tabs/MapTab/Renderers/TransparencyRenderer.cs @@ -157,7 +157,7 @@ public void SetUniforms(int shader) public override void SetDrawCalls(MapGraphics graphics) { - if (graphics.view.mode != MapView.ViewMode.TopDown) + if (graphics.viewMode != MapGraphics.ViewMode.TopDown) graphics.drawLayers[(int)MapGraphics.DrawLayers.Transparency].Add(() => { var error = GL.GetError(); diff --git a/STROOP/Tabs/MapTab/Renderers/TriangleRenderer.cs b/STROOP/Tabs/MapTab/Renderers/TriangleRenderer.cs index 91f9ce636..98fcd2ae7 100644 --- a/STROOP/Tabs/MapTab/Renderers/TriangleRenderer.cs +++ b/STROOP/Tabs/MapTab/Renderers/TriangleRenderer.cs @@ -137,7 +137,7 @@ public override void SetDrawCalls(MapGraphics graphics) return; WriteDataToBuffer(); - if (graphics.view.mode == MapView.ViewMode.ThreeDimensional) + if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) { GL.Enable(EnableCap.DepthTest); GL.DepthFunc(DepthFunction.Lequal); diff --git a/STROOP/Tabs/MapTab/Views/View3D.cs b/STROOP/Tabs/MapTab/Views/View3D.cs new file mode 100644 index 000000000..7a5f29192 --- /dev/null +++ b/STROOP/Tabs/MapTab/Views/View3D.cs @@ -0,0 +1,32 @@ +using OpenTK.Mathematics; +using STROOP.Utilities; + +namespace STROOP.Tabs.MapTab.Views; + +public class View3D : ViewBase, PivotingView +{ + public enum Camera3DMode + { + InGame, + FocusOnPositionAngle, + Free, + } + + public Camera3DMode camera3DMode = Camera3DMode.FocusOnPositionAngle; + public float camera3DDistanceController = 50; + public bool display3DLevelGeometry = true; + + public PositionAngle focusPositionAngle { get; set; } = PositionAngle.Mario; + + void PivotingView.Pivot(PositionAngle pivotPoint) + { + focusPositionAngle = pivotPoint; + camera3DMode = Camera3DMode.FocusOnPositionAngle; + var d = focusPositionAngle.position - position; + yaw = (float)(System.Math.PI / 2 - System.Math.Atan2(d.Z, d.X)); + pitch = (float)-System.Math.Atan2(d.Y, System.Math.Sqrt(d.X * d.X + d.Z * d.Z)); + camera3DDistanceController = 10 * (float)(System.Math.Log(d.Length)); + } + + public override Matrix4 ComputeViewOrientation() => Matrix4.CreateRotationX(pitch) * Matrix4.CreateRotationY(yaw); +} diff --git a/STROOP/Tabs/MapTab/Views/ViewBase.cs b/STROOP/Tabs/MapTab/Views/ViewBase.cs new file mode 100644 index 000000000..63c2365a0 --- /dev/null +++ b/STROOP/Tabs/MapTab/Views/ViewBase.cs @@ -0,0 +1,27 @@ +using OpenTK.Mathematics; +using STROOP.Utilities; + +namespace STROOP.Tabs.MapTab.Views; + +public interface PivotingView +{ + public PositionAngle focusPositionAngle { get; protected set; } + public void Pivot(PositionAngle pivotPoint) => focusPositionAngle = pivotPoint; +} + +public abstract class ViewBase +{ + // TODO: consider what this is (ab)used for + public Vector3 position; + + // TODO: split between keyboard and mouse inputs as well as radial vs linear? + /// Displacement in units per t, where t is either seconds for keyboard keys or some number of pixels for mouse movement. + public float movementSpeed = 2000.0f; + + // TODO: remove from here by using inheritance for mouse events properly + public float yaw, pitch; + + public abstract Matrix4 ComputeViewOrientation(); + + public Vector3 ComputeViewDirection() => ComputeViewOrientation().Row2.Xyz; +} diff --git a/STROOP/Tabs/MapTab/Views/ViewOrthogonal.cs b/STROOP/Tabs/MapTab/Views/ViewOrthogonal.cs new file mode 100644 index 000000000..bcd35ee7a --- /dev/null +++ b/STROOP/Tabs/MapTab/Views/ViewOrthogonal.cs @@ -0,0 +1,15 @@ +using OpenTK.Mathematics; +using STROOP.Utilities; + +namespace STROOP.Tabs.MapTab.Views; + +public class ViewOrthogonal : ViewBase, PivotingView +{ + public Vector2 orthoOffset = Vector2.Zero; + public float orthoRelativeNearPlane = float.NaN, orthoRelativeFarPlane = float.NaN; + public bool displayOrthoLevelGeometry = true; + + public override Matrix4 ComputeViewOrientation() => Matrix4.CreateRotationX(pitch) * Matrix4.CreateRotationY(yaw); + + public PositionAngle focusPositionAngle { get; set; } = PositionAngle.Mario; +} diff --git a/STROOP/Tabs/MapTab/Views/ViewTopDown.cs b/STROOP/Tabs/MapTab/Views/ViewTopDown.cs new file mode 100644 index 000000000..680e46ff9 --- /dev/null +++ b/STROOP/Tabs/MapTab/Views/ViewTopDown.cs @@ -0,0 +1,8 @@ +using OpenTK.Mathematics; + +namespace STROOP.Tabs.MapTab.Views; + +public class ViewTopDown : ViewBase +{ + public override Matrix4 ComputeViewOrientation() => Matrix4.CreateRotationY(yaw); +} From e8ab1e109f91ceca786d7c6ab756cbec172cfa08 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Thu, 6 Aug 2026 06:32:01 +0200 Subject: [PATCH 19/31] manage multiple views via context menu --- STROOP/Tabs/MapTab/MapGraphics.cs | 10 ++++-- STROOP/Tabs/MapTab/MapTab.cs | 54 +++++++++++++++++++++++++--- STROOP/Tabs/MapTab/Views/ViewBase.cs | 2 ++ 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/STROOP/Tabs/MapTab/MapGraphics.cs b/STROOP/Tabs/MapTab/MapGraphics.cs index 3046817ec..bcd36025c 100644 --- a/STROOP/Tabs/MapTab/MapGraphics.cs +++ b/STROOP/Tabs/MapTab/MapGraphics.cs @@ -13,6 +13,7 @@ using STROOP.Structs.Configurations; using STROOP.Tabs.MapTab.Views; using STROOP.Utilities; +using System.Linq; namespace STROOP.Tabs.MapTab { @@ -164,9 +165,9 @@ private enum MapAngle ViewMode.ThreeDimensional => view3D, }; - public readonly ViewTopDown viewTopDown = new(); - public readonly ViewOrthogonal viewOrthogonal = new(); - public readonly View3D view3D = new(); + public ViewTopDown viewTopDown; + public ViewOrthogonal viewOrthogonal; + public View3D view3D; public float MapViewRadius => (float)MoreMath.GetHypotenuse(glControl.Width / 2, glControl.Height / 2) / MapViewScaleValue; @@ -247,6 +248,9 @@ public MapGraphics(MapTab mapTab, GLControl glControl, Func ge this.mapTab = mapTab; this.glControl = glControl; this.getContext = getContext; + view3D = mapTab.views3D.First(); + viewTopDown = mapTab.viewsTopDown.First(); + viewOrthogonal = mapTab.viewsOrthogonal.First(); glControl.MouseDown += (_, _) => glControl.Focus(); keyboardControls = new(glControl); diff --git a/STROOP/Tabs/MapTab/MapTab.cs b/STROOP/Tabs/MapTab/MapTab.cs index 1078fef57..ad5c3964d 100644 --- a/STROOP/Tabs/MapTab/MapTab.cs +++ b/STROOP/Tabs/MapTab/MapTab.cs @@ -6,8 +6,6 @@ using STROOP.Utilities; using System.Windows.Forms; using System.Drawing; -using OpenTK; -using OpenTK.Graphics; using STROOP.Structs.Configurations; using STROOP.Tabs.MapTab.MapObjects; using System.Xml.Linq; @@ -87,6 +85,10 @@ static IEnumerable EnumerateTypes(Func filter) public override HashSet selection => _selection; + public List viewsTopDown = [new() { name = "Mario" }]; + public List viewsOrthogonal = [new() { name = "Mario" }]; + public List views3D = [new() { name = "Mario" }]; + public MapTab() { InitializeComponent(); @@ -170,7 +172,6 @@ public void Load2D() public MapLayout GetMapLayout(object mapLayoutChoice = null) => (mapLayoutChoice ?? comboBoxMapOptionsLevel.SelectedItem) as MapLayout ?? MapAssociations.GetBestMap(); - bool displayingExtendedBoundaries = false; bool needsGeometryRefresh, _needsGeometryRefreshInternal; public bool NeedsGeometryRefresh() => needsGeometryRefresh; @@ -249,7 +250,7 @@ void InitAddTrackerButton() toolStripItem.Click += (sender, e) => addNewTracker(); return toolStripItem; } - )); + )); } } @@ -500,6 +501,7 @@ private void InitializeControls() void ShowRightClickMenu() { + contextMenu?.Dispose(); contextMenu = new ContextMenuStrip(); var onClickPosition = graphics.mapCursorPosition; var copyPositionItem = new ToolStripMenuItem("Copy Cursor Position"); @@ -533,9 +535,53 @@ void ShowRightClickMenu() }; contextMenu.Items.Add(openPopoutItem); + AddViewContextMenuItems(contextMenu, graphics); + contextMenu.Show(Cursor.Position); } + public void AddViewContextMenuItems(ContextMenuStrip contextMenu, MapGraphics mapGraphics) + { + contextMenu.Items.Add(new ToolStripSeparator()); + var rootItem = new ToolStripMenuItem("View"); + foreach (var (mode, list, field) in (IEnumerable<(MapGraphics.ViewMode, IEnumerable, FieldInfo)>) + [ + (MapGraphics.ViewMode.TopDown, viewsTopDown, typeof(MapGraphics).GetField(nameof(MapGraphics.viewTopDown))), + (MapGraphics.ViewMode.Orthogonal, viewsOrthogonal, typeof(MapGraphics).GetField(nameof(MapGraphics.viewOrthogonal))), + (MapGraphics.ViewMode.ThreeDimensional, views3D, typeof(MapGraphics).GetField(nameof(MapGraphics.view3D))), + ]) + { + var modeItem = new ToolStripMenuItem(mode.ToString()); + modeItem.Click += (_, _) => mapGraphics.viewMode = mode; + var currentView = (ViewBase)field.GetValue(mapGraphics); + foreach (var view in list) + { + var viewItem = new ToolStripMenuItem(view.name) { Checked = currentView == view }; + viewItem.Click += (_, _) => + { + mapGraphics.viewMode = mode; + field.SetValue(mapGraphics, view); + }; + modeItem.DropDownItems.Add(viewItem); + } + + var newItem = new ToolStripMenuItem("add ..."); + newItem.Click += (_, _) => + { + var newView = (ViewBase)Activator.CreateInstance(field.FieldType); + foreach (var newField in field.FieldType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + newField.SetValue(newView, newField.GetValue(currentView)); + newView.name = DialogUtilities.GetStringFromDialog("Custom", "Enter a Name") ?? ""; + field.SetValue(mapGraphics, newView); + mapGraphics.viewMode = mode; + list.GetType().GetMethod(nameof(IList.Add)).Invoke(list, [newView]); + }; + modeItem.DropDownItems.Add(newItem); + rootItem.DropDownItems.Add(modeItem); + } + contextMenu.Items.Add(rootItem); + } + private void LoadDefaultTrackers() { if (!System.IO.File.Exists(DEFAULT_TRACKER_FILE)) diff --git a/STROOP/Tabs/MapTab/Views/ViewBase.cs b/STROOP/Tabs/MapTab/Views/ViewBase.cs index 63c2365a0..288bc0bfa 100644 --- a/STROOP/Tabs/MapTab/Views/ViewBase.cs +++ b/STROOP/Tabs/MapTab/Views/ViewBase.cs @@ -11,6 +11,8 @@ public interface PivotingView public abstract class ViewBase { + public string name = "Custom"; + // TODO: consider what this is (ab)used for public Vector3 position; From a28ffcbeb580e541cc58a2efcbdfbf917c08e1d2 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Thu, 6 Aug 2026 07:23:51 +0200 Subject: [PATCH 20/31] add view context menu to map popouts as well --- STROOP/Tabs/MapTab/MapPopout.cs | 38 +++++++++++++++++++++++++-------- STROOP/Tabs/MapTab/MapTab.cs | 29 +++++++++++++------------ 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/STROOP/Tabs/MapTab/MapPopout.cs b/STROOP/Tabs/MapTab/MapPopout.cs index c9e087620..cc8619546 100644 --- a/STROOP/Tabs/MapTab/MapPopout.cs +++ b/STROOP/Tabs/MapTab/MapPopout.cs @@ -2,6 +2,7 @@ using System.Windows.Forms; using OpenTK.GLControl; using STROOP.Core; +using STROOP.Utilities; namespace STROOP.Tabs.MapTab { @@ -9,31 +10,50 @@ public partial class MapPopout : Form { GLControl glControl; MapGraphics graphics; + MapTab mapTab; - public MapPopout(MapTab tab) + public MapPopout(MapTab mapTab) { + this.mapTab = mapTab; InitializeComponent(); - ClientSize = tab.graphics.glControl.ClientRectangle.Size; + ClientSize = mapTab.graphics.glControl.ClientRectangle.Size; // Own GL context, but sharing resources with the main map's context so we can present the // shared color texture the main context renders into. See issue #39. glControl = new GLControl() { APIVersion = new Version(3, 3), - SharedContext = tab.graphics.glControl, + SharedContext = mapTab.graphics.glControl, }; glControl.Bounds = ClientRectangle; glControl.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom; Controls.Add(glControl); // Render in the main map's (shared) context; present into our own context (handled in MapGraphics). - graphics = new MapGraphics(tab, glControl, () => tab.graphics.glControl.Context); - graphics.MapViewAngleValue = tab.graphics.MapViewAngleValue; - graphics.MapViewScaleValue = tab.graphics.MapViewScaleValue; - graphics.currentView.position = tab.graphics.currentView.position; + graphics = new MapGraphics(mapTab, glControl, () => mapTab.graphics.glControl.Context); + graphics.MapViewAngleValue = mapTab.graphics.MapViewAngleValue; + graphics.MapViewScaleValue = mapTab.graphics.MapViewScaleValue; + graphics.currentView.position = mapTab.graphics.currentView.position; Shown += (_, __) => { - using (new AccessScope(tab)) - graphics.Load(() => tab.graphics.rendererCollection); + using (new AccessScope(mapTab)) + graphics.Load(() => mapTab.graphics.rendererCollection); }; + + glControl.MouseDown += (sender, e) => + { + if (e.Button == MouseButtons.Right) + ShowRightClickMenu(); + }; + } + + ContextMenuStrip contextMenu; + void ShowRightClickMenu() + { + contextMenu?.Dispose(); + contextMenu = new ContextMenuStrip(); + + mapTab.AddViewContextMenuItems(contextMenu, graphics); + + contextMenu.Show(Cursor.Position); } public void Redraw() => glControl.Invalidate(); diff --git a/STROOP/Tabs/MapTab/MapTab.cs b/STROOP/Tabs/MapTab/MapTab.cs index ad5c3964d..3df3ca592 100644 --- a/STROOP/Tabs/MapTab/MapTab.cs +++ b/STROOP/Tabs/MapTab/MapTab.cs @@ -503,19 +503,6 @@ void ShowRightClickMenu() { contextMenu?.Dispose(); contextMenu = new ContextMenuStrip(); - var onClickPosition = graphics.mapCursorPosition; - var copyPositionItem = new ToolStripMenuItem("Copy Cursor Position"); - copyPositionItem.Click += (e, args) => CopyUtilities.CopyPosition(onClickPosition); - contextMenu.Items.Add(copyPositionItem); - contextMenu.Items.Add(new ToolStripSeparator()); - - if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) - { - var pivotPositionItem = new ToolStripMenuItem("Pivot This Position"); - pivotPositionItem.Click += (_, _) => (graphics.currentView as PivotingView)?.Pivot(PositionAngle.Custom(onClickPosition)); - contextMenu.Items.Add(pivotPositionItem); - contextMenu.Items.Add(new ToolStripSeparator()); - } foreach (var a in hoverData) a.AddContextMenuItems(this, contextMenu); @@ -535,6 +522,8 @@ void ShowRightClickMenu() }; contextMenu.Items.Add(openPopoutItem); + contextMenu.Items.Add(new ToolStripSeparator()); + AddViewContextMenuItems(contextMenu, graphics); contextMenu.Show(Cursor.Position); @@ -542,7 +531,19 @@ void ShowRightClickMenu() public void AddViewContextMenuItems(ContextMenuStrip contextMenu, MapGraphics mapGraphics) { - contextMenu.Items.Add(new ToolStripSeparator()); + var onClickPosition = graphics.mapCursorPosition; + var copyPositionItem = new ToolStripMenuItem("Copy Cursor Position"); + copyPositionItem.Click += (e, args) => CopyUtilities.CopyPosition(onClickPosition); + contextMenu.Items.Add(copyPositionItem); + + if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) + { + var pivotPositionItem = new ToolStripMenuItem("Pivot This Position"); + pivotPositionItem.Click += (_, _) => (graphics.currentView as PivotingView)?.Pivot(PositionAngle.Custom(onClickPosition)); + contextMenu.Items.Add(pivotPositionItem); + contextMenu.Items.Add(new ToolStripSeparator()); + } + var rootItem = new ToolStripMenuItem("View"); foreach (var (mode, list, field) in (IEnumerable<(MapGraphics.ViewMode, IEnumerable, FieldInfo)>) [ From 1c0b1c8c6cb6f65566d5301ddfcb368d13aa2d1a Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:00:42 +0200 Subject: [PATCH 21/31] update flying controls on all popouts as well --- STROOP/Tabs/MapTab/MapPopout.cs | 3 +-- STROOP/Tabs/MapTab/MapTab.cs | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/STROOP/Tabs/MapTab/MapPopout.cs b/STROOP/Tabs/MapTab/MapPopout.cs index cc8619546..44f4d4e8d 100644 --- a/STROOP/Tabs/MapTab/MapPopout.cs +++ b/STROOP/Tabs/MapTab/MapPopout.cs @@ -2,14 +2,13 @@ using System.Windows.Forms; using OpenTK.GLControl; using STROOP.Core; -using STROOP.Utilities; namespace STROOP.Tabs.MapTab { public partial class MapPopout : Form { GLControl glControl; - MapGraphics graphics; + public readonly MapGraphics graphics; MapTab mapTab; public MapPopout(MapTab mapTab) diff --git a/STROOP/Tabs/MapTab/MapTab.cs b/STROOP/Tabs/MapTab/MapTab.cs index 3df3ca592..0f2bd9462 100644 --- a/STROOP/Tabs/MapTab/MapTab.cs +++ b/STROOP/Tabs/MapTab/MapTab.cs @@ -715,8 +715,11 @@ public void UpdateHover() { using (new AccessScope(this)) { - if (Form.ActiveForm != null && glControlMap2D.ClientRectangle.Contains(glControlMap2D.PointToClient(Cursor.Position))) - graphics.UpdateFlyingControls(Config.CoreLoop.lastFrameTime); + if (Form.ActiveForm != null) + foreach (var g in popouts.Select(x => x.graphics).Append(graphics)) + if (g.glControl.ClientRectangle.Contains(g.glControl.PointToClient(Cursor.Position))) + g.UpdateFlyingControls(Config.CoreLoop.lastFrameTime); + if (!graphics.IsMouseDown(0)) { var newCursor = graphics.mapCursorPosition; From 26c01a5ff1124066bf186edf211d286d54e7dac4 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:32:49 +0200 Subject: [PATCH 22/31] add hovering capabilities to popouts --- STROOP/Tabs/MapTab/MapGraphics.cs | 103 ++++++++++++++++++- STROOP/Tabs/MapTab/MapPopout.cs | 13 +-- STROOP/Tabs/MapTab/MapTab.Designer.cs | 2 +- STROOP/Tabs/MapTab/MapTab.cs | 140 ++++---------------------- 4 files changed, 124 insertions(+), 134 deletions(-) diff --git a/STROOP/Tabs/MapTab/MapGraphics.cs b/STROOP/Tabs/MapTab/MapGraphics.cs index bcd36025c..362734ab0 100644 --- a/STROOP/Tabs/MapTab/MapGraphics.cs +++ b/STROOP/Tabs/MapTab/MapGraphics.cs @@ -14,6 +14,7 @@ using STROOP.Tabs.MapTab.Views; using STROOP.Utilities; using System.Linq; +using System.Reflection; namespace STROOP.Tabs.MapTab { @@ -57,6 +58,9 @@ public bool HoverOrthogonal(Vector3 position, float radius) return (projectedPos.Xy - mousePosition2D).LengthSquared < (radius * radius); } + public bool IsContextMenuOpen() => contextMenu != null && contextMenu.Visible; + ContextMenuStrip contextMenu; + public readonly List[] drawLayers; public Renderers.RendererCollection rendererCollection { get; private set; } @@ -676,7 +680,7 @@ private void OnMouseDown(object sender, MouseEventArgs e) using (new AccessScope(mapTab)) { - mapTab.UpdateHover(); + UpdateHover(); foreach (var data in mapTab.hoverData) if (e.Button == MouseButtons.Left) data.LeftClick(mapCursorPosition); @@ -685,6 +689,31 @@ private void OnMouseDown(object sender, MouseEventArgs e) } } + public void UpdateHover() + { + using (new AccessScope(mapTab)) + { + if (!IsMouseDown(0)) + { + var newCursor = mapCursorPosition; + mapTab.hoverData.Clear(); + foreach (var tracker in mapTab.flowLayoutPanelMapTrackers.EnumerateTrackers()) + if (tracker.IsVisible) + { + var newHover = tracker.mapObject.GetHoverData(this, ref newCursor); + if (fixCursorPlane) + { + cursorViewPlaneDist = Vector3.Dot(currentView.ComputeViewDirection(), newCursor - currentView.position); + UpdateCursor(); + } + + if (newHover != null) + mapTab.hoverData.Add(newHover); + } + } + } + } + private void OnMouseUp(object sender, MouseEventArgs e) { switch (e.Button) @@ -888,5 +917,77 @@ public void UpdateFlyingControls(double frameTime) currentView.position += (right * relativeMovement.X + up * relativeMovement.Y + forwards * relativeMovement.Z) * movement; } } + + public void RecreateContextMenu(Action addAdditionalItems = null) + { + contextMenu?.Dispose(); + contextMenu = new ContextMenuStrip(); + + foreach (var a in mapTab.hoverData) + a.AddContextMenuItems(mapTab, contextMenu); + + if (mapTab.hoverData.Count > 0) + contextMenu.Items.Add(new ToolStripSeparator()); + + AddViewContextMenuItems(contextMenu); + + addAdditionalItems?.Invoke(contextMenu); + + contextMenu.Show(Cursor.Position); + } + + public void AddViewContextMenuItems(ContextMenuStrip contextMenu) + { + var onClickPosition = mapCursorPosition; + var copyPositionItem = new ToolStripMenuItem("Copy Cursor Position"); + copyPositionItem.Click += (_, _) => CopyUtilities.CopyPosition(onClickPosition); + contextMenu.Items.Add(copyPositionItem); + + if (viewMode == ViewMode.ThreeDimensional) + { + var pivotPositionItem = new ToolStripMenuItem("Pivot This Position"); + pivotPositionItem.Click += (_, _) => (currentView as PivotingView)?.Pivot(PositionAngle.Custom(onClickPosition)); + contextMenu.Items.Add(pivotPositionItem); + contextMenu.Items.Add(new ToolStripSeparator()); + } + + var rootItem = new ToolStripMenuItem("View"); + foreach (var (mode, list, field) in (IEnumerable<(ViewMode, IEnumerable, FieldInfo)>) + [ + (ViewMode.TopDown, mapTab.viewsTopDown, typeof(MapGraphics).GetField(nameof(viewTopDown))), + (ViewMode.Orthogonal, mapTab.viewsOrthogonal, typeof(MapGraphics).GetField(nameof(viewOrthogonal))), + (ViewMode.ThreeDimensional, mapTab.views3D, typeof(MapGraphics).GetField(nameof(view3D))), + ]) + { + var modeItem = new ToolStripMenuItem(mode.ToString()); + modeItem.Click += (_, _) => viewMode = mode; + var currentView = (ViewBase)field.GetValue(this); + foreach (var view in list) + { + var viewItem = new ToolStripMenuItem(view.name) { Checked = currentView == view }; + viewItem.Click += (_, _) => + { + viewMode = mode; + field.SetValue(this, view); + }; + modeItem.DropDownItems.Add(viewItem); + } + + var newItem = new ToolStripMenuItem("add ..."); + newItem.Click += (_, _) => + { + var newView = (ViewBase)Activator.CreateInstance(field.FieldType); + foreach (var newField in field.FieldType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + newField.SetValue(newView, newField.GetValue(currentView)); + newView.name = DialogUtilities.GetStringFromDialog("Custom", "Enter a Name") ?? ""; + field.SetValue(this, newView); + viewMode = mode; + list.GetType().GetMethod(nameof(IList.Add)).Invoke(list, [newView]); + }; + modeItem.DropDownItems.Add(newItem); + rootItem.DropDownItems.Add(modeItem); + } + contextMenu.Items.Add(rootItem); + } } } diff --git a/STROOP/Tabs/MapTab/MapPopout.cs b/STROOP/Tabs/MapTab/MapPopout.cs index 44f4d4e8d..8f02d0a7d 100644 --- a/STROOP/Tabs/MapTab/MapPopout.cs +++ b/STROOP/Tabs/MapTab/MapPopout.cs @@ -40,21 +40,10 @@ public MapPopout(MapTab mapTab) glControl.MouseDown += (sender, e) => { if (e.Button == MouseButtons.Right) - ShowRightClickMenu(); + graphics.RecreateContextMenu(); }; } - ContextMenuStrip contextMenu; - void ShowRightClickMenu() - { - contextMenu?.Dispose(); - contextMenu = new ContextMenuStrip(); - - mapTab.AddViewContextMenuItems(contextMenu, graphics); - - contextMenu.Show(Cursor.Position); - } - public void Redraw() => glControl.Invalidate(); protected override void OnClosed(EventArgs e) diff --git a/STROOP/Tabs/MapTab/MapTab.Designer.cs b/STROOP/Tabs/MapTab/MapTab.Designer.cs index df9faed94..d95a0d7ce 100644 --- a/STROOP/Tabs/MapTab/MapTab.Designer.cs +++ b/STROOP/Tabs/MapTab/MapTab.Designer.cs @@ -1150,7 +1150,7 @@ private void InitializeComponent() internal System.Windows.Forms.Label labelMapDataMapSubName; internal System.Windows.Forms.Label labelMapDataMapName; internal System.Windows.Forms.Label labelMapDataPuCoordinates; - private Tabs.MapTab.MapTrackerFlowLayoutPanel flowLayoutPanelMapTrackers; + internal Tabs.MapTab.MapTrackerFlowLayoutPanel flowLayoutPanelMapTrackers; private System.Windows.Forms.ComboBox comboBoxViewMode; internal System.Windows.Forms.ComboBox comboBoxMapOptionsLevel; internal System.Windows.Forms.Label labelViewMode; diff --git a/STROOP/Tabs/MapTab/MapTab.cs b/STROOP/Tabs/MapTab/MapTab.cs index 0f2bd9462..939ef5865 100644 --- a/STROOP/Tabs/MapTab/MapTab.cs +++ b/STROOP/Tabs/MapTab/MapTab.cs @@ -492,95 +492,19 @@ private void InitializeControls() glControlMap2D.MouseDown += (sender, e) => { if (e.Button == MouseButtons.Right) - ShowRightClickMenu(); - }; - } - - bool IsContextMenuOpen() => contextMenu != null && contextMenu.Visible; - ContextMenuStrip contextMenu; - - void ShowRightClickMenu() - { - contextMenu?.Dispose(); - contextMenu = new ContextMenuStrip(); - - foreach (var a in hoverData) - a.AddContextMenuItems(this, contextMenu); - - if (hoverData.Count > 0) - { - contextMenu.Items.Add(new ToolStripSeparator()); - } - - var openPopoutItem = new ToolStripMenuItem("Open Popout"); - openPopoutItem.Click += (e, args) => - { - var popout = new MapPopout(this) { Owner = FindForm() }; - popout.Show(); - popout.FormClosed += (_, __) => popouts.Remove(popout); - popouts.Add(popout); - }; - contextMenu.Items.Add(openPopoutItem); - - contextMenu.Items.Add(new ToolStripSeparator()); - - AddViewContextMenuItems(contextMenu, graphics); - - contextMenu.Show(Cursor.Position); - } - - public void AddViewContextMenuItems(ContextMenuStrip contextMenu, MapGraphics mapGraphics) - { - var onClickPosition = graphics.mapCursorPosition; - var copyPositionItem = new ToolStripMenuItem("Copy Cursor Position"); - copyPositionItem.Click += (e, args) => CopyUtilities.CopyPosition(onClickPosition); - contextMenu.Items.Add(copyPositionItem); - - if (graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional) - { - var pivotPositionItem = new ToolStripMenuItem("Pivot This Position"); - pivotPositionItem.Click += (_, _) => (graphics.currentView as PivotingView)?.Pivot(PositionAngle.Custom(onClickPosition)); - contextMenu.Items.Add(pivotPositionItem); - contextMenu.Items.Add(new ToolStripSeparator()); - } - - var rootItem = new ToolStripMenuItem("View"); - foreach (var (mode, list, field) in (IEnumerable<(MapGraphics.ViewMode, IEnumerable, FieldInfo)>) - [ - (MapGraphics.ViewMode.TopDown, viewsTopDown, typeof(MapGraphics).GetField(nameof(MapGraphics.viewTopDown))), - (MapGraphics.ViewMode.Orthogonal, viewsOrthogonal, typeof(MapGraphics).GetField(nameof(MapGraphics.viewOrthogonal))), - (MapGraphics.ViewMode.ThreeDimensional, views3D, typeof(MapGraphics).GetField(nameof(MapGraphics.view3D))), - ]) - { - var modeItem = new ToolStripMenuItem(mode.ToString()); - modeItem.Click += (_, _) => mapGraphics.viewMode = mode; - var currentView = (ViewBase)field.GetValue(mapGraphics); - foreach (var view in list) - { - var viewItem = new ToolStripMenuItem(view.name) { Checked = currentView == view }; - viewItem.Click += (_, _) => + graphics.RecreateContextMenu(contextMenu => { - mapGraphics.viewMode = mode; - field.SetValue(mapGraphics, view); - }; - modeItem.DropDownItems.Add(viewItem); - } - - var newItem = new ToolStripMenuItem("add ..."); - newItem.Click += (_, _) => - { - var newView = (ViewBase)Activator.CreateInstance(field.FieldType); - foreach (var newField in field.FieldType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) - newField.SetValue(newView, newField.GetValue(currentView)); - newView.name = DialogUtilities.GetStringFromDialog("Custom", "Enter a Name") ?? ""; - field.SetValue(mapGraphics, newView); - mapGraphics.viewMode = mode; - list.GetType().GetMethod(nameof(IList.Add)).Invoke(list, [newView]); - }; - modeItem.DropDownItems.Add(newItem); - rootItem.DropDownItems.Add(modeItem); - } - contextMenu.Items.Add(rootItem); + var openPopoutItem = new ToolStripMenuItem("Open Popout"); + openPopoutItem.Click += (_, _) => + { + var popout = new MapPopout(this) { Owner = FindForm() }; + popout.Show(); + popout.FormClosed += (_, __) => popouts.Remove(popout); + popouts.Add(popout); + }; + contextMenu.Items.Add(openPopoutItem); + }); + }; } private void LoadDefaultTrackers() @@ -711,36 +635,6 @@ void AddViewModeContextMenu() }; } - public void UpdateHover() - { - using (new AccessScope(this)) - { - if (Form.ActiveForm != null) - foreach (var g in popouts.Select(x => x.graphics).Append(graphics)) - if (g.glControl.ClientRectangle.Contains(g.glControl.PointToClient(Cursor.Position))) - g.UpdateFlyingControls(Config.CoreLoop.lastFrameTime); - - if (!graphics.IsMouseDown(0)) - { - var newCursor = graphics.mapCursorPosition; - hoverData.Clear(); - foreach (var tracker in flowLayoutPanelMapTrackers.EnumerateTrackers()) - if (tracker.IsVisible) - { - var newHover = tracker.mapObject.GetHoverData(graphics, ref newCursor); - if (graphics.fixCursorPlane) - { - graphics.cursorViewPlaneDist = Vector3.Dot(graphics.currentView.ComputeViewDirection(), (newCursor - graphics.currentView.position)); - graphics.UpdateCursor(); - } - - if (newHover != null) - hoverData.Add(newHover); - } - } - } - } - public override void Update(bool active) { if (!_isLoaded2D) return; @@ -767,8 +661,14 @@ public override void Update(bool active) RequireGeometryUpdate(); } - if (!IsContextMenuOpen()) - UpdateHover(); + if (Form.ActiveForm != null) + foreach (var g in popouts.Select(x => x.graphics).Append(graphics)) + if (!g.IsContextMenuOpen() && g.glControl.ClientRectangle.Contains(g.glControl.PointToClient(Cursor.Position))) + { + g.UpdateFlyingControls(Config.CoreLoop.lastFrameTime); + g.UpdateHover(); + } + using (new AccessScope(this)) { flowLayoutPanelMapTrackers.UpdateControl(); From e37439ee8dad5ede2d9ba1db3f5532bdf3cab712 Mon Sep 17 00:00:00 2001 From: Aurumaker72 <48759429+Aurumaker72@users.noreply.github.com> Date: Sat, 16 May 2026 08:06:43 +0200 Subject: [PATCH 23/31] chore: update to .NET 10 --- STROOP/STROOP.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STROOP/STROOP.csproj b/STROOP/STROOP.csproj index 76291479a..613d01d2b 100644 --- a/STROOP/STROOP.csproj +++ b/STROOP/STROOP.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows7.0 WinExe true false From d0a9c2f31bc1b6b010682e1244e74dc7405f0c0f Mon Sep 17 00:00:00 2001 From: Aurumaker72 <48759429+Aurumaker72@users.noreply.github.com> Date: Sat, 16 May 2026 08:23:51 +0200 Subject: [PATCH 24/31] fix workflows --- .github/workflows/build-debug.yml | 4 ++-- .github/workflows/build-release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-debug.yml b/.github/workflows/build-debug.yml index 73d3807eb..04bd97ed4 100644 --- a/.github/workflows/build-debug.yml +++ b/.github/workflows/build-debug.yml @@ -1,4 +1,4 @@ -# .github/workflows/debug-build.yml +# .github/workflows/debug-build.yml name: PR Debug Build (Development) on: @@ -16,7 +16,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Restore dependencies run: dotnet restore diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 01776aac1..42114e063 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Restore dependencies run: dotnet restore From eebd8ca096aa390ff7e46f8302a92f41a58d078f Mon Sep 17 00:00:00 2001 From: abart27 <48759429+abart27@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:58:29 +0200 Subject: [PATCH 25/31] hush dumb errors --- STROOP/STROOP.csproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/STROOP/STROOP.csproj b/STROOP/STROOP.csproj index 613d01d2b..b88c79bdc 100644 --- a/STROOP/STROOP.csproj +++ b/STROOP/STROOP.csproj @@ -291,5 +291,7 @@ - + + $(NoWarn);WFO1000 + From 5e6edd9d96f0471e475d8e06f4bf7a3f0aaf0755 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Fri, 4 Sep 2026 23:33:40 +0200 Subject: [PATCH 26/31] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f4c65b52b..26a9adb00 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ If this updated feature set doesn't suit your needs, you can of course still get As of the current build, STROOP has the following system requirements: * Windows 11 / Windows 10 / Windows 8.1 / Windows 8 / Windows 7 64-bit or 32-bit * OpenGL 3.2 or greater - * [.NET 8.0 Runtime](https://dotnet.microsoft.com/download/dotnet/8.0) or higher + * [.NET 10.0 Runtime](https://dotnet.microsoft.com/download/dotnet/10.0) or higher * [Mupen](https://mupen64.com/) is recommended for TASing. (Nemu64 and some other emulators may work, but that's a bit of a shot in the dark) * 64 Marios (Must be super) * Marios must be American, Japanese or PAL @@ -30,7 +30,7 @@ If this updated feature set doesn't suit your needs, you can of course still get ## Building Requirements: - * A [dotnet 8.0 (or higher) SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) in any capacity + * A [dotnet 10.0 (or higher) SDK](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) in any capacity * An internet connection for the [NuGet](https://nuget.org) package dependencies, such as OpenTK. A simple `dotnet build` in the repository root directory will create a debug build.
From 71ae6a4306f599d7f331882d6cc2d6b717beee6a Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:48:04 +0200 Subject: [PATCH 27/31] optionally show indicators for non-4/4-quarter-step frames in previous positions --- .../MapObjects/MapPreviousPositionsObject.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/STROOP/Tabs/MapTab/MapObjects/MapPreviousPositionsObject.cs b/STROOP/Tabs/MapTab/MapObjects/MapPreviousPositionsObject.cs index 9f95a386c..a60717a66 100644 --- a/STROOP/Tabs/MapTab/MapObjects/MapPreviousPositionsObject.cs +++ b/STROOP/Tabs/MapTab/MapObjects/MapPreviousPositionsObject.cs @@ -6,8 +6,6 @@ using STROOP.Structs; using System.Windows.Forms; using OpenTK.Mathematics; -using STROOP.Core; -using STROOP.Variables.SM64MemoryLayout; using STROOP.Variables.Utilities; namespace STROOP.Tabs.MapTab.MapObjects @@ -15,10 +13,11 @@ namespace STROOP.Tabs.MapTab.MapObjects [ObjectDescription("Previous Positions", "Movement")] public class MapPreviousPositionsObject : MapObject { - public struct DataPoint((float x, float y, float z, ushort angle, ushort _) srcData, Lazy tex) + public class DataPoint((float x, float y, float z, ushort angle, ushort _) srcData, Lazy tex) { public float x = srcData.x, y = srcData.y, z = srcData.z, angle = srcData.angle; public Lazy tex = tex; + public int? earlyQs = null; public bool ExactMatch(DataPoint other) => x == other.x && y == other.y && z == other.z && angle == other.angle; @@ -47,7 +46,10 @@ public bool ExactMatch(DataPoint other) uint numFramesToShow = 16; ToolStripMenuItem itemSkipIdenticalPoints = new ToolStripMenuItem("Skip identical points"); - bool skipIdenticalPoints {get => itemSkipIdenticalPoints.Checked; set => itemSkipIdenticalPoints.Checked = value; } + bool skipIdenticalPoints { get => itemSkipIdenticalPoints.Checked; set => itemSkipIdenticalPoints.Checked = value; } + + ToolStripMenuItem itemShowTruncatedQsIndicators = new ToolStripMenuItem("Highlight < 4/4 quarter-steps"); + bool showTruncatedQsIndicators { get => itemShowTruncatedQsIndicators.Checked; set => itemShowTruncatedQsIndicators.Checked = value; } public MapPreviousPositionsObject() : base() @@ -73,12 +75,22 @@ protected override void DrawTopDown(MapGraphics graphics) { var data = GetData(); foreach (var dataPoint in data) + { DrawIcon( graphics, graphics.viewMode == MapGraphics.ViewMode.ThreeDimensional, dataPoint.x, dataPoint.y, dataPoint.z, dataPoint.angle, dataPoint.tex.Value, new Vector4(1)); + if (showTruncatedQsIndicators && dataPoint.earlyQs != null) + graphics.textRenderer.AddText( + $"{dataPoint.earlyQs.Value}/4", + new(dataPoint.x, dataPoint.y, dataPoint.z), + Color.Red, + StringAlignment.Center, + Renderers.TextRenderer.Fonts.large + ); + } if (OutlineWidth != 0) { @@ -132,8 +144,11 @@ public List GetData() for (int i = 0; i < 4; i++) { int baseIndex = numBaseFrames + i * 4; - if (qsData[baseIndex].gtLo != (ushort)expectedGt) + if (qsData[baseIndex].gtLo != (ushort)expectedGt && allResults.Count > 0) + { + allResults[^1].earlyQs = i; break; + } for (int k = 0; k < 4; k++) { @@ -185,10 +200,12 @@ protected override ContextMenuStrip GetContextMenuStrip(MapTracker targetTracker skipIdenticalPoints = true; itemSkipIdenticalPoints.Click += (sender, e) => skipIdenticalPoints = !skipIdenticalPoints; + itemShowTruncatedQsIndicators.Click += (sender, e) => showTruncatedQsIndicators = !showTruncatedQsIndicators; _contextMenuStrip = new ContextMenuStrip(); _contextMenuStrip.Items.Add(itemShowEachPoint); _contextMenuStrip.Items.Add(itemSkipIdenticalPoints); + _contextMenuStrip.Items.Add(itemShowTruncatedQsIndicators); _contextMenuStrip.Items.Add(itemSetNumFrames); return _contextMenuStrip; From c50ef50cff3255843b48c84466229eeab871b94d Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:49:37 +0200 Subject: [PATCH 28/31] add rudimentary object ghost support --- HackSources/Ghosts/ghost_loop.asm | 32 ++++++++----- STROOP/Resources/Hacks/GhostHackJP.hck | 4 +- STROOP/Resources/Hacks/GhostHackUS.hck | 2 +- STROOP/Tabs/GhostTab/Ghost.cs | 51 +++++++++++++++++--- STROOP/Tabs/GhostTab/GhostTab.cs | 66 +++++++++++++------------- 5 files changed, 101 insertions(+), 54 deletions(-) diff --git a/HackSources/Ghosts/ghost_loop.asm b/HackSources/Ghosts/ghost_loop.asm index 7d213cd96..abcb36a9d 100644 --- a/HackSources/Ghosts/ghost_loop.asm +++ b/HackSources/Ghosts/ghost_loop.asm @@ -13,7 +13,7 @@ NumRequestedGhosts equ 0x7FFF ; Offset from extended RAM start to the b PointerToFirstGhost equ 0x7FF8 ; Offset from extended RAM start to the 4 byte pointer to the first ghost node NegativeGhostStructSize equ 0xFF98 ; The negative size of a single ghost node, used to iterate ghosts like a reversed array AnimationBufferSize equ 0x4000 ; The number of bytes reserved for animation data for each ghost -FirstAnimationBufferAddrHi equ 0x8050 ; The Hi part of the address pointing to the animation buffer used by the first ghost +FirstAnimationBufferAddrHi equ GhostBaseHi + 0x10 ; The Hi part of the address pointing to the animation buffer used by the first ghost InitializedGhostsFlag equ 0x40 ; A custom bit flag that can be set on the Mario object, indicating whether the ghost hack is active .n64 @@ -112,7 +112,7 @@ sll t1, RegProcessedGhostCount, 0xC addu t0, t0, t1 GhostBaseHi_LUI_PLUS_1: -lui at, GhostBaseHi + 1 +lui at, GhostBaseHi + 1 ; +1 because the addiu 0x9B00 below sign-extends; the pair lands at base + 0x9B00 addu t0, t0, at addiu t0, t0, 0x9B00 @@ -126,30 +126,40 @@ sw t1, 0x24 (RegCurrentGhost) lw t1, 0x08 (t0) sw t1, 0x28 (RegCurrentGhost) -; angles (TODO: store angles as s16 in file?) -lw t1, 0x10 (t0) +; angles +lh t1, 0x10 (t0) sh t1, 0x1A (RegCurrentGhost) -lw t1, 0x14 (t0) +lh t1, 0x12 (t0) sh t1, 0x1C (RegCurrentGhost) -lw t1, 0x18 (t0) +lh t1, 0x14 (t0) sh t1, 0x1E (RegCurrentGhost) +lw t1, 0x18 (t0) ; "graphics" in STROOP +lw t3, 0x0C (t0) ; "animation" in STROOP +bnez t1, @TREAT_AS_OBJECT +lh t2, 0x16 (t0) ; animation frame ; do the hacky thing with Mario animations lui t8, 0x8037 sw RegCurrentGhost, 0x0580 (t8) sw RegAnimationBuffer, 0x05C0 (t8) -lh t1, 0x1C (t0) -sh t1, 0x38 (SP) +sh t2, 0x38 (SP) ori a0, t8, 0x04F8 sh r0, 0x38 (RegCurrentGhost) sw r0, 0x05BC (t8) jal set_mario_animation lw a1, 0xC (t0) -lh t1, 0x38 (SP) -sh t1, 0x40 (RegCurrentGhost) +lh t2, 0x38 (SP) +lui t1, 0x800F +ori t1, t1, 0x0860 +beq r0, r0, @COMMON_SETTERS +addiu RegAnimationBuffer, RegAnimationBuffer, AnimationBufferSize +@TREAT_AS_OBJECT: +sw t3, 0x3C (RegCurrentGhost) +@COMMON_SETTERS: +sw t1, 0x14 (RegCurrentGhost) +sh t2, 0x40 (RegCurrentGhost) ; move on to next ghost -addiu RegAnimationBuffer, RegAnimationBuffer, AnimationBufferSize addiu RegPointerToCurrentGhost, RegPointerToCurrentGhost, NegativeGhostStructSize addiu RegProcessedGhostCount, RegProcessedGhostCount, 0x1 diff --git a/STROOP/Resources/Hacks/GhostHackJP.hck b/STROOP/Resources/Hacks/GhostHackJP.hck index f83067165..2bfcfeff5 100644 --- a/STROOP/Resources/Hacks/GhostHackJP.hck +++ b/STROOP/Resources/Hacks/GhostHackJP.hck @@ -1,6 +1,6 @@ 8027ABD8: 0C 10 20 00 -80408000: 27 BD FF C0 3C 08 80 36 8D 08 FD E8 10 08 00 6B AF BF 00 34 AF B4 00 30 AF B3 00 2C AF B2 00 28 AF B1 00 24 AF B0 00 20 00 08 A0 25 86 88 00 02 31 09 00 40 15 20 00 06 35 09 00 40 A6 89 00 02 10 00 00 58 3C 11 80 40 10 00 00 53 36 31 7F F8 3C 13 80 50 3C 18 80 37 34 01 00 BD A7 01 05 A8 37 01 05 B8 AF 01 05 98 3C 01 80 06 24 21 40 40 AF 01 05 B8 00 00 80 25 3C 01 80 40 34 31 7F F8 80 21 7F FF 10 01 00 44 00 00 00 00 8E 28 00 00 15 00 00 0E 00 00 20 25 26 25 FF 9C 8E 86 00 14 3C 07 80 38 34 E1 5F DC AF A1 00 10 34 E1 5F E4 AF A1 00 14 0C 0D EE 78 34 E7 5F D0 AE 22 00 00 8E 84 00 0C 0C 0D F0 11 00 40 28 25 8E 32 00 00 82 89 00 18 A2 49 00 18 8E 89 00 38 AE 49 00 38 3C 01 80 33 8C 28 C6 94 31 08 00 7F 00 08 41 40 00 10 4B 00 01 09 40 21 3C 01 80 41 01 01 40 21 25 08 9B 00 8D 09 00 00 AE 49 00 20 8D 09 00 04 AE 49 00 24 8D 09 00 08 AE 49 00 28 8D 09 00 10 A6 49 00 1A 8D 09 00 14 A6 49 00 1C 8D 09 00 18 A6 49 00 1E 3C 18 80 37 AF 12 05 80 AF 13 05 C0 85 09 00 1C A7 A9 00 38 37 04 04 F8 A6 40 00 38 AF 00 05 BC 0C 09 41 FA 8D 05 00 0C 87 A9 00 38 A6 49 00 40 26 73 40 00 26 31 FF 98 26 10 00 01 3C 01 80 40 80 21 7F FF 02 01 40 2B 15 00 FF C3 A2 50 00 60 8E 32 00 00 12 40 00 06 00 12 20 25 0C 0D F0 2F AE 20 00 00 8E 24 00 00 14 80 FF FC 26 31 FF 98 8F BF 00 34 8F B4 00 30 8F B3 00 2C 8F B2 00 28 8F B1 00 24 8F B0 00 20 03 E0 00 08 27 BD 00 40 +80408000: 27 BD FF C0 3C 08 80 36 8D 08 FD E8 10 08 00 73 AF BF 00 34 AF B4 00 30 AF B3 00 2C AF B2 00 28 AF B1 00 24 AF B0 00 20 00 08 A0 25 86 88 00 02 31 09 00 40 15 20 00 06 35 09 00 40 A6 89 00 02 10 00 00 60 3C 11 80 40 10 00 00 5B 36 31 7F F8 3C 13 80 50 3C 18 80 37 34 01 00 BD A7 01 05 A8 37 01 05 B8 AF 01 05 98 3C 01 80 06 24 21 40 40 AF 01 05 B8 00 00 80 25 3C 01 80 40 34 31 7F F8 80 21 7F FF 10 01 00 4C 00 00 00 00 8E 28 00 00 15 00 00 0E 00 00 20 25 26 25 FF 9C 8E 86 00 14 3C 07 80 38 34 E1 5F DC AF A1 00 10 34 E1 5F E4 AF A1 00 14 0C 0D EE 78 34 E7 5F D0 AE 22 00 00 8E 84 00 0C 0C 0D F0 11 00 40 28 25 8E 32 00 00 82 89 00 18 A2 49 00 18 8E 89 00 38 AE 49 00 38 3C 01 80 33 8C 28 C6 94 31 08 00 7F 00 08 41 40 00 10 4B 00 01 09 40 21 3C 01 80 41 01 01 40 21 25 08 9B 00 8D 09 00 00 AE 49 00 20 8D 09 00 04 AE 49 00 24 8D 09 00 08 AE 49 00 28 85 09 00 10 A6 49 00 1A 85 09 00 12 A6 49 00 1C 85 09 00 14 A6 49 00 1E 8D 09 00 18 8D 0B 00 0C 15 20 00 0F 85 0A 00 16 3C 18 80 37 AF 12 05 80 AF 13 05 C0 A7 AA 00 38 37 04 04 F8 A6 40 00 38 AF 00 05 BC 0C 09 41 FA 8D 05 00 0C 87 AA 00 38 3C 09 80 0F 35 29 08 60 10 00 00 02 26 73 40 00 AE 4B 00 3C AE 49 00 14 A6 4A 00 40 26 31 FF 98 26 10 00 01 3C 01 80 40 80 21 7F FF 02 01 40 2B 15 00 FF BB A2 50 00 60 8E 32 00 00 12 40 00 06 00 12 20 25 0C 0D F0 2F AE 20 00 00 8E 24 00 00 14 80 FF FC 26 31 FF 98 8F BF 00 34 8F B4 00 30 8F B3 00 2C 8F B2 00 28 8F B1 00 24 8F B0 00 20 03 E0 00 08 27 BD 00 40 80408200: 27 BD FF C0 AF BF 00 14 3C 09 04 01 35 28 19 A0 3C 01 80 36 8C 21 FD E8 3C 18 80 33 8F 18 CF A0 13 01 00 02 00 00 50 25 83 0A 00 60 35 29 19 78 AF A8 00 18 AF A9 00 1C AF AA 00 20 24 01 00 01 14 24 00 1B 34 04 00 38 0C 09 E2 5F 00 00 00 00 3C 0C 06 00 AC 4C 00 10 8F A1 00 18 AC 41 00 14 3C 01 03 88 34 21 00 10 AC 41 00 18 AC 41 00 00 3C 08 80 40 35 08 83 00 8F A9 00 20 00 09 49 40 01 28 50 21 AC 4A 00 1C AC 4A 00 04 3C 01 03 86 34 21 00 10 AC 41 00 20 AC 41 00 08 25 4B 00 08 AC 4B 00 24 AC 4B 00 0C AC 4C 00 28 8F A8 00 1C AC 48 00 2C 3C 01 B8 00 AC 41 00 30 8F BF 00 14 03 E0 00 08 27 BD 00 40 @@ -10,4 +10,4 @@ 80276AF4: 27 BD FF D0 AF BF 00 14 24 01 00 01 14 81 00 1A 00 00 10 25 00 A0 20 25 3C 08 80 34 25 08 A0 40 8C B8 00 18 00 18 C8 80 03 38 C8 21 00 19 C8 C0 03 28 48 21 3C 08 80 33 8D 08 CF A0 3C 01 80 36 8C 21 FD E8 15 01 00 06 85 2C 00 08 31 8D 01 00 11 A0 00 07 34 05 00 FF 10 00 00 05 31 85 00 FF 81 18 00 61 13 00 00 02 34 05 00 FF 34 05 00 7F 0C 09 DA 78 00 00 00 00 8F BF 00 14 03 E0 00 08 27 BD 00 30 -80277128: 3C 08 80 33 8D 08 CF A0 3C 01 80 36 8C 21 FD E8 15 01 00 05 3C 19 80 34 87 2A A0 48 00 0A 52 02 10 00 00 03 A4 AA 00 1E 81 0A 00 61 A4 AA 00 1E 03 E0 00 08 00 00 10 25 \ No newline at end of file +80277128: 3C 08 80 33 8D 08 CF A0 3C 01 80 36 8C 21 FD E8 15 01 00 05 3C 19 80 34 87 2A A0 48 00 0A 52 02 10 00 00 03 A4 AA 00 1E 81 0A 00 61 A4 AA 00 1E 03 E0 00 08 00 00 10 25 diff --git a/STROOP/Resources/Hacks/GhostHackUS.hck b/STROOP/Resources/Hacks/GhostHackUS.hck index aef9fd76a..8c40a70f8 100644 --- a/STROOP/Resources/Hacks/GhostHackUS.hck +++ b/STROOP/Resources/Hacks/GhostHackUS.hck @@ -1,6 +1,6 @@ 8027B188: 0C 10 20 00 -80408000: 27 BD FF C0 3C 08 80 36 8D 08 11 58 10 08 00 6B AF BF 00 34 AF B4 00 30 AF B3 00 2C AF B2 00 28 AF B1 00 24 AF B0 00 20 00 08 A0 25 86 88 00 02 31 09 00 40 15 20 00 06 35 09 00 40 A6 89 00 02 10 00 00 58 3C 11 80 40 10 00 00 53 36 31 7F F8 3C 13 80 50 3C 18 80 37 34 01 00 BD A7 01 05 A8 37 01 05 B8 AF 01 05 98 3C 01 80 06 24 21 40 40 AF 01 05 B8 00 00 80 25 3C 01 80 40 34 31 7F F8 80 21 7F FF 10 01 00 44 00 00 00 00 8E 28 00 00 15 00 00 0E 00 00 20 25 26 25 FF 9C 8E 86 00 14 3C 07 80 38 34 E1 5F DC AF A1 00 10 34 E1 5F E4 AF A1 00 14 0C 0D EE 78 34 E7 5F D0 AE 22 00 00 8E 84 00 0C 0C 0D F0 11 00 40 28 25 8E 32 00 00 82 89 00 18 A2 49 00 18 8E 89 00 38 AE 49 00 38 3C 01 80 33 8C 28 D5 D4 31 08 00 7F 00 08 41 40 00 10 4B 00 01 09 40 21 3C 01 80 41 01 01 40 21 25 08 9B 00 8D 09 00 00 AE 49 00 20 8D 09 00 04 AE 49 00 24 8D 09 00 08 AE 49 00 28 8D 09 00 10 A6 49 00 1A 8D 09 00 14 A6 49 00 1C 8D 09 00 18 A6 49 00 1E 3C 18 80 37 AF 12 05 80 AF 13 05 C0 85 09 00 1C A7 A9 00 38 37 04 04 F8 A6 40 00 38 AF 00 05 BC 0C 09 42 6E 8D 05 00 0C 87 A9 00 38 A6 49 00 40 26 73 40 00 26 31 FF 98 26 10 00 01 3C 01 80 40 80 21 7F FF 02 01 40 2B 15 00 FF C3 A2 50 00 60 8E 32 00 00 12 40 00 06 00 12 20 25 0C 0D F0 2F AE 20 00 00 8E 24 00 00 14 80 FF FC 26 31 FF 98 8F BF 00 34 8F B4 00 30 8F B3 00 2C 8F B2 00 28 8F B1 00 24 8F B0 00 20 03 E0 00 08 27 BD 00 40 +80408000: 27 BD FF C0 3C 08 80 36 8D 08 11 58 10 08 00 73 AF BF 00 34 AF B4 00 30 AF B3 00 2C AF B2 00 28 AF B1 00 24 AF B0 00 20 00 08 A0 25 86 88 00 02 31 09 00 40 15 20 00 06 35 09 00 40 A6 89 00 02 10 00 00 60 3C 11 80 40 10 00 00 5B 36 31 7F F8 3C 13 80 50 3C 18 80 37 34 01 00 BD A7 01 05 A8 37 01 05 B8 AF 01 05 98 3C 01 80 06 24 21 40 40 AF 01 05 B8 00 00 80 25 3C 01 80 40 34 31 7F F8 80 21 7F FF 10 01 00 4C 00 00 00 00 8E 28 00 00 15 00 00 0E 00 00 20 25 26 25 FF 9C 8E 86 00 14 3C 07 80 38 34 E1 5F DC AF A1 00 10 34 E1 5F E4 AF A1 00 14 0C 0D EE 78 34 E7 5F D0 AE 22 00 00 8E 84 00 0C 0C 0D F0 11 00 40 28 25 8E 32 00 00 82 89 00 18 A2 49 00 18 8E 89 00 38 AE 49 00 38 3C 01 80 33 8C 28 D5 D4 31 08 00 7F 00 08 41 40 00 10 4B 00 01 09 40 21 3C 01 80 41 01 01 40 21 25 08 9B 00 8D 09 00 00 AE 49 00 20 8D 09 00 04 AE 49 00 24 8D 09 00 08 AE 49 00 28 85 09 00 10 A6 49 00 1A 85 09 00 12 A6 49 00 1C 85 09 00 14 A6 49 00 1E 8D 09 00 18 8D 0B 00 0C 15 20 00 0F 85 0A 00 16 3C 18 80 37 AF 12 05 80 AF 13 05 C0 A7 AA 00 38 37 04 04 F8 A6 40 00 38 AF 00 05 BC 0C 09 42 6E 8D 05 00 0C 87 AA 00 38 3C 09 80 0F 35 29 08 60 10 00 00 02 26 73 40 00 AE 4B 00 3C AE 49 00 14 A6 4A 00 40 26 31 FF 98 26 10 00 01 3C 01 80 40 80 21 7F FF 02 01 40 2B 15 00 FF BB A2 50 00 60 8E 32 00 00 12 40 00 06 00 12 20 25 0C 0D F0 2F AE 20 00 00 8E 24 00 00 14 80 FF FC 26 31 FF 98 8F BF 00 34 8F B4 00 30 8F B3 00 2C 8F B2 00 28 8F B1 00 24 8F B0 00 20 03 E0 00 08 27 BD 00 40 80408200: 27 BD FF C0 AF BF 00 14 3C 09 04 01 35 28 19 A0 3C 01 80 36 8C 21 11 58 3C 18 80 33 8F 18 DF 00 13 01 00 02 00 00 50 25 83 0A 00 60 35 29 19 78 AF A8 00 18 AF A9 00 1C AF AA 00 20 24 01 00 01 14 24 00 1B 34 04 00 38 0C 09 E3 CB 00 00 00 00 3C 0C 06 00 AC 4C 00 10 8F A1 00 18 AC 41 00 14 3C 01 03 88 34 21 00 10 AC 41 00 18 AC 41 00 00 3C 08 80 40 35 08 83 00 8F A9 00 20 00 09 49 40 01 28 50 21 AC 4A 00 1C AC 4A 00 04 3C 01 03 86 34 21 00 10 AC 41 00 20 AC 41 00 08 25 4B 00 08 AC 4B 00 24 AC 4B 00 0C AC 4C 00 28 8F A8 00 1C AC 48 00 2C 3C 01 B8 00 AC 41 00 30 8F BF 00 14 03 E0 00 08 27 BD 00 40 diff --git a/STROOP/Tabs/GhostTab/Ghost.cs b/STROOP/Tabs/GhostTab/Ghost.cs index e077ba224..5ec34402e 100644 --- a/STROOP/Tabs/GhostTab/Ghost.cs +++ b/STROOP/Tabs/GhostTab/Ghost.cs @@ -35,16 +35,21 @@ public GhostPositionAngle(Ghost g) : base() public void SetGlobalTimer(uint globalTimer) { GhostFrame newFrame; - if (g.playbackFrames.TryGetValue(globalTimer, out newFrame)) + if (g.frames.TryGetValue(globalTimer, out newFrame)) currentFrame = newFrame; } public override Vector4 GetArrowColor(Vector4 baseColor) => color; } + public record struct PlaybackFrame(GhostFrame frame, uint animation); + + const uint OBJECT_EXTRA_MAGIC = 0x4F424A54; + public uint playbackBaseFrame = 0; - public Dictionary playbackFrames = new Dictionary(); - public GhostFrame lastValidPlaybackFrame, currentFrame; + public Dictionary frames = new Dictionary(); + public PlaybackFrame lastValidPlaybackFrame; + public GhostFrame currentFrame; public uint originalPlaybackBaseFrame { get; private set; } public uint maxFrame { get; private set; } public uint numFrames => maxFrame + 1; @@ -53,15 +58,19 @@ public void SetGlobalTimer(uint globalTimer) public GhostPositionAngle positionAngle { get; private set; } public bool transparent = true; + /// The graphics pointer to pass to the hack - 0 is interpreted as "Mario" by the hack. + public uint nonMarioGraphics = 0; + public Dictionary animationSwitches = new(); + public Ghost() { positionAngle = new GhostPositionAngle(this); } - public Ghost(uint playbackBaseFrame, Dictionary playbackFrames) + public Ghost(uint playbackBaseFrame, Dictionary frames) { this.playbackBaseFrame = originalPlaybackBaseFrame = playbackBaseFrame; - this.playbackFrames = playbackFrames; + this.frames = frames; } public static Ghost FromFile(BinaryReader reader) @@ -75,9 +84,21 @@ public static Ghost FromFile(BinaryReader reader) { var index = reader.ReadUInt32(); var frame = GhostFrame.ReadFrom(reader); - result.playbackFrames[index] = frame; + result.frames[index] = frame; result.maxFrame = Math.Max(result.maxFrame, index); } + + if (reader.BaseStream.Position > reader.BaseStream.Length - 4 || reader.ReadUInt32() != OBJECT_EXTRA_MAGIC) + return result; + + result.nonMarioGraphics = reader.ReadUInt32(); + var numAnimationSwitches = reader.ReadUInt32(); + for (int i = 0; i < numAnimationSwitches; i++) + { + var key = reader.ReadUInt32(); + var value = reader.ReadUInt32(); + result.animationSwitches[key] = value; + } } catch (IOException) { @@ -86,6 +107,24 @@ public static Ghost FromFile(BinaryReader reader) return result; } + public void ToFile(BinaryWriter wr) + { + wr.Write(originalPlaybackBaseFrame); + wr.Write(frames.Count); + foreach (var frame in frames) + { + wr.Write(frame.Key); + frame.Value.WriteTo(wr); + } + + wr.Write(OBJECT_EXTRA_MAGIC); + wr.Write(animationSwitches.Count); + foreach (var kvp in animationSwitches) + { + wr.Write(kvp.Key); + wr.Write(kvp.Value); + } + } public override string ToString() { diff --git a/STROOP/Tabs/GhostTab/GhostTab.cs b/STROOP/Tabs/GhostTab/GhostTab.cs index 962c50140..a49ff37b7 100644 --- a/STROOP/Tabs/GhostTab/GhostTab.cs +++ b/STROOP/Tabs/GhostTab/GhostTab.cs @@ -27,7 +27,7 @@ public partial class GhostTab : STROOPTab const uint FirstAnimationBufferAddrHi_LUI_1 = 0x50; const uint GhostBaseHi_LUI_PLUS_1 = 0xF8; const uint COLORED_HATS_GhostBaseHi_LUI = 0x70; - static readonly uint[] GhostBaseHi_LUI = [0x44, 0x78, 0x170]; + static readonly uint[] GhostBaseHi_LUI = [0x44, 0x78, 0x190]; /// The variable part to move the ghost loop and colored hats code with. ushort ghostHackBaseHi => @@ -124,22 +124,33 @@ public override void Update(bool active) int i = (tm + globalTimer) & 0x7F; GhostFrame newFrame = default(GhostFrame); var index = globalTimer + tm - ghost.playbackBaseFrame; - if (index >= 0 && ghost.playbackFrames.TryGetValue((uint)index, out newFrame)) - ghost.lastValidPlaybackFrame = newFrame; - - Array.Copy(BitConverter.GetBytes(ghost.lastValidPlaybackFrame.position.X), 0, buffer, i * 0x20 + 0x00, 4); - Array.Copy(BitConverter.GetBytes(ghost.lastValidPlaybackFrame.position.Y), 0, buffer, i * 0x20 + 0x04, 4); - Array.Copy(BitConverter.GetBytes(ghost.lastValidPlaybackFrame.position.Z), 0, buffer, i * 0x20 + 0x08, 4); - Array.Copy(BitConverter.GetBytes(ghost.lastValidPlaybackFrame.animationIndex), 0, buffer, i * 0x20 + 0x0C, 2); - Array.Copy(BitConverter.GetBytes(ghost.lastValidPlaybackFrame.oPitch), 0, buffer, i * 0x20 + 0x10, 4); - Array.Copy(BitConverter.GetBytes(ghost.lastValidPlaybackFrame.oYaw), 0, buffer, i * 0x20 + 0x14, 4); - Array.Copy(BitConverter.GetBytes(ghost.lastValidPlaybackFrame.oRoll), 0, buffer, i * 0x20 + 0x18, 4); - Array.Copy(BitConverter.GetBytes(ghost.lastValidPlaybackFrame.animationFrame), 0, buffer, i * 0x20 + 0x1E, 2); + if (index >= 0 && ghost.frames.TryGetValue((uint)index, out newFrame)) + ghost.lastValidPlaybackFrame = new( + newFrame, + ghost.animationSwitches.TryGetValue((uint)index, out var value) + ? value + : ghost.lastValidPlaybackFrame.animation == 0 + ? ghost.animationSwitches.OrderBy(x => x.Key).FirstOrDefault().Value + : ghost.lastValidPlaybackFrame.animation + ); + + (var frame, var animation) = ghost.lastValidPlaybackFrame; + animation = animation == 0 ? (uint)frame.animationIndex : animation; + + Array.Copy(BitConverter.GetBytes(frame.position.X), 0, buffer, i * 0x20 + 0x00, 4); + Array.Copy(BitConverter.GetBytes(frame.position.Y), 0, buffer, i * 0x20 + 0x04, 4); + Array.Copy(BitConverter.GetBytes(frame.position.Z), 0, buffer, i * 0x20 + 0x08, 4); + Array.Copy(BitConverter.GetBytes(ghost.nonMarioGraphics != 0 ? animation : frame.animationIndex), 0, buffer, i * 0x20 + 0x0C, 4); + Array.Copy(BitConverter.GetBytes(frame.oPitch), 0, buffer, i * 0x20 + 0x12, 2); + Array.Copy(BitConverter.GetBytes(frame.oYaw), 0, buffer, i * 0x20 + 0x10, 2); + Array.Copy(BitConverter.GetBytes(frame.oRoll), 0, buffer, i * 0x20 + 0x16, 2); + Array.Copy(BitConverter.GetBytes(ghost.nonMarioGraphics), 0, buffer, i * 0x20 + 0x18, 4); + Array.Copy(BitConverter.GetBytes(frame.animationFrame), 0, buffer, i * 0x20 + 0x14, 2); } GhostFrame currentFrame; var idx = (globalTimer - 1) - ghost.playbackBaseFrame; - if (idx >= 0 && ghost.playbackFrames.TryGetValue((uint)idx, out currentFrame)) + if (idx >= 0 && ghost.frames.TryGetValue((uint)idx, out currentFrame)) { ghost.positionAngle.SetGlobalTimer((uint)idx); ghost.currentFrame = currentFrame; @@ -175,10 +186,11 @@ public override void Update(bool active) Array.Copy(BitConverter.GetBytes(yTargetPosition + ghostIndex * 500 / numGhosts), 0, buffer, i * 0x20 + 0x04, 4); Array.Copy(BitConverter.GetBytes(z), 0, buffer, i * 0x20 + 0x08, 4); Array.Copy(BitConverter.GetBytes((ushort)0x2A), 0, buffer, i * 0x20 + 0x0C, 2); - Array.Copy(BitConverter.GetBytes((uint)0), 0, buffer, i * 0x20 + 0x10, 4); - Array.Copy(BitConverter.GetBytes((uint)(f * ushort.MaxValue + 0x8000)), 0, buffer, i * 0x20 + 0x14, 4); - Array.Copy(BitConverter.GetBytes(0xE800 + barrelRoll), 0, buffer, i * 0x20 + 0x18, 4); - Array.Copy(BitConverter.GetBytes((ushort)0), 0, buffer, i * 0x20 + 0x1E, 2); + Array.Copy(BitConverter.GetBytes((ushort)0), 0, buffer, i * 0x20 + 0x12, 2); + Array.Copy(BitConverter.GetBytes((ushort)(f * ushort.MaxValue + 0x8000)), 0, buffer, i * 0x20 + 0x10, 2); + Array.Copy(BitConverter.GetBytes((ushort)(0xE800 + barrelRoll)), 0, buffer, i * 0x20 + 0x16, 2); + Array.Copy(BitConverter.GetBytes(0), 0, buffer, i * 0x20 + 0x18, 4); + Array.Copy(BitConverter.GetBytes((ushort)(globalTimer % 32)), 0, buffer, i * 0x20 + 0x14, 2); } } @@ -290,8 +302,6 @@ bool UpdateHackStatus() bool enabled = ghostHack.Status != RomHack.EnabledStatus.Disabled; labelHackActiveState.Text = (ghostsActive && enabled) ? "Ghost hack is enabled." : (enabled ? "Ghost hack is enabled\nbut not running.\nInside a level,\nsave state and load state,\nthen frame advance." : "Ghost hack is disabled."); buttonDisableGhostHack.Enabled = enabled; - lblRAMOffsetBase.Visible = !enabled; - txtRAMOffsetBase.Visible = !enabled; return true; } @@ -340,21 +350,9 @@ private void buttonSaveGhost_Click(object sender, EventArgs e) } ); - foreach (var fn in ghostFileNames) - using (var wr = new BinaryWriter(new FileStream(fn.Item2, FileMode.Create))) - { - int missedFrameCount = 0; - uint lastFrame = 0; - wr.Write(fn.Item1.originalPlaybackBaseFrame); - wr.Write(fn.Item1.playbackFrames.Count); - foreach (var frame in fn.Item1.playbackFrames) - { - missedFrameCount += (int)(frame.Key - lastFrame - 1); - lastFrame = frame.Key; - wr.Write(frame.Key); - frame.Value.WriteTo(wr); - } - } + foreach (var (ghost, fileName) in ghostFileNames) + using (var wr = new BinaryWriter(new FileStream(fileName, FileMode.Create))) + ghost.ToFile(wr); } } From f924ce051e5b279450aa9d0f07c069016c6288ef Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Fri, 18 Sep 2026 20:29:05 +0200 Subject: [PATCH 29/31] add section about SM64LuaRedux object ghost recording to ghost help --- STROOP/Tabs/GhostTab/GhostTabHelp.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/STROOP/Tabs/GhostTab/GhostTabHelp.cs b/STROOP/Tabs/GhostTab/GhostTabHelp.cs index 20ada9275..d4fd75bf3 100644 --- a/STROOP/Tabs/GhostTab/GhostTabHelp.cs +++ b/STROOP/Tabs/GhostTab/GhostTabHelp.cs @@ -7,13 +7,17 @@ partial class GhostTab private void buttonTutorialRecord_Click(object sender, EventArgs e) { Forms.InfoForm frm = new Forms.InfoForm(); - frm.Size = new System.Drawing.Size(850, 250); + frm.Size = new System.Drawing.Size(900, 350); frm.SetText("Ghost Help", "How to record ghosts", @"To record a ghost use the 'recordghost.lua' script. This script should be located next to your STROOP executable. (You can move it to a different location though.) When you press 'Start', a new recording will begin at the current frame. Hitting 'Stop' will save the ghost to 'tmp.ghost' at the location of the script file. + +A similar method is to record through SM64LuaRedux, whose latest version also supports rudimentary ghosts of objects. +Note that some objects may crash or cause unwanted behavior. + You can then load this file into STROOP to play it back later, or store it somewhere else. You can also use a File Watcher to automatically load in the last recorded ghost (see ""Using File Watchers"")."); frm.ShowDialog(); From 50b0a84b921c4ce4118b1735f2758ceeb2f6a063 Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Sat, 19 Sep 2026 19:12:28 +0200 Subject: [PATCH 30/31] send ghosts with no information of a frame 'far' away --- STROOP/Tabs/GhostTab/GhostTab.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/STROOP/Tabs/GhostTab/GhostTab.cs b/STROOP/Tabs/GhostTab/GhostTab.cs index a49ff37b7..82a8e81df 100644 --- a/STROOP/Tabs/GhostTab/GhostTab.cs +++ b/STROOP/Tabs/GhostTab/GhostTab.cs @@ -137,9 +137,14 @@ public override void Update(bool active) (var frame, var animation) = ghost.lastValidPlaybackFrame; animation = animation == 0 ? (uint)frame.animationIndex : animation; - Array.Copy(BitConverter.GetBytes(frame.position.X), 0, buffer, i * 0x20 + 0x00, 4); - Array.Copy(BitConverter.GetBytes(frame.position.Y), 0, buffer, i * 0x20 + 0x04, 4); - Array.Copy(BitConverter.GetBytes(frame.position.Z), 0, buffer, i * 0x20 + 0x08, 4); + const short FAR = 30_000; + var position = ghost.frames.ContainsKey((uint)(globalTimer - ghost.playbackBaseFrame - 1)) + ? frame.position + : new Vector3(FAR, FAR, FAR); + + Array.Copy(BitConverter.GetBytes(position.X), 0, buffer, i * 0x20 + 0x00, 4); + Array.Copy(BitConverter.GetBytes(position.Y), 0, buffer, i * 0x20 + 0x04, 4); + Array.Copy(BitConverter.GetBytes(position.Z), 0, buffer, i * 0x20 + 0x08, 4); Array.Copy(BitConverter.GetBytes(ghost.nonMarioGraphics != 0 ? animation : frame.animationIndex), 0, buffer, i * 0x20 + 0x0C, 4); Array.Copy(BitConverter.GetBytes(frame.oPitch), 0, buffer, i * 0x20 + 0x12, 2); Array.Copy(BitConverter.GetBytes(frame.oYaw), 0, buffer, i * 0x20 + 0x10, 2); @@ -209,9 +214,7 @@ public override void Update(bool active) IEnumerable GetSelectedGhosts() { - var lst = listBoxGhosts.SelectedItems.ConvertAndRemoveNull(_ => _ as Ghost); - lst.Sort((a, b) => a.transparent && !b.transparent ? 1 : (a.transparent == b.transparent ? 0 : -1)); - return lst; + return listBoxGhosts.SelectedItems.OfType(); } void AddGhost(string name, Ghost newGhost) From 7c8285ae975e60824472530cdc82fdcba9eccdfc Mon Sep 17 00:00:00 2001 From: FramePerfection <1663221+FramePerfection@users.noreply.github.com> Date: Sat, 19 Sep 2026 19:29:40 +0200 Subject: [PATCH 31/31] bump version --- STROOP/Forms/StroopMainForm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STROOP/Forms/StroopMainForm.cs b/STROOP/Forms/StroopMainForm.cs index dc89f6a62..74e9780ad 100644 --- a/STROOP/Forms/StroopMainForm.cs +++ b/STROOP/Forms/StroopMainForm.cs @@ -26,7 +26,7 @@ namespace STROOP public partial class StroopMainForm : Form { // STROOP VERSION NAME - const string _version = "Refactor 0.8.0"; + const string _version = "Refactor 0.9.0"; public event Action Updating;