From d124f2c28858a24c48d19046fbf7ebd8502e45d9 Mon Sep 17 00:00:00 2001 From: Boki Date: Fri, 6 Mar 2026 00:38:30 -0500 Subject: [PATCH] optimizations --- entities.json | 38 ++ profiles/GooGoGaaGa_Default_Copy.json | 166 -------- profiles/_assignments.json | 5 - profiles/_config.json | 88 +++++ profiles/dudemoko_Default.json | 44 +-- src/Automata.Ui/Overlay/D2dNativeMethods.cs | 7 + src/Automata.Ui/Overlay/D2dOverlay.cs | 11 +- src/Automata.Ui/Overlay/D2dRenderContext.cs | 14 + src/Automata.Ui/Overlay/IOverlayLayer.cs | 3 +- .../Overlay/Layers/D2dDebugTextLayer.cs | 39 +- .../Overlay/Layers/D2dEntityLabelLayer.cs | 9 +- src/Automata.Ui/ViewModels/MemoryViewModel.cs | 19 +- src/Automata.Ui/ViewModels/RobotoViewModel.cs | 111 +++++- .../ViewModels/SkillProfileViewModel.cs | 28 ++ src/Automata.Ui/Views/MainWindow.axaml | 236 ++++++------ src/Roboto.Core/Actions.cs | 3 +- src/Roboto.Core/GameState.cs | 2 + src/Roboto.Core/ProfileManager.cs | 137 +++++-- src/Roboto.Core/QuestInfo.cs | 22 ++ src/Roboto.Core/SkillProfile.cs | 48 +++ src/Roboto.Core/TerrainQuery.cs | 88 +++++ src/Roboto.Data/GameStateEnricher.cs | 34 +- src/Roboto.Data/MemoryPoller.cs | 105 ++++- src/Roboto.GameOffsets/Components/Mods.cs | 55 +-- .../Components/Targetable.cs | 10 +- .../Diagnostics/MemoryDiagnostics.cs | 3 +- src/Roboto.Memory/GameMemoryReader.cs | 73 ++-- .../Infrastructure/MemoryProfiler.cs | 65 ++++ .../Infrastructure/ProcessMemory.cs | 23 +- src/Roboto.Memory/Objects/AreaInstance.cs | 52 ++- src/Roboto.Memory/Objects/AreaTemplate.cs | 7 + src/Roboto.Memory/Objects/EntityList.cs | 364 ++++++++++++------ src/Roboto.Memory/Objects/GameStates.cs | 18 +- src/Roboto.Memory/Objects/InGameState.cs | 6 + src/Roboto.Memory/Objects/PlayerSkills.cs | 14 +- src/Roboto.Memory/Objects/QuestFlags.cs | 11 +- src/Roboto.Memory/Objects/Terrain.cs | 32 +- src/Roboto.Memory/Objects/UIElements.cs | 64 ++- src/Roboto.Memory/test.csv | 17 - src/Roboto.Navigation/NavigationController.cs | 33 +- src/Roboto.Navigation/PathFinder.cs | 39 +- src/Roboto.Systems/CombatSystem.cs | 144 ++++++- src/Roboto.Systems/MovementSystem.cs | 8 + src/Roboto.Systems/ThreatSystem.cs | 7 + 44 files changed, 1663 insertions(+), 639 deletions(-) delete mode 100644 profiles/GooGoGaaGa_Default_Copy.json delete mode 100644 profiles/_assignments.json create mode 100644 profiles/_config.json create mode 100644 src/Roboto.Core/QuestInfo.cs create mode 100644 src/Roboto.Core/TerrainQuery.cs create mode 100644 src/Roboto.Memory/Infrastructure/MemoryProfiler.cs delete mode 100644 src/Roboto.Memory/test.csv diff --git a/entities.json b/entities.json index f3c00fa..6e97e97 100644 --- a/entities.json +++ b/entities.json @@ -21,18 +21,25 @@ "Metadata/Chests/LeagueIncursion/EncounterChest", "Metadata/Chests/MossyBoulder1", "Metadata/Chests/MossyBoulder2", + "Metadata/Chests/MossyBoulder3", "Metadata/Chests/MossyChest11", "Metadata/Chests/MossyChest11MagicAndRare", "Metadata/Chests/MossyChest13", "Metadata/Chests/MossyChest14", "Metadata/Chests/MossyChest14MagicAndRare", "Metadata/Chests/MossyChest17", + "Metadata/Chests/MossyChest17MagicAndRare", "Metadata/Chests/MossyChest20", "Metadata/Chests/MossyChest21", "Metadata/Chests/MossyChest26", "Metadata/Chests/MuddyChest1", + "Metadata/Chests/RedvaleChest16", + "Metadata/Chests/RedvaleChest18", + "Metadata/Chests/RedvaleChest22", + "Metadata/Chests/RedvaleChest4", "Metadata/Chests/SirenEggs/SirenEgg_02", "Metadata/Critters/BloodWorm/BloodWormBrown", + "Metadata/Critters/Butterfly/ButterflyRed", "Metadata/Critters/Chicken/Chicken_kingsmarch", "Metadata/Critters/Crow/Crow", "Metadata/Critters/Ferret/Ferret", @@ -62,12 +69,15 @@ "Metadata/MiscellaneousObjects/CameraZoom/MinorZoomIn", "Metadata/MiscellaneousObjects/CameraZoom/TreeOfSouls", "Metadata/MiscellaneousObjects/Checkpoint", + "Metadata/MiscellaneousObjects/CheckpointBoss_Rustking", "Metadata/MiscellaneousObjects/CheckpointTutorial", "Metadata/MiscellaneousObjects/Doodad", "Metadata/MiscellaneousObjects/DoodadInvisible", "Metadata/MiscellaneousObjects/DoodadNoBlocking", "Metadata/MiscellaneousObjects/Environment/EnvLineEnd", "Metadata/MiscellaneousObjects/Environment/EnvLineStart", + "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowBlocking_10_1", + "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowBlocking_15_1", "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowBlocking_20_1", "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowBlocking_5_1", "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowBlocking_6_1", @@ -76,6 +86,7 @@ "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowSink_6_4", "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowSink_8_8", "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowSource_4.75_1", + "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowSource_4_4", "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowSource_6_4", "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowSource_6_6", "Metadata/MiscellaneousObjects/Environment/NonDefaultFlows/FlowSource_8_8", @@ -100,7 +111,11 @@ "Metadata/Monsters/BansheeRemake/WitchHutBanshee", "Metadata/Monsters/CarnivorousPlantEater/OldForest/BossRoomMinimapIcon", "Metadata/Monsters/CarnivorousPlantEater/OldForest/CarnivorousPlantEaterOldForest_", + "Metadata/Monsters/Daemon/Archnemesis/FlameWalkerDaemon", "Metadata/Monsters/Daemon/FungalBurstDaemon", + "Metadata/Monsters/Daemon/LightningCloneRetaliationDaemon", + "Metadata/Monsters/Daemon/RustKingQuestChestDaemon_", + "Metadata/Monsters/Frog/PaleFrog1", "Metadata/Monsters/FungusZombie/FungalBurstMushrooms/FungalBurstSpawner", "Metadata/Monsters/FungusZombie/FungusZombieLarge", "Metadata/Monsters/FungusZombie/FungusZombieMedium", @@ -112,6 +127,7 @@ "Metadata/Monsters/HuhuGrub/HuhuGrubLarvaeEmergeSummoned1_", "Metadata/Monsters/HuhuGrub/HuhuGrubLarvaeRanged1", "Metadata/Monsters/InvisibleFire/MDCarrionCroneWave", + "Metadata/Monsters/MonsterMods/GroundOnDeath/ColdSnapGroundDaemonParent", "Metadata/Monsters/MonsterMods/GroundOnDeath/ShockedGroundDaemonParent", "Metadata/Monsters/MonsterMods/OnDeathColdExplosionParent", "Metadata/Monsters/MudBurrower/Arena_Blocker", @@ -127,6 +143,14 @@ "Metadata/Monsters/NPC/DogTrader_", "Metadata/Monsters/QuillCrab/QuillCrab", "Metadata/Monsters/QuillCrab/QuillCrabBig", + "Metadata/Monsters/RisenArbalest__", + "Metadata/Monsters/SkeletonSoldier/Rusted/RustedSoldierBow", + "Metadata/Monsters/SkeletonSoldier/Rusted/RustedSoldierBowQuest", + "Metadata/Monsters/SkeletonSoldier/Rusted/RustedSoldierCrossbow", + "Metadata/Monsters/SkeletonSoldier/Rusted/RustedSoldierOneHandSword", + "Metadata/Monsters/SkeletonSoldier/Rusted/RustedSoldierOneHandSwordQuest", + "Metadata/Monsters/SkeletonSoldier/Rusted/RustedSoldierOneHandSwordShield", + "Metadata/Monsters/SkeletonSoldier/Rusted/RustedSoldierOneHandSwordShieldQuest", "Metadata/Monsters/Skeletons/RetchSkeletonOneHandSword", "Metadata/Monsters/Skeletons/RetchSkeletonOneHandSwordShield", "Metadata/Monsters/SnakeFlowerMan/BloomSerpentEmerge1", @@ -202,17 +226,22 @@ "Metadata/Pet/ScavengerBat/ScavengerBat", "Metadata/Pet/WayfinderWolf/WayfinderWolf", "Metadata/Projectiles/CarrionCroneIceSpear", + "Metadata/Projectiles/DefaultArrow", "Metadata/Projectiles/Fireball", "Metadata/Projectiles/HagBossIceShard", "Metadata/Projectiles/HuhuGrubLarvaeMortar", "Metadata/Projectiles/IceSpear", + "Metadata/Projectiles/MonsterLightningArrowMock", "Metadata/Projectiles/MudBurrowerAcidMortarSmall", "Metadata/Projectiles/MudBurrowerBloodProj", "Metadata/Projectiles/MudBurrowerGoopMortar", "Metadata/Projectiles/MudBurrowerGoopProjectile", "Metadata/Projectiles/QuillCrabShrapnel", "Metadata/Projectiles/QuillCrabSpike", + "Metadata/Projectiles/RisenArbalestBasicProjectile", + "Metadata/Projectiles/RisenArbalestBurningSnipe", "Metadata/Projectiles/SlingUrchinProjectile", + "Metadata/Projectiles/SnakeFlowerManSpit", "Metadata/Projectiles/Spark", "Metadata/Projectiles/Twister", "Metadata/QuestObjects/Four_Act1/TreeOfSoulsRoots", @@ -245,12 +274,20 @@ "Metadata/Terrain/Gallows/Act1/1_4/Objects/TreeOfSoulsNailStake2", "Metadata/Terrain/Gallows/Act1/1_4/Objects/TreeOfSoulsNailStake3", "Metadata/Terrain/Gallows/Act1/1_4/Objects/WitchHutTitle", + "Metadata/Terrain/Gallows/Act1/1_5/Objects/Controller1", + "Metadata/Terrain/Gallows/Act1/1_5/Objects/Controller2", + "Metadata/Terrain/Gallows/Act1/1_5/Objects/Controller3", + "Metadata/Terrain/Gallows/Act1/1_5/Objects/ControllerSpawner", + "Metadata/Terrain/Gallows/Act1/1_5/Objects/QuestChestBase", + "Metadata/Terrain/Gallows/Act1/1_5/Objects/T2_WeaponDrop", + "Metadata/Terrain/Gallows/Act1/1_5/Objects/TheRustKingInert", "Metadata/Terrain/Gallows/Act1/1_town_ExileEncampment/Objects/Act1_finished_LightController", "Metadata/Terrain/Gallows/Act1/1_town_ExileEncampment/Objects/CraftingBenchEzomyte", "Metadata/Terrain/Gallows/Act1/1_town_ExileEncampment/Objects/CraftingBench_DisableRendering", "Metadata/Terrain/Gallows/Act1/1_town_ExileEncampment/Objects/CraftingBench_EnableRendering", "Metadata/Terrain/Gallows/Act1/1_town_ExileEncampment/Objects/VisitedAct2_DisableRendering", "Metadata/Terrain/Gallows/Act1/1_town_ExileEncampment/Objects/VisitedAct2_EnableRendering", + "Metadata/Terrain/Gallows/Leagues/Incursion/Objects/TemplePortal", "Metadata/Terrain/Tools/AudioTools/G1_1/TownEntrance", "Metadata/Terrain/Tools/AudioTools/G1_2/BurrowEntrance", "Metadata/Terrain/Tools/AudioTools/G1_2/ForestEntrance", @@ -258,6 +295,7 @@ "Metadata/Terrain/Tools/AudioTools/G1_2/RiverRapidsMedium", "Metadata/Terrain/Tools/AudioTools/G1_3/TunnelA", "Metadata/Terrain/Tools/AudioTools/G1_4/WitchHutIndoorAudio", + "Metadata/Terrain/Tools/AudioTools/G1_5/BattleAudio", "Metadata/Terrain/Tools/AudioTools/G1_5/OldForestEntrance", "Metadata/Terrain/Tools/AudioTools/G1_Town/FurnaceFireAudio", "Metadata/Terrain/Tools/AudioTools/G1_Town/InsideWaterMillAudio" diff --git a/profiles/GooGoGaaGa_Default_Copy.json b/profiles/GooGoGaaGa_Default_Copy.json deleted file mode 100644 index deebd77..0000000 --- a/profiles/GooGoGaaGa_Default_Copy.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "Name": "GooGoGaaGa_Default_Copy", - "CreatedAt": "2026-03-03T16:40:21.060139Z", - "LastModified": "2026-03-03T16:57:12.368683Z", - "Flasks": { - "LifeFlaskThreshold": 50, - "ManaFlaskThreshold": 50, - "FlaskCooldownMs": 4000, - "LifeFlaskScanCode": 2, - "ManaFlaskScanCode": 3 - }, - "Combat": { - "GlobalCooldownMs": 500, - "AttackRange": 600, - "SafeRange": 400, - "KiteEnabled": false, - "KiteRange": 300, - "KiteDelayMs": 200 - }, - "Skills": [ - { - "SlotIndex": 0, - "Label": "LMB", - "SkillName": "MeleeSpearOffHand", - "InputType": "LeftClick", - "ScanCode": 0, - "Priority": 2, - "IsEnabled": true, - "CooldownMs": 300, - "RangeMin": 0, - "RangeMax": 10, - "TargetSelection": "Nearest", - "RequiresTarget": true, - "IsAura": false, - "IsMovementSkill": false, - "MinMonstersInRange": 1, - "MaintainPressed": false - }, - { - "SlotIndex": 1, - "Label": "RMB", - "SkillName": "SpearThrow", - "InputType": "RightClick", - "ScanCode": 0, - "Priority": 1, - "IsEnabled": true, - "CooldownMs": 300, - "RangeMin": 0, - "RangeMax": 800, - "TargetSelection": "All", - "RequiresTarget": true, - "IsAura": false, - "IsMovementSkill": false, - "MinMonstersInRange": 1, - "MaintainPressed": false - }, - { - "SlotIndex": 2, - "Label": "MMB", - "SkillName": "Twister", - "InputType": "MiddleClick", - "ScanCode": 0, - "Priority": 0, - "IsEnabled": true, - "CooldownMs": 300, - "RangeMin": 0, - "RangeMax": 1000, - "TargetSelection": "All", - "RequiresTarget": true, - "IsAura": false, - "IsMovementSkill": false, - "MinMonstersInRange": 1, - "MaintainPressed": false - }, - { - "SlotIndex": 3, - "Label": "Q", - "SkillName": null, - "InputType": "KeyPress", - "ScanCode": 16, - "Priority": 3, - "IsEnabled": true, - "CooldownMs": 300, - "RangeMin": 0, - "RangeMax": 600, - "TargetSelection": "Nearest", - "RequiresTarget": true, - "IsAura": false, - "IsMovementSkill": false, - "MinMonstersInRange": 1, - "MaintainPressed": false - }, - { - "SlotIndex": 4, - "Label": "E", - "SkillName": null, - "InputType": "KeyPress", - "ScanCode": 18, - "Priority": 4, - "IsEnabled": true, - "CooldownMs": 300, - "RangeMin": 0, - "RangeMax": 600, - "TargetSelection": "Nearest", - "RequiresTarget": true, - "IsAura": false, - "IsMovementSkill": false, - "MinMonstersInRange": 1, - "MaintainPressed": false - }, - { - "SlotIndex": 5, - "Label": "R", - "SkillName": null, - "InputType": "KeyPress", - "ScanCode": 19, - "Priority": 5, - "IsEnabled": true, - "CooldownMs": 300, - "RangeMin": 0, - "RangeMax": 600, - "TargetSelection": "Nearest", - "RequiresTarget": true, - "IsAura": false, - "IsMovementSkill": false, - "MinMonstersInRange": 1, - "MaintainPressed": false - }, - { - "SlotIndex": 6, - "Label": "T", - "SkillName": null, - "InputType": "KeyPress", - "ScanCode": 20, - "Priority": 6, - "IsEnabled": true, - "CooldownMs": 300, - "RangeMin": 0, - "RangeMax": 600, - "TargetSelection": "Nearest", - "RequiresTarget": true, - "IsAura": false, - "IsMovementSkill": false, - "MinMonstersInRange": 1, - "MaintainPressed": false - }, - { - "SlotIndex": 7, - "Label": "F", - "SkillName": null, - "InputType": "KeyPress", - "ScanCode": 33, - "Priority": 7, - "IsEnabled": true, - "CooldownMs": 300, - "RangeMin": 0, - "RangeMax": 600, - "TargetSelection": "Nearest", - "RequiresTarget": true, - "IsAura": false, - "IsMovementSkill": false, - "MinMonstersInRange": 1, - "MaintainPressed": false - } - ] -} \ No newline at end of file diff --git a/profiles/_assignments.json b/profiles/_assignments.json deleted file mode 100644 index c1f9fba..0000000 --- a/profiles/_assignments.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "GooGoGaaGa": "GooGoGaaGa_Default_Copy", - "terdsare": "terdsare_Default", - "dudemoko": "dudemoko_Default" -} \ No newline at end of file diff --git a/profiles/_config.json b/profiles/_config.json new file mode 100644 index 0000000..3c0c78f --- /dev/null +++ b/profiles/_config.json @@ -0,0 +1,88 @@ +{ + "LastCharacter": "GooGoGaaGa", + "Assignments": { + "GooGoGaaGa": "dudemoko_Default", + "terdsare": "terdsare_Default", + "dudemoko": "dudemoko_Default" + }, + "SkillDefaults": { + "MeleeSpearOffHand": { + "Priority": 0, + "IsEnabled": true, + "CooldownMs": 300, + "RangeMin": 0, + "RangeMax": 100, + "TargetSelection": "Nearest", + "RequiresTarget": true, + "IsAura": false, + "IsMovementSkill": false, + "MinMonstersInRange": 1, + "MaintainPressed": false + }, + "SpearThrow": { + "Priority": 2, + "IsEnabled": true, + "CooldownMs": 300, + "RangeMin": 0, + "RangeMax": 800, + "TargetSelection": "Nearest", + "RequiresTarget": true, + "IsAura": false, + "IsMovementSkill": false, + "MinMonstersInRange": 1, + "MaintainPressed": false + }, + "ShieldBlock": { + "Priority": 2, + "IsEnabled": true, + "CooldownMs": 300, + "RangeMin": 0, + "RangeMax": 600, + "TargetSelection": "Nearest", + "RequiresTarget": true, + "IsAura": false, + "IsMovementSkill": false, + "MinMonstersInRange": 1, + "MaintainPressed": false + }, + "WhirlingSlash": { + "Priority": 0, + "IsEnabled": true, + "CooldownMs": 300, + "RangeMin": 0, + "RangeMax": 300, + "TargetSelection": "All", + "RequiresTarget": false, + "IsAura": false, + "IsMovementSkill": false, + "MinMonstersInRange": 3, + "MaintainPressed": false + }, + "Twister": { + "Priority": 0, + "IsEnabled": true, + "CooldownMs": 300, + "RangeMin": 0, + "RangeMax": 800, + "TargetSelection": "All", + "RequiresTarget": false, + "IsAura": false, + "IsMovementSkill": false, + "MinMonstersInRange": 1, + "MaintainPressed": false + }, + "Spark": { + "Priority": 0, + "IsEnabled": true, + "CooldownMs": 300, + "RangeMin": 0, + "RangeMax": 1000, + "TargetSelection": "Nearest", + "RequiresTarget": false, + "IsAura": false, + "IsMovementSkill": false, + "MinMonstersInRange": 1, + "MaintainPressed": false + } + } +} \ No newline at end of file diff --git a/profiles/dudemoko_Default.json b/profiles/dudemoko_Default.json index d338f17..4abf757 100644 --- a/profiles/dudemoko_Default.json +++ b/profiles/dudemoko_Default.json @@ -1,7 +1,7 @@ { "Name": "dudemoko_Default", "CreatedAt": "2026-03-05T05:44:54.8014147Z", - "LastModified": "2026-03-05T05:44:54.8014154Z", + "LastModified": "2026-03-06T03:52:50.0165867Z", "Flasks": { "LifeFlaskThreshold": 50, "ManaFlaskThreshold": 50, @@ -12,8 +12,8 @@ "Combat": { "GlobalCooldownMs": 500, "AttackRange": 600, - "SafeRange": 400, - "KiteEnabled": false, + "SafeRange": 300, + "KiteEnabled": true, "KiteRange": 300, "KiteDelayMs": 200 }, @@ -21,14 +21,14 @@ { "SlotIndex": 0, "Label": "LMB", - "SkillName": null, + "SkillName": "MeleeSpearOffHand", "InputType": "LeftClick", "ScanCode": 0, "Priority": 0, "IsEnabled": true, "CooldownMs": 300, "RangeMin": 0, - "RangeMax": 600, + "RangeMax": 100, "TargetSelection": "Nearest", "RequiresTarget": true, "IsAura": false, @@ -39,14 +39,14 @@ { "SlotIndex": 1, "Label": "RMB", - "SkillName": null, + "SkillName": "SpearThrow", "InputType": "RightClick", "ScanCode": 0, - "Priority": 1, + "Priority": 2, "IsEnabled": true, "CooldownMs": 300, "RangeMin": 0, - "RangeMax": 600, + "RangeMax": 800, "TargetSelection": "Nearest", "RequiresTarget": true, "IsAura": false, @@ -57,7 +57,7 @@ { "SlotIndex": 2, "Label": "MMB", - "SkillName": null, + "SkillName": "ShieldBlock", "InputType": "MiddleClick", "ScanCode": 0, "Priority": 2, @@ -75,34 +75,34 @@ { "SlotIndex": 3, "Label": "Q", - "SkillName": null, + "SkillName": "WhirlingSlash", "InputType": "KeyPress", "ScanCode": 16, - "Priority": 3, + "Priority": 0, "IsEnabled": true, "CooldownMs": 300, "RangeMin": 0, - "RangeMax": 600, - "TargetSelection": "Nearest", - "RequiresTarget": true, + "RangeMax": 300, + "TargetSelection": "All", + "RequiresTarget": false, "IsAura": false, "IsMovementSkill": false, - "MinMonstersInRange": 1, + "MinMonstersInRange": 3, "MaintainPressed": false }, { "SlotIndex": 4, "Label": "E", - "SkillName": null, + "SkillName": "Twister", "InputType": "KeyPress", "ScanCode": 18, - "Priority": 4, + "Priority": 0, "IsEnabled": true, "CooldownMs": 300, "RangeMin": 0, - "RangeMax": 600, - "TargetSelection": "Nearest", - "RequiresTarget": true, + "RangeMax": 800, + "TargetSelection": "All", + "RequiresTarget": false, "IsAura": false, "IsMovementSkill": false, "MinMonstersInRange": 1, @@ -111,7 +111,7 @@ { "SlotIndex": 5, "Label": "R", - "SkillName": null, + "SkillName": "Spark", "InputType": "KeyPress", "ScanCode": 19, "Priority": 5, @@ -129,7 +129,7 @@ { "SlotIndex": 6, "Label": "T", - "SkillName": null, + "SkillName": "MeleeUnarmed", "InputType": "KeyPress", "ScanCode": 20, "Priority": 6, diff --git a/src/Automata.Ui/Overlay/D2dNativeMethods.cs b/src/Automata.Ui/Overlay/D2dNativeMethods.cs index 8befc8f..f33e9f7 100644 --- a/src/Automata.Ui/Overlay/D2dNativeMethods.cs +++ b/src/Automata.Ui/Overlay/D2dNativeMethods.cs @@ -145,6 +145,13 @@ internal static partial class D2dNativeMethods [LibraryImport("winmm.dll")] internal static partial uint timeEndPeriod(uint uPeriod); + // --- user32.dll (input) --- + + [LibraryImport("user32.dll")] + internal static partial short GetAsyncKeyState(int vKey); + + internal const int VK_F10 = 0x79; + // --- Helpers --- internal static void ShowNoActivate(nint hwnd) => ShowWindow(hwnd, SW_SHOWNOACTIVATE); diff --git a/src/Automata.Ui/Overlay/D2dOverlay.cs b/src/Automata.Ui/Overlay/D2dOverlay.cs index 8f91cac..5cc155b 100644 --- a/src/Automata.Ui/Overlay/D2dOverlay.cs +++ b/src/Automata.Ui/Overlay/D2dOverlay.cs @@ -3,6 +3,7 @@ using System.Runtime; using System.Runtime.InteropServices; using Automata.Bot; using Automata.Ui.Overlay.Layers; +using Roboto.Memory; using Vortice.Mathematics; using static Automata.Ui.Overlay.D2dNativeMethods; @@ -94,6 +95,7 @@ public sealed class D2dOverlay int focusCounter = 0; bool shown = true; long lastFrameTimestamp = Stopwatch.GetTimestamp(); + bool f10WasDown = false; ShowNoActivate(hwnd); Console.WriteLine($"[D2dOverlay] Started (hwnd={hwnd:X})"); @@ -108,6 +110,12 @@ public sealed class D2dOverlay UpdateFocusVisibility(ref focusCounter, ref shown, hwnd); UpdateFps(fpsWatch, ref frameCount, ref fps); + // F10 toggle for memory profiler + var f10Down = (GetAsyncKeyState(VK_F10) & 0x8000) != 0; + if (f10Down && !f10WasDown) + MemoryProfiler.IsEnabled = !MemoryProfiler.IsEnabled; + f10WasDown = f10Down; + var state = BuildState(fps, timing); var snapMs = ElapsedMs(frameStart); @@ -203,7 +211,8 @@ public sealed class D2dOverlay LootLabels: _bot.LootDebugDetector.Latest, FightPosition: _bot.KulemakExecutor.FightPosition, Fps: fps, - Timing: timing); + Timing: timing, + ProfilerData: MemoryProfiler.LatestData); } private void Render(D2dRenderContext ctx, OverlayState state, double[] layerMs, int layerCount) diff --git a/src/Automata.Ui/Overlay/D2dRenderContext.cs b/src/Automata.Ui/Overlay/D2dRenderContext.cs index 884047c..c81fa3c 100644 --- a/src/Automata.Ui/Overlay/D2dRenderContext.cs +++ b/src/Automata.Ui/Overlay/D2dRenderContext.cs @@ -36,6 +36,12 @@ public sealed class D2dRenderContext : IDisposable public ID2D1SolidColorBrush DebugTextBrush { get; private set; } = null!; public ID2D1SolidColorBrush TimingBrush { get; private set; } = null!; public ID2D1SolidColorBrush DebugBgBrush { get; private set; } = null!; + public ID2D1SolidColorBrush ProfilerBrush { get; private set; } = null!; + + // Rarity brushes for entity labels + public ID2D1SolidColorBrush MagicBrush { get; private set; } = null!; + public ID2D1SolidColorBrush RareBrush { get; private set; } = null!; + public ID2D1SolidColorBrush UniqueBrush { get; private set; } = null!; // Text formats public IDWriteTextFormat LabelFormat { get; } // 12pt — enemy labels @@ -93,6 +99,10 @@ public sealed class D2dRenderContext : IDisposable DebugTextBrush = RenderTarget.CreateSolidColorBrush(new Color4(80 / 255f, 1f, 80 / 255f, 1f)); TimingBrush = RenderTarget.CreateSolidColorBrush(new Color4(1f, 200 / 255f, 80 / 255f, 1f)); DebugBgBrush = RenderTarget.CreateSolidColorBrush(new Color4(0f, 0f, 0f, 160 / 255f)); + ProfilerBrush = RenderTarget.CreateSolidColorBrush(new Color4(180 / 255f, 140 / 255f, 1f, 1f)); // light purple + MagicBrush = RenderTarget.CreateSolidColorBrush(new Color4(0.4f, 0.53f, 1f, 1f)); // #6688FF + RareBrush = RenderTarget.CreateSolidColorBrush(new Color4(1f, 0.93f, 0.34f, 1f)); // #FFEE57 + UniqueBrush = RenderTarget.CreateSolidColorBrush(new Color4(1f, 0.55f, 0f, 1f)); // #FF8C00 } private void DisposeBrushes() @@ -111,6 +121,10 @@ public sealed class D2dRenderContext : IDisposable DebugTextBrush?.Dispose(); TimingBrush?.Dispose(); DebugBgBrush?.Dispose(); + ProfilerBrush?.Dispose(); + MagicBrush?.Dispose(); + RareBrush?.Dispose(); + UniqueBrush?.Dispose(); } /// diff --git a/src/Automata.Ui/Overlay/IOverlayLayer.cs b/src/Automata.Ui/Overlay/IOverlayLayer.cs index 1bd78fb..b220771 100644 --- a/src/Automata.Ui/Overlay/IOverlayLayer.cs +++ b/src/Automata.Ui/Overlay/IOverlayLayer.cs @@ -19,7 +19,8 @@ public record OverlayState( IReadOnlyList LootLabels, (double X, double Y)? FightPosition, double Fps, - RenderTiming? Timing); + RenderTiming? Timing, + Dictionary? ProfilerData = null); public class RenderTiming { diff --git a/src/Automata.Ui/Overlay/Layers/D2dDebugTextLayer.cs b/src/Automata.Ui/Overlay/Layers/D2dDebugTextLayer.cs index 2fbd137..f5d6c7f 100644 --- a/src/Automata.Ui/Overlay/Layers/D2dDebugTextLayer.cs +++ b/src/Automata.Ui/Overlay/Layers/D2dDebugTextLayer.cs @@ -15,11 +15,12 @@ internal sealed class D2dDebugTextLayer : ID2dOverlayLayer, IDisposable private readonly CachedLine[] _left = new CachedLine[8]; private readonly CachedLine[] _right = new CachedLine[8]; + private readonly CachedLine[] _profiler = new CachedLine[20]; public void Draw(D2dRenderContext ctx, OverlayState state) { var rt = ctx.RenderTarget; - int lc = 0, rc = 0; + int lc = 0, rc = 0, pc = 0; // Left column: game state UpdateCache(ctx, _left, ref lc, $"FPS: {state.Fps:F0}", ctx.DebugTextBrush); @@ -40,12 +41,28 @@ internal sealed class D2dDebugTextLayer : ID2dOverlayLayer, IDisposable UpdateCache(ctx, _right, ref rc, $"render total: {t.TotalRenderMs:F2}ms", ctx.TimingBrush); } + // Profiler column (F10 toggle) + if (state.ProfilerData is { Count: > 0 } data) + { + UpdateCache(ctx, _profiler, ref pc, "-- Profiler (F10) --", ctx.ProfilerBrush); + long totalReads = 0, totalBytes = 0; + foreach (var kvp in data.OrderByDescending(x => x.Value.Reads)) + { + var (reads, bytes) = kvp.Value; + totalReads += reads; + totalBytes += bytes; + UpdateCache(ctx, _profiler, ref pc, $"{kvp.Key,-14} {reads,5}r {bytes / 1024,4}KB", ctx.ProfilerBrush); + } + UpdateCache(ctx, _profiler, ref pc, $"{"Total",-14} {totalReads,5}r {totalBytes / 1024,4}KB", ctx.ProfilerBrush); + } + // Measure columns Measure(_left, lc, out var leftW, out var leftH); Measure(_right, rc, out var rightW, out var rightH); + Measure(_profiler, pc, out var profW, out var profH); - var totalW = leftW + (rc > 0 ? ColumnGap + rightW : 0); - var totalH = Math.Max(leftH, rightH); + var totalW = leftW + (rc > 0 ? ColumnGap + rightW : 0) + (pc > 0 ? ColumnGap + profW : 0); + var totalH = Math.Max(Math.Max(leftH, rightH), profH); // Background rt.FillRectangle( @@ -53,9 +70,20 @@ internal sealed class D2dDebugTextLayer : ID2dOverlayLayer, IDisposable ctx.DebugBgBrush); // Draw columns - DrawColumn(rt, _left, lc, StartX, StartY); + var x = StartX; + DrawColumn(rt, _left, lc, x, StartY); + x += leftW; if (rc > 0) - DrawColumn(rt, _right, rc, StartX + leftW + ColumnGap, StartY); + { + x += ColumnGap; + DrawColumn(rt, _right, rc, x, StartY); + x += rightW; + } + if (pc > 0) + { + x += ColumnGap; + DrawColumn(rt, _profiler, pc, x, StartY); + } } private static void Measure(CachedLine[] col, int count, out float maxW, out float totalH) @@ -98,6 +126,7 @@ internal sealed class D2dDebugTextLayer : ID2dOverlayLayer, IDisposable { for (int i = 0; i < _left.Length; i++) _left[i].Layout?.Dispose(); for (int i = 0; i < _right.Length; i++) _right[i].Layout?.Dispose(); + for (int i = 0; i < _profiler.Length; i++) _profiler[i].Layout?.Dispose(); } private struct CachedLine diff --git a/src/Automata.Ui/Overlay/Layers/D2dEntityLabelLayer.cs b/src/Automata.Ui/Overlay/Layers/D2dEntityLabelLayer.cs index 878fac5..de046de 100644 --- a/src/Automata.Ui/Overlay/Layers/D2dEntityLabelLayer.cs +++ b/src/Automata.Ui/Overlay/Layers/D2dEntityLabelLayer.cs @@ -86,7 +86,14 @@ internal sealed class D2dEntityLabelLayer : ID2dOverlayLayer, IDisposable new RectangleF(labelX - 2, labelY - 1, m.Width + 4, m.Height + 2), ctx.LabelBgBrush); - rt.DrawTextLayout(new Vector2(labelX, labelY), layout, ctx.Cyan); + var brush = entry.Rarity switch + { + 1 => ctx.MagicBrush, + 2 => ctx.RareBrush, + 3 => ctx.UniqueBrush, + _ => ctx.White, + }; + rt.DrawTextLayout(new Vector2(labelX, labelY), layout, brush); } } diff --git a/src/Automata.Ui/ViewModels/MemoryViewModel.cs b/src/Automata.Ui/ViewModels/MemoryViewModel.cs index 48fc044..1c9ac12 100644 --- a/src/Automata.Ui/ViewModels/MemoryViewModel.cs +++ b/src/Automata.Ui/ViewModels/MemoryViewModel.cs @@ -14,6 +14,7 @@ namespace Automata.Ui.ViewModels; public partial class MemoryNodeViewModel : ObservableObject { [ObservableProperty] private string _name; + [ObservableProperty] private string _nameColor = "#8b949e"; [ObservableProperty] private string _value = ""; [ObservableProperty] private string _valueColor = "#484f58"; [ObservableProperty] private bool _isExpanded; @@ -995,8 +996,9 @@ public partial class MemoryViewModel : ObservableObject { if (_entityListNode is null) return; - // Group by type, sorted by type name + // Group by type, sorted by type name (exclude dead entities) var groups = entities + .Where(e => e.IsAlive || !e.HasVitals) .GroupBy(e => e.Type) .OrderBy(g => g.Key.ToString()); @@ -1031,17 +1033,19 @@ public partial class MemoryViewModel : ObservableObject var e = sorted[i]; var label = FormatEntityName(e); var value = FormatEntityValue(e); + var color = RarityColor(e.Rarity); if (i < groupNode.Children.Count) { var existing = groupNode.Children[i]; existing.Name = label; + existing.NameColor = color; existing.Set(value, e.HasPosition); UpdateEntityChildren(existing, e); } else { - var node = new MemoryNodeViewModel(label) { IsExpanded = false }; + var node = new MemoryNodeViewModel(label) { IsExpanded = false, NameColor = color }; node.Set(value, e.HasPosition); UpdateEntityChildren(node, e); groupNode.Children.Add(node); @@ -1105,6 +1109,17 @@ public partial class MemoryViewModel : ObservableObject return parts.Count > 0 ? string.Join(" ", parts) : "—"; } + /// + /// Map entity rarity to label color: white=normal, blue=magic, yellow=rare, orange=unique. + /// + private static string RarityColor(int rarity) => rarity switch + { + 1 => "#6688ff", // Magic — blue + 2 => "#ffee57", // Rare — yellow + 3 => "#ff8c00", // Unique — orange + _ => "#8b949e", // Normal / non-monster — default gray + }; + private static void UpdateEntityChildren(MemoryNodeViewModel node, Entity e) { // Build children: address, position, vitals, components diff --git a/src/Automata.Ui/ViewModels/RobotoViewModel.cs b/src/Automata.Ui/ViewModels/RobotoViewModel.cs index 61e7215..f6a1251 100644 --- a/src/Automata.Ui/ViewModels/RobotoViewModel.cs +++ b/src/Automata.Ui/ViewModels/RobotoViewModel.cs @@ -30,12 +30,14 @@ public readonly struct EntityOverlayEntry { public readonly float X, Y; public readonly string Label; + public readonly int Rarity; // 0=Normal, 1=Magic, 2=Rare, 3=Unique - public EntityOverlayEntry(float x, float y, string label) + public EntityOverlayEntry(float x, float y, string label, int rarity = 0) { X = x; Y = y; Label = label; + Rarity = rarity; } } @@ -50,10 +52,11 @@ public partial class EntityListItem : ObservableObject public string Distance { get; set; } public float X { get; set; } public float Y { get; set; } + public int Rarity { get; } [ObservableProperty] private bool _isChecked; - public EntityListItem(uint id, string label, string category, float distance, float x, float y) + public EntityListItem(uint id, string label, string category, float distance, float x, float y, int rarity = 0) { Id = id; Label = label; @@ -61,6 +64,7 @@ public partial class EntityListItem : ObservableObject Distance = $"{distance:F0}"; X = x; Y = y; + Rarity = rarity; } } @@ -93,11 +97,21 @@ public partial class RobotoViewModel : ObservableObject, IDisposable // Systems [ObservableProperty] private string _systemsInfo = "—"; + [ObservableProperty] private string _inactiveSystems = ""; [ObservableProperty] private string _apmInfo = "0"; // Navigation [ObservableProperty] private string _navMode = "Idle"; [ObservableProperty] private string _navStatus = "—"; + [ObservableProperty] private string _progressionPhase = "—"; + [ObservableProperty] private string _progressionTarget = "—"; + [ObservableProperty] private string _questTarget = "—"; + [ObservableProperty] private string _lootStatus = "—"; + + // Memory stats + [ObservableProperty] private string _memReads = "—"; + [ObservableProperty] private string _memBandwidth = "—"; + [ObservableProperty] private string _memEntities = "—"; // Terrain minimap [ObservableProperty] private Bitmap? _terrainImage; @@ -183,6 +197,17 @@ public partial class RobotoViewModel : ObservableObject, IDisposable logWatcher.AreaEntered += area => _engine.SetCurrentAreaName(area); if (logWatcher.CurrentArea is { Length: > 0 } current) _engine.SetCurrentAreaName(current); + + // Load last character's profile so the Profile tab is populated before bot starts + var lastChar = _engine.Profiles.GetLastCharacter() + ?? _engine.Profiles.GetMostRecentCharacter(); + if (lastChar is { Length: > 0 }) + { + CharacterName = lastChar; + var profile = _engine.Profiles.LoadForCharacter(lastChar); + _engine.ApplyProfile(profile); + PopulateFromProfile(profile); + } } [RelayCommand] @@ -215,6 +240,13 @@ public partial class RobotoViewModel : ObservableObject, IDisposable ApmInfo = "0"; NavMode = "Idle"; NavStatus = "—"; + ProgressionPhase = "—"; + ProgressionTarget = "—"; + QuestTarget = "—"; + LootStatus = "—"; + MemReads = "—"; + MemBandwidth = "—"; + MemEntities = "—"; Entities.Clear(); OverlayData = null; SharedCache = null; @@ -280,8 +312,8 @@ public partial class RobotoViewModel : ObservableObject, IDisposable [RelayCommand] private void DeleteProfile() { - var charName = _engine.Cache.CharacterName; - if (charName is null || SelectedProfile is null) return; + var charName = CharacterName; + if (charName is "—" or null || SelectedProfile is null) return; // Don't allow deleting the last profile if (AvailableProfiles.Count <= 1) return; @@ -308,8 +340,8 @@ public partial class RobotoViewModel : ObservableObject, IDisposable { if (_suppressProfileSwitch || value is null) return; - var charName = _engine.Cache.CharacterName; - if (charName is null) return; + var charName = CharacterName; + if (charName is "—" or null) return; // Don't re-switch if it's already the active profile if (_engine.ActiveProfile?.Name == value) return; @@ -319,8 +351,8 @@ public partial class RobotoViewModel : ObservableObject, IDisposable private void SwitchToProfile(string profileName) { - var charName = _engine.Cache.CharacterName; - if (charName is null) return; + var charName = CharacterName; + if (charName is "—" or null) return; _engine.Profiles.AssignToCharacter(charName, profileName); var profile = _engine.Profiles.LoadForCharacter(charName); @@ -348,7 +380,11 @@ public partial class RobotoViewModel : ObservableObject, IDisposable // Skills SkillProfiles.Clear(); foreach (var skill in profile.Skills) - SkillProfiles.Add(new SkillProfileViewModel(skill)); + { + var vm = new SkillProfileViewModel(skill); + vm.SetProfileManager(_engine.Profiles); + SkillProfiles.Add(vm); + } RefreshAvailableProfiles(); _suppressProfileSwitch = true; @@ -395,9 +431,11 @@ public partial class RobotoViewModel : ObservableObject, IDisposable PlayerMana = p.ManaTotal > 0 ? $"{p.ManaCurrent}/{p.ManaTotal} ({p.ManaPercent:F0}%)" : "—"; PlayerEs = p.EsTotal > 0 ? $"{p.EsCurrent}/{p.EsTotal} ({p.EsPercent:F0}%)" : "—"; - AreaInfo = state.AreaHash != 0 - ? $"Level {state.AreaLevel} (0x{state.AreaHash:X8})" - : "—"; + AreaInfo = state.CurrentAreaName is { Length: > 0 } + ? $"{state.CurrentAreaName} (Lv {state.AreaLevel})" + : state.AreaHash != 0 + ? $"Level {state.AreaLevel} (0x{state.AreaHash:X8})" + : "—"; DangerLevel = state.Danger.ToString(); EntityCount = $"{state.Entities.Count} total"; HostileCount = $"{state.HostileMonsters.Count} hostile"; @@ -408,16 +446,36 @@ public partial class RobotoViewModel : ObservableObject, IDisposable var systems = _engine.Systems; var enabled = systems.Count(s => s.IsEnabled); SystemsInfo = $"{enabled}/{systems.Count} active"; + var disabled = systems.Where(s => !s.IsEnabled).Select(s => s.Name); + InactiveSystems = string.Join(", ", disabled); // Navigation NavMode = _engine.Nav.Mode.ToString(); NavStatus = _engine.Nav.Status; + // Progression + var prog = _engine.Progression; + if (prog is not null) + { + ProgressionPhase = prog.PhaseName; + ProgressionTarget = prog.TargetTransitionName ?? "—"; + QuestTarget = prog.QuestTargetName is { } qt + ? $"{qt}{(prog.IsQuestDriven ? " (quest)" : "")}" + : "—"; + LootStatus = prog.IsLootingActive ? "Picking up" : "—"; + } + + // Memory stats + var poller = _engine.Poller; + MemReads = $"{poller.ReadsPerSec}/s"; + MemBandwidth = $"{poller.KBPerSec} KB/s"; + MemEntities = $"{poller.EntityCount}"; + // Character name if (p.CharacterName is { Length: > 0 }) CharacterName = p.CharacterName; - // Populate available skill names from memory (stripped of "Player" suffix) + // Sync skill names from memory into profile slots if (p.Skills.Count > 0) { var names = p.Skills @@ -429,6 +487,7 @@ public partial class RobotoViewModel : ObservableObject, IDisposable foreach (var vm in SkillProfiles) { + // Populate available names dropdown var current = vm.AvailableSkillNames; if (current.Count != names.Count || !current.SequenceEqual(names)) { @@ -436,6 +495,25 @@ public partial class RobotoViewModel : ObservableObject, IDisposable foreach (var n in names) current.Add(n); } + + // Auto-fill skill name from memory by matching slot index + var memSkill = p.Skills.FirstOrDefault(s => s.SlotIndex == vm.SlotIndex); + if (memSkill?.Name is { Length: > 0 }) + { + var cleanName = SkillProfileViewModel.CleanSkillName(memSkill.Name); + if (vm.SkillName != cleanName) + { + vm.SkillName = cleanName; + + // Apply saved defaults for this skill if available + var defaults = _engine.Profiles.GetSkillDefault(cleanName); + if (defaults is not null) + { + defaults.ApplyTo(vm.Model); + vm.RefreshFromModel(); + } + } + } } } @@ -457,10 +535,13 @@ public partial class RobotoViewModel : ObservableObject, IDisposable Entities.Clear(); foreach (var e in state.Entities) { + // Skip dead monsters + if (e.Category == EntityCategory.Monster && !e.IsAlive) continue; + var shortLabel = e.Category == EntityCategory.AreaTransition && e.TransitionName is not null ? $"AreaTransition — {e.TransitionName}" : GetShortLabel(e.Path); - var item = new EntityListItem(e.Id, shortLabel, e.Category.ToString(), e.DistanceToPlayer, e.Position.X, e.Position.Y); + var item = new EntityListItem(e.Id, shortLabel, e.Category.ToString(), e.DistanceToPlayer, e.Position.X, e.Position.Y, (int)e.Rarity); if (checkedIds.Contains(e.Id)) item.IsChecked = true; Entities.Add(item); @@ -472,7 +553,7 @@ public partial class RobotoViewModel : ObservableObject, IDisposable foreach (var item in Entities) { if (!showAll && !item.IsChecked) continue; - overlayEntries.Add(new EntityOverlayEntry(item.X, item.Y, item.Label)); + overlayEntries.Add(new EntityOverlayEntry(item.X, item.Y, item.Label, item.Rarity)); } if (overlayEntries.Count > 0) diff --git a/src/Automata.Ui/ViewModels/SkillProfileViewModel.cs b/src/Automata.Ui/ViewModels/SkillProfileViewModel.cs index cba7d65..6f685f6 100644 --- a/src/Automata.Ui/ViewModels/SkillProfileViewModel.cs +++ b/src/Automata.Ui/ViewModels/SkillProfileViewModel.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using Roboto.Core; namespace Automata.Ui.ViewModels; @@ -7,6 +8,10 @@ namespace Automata.Ui.ViewModels; public partial class SkillProfileViewModel : ObservableObject { private readonly SkillProfile _model; + private ProfileManager? _profileManager; + + /// Set by the parent VM so the save-default command can persist. + public void SetProfileManager(ProfileManager pm) => _profileManager = pm; public SkillProfileViewModel(SkillProfile model) { @@ -71,6 +76,29 @@ public partial class SkillProfileViewModel : ObservableObject partial void OnMinMonstersInRangeChanged(int value) => _model.MinMonstersInRange = value; partial void OnMaintainPressedChanged(bool value) => _model.MaintainPressed = value; + [RelayCommand] + private void SaveDefault() + { + if (_profileManager is null || SkillName is not { Length: > 0 }) return; + _profileManager.SaveSkillDefault(SkillName, SkillDefaults.FromProfile(_model)); + } + + /// Re-read all fields from the underlying model (after defaults are applied). + public void RefreshFromModel() + { + Priority = _model.Priority; + IsEnabled = _model.IsEnabled; + CooldownMs = _model.CooldownMs; + RangeMin = _model.RangeMin; + RangeMax = _model.RangeMax; + TargetSelection = _model.TargetSelection; + RequiresTarget = _model.RequiresTarget; + IsAura = _model.IsAura; + IsMovementSkill = _model.IsMovementSkill; + MinMonstersInRange = _model.MinMonstersInRange; + MaintainPressed = _model.MaintainPressed; + } + // ComboBox binding sources public static SkillInputType[] InputTypes { get; } = Enum.GetValues(); diff --git a/src/Automata.Ui/Views/MainWindow.axaml b/src/Automata.Ui/Views/MainWindow.axaml index 382e11b..cb37b84 100644 --- a/src/Automata.Ui/Views/MainWindow.axaml +++ b/src/Automata.Ui/Views/MainWindow.axaml @@ -828,7 +828,7 @@ - @@ -859,11 +859,132 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +