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 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..30e144b92 --- /dev/null +++ b/HackSources/Ghosts/export_moving_code_references.asm @@ -0,0 +1,8 @@ +.create "./build/DynamicOffsets.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/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/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..abcb36a9d 100644 --- a/HackSources/Ghosts/ghost_loop.asm +++ b/HackSources/Ghosts/ghost_loop.asm @@ -8,12 +8,12 @@ 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 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 @@ -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 ; +1 because the addiu 0x9B00 below sign-extends; the pair lands at base + 0x9B00 addu t0, t0, at addiu t0, t0, 0x9B00 @@ -121,51 +126,63 @@ 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 -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 +192,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/README.md b/README.md index cf9286ea7..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.
@@ -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 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 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; diff --git a/STROOP/Controls/VariablePanel/VariablePanel.cs b/STROOP/Controls/VariablePanel/VariablePanel.cs index c11bf7d5a..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; @@ -411,19 +409,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) 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; diff --git a/STROOP/Resources/Hacks/GhostHackJP.hck b/STROOP/Resources/Hacks/GhostHackJP.hck index df2629e62..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: 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 diff --git a/STROOP/Resources/Hacks/GhostHackUS.hck b/STROOP/Resources/Hacks/GhostHackUS.hck index d0067db58..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 @@ -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 diff --git a/STROOP/STROOP.csproj b/STROOP/STROOP.csproj index 76291479a..b88c79bdc 100644 --- a/STROOP/STROOP.csproj +++ b/STROOP/STROOP.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows7.0 WinExe true false @@ -291,5 +291,7 @@ - + + $(NoWarn);WFO1000 + 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..14c01c9fd 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 => ghostRegionBase + 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(ghostRegionBase + COLORED_HATS_CODE_OFFSET, addr + 0x14); Config.Stream.SetValue((ushort)0x12A, addr); } } 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.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 a571e2f25..82a8e81df 100644 --- a/STROOP/Tabs/GhostTab/GhostTab.cs +++ b/STROOP/Tabs/GhostTab/GhostTab.cs @@ -13,12 +13,36 @@ using STROOP.Variables; using STROOP.Variables.SM64MemoryLayout; using STROOP.Variables.Utilities; +using System.Globalization; namespace STROOP.Tabs.GhostTab { public partial class GhostTab : STROOPTab { - const uint bufferBaseAddress = 0x80409B00; + + const uint GHOST_LOOP_CODE_OFFSET = 0x8000u; + const uint HACK_FILE_BASE_OFFSET = 0x80400000; + + // 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, 0x190]; + + /// 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 numGhostsAddr => ghostRegionBase + 0x7FFFu; + uint firstGhostPointerAddr => ghostRegionBase + 0x7FF8u; + uint disableRequestAddr => ghostRegionBase + 0x7FFCu; + + uint bufferBaseAddress => ghostRegionBase + 0x9B00u; static IEnumerable GetActiveGhostIndices() { @@ -49,7 +73,6 @@ static void AddSpecialVariables() int lastGlobalTimer; Ghost selectedGhost => listBoxGhosts.SelectedItem as Ghost; - GhostFrame lastValidPlaybackFrame => selectedGhost?.lastValidPlaybackFrame ?? default(GhostFrame); public GhostTab() { @@ -85,7 +108,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, numGhostsAddr); WriteMarioColorToStream(); } @@ -101,22 +124,38 @@ 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; + + 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); + 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; @@ -152,10 +191,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); } } @@ -165,7 +205,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)(firstGhostPointerAddr - ghostIndex * 0x68)); Config.Stream.SetValue((byte)(ghostTransparent ? 1 : 0), ptr + 0x61); lastGlobalTimer = globalTimer; } @@ -174,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) @@ -248,16 +286,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(firstGhostPointerAddr); bool ghostsActive = (ghostPointer & 0xFF000000) == 0x80000000; - bool shouldDisable = Config.Stream.GetByte(0x80407FFC) == 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, 0x80407FFC); + Config.Stream.SetValue((byte)0, disableRequestAddr); } else return true; @@ -315,21 +353,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); } } @@ -344,14 +370,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] = ghostRegionBase + GHOST_LOOP_CODE_OFFSET, + [HACK_FILE_BASE_OFFSET + COLORED_HATS_CODE_OFFSET] = ghostRegionBase + COLORED_HATS_CODE_OFFSET, + }); + Config.Stream.WriteRam(new byte[4], disableRequestAddr, EndiannessType.Little); + Config.Stream.WriteRam(new byte[0x70], ghostRegionBase + 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], ghostRegionBase + GHOST_LOOP_CODE_OFFSET - 0x1000u, EndiannessType.Big); + + // Modify code for moving parts + ushort luiGhostBaseValue = ghostHackBaseHi; + ushort luiFirstAnimationValue = (ushort)(luiGhostBaseValue + 0x10); + + ApplyLui((ushort)(luiGhostBaseValue + 1), ghostRegionBase + GHOST_LOOP_CODE_OFFSET + GhostBaseHi_LUI_PLUS_1); + foreach (var offset in GhostBaseHi_LUI) + ApplyLui(luiGhostBaseValue, ghostRegionBase + GHOST_LOOP_CODE_OFFSET + offset); + ApplyLui(luiFirstAnimationValue, ghostRegionBase + GHOST_LOOP_CODE_OFFSET + FirstAnimationBufferAddrHi_LUI_1); + + ApplyLui(luiGhostBaseValue, ghostRegionBase + COLORED_HATS_CODE_OFFSET + COLORED_HATS_GhostBaseHi_LUI); + + 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); + + void ApplyLui(ushort value, uint address) + => Config.Stream.SetValue(value, address + 2); } } @@ -366,8 +414,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, numGhostsAddr); + Config.Stream.SetValue((byte)0xFF, disableRequestAddr); } } diff --git a/STROOP/Tabs/GhostTab/GhostTabHelp.cs b/STROOP/Tabs/GhostTab/GhostTabHelp.cs index dca138664..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(); @@ -47,7 +51,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 +63,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. 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 898933596..362734ab0 100644 --- a/STROOP/Tabs/MapTab/MapGraphics.cs +++ b/STROOP/Tabs/MapTab/MapGraphics.cs @@ -1,17 +1,20 @@ using System; using System.Collections.Generic; -using OpenTK; using OpenTK.Graphics.OpenGL; using System.Windows.Forms; using System.Drawing; using OpenTK.GLControl; using OpenTK.Mathematics; +using OpenTK.Windowing.Common; using STROOP.Controls; using STROOP.Core; using STROOP.Extensions; using STROOP.Structs; using STROOP.Structs.Configurations; +using STROOP.Tabs.MapTab.Views; using STROOP.Utilities; +using System.Linq; +using System.Reflection; namespace STROOP.Tabs.MapTab { @@ -30,6 +33,13 @@ public enum DrawLayers Overlay, } + public enum ViewMode + { + TopDown, + Orthogonal, + ThreeDimensional + } + static Vector3 ProjectOnLineSegment(Vector3 p, Vector3 A, Vector3 B) { Vector3 d = B - A; @@ -48,13 +58,8 @@ 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 bool IsContextMenuOpen() => contextMenu != null && contextMenu.Visible; + ContextMenuStrip contextMenu; public readonly List[] drawLayers; @@ -76,6 +81,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; @@ -150,28 +159,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 ViewTopDown viewTopDown; + public ViewOrthogonal viewOrthogonal; + public View3D view3D; 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; @@ -199,7 +222,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; } @@ -212,17 +235,29 @@ public Vector2 mousePosition2D public readonly KeyboardControls keyboardControls; - Func getContext; + Func getContext; - public MapGraphics(MapTab mapTab, GLControl glControl, Func getContext = null) + /// + /// 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) { 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); - view = new MapView(); drawLayers = new List[Enum.GetNames(typeof(DrawLayers)).Length]; for (int i = 0; i < drawLayers.Length; i++) drawLayers[i] = new List(); @@ -290,9 +325,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 +344,7 @@ public void Load(Func getRenderers) GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest); InitMainSurfaces(); + InitOrUpdatePresentFrameBuffer(); }); rendererCollection = getRenderers(); @@ -350,14 +389,39 @@ 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 +434,6 @@ private void OnPaint() if (glControl.Cursor != cursor) glControl.Cursor = cursor; - (getContext != null ? getContext() : glControl.Context).MakeCurrent(); UpdateMapView(); GL.BindFramebuffer(FramebufferTarget.Framebuffer, mainFrameBuffer); @@ -389,12 +452,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) @@ -409,11 +472,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(); + } } } @@ -432,63 +511,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; @@ -509,7 +588,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; @@ -521,7 +600,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)); @@ -538,18 +617,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); @@ -569,15 +656,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; @@ -586,14 +673,14 @@ 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; } using (new AccessScope(mapTab)) { - mapTab.UpdateHover(); + UpdateHover(); foreach (var data in mapTab.hoverData) if (e.Button == MouseButtons.Left) data.LeftClick(mapCursorPosition); @@ -602,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) @@ -623,7 +735,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)) @@ -641,7 +753,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; } } @@ -649,24 +761,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)); } } } @@ -681,38 +793,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; } } @@ -721,9 +833,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); @@ -732,7 +844,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; @@ -746,12 +858,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; } } @@ -762,16 +874,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 @@ -802,8 +914,80 @@ 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; + } + } + + 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/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..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.view.mode == MapView.ViewMode.ThreeDimensional, + 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; 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 1c54a087a..e95fd6c6b 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 { @@ -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) { @@ -104,6 +100,20 @@ 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]), + ]; + + ToolStripMenuItem[] itemsShownMeasurements = new ToolStripMenuItem[8]; + Vector3 a, b; Func aProvider, bProvider; @@ -115,9 +125,17 @@ 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++) + { + 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; @@ -129,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; } @@ -136,13 +156,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 +176,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 +196,7 @@ protected override void Draw3D(MapGraphics graphics) else { nameString += "y"; - colorIndex |= 2; + displayIndex |= 2; } if (!float.IsNaN(end.Z)) @@ -187,22 +204,33 @@ 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); + 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 + ); + } } }); } + protected override void DrawOrthogonal(MapGraphics graphics) + => DrawTopDown(graphics); + public override IHoverData GetHoverData(MapGraphics graphics, ref Vector3 position) { 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)) @@ -218,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 b9e3d94c6..8f02d0a7d 100644 --- a/STROOP/Tabs/MapTab/MapPopout.cs +++ b/STROOP/Tabs/MapTab/MapPopout.cs @@ -1,33 +1,46 @@ -using OpenTK; -using System; +using System; 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 tab) + public MapPopout(MapTab mapTab) { + this.mapTab = mapTab; InitializeComponent(); - ClientSize = tab.graphics.glControl.ClientRectangle.Size; - glControl = new GLControl(); + 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 = mapTab.graphics.glControl, + }; glControl.Bounds = ClientRectangle; glControl.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom; Controls.Add(glControl); - 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; + // Render in the main map's (shared) context; present into our own context (handled in MapGraphics). + 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) + graphics.RecreateContextMenu(); }; } 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 10dd5b85a..939ef5865 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; @@ -15,6 +13,7 @@ using OpenTK.Mathematics; using STROOP.Core; using STROOP.Core.Utilities; +using STROOP.Tabs.MapTab.Views; using STROOP.Variables.SM64MemoryLayout; using STROOP.Variables.Utilities; @@ -86,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(); @@ -169,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; @@ -248,7 +250,7 @@ void InitAddTrackerButton() toolStripItem.Click += (sender, e) => addNewTracker(); return toolStripItem; } - )); + )); } } @@ -490,53 +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 = 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.view.mode == MapView.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); - }; - contextMenu.Items.Add(pivotPositionItem); - contextMenu.Items.Add(new ToolStripSeparator()); - } - - 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); + graphics.RecreateContextMenu(contextMenu => + { + 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); + }); }; - contextMenu.Items.Add(openPopoutItem); - - contextMenu.Show(Cursor.Position); } private void LoadDefaultTrackers() @@ -584,18 +552,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 +571,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 +602,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); } @@ -667,33 +635,6 @@ void AddViewModeContextMenu() }; } - public void UpdateHover() - { - using (new AccessScope(this)) - { - if (Form.ActiveForm != null && glControlMap2D.ClientRectangle.Contains(glControlMap2D.PointToClient(Cursor.Position))) - graphics.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.view.ComputeViewDirection(), (newCursor - graphics.view.position)); - graphics.UpdateCursor(); - } - - if (newHover != null) - hoverData.Add(newHover); - } - } - } - } - public override void Update(bool active) { if (!_isLoaded2D) return; @@ -720,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(); @@ -742,13 +689,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 +926,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/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), }); } 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..288bc0bfa --- /dev/null +++ b/STROOP/Tabs/MapTab/Views/ViewBase.cs @@ -0,0 +1,29 @@ +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 +{ + public string name = "Custom"; + + // 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); +} diff --git a/STROOP/Utilities/VariableSelectionUtilities.cs b/STROOP/Utilities/VariableSelectionUtilities.cs index 1bf7e6407..add91f6b2 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)); @@ -238,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++)