-
Notifications
You must be signed in to change notification settings - Fork 3
Add Dune Game Engine Window with DS-relative live memory view #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Copilot
wants to merge
26
commits into
main
Choose a base branch
from
copilot/add-additional-view-dune-engine
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
e4dd54c
Initial plan
Copilot 43a39b8
Changes before error encountered
Copilot 6be0f98
Add Dune Game Engine Window with live memory view
Copilot beb901e
Address code review feedback
Copilot 08fe88a
Add comprehensive game state tabs: Player Stats, NPCs, Sietches, Troops
Copilot 11e2707
Refactor: Remove regions, add dynamic data grids for sietches/troops
Copilot 6600569
Add IPauseHandler integration, NPCs, Smugglers, Locations tabs with p…
Copilot 1d584c0
fix: window wasn't shown
maximilien-noal a32fc86
Fix charisma calculation and improve documentation
Copilot 992574a
Address code review feedback
Copilot be47a22
Fix NPC and Smuggler entry size calculations
Copilot 729388c
Add location status constants and use named constant
Copilot a1e2f6e
Use absolute memory addressing instead of DS-relative
Copilot 39bb850
Improve documentation for absolute memory addressing
Copilot 5fcef0f
Use multiple memory segments for correct data access
Copilot 9390b1e
Restore RefreshSietches call per code review feedback
Copilot 77cf22a
Address copilot AI review comments
Copilot fec175c
Fix memory addresses to use single DS segment 0x1138
Copilot dcb9caf
Fix location coordinates to read single bytes at correct offsets
Copilot 37aa0f0
Fix memory segment addresses based on problem statement
Copilot d1e9a5e
WIP: Switch to DS-relative addressing with MemoryBasedDataStructureWi…
Copilot 4c0b6f9
Complete DuneGameState API with all required properties and methods
Copilot dae09d3
Fix build errors - add missing method aliases and fix static/instance…
Copilot d65f628
fix: some build error (not all)
maximilien-noal 255cdfa
Add all missing accessor methods and remove HNM Video tab
Copilot f54d579
Fix build errors: add GetSietchStatus and make IsTroopFremen static
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| namespace Cryogenic.GameEngineWindow; | ||
|
|
||
| using System; | ||
|
|
||
| using Avalonia.Threading; | ||
|
|
||
| using Cryogenic.GameEngineWindow.ViewModels; | ||
| using Cryogenic.GameEngineWindow.Views; | ||
|
|
||
| using Spice86.Core.Emulator.CPU.Registers; | ||
| using Spice86.Core.Emulator.Memory.ReaderWriter; | ||
| using Spice86.Core.Emulator.VM; | ||
|
|
||
| public static class GameEngineWindowManager { | ||
| private static DuneGameStateWindow? _window; | ||
| private static DuneGameStateViewModel? _viewModel; | ||
| private static bool _isWindowOpen; | ||
|
|
||
| public static void ShowWindow(IByteReaderWriter memory, SegmentRegisters segmentRegisters, IPauseHandler? pauseHandler = null) { | ||
| Dispatcher.UIThread.Post(() => { | ||
| if (_window != null && _isWindowOpen) { | ||
| _window.Show(); | ||
| _window.Activate(); | ||
| return; | ||
| } | ||
|
|
||
| _viewModel?.Dispose(); | ||
| _viewModel = new DuneGameStateViewModel(memory, segmentRegisters, pauseHandler); | ||
| _window = new DuneGameStateWindow { | ||
| DataContext = _viewModel | ||
| }; | ||
|
|
||
| _window.Closed += (sender, args) => { | ||
| _isWindowOpen = false; | ||
| _viewModel?.Dispose(); | ||
| _viewModel = null; | ||
| _window = null; | ||
| }; | ||
|
|
||
| _isWindowOpen = true; | ||
| _window.Show(); | ||
| }); | ||
| } | ||
|
|
||
| public static void CloseWindow() { | ||
| if (!Dispatcher.UIThread.CheckAccess()) { | ||
| Dispatcher.UIThread.Post(CloseWindow); | ||
| return; | ||
| } | ||
|
|
||
| _window?.Close(); | ||
| _viewModel?.Dispose(); | ||
| _window = null; | ||
| _viewModel = null; | ||
| _isWindowOpen = false; | ||
| } | ||
|
|
||
| public static bool IsWindowOpen => _isWindowOpen; | ||
| } |
164 changes: 164 additions & 0 deletions
164
src/Cryogenic/GameEngineWindow/Models/DuneGameState.Locations.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| namespace Cryogenic.GameEngineWindow.Models; | ||
|
|
||
| /// <summary> | ||
| /// Location/Sietch structure accessors for Dune game state. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Location structure (28 bytes per entry, 70 max locations): | ||
| /// - Offset 0: Name first byte (region: 01-0C) | ||
| /// - Offset 1: Name second byte (type: 01-0B, 0A=village) | ||
| /// - Offset 2-7: Coordinates (6 bytes) | ||
| /// - Offset 8: Appearance type | ||
| /// - Offset 9: Housed troop ID | ||
| /// - Offset 10: Status flags | ||
| /// - Offset 11-15: Stage for discovery | ||
| /// - Offset 16: Spice field ID | ||
| /// - Offset 17: Spice amount | ||
| /// - Offset 18: Spice density | ||
| /// - Offset 19: Field J | ||
| /// - Offset 20: Harvesters count | ||
| /// - Offset 21: Ornithopters count | ||
| /// - Offset 22: Krys knives count | ||
| /// - Offset 23: Laser guns count | ||
| /// - Offset 24: Weirding modules count | ||
| /// - Offset 25: Atomics count | ||
| /// - Offset 26: Bulbs count | ||
| /// - Offset 27: Water amount | ||
| /// </remarks> | ||
| public partial class DuneGameState { | ||
| public byte GetLocationNameFirst(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize)]; | ||
| } | ||
|
|
||
| public byte GetLocationNameSecond(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 1]; | ||
| } | ||
|
|
||
| public static string GetLocationNameStr(byte first, byte second) { | ||
| string str = first switch { | ||
| 0x01 => "Arrakeen", | ||
| 0x02 => "Carthag", | ||
| 0x03 => "Tuono", | ||
| 0x04 => "Habbanya", | ||
| 0x05 => "Oxtyn", | ||
| 0x06 => "Tsympo", | ||
| 0x07 => "Bledan", | ||
| 0x08 => "Ergsun", | ||
| 0x09 => "Haga", | ||
| 0x0a => "Cielago", | ||
| 0x0b => "Sihaya", | ||
| 0x0c => "Celimyn", | ||
| _ => $"Unknown({first:X2})" | ||
| }; | ||
|
|
||
| str += second switch { | ||
| 0x01 => " (Atreides)", | ||
| 0x02 => " (Harkonnen)", | ||
| 0x03 => "-Tabr", | ||
| 0x04 => "-Timin", | ||
| 0x05 => "-Tuek", | ||
| 0x06 => "-Harg", | ||
| 0x07 => "-Clam", | ||
| 0x08 => "-Tsymyn", | ||
| 0x09 => "-Siet", | ||
| 0x0a => "-Pyons", | ||
| 0x0b => "-Pyort", | ||
| _ => "" | ||
| }; | ||
|
|
||
| return str; | ||
| } | ||
|
|
||
| public static string GetLocationTypeStr(byte second) => second switch { | ||
| 0x01 => "Atreides Palace", | ||
| 0x02 => "Harkonnen Palace", | ||
| 0x03 => "Sietch (Tabr)", | ||
| 0x04 => "Sietch (Timin)", | ||
| 0x05 => "Sietch (Tuek)", | ||
| 0x06 => "Sietch (Harg)", | ||
| 0x07 => "Sietch (Clam)", | ||
| 0x08 => "Sietch (Tsymyn)", | ||
| 0x09 => "Sietch (Siet)", | ||
| 0x0a => "Village (Pyons)", | ||
| 0x0b => "Sietch (Pyort)", | ||
| _ => $"Unknown Type ({second:X2})" | ||
| }; | ||
|
|
||
| public byte GetLocationAppearance(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 8]; | ||
| } | ||
|
|
||
| public byte GetLocationHousedTroopId(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 9]; | ||
| } | ||
|
|
||
| public byte GetLocationStatus(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 10]; | ||
| } | ||
|
|
||
| public byte GetLocationSpiceFieldId(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 16]; | ||
| } | ||
|
|
||
| public byte GetLocationSpiceAmount(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 17]; | ||
| } | ||
|
|
||
| public byte GetLocationSpiceDensity(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 18]; | ||
| } | ||
|
|
||
| public byte GetLocationHarvesters(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 20]; | ||
| } | ||
|
|
||
| public byte GetLocationOrnithopters(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 21]; | ||
| } | ||
|
|
||
| public byte GetLocationKrysKnives(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 22]; | ||
| } | ||
|
|
||
| public byte GetLocationLaserGuns(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 23]; | ||
| } | ||
|
|
||
| public byte GetLocationWeirdingModules(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 24]; | ||
| } | ||
|
|
||
| public byte GetLocationAtomics(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 25]; | ||
| } | ||
|
|
||
| public byte GetLocationBulbs(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 26]; | ||
| } | ||
|
|
||
| public byte GetLocationWater(int index) { | ||
| if (index < 0 || index >= MaxLocations) return 0; | ||
| return UInt8[LocationBaseOffset + (index * LocationEntrySize) + 27]; | ||
| } | ||
|
|
||
| public (ushort X, ushort Y) GetLocationCoordinates(int index) { | ||
| if (index < 0 || index >= MaxLocations) return (0, 0); | ||
| var baseOffset = LocationBaseOffset + (index * LocationEntrySize); | ||
| return (UInt16[baseOffset + 2], UInt16[baseOffset + 4]); | ||
| } | ||
| } |
70 changes: 70 additions & 0 deletions
70
src/Cryogenic/GameEngineWindow/Models/DuneGameState.Npcs.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| namespace Cryogenic.GameEngineWindow.Models; | ||
|
|
||
| /// <summary> | ||
| /// NPC structure accessors for Dune game state. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// NPC structure (8 bytes per entry + 8 bytes padding = 16 bytes total, 16 NPCs max): | ||
| /// - Offset 0: Sprite identificator | ||
| /// - Offset 1: Field B | ||
| /// - Offset 2: Room location | ||
| /// - Offset 3: Type of place | ||
| /// - Offset 4: Field E | ||
| /// - Offset 5: Exact place | ||
| /// - Offset 6: For dialogue flag | ||
| /// - Offset 7: Field H | ||
| /// </remarks> | ||
| public partial class DuneGameState { | ||
| public byte GetNpcSpriteId(int index) { | ||
| if (index < 0 || index >= MaxNpcs) return 0; | ||
| return UInt8[NpcBaseOffset + (index * NpcEntrySize)]; | ||
| } | ||
|
|
||
| public byte GetNpcRoomLocation(int index) { | ||
| if (index < 0 || index >= MaxNpcs) return 0; | ||
| return UInt8[NpcBaseOffset + (index * NpcEntrySize) + 2]; | ||
| } | ||
|
|
||
| public byte GetNpcPlaceType(int index) { | ||
| if (index < 0 || index >= MaxNpcs) return 0; | ||
| return UInt8[NpcBaseOffset + (index * NpcEntrySize) + 3]; | ||
| } | ||
|
|
||
| public byte GetNpcExactPlace(int index) { | ||
| if (index < 0 || index >= MaxNpcs) return 0; | ||
| return UInt8[NpcBaseOffset + (index * NpcEntrySize) + 5]; | ||
| } | ||
|
|
||
| public byte GetNpcDialogueFlag(int index) { | ||
| if (index < 0 || index >= MaxNpcs) return 0; | ||
| return UInt8[NpcBaseOffset + (index * NpcEntrySize) + 6]; | ||
| } | ||
|
|
||
| public static string GetNpcName(byte npcId) => npcId switch { | ||
| 0 => "None", | ||
| 1 => "Duke Leto Atreides", | ||
| 2 => "Jessica Atreides", | ||
| 3 => "Thufir Hawat", | ||
| 4 => "Duncan Idaho", | ||
| 5 => "Gurney Halleck", | ||
| 6 => "Stilgar", | ||
| 7 => "Liet Kynes", | ||
| 8 => "Chani", | ||
| 9 => "Harah", | ||
| 10 => "Baron Harkonnen", | ||
| 11 => "Feyd-Rautha", | ||
| 12 => "Emperor Shaddam IV", | ||
| 13 => "Harkonnen Captains", | ||
| 14 => "Smugglers", | ||
| 15 => "The Fremen", | ||
| 16 => "The Fremen", | ||
| _ => $"NPC #{npcId}" | ||
| }; | ||
|
|
||
| public static string GetNpcPlaceTypeDescription(byte placeType) => placeType switch { | ||
| 0 => "Not present", | ||
| 1 => "Palace room", | ||
| 2 => "Desert/Outside", | ||
| _ => $"Type {placeType:X2}" | ||
| }; | ||
| } |
88 changes: 88 additions & 0 deletions
88
src/Cryogenic/GameEngineWindow/Models/DuneGameState.Smugglers.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| namespace Cryogenic.GameEngineWindow.Models; | ||
|
|
||
| /// <summary> | ||
| /// Smuggler structure accessors for Dune game state. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Smuggler structure (14 bytes per entry + 3 bytes padding = 17 bytes total, 6 smugglers max): | ||
| /// - Offset 0: Region/location byte | ||
| /// - Offset 1: Willingness to haggle | ||
| /// - Offset 2: Field C | ||
| /// - Offset 3: Field D | ||
| /// - Offset 4: Harvesters in stock | ||
| /// - Offset 5: Ornithopters in stock | ||
| /// - Offset 6: Krys knives in stock | ||
| /// - Offset 7: Laser guns in stock | ||
| /// - Offset 8: Weirding modules in stock | ||
| /// - Offset 9: Harvester price (x10) | ||
| /// - Offset 10: Ornithopter price (x10) | ||
| /// - Offset 11: Krys knife price (x10) | ||
| /// - Offset 12: Laser gun price (x10) | ||
| /// - Offset 13: Weirding module price (x10) | ||
| /// </remarks> | ||
| public partial class DuneGameState { | ||
| public byte GetSmugglerRegion(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize)]; | ||
| } | ||
|
|
||
| public byte GetSmugglerWillingnessToHaggle(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 1]; | ||
| } | ||
|
|
||
| public byte GetSmugglerHarvesters(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 4]; | ||
| } | ||
|
|
||
| public byte GetSmugglerOrnithopters(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 5]; | ||
| } | ||
|
|
||
| public byte GetSmugglerKrysKnives(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 6]; | ||
| } | ||
|
|
||
| public byte GetSmugglerLaserGuns(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 7]; | ||
| } | ||
|
|
||
| public byte GetSmugglerWeirdingModules(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 8]; | ||
| } | ||
|
|
||
| public ushort GetSmugglerHarvesterPrice(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return (ushort)(UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 9] * 10); | ||
| } | ||
|
|
||
| public ushort GetSmugglerOrnithopterPrice(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return (ushort)(UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 10] * 10); | ||
| } | ||
|
|
||
| public ushort GetSmugglerKrysKnifePrice(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return (ushort)(UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 11] * 10); | ||
| } | ||
|
|
||
| public ushort GetSmugglerLaserGunPrice(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return (ushort)(UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 12] * 10); | ||
| } | ||
|
|
||
| public ushort GetSmugglerWeirdingModulePrice(int index) { | ||
| if (index < 0 || index >= MaxSmugglers) return 0; | ||
| return (ushort)(UInt8[SmugglerBaseOffset + (index * SmugglerEntrySize) + 13] * 10); | ||
| } | ||
|
|
||
| public string GetSmugglerLocationName(int index) { | ||
| byte region = GetSmugglerRegion(index); | ||
| return GetLocationNameStr(region, 0x0A); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
CommunityToolkit.Mvvmpackage is added but not used anywhere in the codebase. The ViewModels implementINotifyPropertyChangedmanually rather than using attributes from this toolkit. Consider removing this unused dependency to reduce the package footprint, or utilize the toolkit's features (e.g.,[ObservableProperty],[RelayCommand]) to simplify the ViewModel code.