diff --git a/Nexus.sln b/Automata.sln similarity index 75% rename from Nexus.sln rename to Automata.sln index 05014f4..26c298e 100644 --- a/Nexus.sln +++ b/Automata.sln @@ -5,39 +5,29 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{67A27DFE-D2C5-479D-86FE-7E156BD0CFAA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Core", "src\Nexus.Core\Nexus.Core.csproj", "{A31E6F94-A702-4B58-8317-83658E556B5C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Core", "src\Automata.Core\Automata.Core.csproj", "{6432F6A5-11A0-4960-AFFC-E810D4325C35}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.GameOffsets", "src\Nexus.GameOffsets\Nexus.GameOffsets.csproj", "{C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Game", "src\Automata.Game\Automata.Game.csproj", "{97B8362D-777C-4ED1-B964-D6598B333E4C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Memory", "src\Nexus.Memory\Nexus.Memory.csproj", "{B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Screen", "src\Automata.Screen\Automata.Screen.csproj", "{F92C5EA2-8999-41BC-9B28-D52AD5F3542C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Input", "src\Nexus.Input\Nexus.Input.csproj", "{E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Items", "src\Automata.Items\Automata.Items.csproj", "{9CAB0D49-1E24-4F76-ABF8-9A5ED6819F00}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Systems", "src\Nexus.Systems\Nexus.Systems.csproj", "{95AC4C34-26A0-4D7F-A712-375EB28B54B8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Trade", "src\Automata.Trade\Automata.Trade.csproj", "{8F73A696-EB54-4C6F-9603-5A6BAC5D334A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Pathfinding", "src\Nexus.Pathfinding\Nexus.Pathfinding.csproj", "{F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Log", "src\Automata.Log\Automata.Log.csproj", "{B68D787D-7A83-4D8F-9F10-0B72C2E99B49}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Data", "src\Nexus.Data\Nexus.Data.csproj", "{1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Bot", "src\Automata.Bot\Automata.Bot.csproj", "{188C4F87-153F-4182-B816-9FB56F08CF3A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Engine", "src\Nexus.Engine\Nexus.Engine.csproj", "{C2E97306-20E4-4A69-A7AB-541A72614C76}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Inventory", "src\Automata.Inventory\Automata.Inventory.csproj", "{F186DDC8-6843-43E9-8BD3-9F914C5E784E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Game", "src\Nexus.Game\Nexus.Game.csproj", "{97B8362D-777C-4ED1-B964-D6598B333E4C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Ui", "src\Automata.Ui\Automata.Ui.csproj", "{859F870E-F013-4C2B-AFEC-7A8C6A5FE3F3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Screen", "src\Nexus.Screen\Nexus.Screen.csproj", "{F92C5EA2-8999-41BC-9B28-D52AD5F3542C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automata.Navigation", "src\Automata.Navigation\Automata.Navigation.csproj", "{D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Items", "src\Nexus.Items\Nexus.Items.csproj", "{9CAB0D49-1E24-4F76-ABF8-9A5ED6819F00}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roboto.Memory", "src\Roboto.Memory\Roboto.Memory.csproj", "{B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Trade", "src\Nexus.Trade\Nexus.Trade.csproj", "{8F73A696-EB54-4C6F-9603-5A6BAC5D334A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Log", "src\Nexus.Log\Nexus.Log.csproj", "{B68D787D-7A83-4D8F-9F10-0B72C2E99B49}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Bot", "src\Nexus.Bot\Nexus.Bot.csproj", "{188C4F87-153F-4182-B816-9FB56F08CF3A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Inventory", "src\Nexus.Inventory\Nexus.Inventory.csproj", "{F186DDC8-6843-43E9-8BD3-9F914C5E784E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Navigation", "src\Nexus.Navigation\Nexus.Navigation.csproj", "{D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Ui", "src\Nexus.Ui\Nexus.Ui.csproj", "{859F870E-F013-4C2B-AFEC-7A8C6A5FE3F3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roboto.GameOffsets", "src\Roboto.GameOffsets\Roboto.GameOffsets.csproj", "{C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{652F700E-4F84-4E66-BD62-717D3A8D6FBC}" EndProject @@ -57,7 +47,19 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sidekick.Data", "lib\Sideki EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sidekick.Data.Builder", "lib\Sidekick\src\Sidekick.Data.Builder\Sidekick.Data.Builder.csproj", "{E5C26A34-5EDF-488B-93C7-F8738F2CEB97}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexus.Simulator", "src\Nexus.Simulator\Nexus.Simulator.csproj", "{9198C826-9356-4763-87EF-BBC7166B745B}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "roboto", "roboto", "{D1A2B3C4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roboto.Core", "src\Roboto.Core\Roboto.Core.csproj", "{A31E6F94-A702-4B58-8317-83658E556B5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roboto.Input", "src\Roboto.Input\Roboto.Input.csproj", "{E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roboto.Systems", "src\Roboto.Systems\Roboto.Systems.csproj", "{95AC4C34-26A0-4D7F-A712-375EB28B54B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roboto.Engine", "src\Roboto.Engine\Roboto.Engine.csproj", "{C2E97306-20E4-4A69-A7AB-541A72614C76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roboto.Navigation", "src\Roboto.Navigation\Roboto.Navigation.csproj", "{F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roboto.Data", "src\Roboto.Data\Roboto.Data.csproj", "{1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -68,38 +70,10 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A31E6F94-A702-4B58-8317-83658E556B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A31E6F94-A702-4B58-8317-83658E556B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A31E6F94-A702-4B58-8317-83658E556B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A31E6F94-A702-4B58-8317-83658E556B5C}.Release|Any CPU.Build.0 = Release|Any CPU - {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}.Release|Any CPU.Build.0 = Release|Any CPU - {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}.Release|Any CPU.Build.0 = Release|Any CPU - {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}.Release|Any CPU.Build.0 = Release|Any CPU - {95AC4C34-26A0-4D7F-A712-375EB28B54B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {95AC4C34-26A0-4D7F-A712-375EB28B54B8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {95AC4C34-26A0-4D7F-A712-375EB28B54B8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {95AC4C34-26A0-4D7F-A712-375EB28B54B8}.Release|Any CPU.Build.0 = Release|Any CPU - {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}.Release|Any CPU.Build.0 = Release|Any CPU - {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}.Release|Any CPU.Build.0 = Release|Any CPU - {C2E97306-20E4-4A69-A7AB-541A72614C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C2E97306-20E4-4A69-A7AB-541A72614C76}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C2E97306-20E4-4A69-A7AB-541A72614C76}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C2E97306-20E4-4A69-A7AB-541A72614C76}.Release|Any CPU.Build.0 = Release|Any CPU + {6432F6A5-11A0-4960-AFFC-E810D4325C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6432F6A5-11A0-4960-AFFC-E810D4325C35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6432F6A5-11A0-4960-AFFC-E810D4325C35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6432F6A5-11A0-4960-AFFC-E810D4325C35}.Release|Any CPU.Build.0 = Release|Any CPU {97B8362D-777C-4ED1-B964-D6598B333E4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {97B8362D-777C-4ED1-B964-D6598B333E4C}.Debug|Any CPU.Build.0 = Debug|Any CPU {97B8362D-777C-4ED1-B964-D6598B333E4C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -128,14 +102,22 @@ Global {F186DDC8-6843-43E9-8BD3-9F914C5E784E}.Debug|Any CPU.Build.0 = Debug|Any CPU {F186DDC8-6843-43E9-8BD3-9F914C5E784E}.Release|Any CPU.ActiveCfg = Release|Any CPU {F186DDC8-6843-43E9-8BD3-9F914C5E784E}.Release|Any CPU.Build.0 = Release|Any CPU - {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}.Release|Any CPU.Build.0 = Release|Any CPU {859F870E-F013-4C2B-AFEC-7A8C6A5FE3F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {859F870E-F013-4C2B-AFEC-7A8C6A5FE3F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {859F870E-F013-4C2B-AFEC-7A8C6A5FE3F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {859F870E-F013-4C2B-AFEC-7A8C6A5FE3F3}.Release|Any CPU.Build.0 = Release|Any CPU + {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B}.Release|Any CPU.Build.0 = Release|Any CPU + {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B}.Release|Any CPU.Build.0 = Release|Any CPU + {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F}.Release|Any CPU.Build.0 = Release|Any CPU {B858F6F2-389F-475A-87FE-E4E01DA3E948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B858F6F2-389F-475A-87FE-E4E01DA3E948}.Debug|Any CPU.Build.0 = Debug|Any CPU {B858F6F2-389F-475A-87FE-E4E01DA3E948}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -168,20 +150,33 @@ Global {E5C26A34-5EDF-488B-93C7-F8738F2CEB97}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5C26A34-5EDF-488B-93C7-F8738F2CEB97}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5C26A34-5EDF-488B-93C7-F8738F2CEB97}.Release|Any CPU.Build.0 = Release|Any CPU - {9198C826-9356-4763-87EF-BBC7166B745B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9198C826-9356-4763-87EF-BBC7166B745B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9198C826-9356-4763-87EF-BBC7166B745B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9198C826-9356-4763-87EF-BBC7166B745B}.Release|Any CPU.Build.0 = Release|Any CPU + {A31E6F94-A702-4B58-8317-83658E556B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A31E6F94-A702-4B58-8317-83658E556B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A31E6F94-A702-4B58-8317-83658E556B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A31E6F94-A702-4B58-8317-83658E556B5C}.Release|Any CPU.Build.0 = Release|Any CPU + {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A}.Release|Any CPU.Build.0 = Release|Any CPU + {95AC4C34-26A0-4D7F-A712-375EB28B54B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95AC4C34-26A0-4D7F-A712-375EB28B54B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95AC4C34-26A0-4D7F-A712-375EB28B54B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95AC4C34-26A0-4D7F-A712-375EB28B54B8}.Release|Any CPU.Build.0 = Release|Any CPU + {C2E97306-20E4-4A69-A7AB-541A72614C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2E97306-20E4-4A69-A7AB-541A72614C76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2E97306-20E4-4A69-A7AB-541A72614C76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2E97306-20E4-4A69-A7AB-541A72614C76}.Release|Any CPU.Build.0 = Release|Any CPU + {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D}.Release|Any CPU.Build.0 = Release|Any CPU + {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {A31E6F94-A702-4B58-8317-83658E556B5C} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} - {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} - {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} - {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} - {95AC4C34-26A0-4D7F-A712-375EB28B54B8} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} - {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} - {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} - {C2E97306-20E4-4A69-A7AB-541A72614C76} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} + {6432F6A5-11A0-4960-AFFC-E810D4325C35} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} {97B8362D-777C-4ED1-B964-D6598B333E4C} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} {F92C5EA2-8999-41BC-9B28-D52AD5F3542C} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} {9CAB0D49-1E24-4F76-ABF8-9A5ED6819F00} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} @@ -189,8 +184,10 @@ Global {B68D787D-7A83-4D8F-9F10-0B72C2E99B49} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} {188C4F87-153F-4182-B816-9FB56F08CF3A} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} {F186DDC8-6843-43E9-8BD3-9F914C5E784E} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} - {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} {859F870E-F013-4C2B-AFEC-7A8C6A5FE3F3} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} + {D3F7A2E1-9B4C-4E8D-A6F5-1C2D3E4F5A6B} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} + {B7E3F1A2-4D5C-6E7F-8A9B-0C1D2E3F4A5B} = {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} + {C8D9E0F1-2A3B-4C5D-6E7F-8A9B0C1D2E3F} = {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} {B858F6F2-389F-475A-87FE-E4E01DA3E948} = {652F700E-4F84-4E66-BD62-717D3A8D6FBC} {6FEA655D-18E4-4DA1-839F-A41433B03FBB} = {652F700E-4F84-4E66-BD62-717D3A8D6FBC} {74FD0F88-86BC-49AE-9A16-136D92A10090} = {652F700E-4F84-4E66-BD62-717D3A8D6FBC} @@ -199,6 +196,12 @@ Global {8CEE036C-A229-4F22-BD0E-D7CDAE13E54F} = {652F700E-4F84-4E66-BD62-717D3A8D6FBC} {9428D5D4-4061-467A-BD26-C1FEED95E8E6} = {652F700E-4F84-4E66-BD62-717D3A8D6FBC} {E5C26A34-5EDF-488B-93C7-F8738F2CEB97} = {652F700E-4F84-4E66-BD62-717D3A8D6FBC} - {9198C826-9356-4763-87EF-BBC7166B745B} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} + {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} = {67A27DFE-D2C5-479D-86FE-7E156BD0CFAA} + {A31E6F94-A702-4B58-8317-83658E556B5C} = {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} + {E61E96C5-3DE7-4B31-A7AD-CCA99BEF8E4A} = {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} + {95AC4C34-26A0-4D7F-A712-375EB28B54B8} = {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} + {C2E97306-20E4-4A69-A7AB-541A72614C76} = {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} + {F4B5C6D7-E8F9-0A1B-2C3D-4E5F6A7B8C9D} = {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} + {1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6} = {D1A2B3C4-E5F6-7890-ABCD-EF1234567890} EndGlobalSection EndGlobal diff --git a/docs/architecture-overview.md b/docs/architecture-overview.md deleted file mode 100644 index 04e79de..0000000 --- a/docs/architecture-overview.md +++ /dev/null @@ -1,188 +0,0 @@ -# Nexus — Architecture Overview - -## What Is This - -A modular C# automation framework for POE2. The system reads game memory, builds a structured game state, runs AI systems (combat, navigation, threat assessment), and emits input commands. A separate trade pipeline monitors the trade site via a Node.js daemon and executes buy flows. - -When the real game isn't available, a standalone **Simulator** replaces the memory layer with a procedural game world — same bot systems, same interfaces, zero game dependency. - -## Solution Structure - -``` -Nexus.sln (net8.0-windows10.0.19041.0) -│ -├── Core Layer (shared types, no dependencies) -│ └── Nexus.Core -│ -├── Infrastructure Layer (reads from external sources) -│ ├── Nexus.GameOffsets Pure offset structs for memory reading -│ ├── Nexus.Memory Process memory reading (RPM, pattern scan) -│ ├── Nexus.Screen Screen capture, OCR, grid detection -│ ├── Nexus.Log Client.txt game log watcher -│ └── Nexus.Input Win32 SendInput / Interception driver -│ -├── Data Layer (transforms raw data into typed game state) -│ └── Nexus.Data EntityMapper, EntityClassifier, GameDataCache, MemoryPoller, GameStateEnricher -│ -├── Logic Layer (AI systems that decide what to do) -│ ├── Nexus.Systems ThreatSystem, MovementSystem, CombatSystem, ResourceSystem, LootSystem -│ ├── Nexus.Engine BotEngine (orchestrator), AreaProgressionSystem, MovementKeyTracker -│ └── Nexus.Pathfinding NavigationController, A* PathFinder -│ -├── Game Interaction Layer (acts on the game) -│ ├── Nexus.Game Window focus, input sending, clipboard -│ ├── Nexus.Items Item parsing via Sidekick -│ ├── Nexus.Inventory Stash/inventory grid management -│ ├── Nexus.Navigation Minimap-based real-time navigation -│ └── Nexus.Trade Trade daemon IPC (Node.js Playwright) -│ -├── Orchestration Layer (top-level coordination) -│ ├── Nexus.Bot BotOrchestrator, Trade/Mapping/Crafting executors -│ └── Nexus.Ui Avalonia 11.2 desktop GUI (entry point) -│ -└── Testing Layer - └── Nexus.Simulator Standalone game world for bot testing -``` - -## Dependency Flow - -``` -Nexus.Core - │ - ├── Nexus.GameOffsets ──→ Nexus.Memory ──→ Nexus.Data ──→ Nexus.Engine - │ │ │ - ├── Nexus.Input │ Nexus.Systems - │ │ │ - ├── Nexus.Screen ◄───────────────────────────────┘ │ - ├── Nexus.Game │ - ├── Nexus.Log │ - │ │ - ├── Nexus.Pathfinding ◄─────────────────────────────────────────┘ - │ - ├── Nexus.Items, Nexus.Inventory, Nexus.Navigation, Nexus.Trade - │ - ├── Nexus.Bot (consumes all above) - │ - ├── Nexus.Ui (DI hub, entry point, consumes all) - │ - └── Nexus.Simulator (Core, Data, Systems, Pathfinding — NOT Memory/Input/Screen) -``` - -## Data Flow — Per Tick - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ MemoryPoller Thread (60Hz hot / 10Hz cold) │ -│ │ -│ Hot tick (4 RPM calls): │ -│ Camera matrix, Player position, Player vitals, Loading state │ -│ │ -│ Cold tick (full hierarchical read): │ -│ Entity tree traversal → classification → EntitySnapshot[] │ -│ Terrain grid, Skills, Quests, UI elements │ -│ │ -│ Writes to GameDataCache (volatile references, lock-free) │ -└────────────────────────────┬────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────────┐ -│ BotEngine Logic Thread (25Hz default) │ -│ │ -│ 1. Read latest GameState from cache │ -│ 2. GameStateEnricher → NearestEnemies, ThreatMap, DangerLevel │ -│ 3. Clear ActionQueue │ -│ 4. NavigationController.Update() → path, DesiredDirection │ -│ 5. Run systems in priority order: │ -│ ThreatSystem (50) → MovementSystem (100) → │ -│ AreaProgressionSystem (199) → NavigationSystem (200) → │ -│ CombatSystem (300) → ResourceSystem (400) → LootSystem (500) │ -│ 6. ActionQueue.Resolve() → conflict resolution │ -│ 7. ExecuteActions() → IInputController │ -└────────────────────────────┬────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────────┐ -│ IInputController (Win32 SendInput or Interception driver) │ -│ │ -│ WASD keys (scan codes), mouse movement (Bézier curves), │ -│ skill casts, flask presses, clicks │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -## Action Resolution - -Systems submit actions to a shared `ActionQueue`. The queue resolves conflicts: - -| Action Type | Behavior | -|-------------|----------| -| FlaskAction | Always passes through | -| MoveAction (priority ≤ 10) | Urgent flee — blocks CastAction | -| MoveAction (priority > 10) | Normal move — allows CastAction alongside | -| CastAction | Passes unless blocked by urgent flee | -| Other (Key, Click, Chat) | Always passes through | - -Priority values: lower number = higher priority. ThreatSystem uses priority 5 for emergency flee (blocks all casting). - -## System Priority Table - -| Priority | System | Purpose | -|----------|--------|---------| -| 50 | ThreatSystem | Emergency flee on High/Critical danger | -| 100 | MovementSystem | Soft avoidance via inverse-square repulsion | -| 199 | AreaProgressionSystem | Quest-driven area traversal and transitions | -| 200 | NavigationSystem | Submits NavigationController's direction | -| 300 | CombatSystem | Skill rotation, target selection, kiting | -| 400 | ResourceSystem | Flask usage on life/mana thresholds | -| 500 | LootSystem | Item pickup (stub) | - -## Thread Model - -| Thread | Rate | Responsibility | -|--------|------|----------------| -| MemoryPoller | 60Hz hot, 10Hz cold | Read game memory → GameDataCache | -| BotEngine Logic | 25Hz | Run AI systems → emit actions | -| Render (Simulator only) | vsync | ImGui + Veldrid drawing | -| Trade Daemon | event-driven | Node.js Playwright → stdin/stdout JSON IPC | -| Log Watcher | 200ms poll | Client.txt → area/whisper/trade events | - -Cross-thread safety: GameDataCache uses `volatile` references. No locks — writer (MemoryPoller) atomically swaps reference types, readers (BotEngine) get consistent snapshots. - -## Simulator Architecture - -When the real game isn't available, `Nexus.Simulator` replaces the memory pipeline: - -``` -┌─────────────────────────────────────────────────────┐ -│ Nexus.Simulator.exe │ -│ │ -│ ┌──────────┐ GameState ┌──────────────────┐ │ -│ │ SimWorld │───────────────►│ GameDataCache │ │ -│ │ (terrain, │ SimPoller + │ (same as prod) │ │ -│ │ enemies, │ StateBuilder └────────┬─────────┘ │ -│ │ player) │ │ │ -│ └─────┬────┘ ┌────────▼─────────┐ │ -│ │ │ Bot Systems │ │ -│ │◄─────────────────────│ (unchanged) │ │ -│ │ SimInputController └──────────────────┘ │ -│ │ │ -│ ┌─────▼───────────────────────────────────────┐ │ -│ │ ImGui + Veldrid Renderer (isometric 2D) │ │ -│ └─────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────┘ -``` - -Bot systems don't know they're in a simulation — they see identical `GameState` objects and emit actions to `IInputController`. - -## Key Design Decisions - -| Decision | Rationale | -|----------|-----------| -| Immutable records (GameState, EntitySnapshot) | Thread-safe sharing without locks | -| Priority-based ActionQueue | Natural conflict resolution without hardcoded if-else | -| Two-tier memory polling (hot/cold) | Balance responsiveness (position at 60Hz) with CPU cost (entities at 10Hz) | -| Scan codes over virtual keys | Games read hardware scan codes, not VK codes | -| Separate Memory → Data layers | Memory reads raw bytes; Data interprets and classifies. Clean testability. | -| IInputController interface | Swap real Win32 input for simulated input without changing bot logic | -| BFS exploration + A* pathfinding | BFS finds what to explore; A* finds how to get there | -| CharacterProfile auto-detection | Automatically applies combat/flask settings per character name | -| External daemons (Trade, OCR) | Isolate browser/OCR concerns from main process | diff --git a/docs/core.md b/docs/core.md deleted file mode 100644 index e3dfa68..0000000 --- a/docs/core.md +++ /dev/null @@ -1,158 +0,0 @@ -# Nexus.Core — Shared Types & Abstractions - -The foundation layer. Every other project depends on Core. Contains no logic — only types, interfaces, configuration, and utilities. - -## GameState - -Central immutable snapshot updated once per tick. All systems read from this; none mutate it. - -``` -GameState -├── Timing: TickNumber, DeltaTime, TimestampMs -├── Player: PlayerState -│ ├── CharacterName, Position, Z, HasPosition -│ ├── Life/Mana/ES: Current, Total, Percent (derived) -│ ├── ActionId (actor animation state) -│ ├── Skills: SkillState[] (cooldowns, charges, cast state) -│ ├── Flasks: FlaskState[] (charges, active, cooldown) -│ └── Buffs: Buff[] (name, duration, charges, isDebuff) -├── Entities: EntitySnapshot[] (all game entities) -├── HostileMonsters: EntitySnapshot[] (alive monsters, filtered) -├── NearbyLoot: EntitySnapshot[] (world items, filtered) -├── NearestEnemies: EntitySnapshot[] (sorted by distance — enriched) -├── Terrain: WalkabilitySnapshot (grid, offsets) -├── Area: AreaHash, AreaLevel, CurrentAreaName -├── UI: IsLoading, IsEscapeOpen, CameraMatrix -├── Quests: ActiveQuests[], UiQuests[], Quests[] (enriched with paths) -├── Threats: ThreatMap (zone buckets, centroid, rarity flags) -├── Danger: DangerLevel (Safe/Low/Medium/High/Critical — enriched) -└── GroundEffects: GroundEffect[] (hazard positions) -``` - -## EntitySnapshot - -Immutable record for any game entity: - -- **Identity**: Id (uint), Path, Metadata, Category (EntityCategory enum) -- **Spatial**: Position (Vector2), Z, DistanceToPlayer -- **Combat**: IsAlive, LifeCurrent/Total, IsTargetable, ThreatLevel, Rarity, ModNames -- **State**: ActionId, IsAttacking, IsMoving, Components (HashSet) -- **Special**: TransitionName/State, ItemBaseName, IsQuestItem, LabelOffset - -**EntityCategory** (17 types): Unknown, Player, Monster, Npc, WorldItem, Chest, Shrine, Portal, AreaTransition, Effect, Terrain, MiscObject, Waypoint, Door, Doodad, TownPortal, Critter - -**MonsterRarity**: White, Magic, Rare, Unique - -**MonsterThreatLevel**: None, Normal, Magic, Rare, Unique - -## Actions - -Polymorphic action system. All inherit from `BotAction` with a `Priority` field. - -| Action | Fields | Purpose | -|--------|--------|---------| -| MoveAction | Direction (Vector2) | WASD movement | -| CastAction | SkillScanCode, TargetScreenPos or TargetEntityId | Skill usage | -| FlaskAction | FlaskScanCode | Flask consumption | -| KeyAction | ScanCode, Type (Press/Down/Up) | Raw keyboard | -| ClickAction | ScreenPosition, Type (Left/Right/Middle) | Mouse click | -| ChatAction | Message | Send chat message | -| WaitAction | DurationMs | Delay | - -## ActionQueue - -Manages conflict resolution between competing system outputs. - -**Resolve() rules:** -1. FlaskActions always pass through -2. Get highest-priority MoveAction and CastAction -3. If MoveAction priority ≤ 10 (urgent flee): include move, **block** cast -4. Else: include both move and cast -5. All other action types pass through - -## ISystem Interface - -```csharp -public interface ISystem -{ - int Priority { get; } // Execution order (lower = first) - string Name { get; } - bool IsEnabled { get; set; } - void Update(GameState state, ActionQueue actions); -} -``` - -**SystemPriority constants**: Threat=50, Movement=100, Navigation=200, Combat=300, Resource=400, Loot=500 - -## IInputController Interface - -Abstraction over Win32 input. Two implementations: SendInputController (vanilla), InterceptionInputController (driver). - -```csharp -public interface IInputController -{ - bool IsInitialized { get; } - void KeyDown(ushort scanCode); - void KeyUp(ushort scanCode); - void KeyPress(ushort scanCode, int holdMs = 50); - void MouseMoveTo(int x, int y); - void SmoothMoveTo(int x, int y); // Bézier curve interpolation - void LeftClick(int x, int y); - void RightClick(int x, int y); - void MiddleClick(int x, int y); - void LeftDown(); void LeftUp(); - void RightDown(); void RightUp(); -} -``` - -## WalkabilitySnapshot - -Grid-based terrain with offset support for infinite expansion: - -```csharp -public record WalkabilitySnapshot -{ - public int Width, Height; - public byte[] Data; // Row-major; 0=wall, nonzero=walkable - public int OffsetX, OffsetY; // Absolute grid coords of top-left corner - - public bool IsWalkable(int gx, int gy) - { - var lx = gx - OffsetX; // Absolute → local - var ly = gy - OffsetY; - if (out of bounds) return false; - return Data[ly * Width + lx] != 0; - } -} -``` - -Coordinate conversion: `WorldToGrid = 23f / 250f ≈ 0.092` -- World → Grid: `gx = (int)(worldX * WorldToGrid)` -- Grid → World: `worldX = gx / WorldToGrid` - -## Configuration - -**BotConfig** — Static bot parameters: -- Tick rates: LogicTickRateHz=60, MemoryPollRateHz=30 -- Movement: SafeDistance=400, RepulsionWeight=1.5, WaypointReachedDistance=80 -- Humanization: MinReactionDelayMs=50, MaxReactionDelayMs=150, ClickJitterRadius=3, MaxApm=250 - -**CharacterProfile** — Per-character settings: -- Skills (8 slots: LMB, RMB, MMB, Q, E, R, T, F) with priority, cooldown, range, target selection -- Flasks (thresholds, scan codes, cooldown) -- Combat (global cooldown, attack/safe/kite ranges) - -**SkillProfile** — Per-skill configuration: -- InputType (KeyPress/LeftClick/RightClick/MiddleClick) -- TargetSelection (Nearest/All/Rarest/MagicPlus/RarePlus/UniqueOnly) -- RequiresTarget, IsAura, IsMovementSkill, MaintainPressed -- MinMonstersInRange (AOE threshold) - -## Utilities - -- **WorldToScreen.Project()** — Matrix projection: world coords → screen coords via camera matrix -- **TerrainQuery.HasLineOfSight()** — Bresenham line walk on walkability grid -- **TerrainQuery.FindWalkableDirection()** — Rotates direction ±45°/90°/135°/180° to find clear path -- **Helpers.Sleep()** — Task delay with ±10% variance -- **DangerLevel**: Safe, Low, Medium, High, Critical -- **ThreatMap**: TotalHostiles, CloseRange(<300), MidRange(300-600), FarRange(600-1200), ClosestDistance, ThreatCentroid, HasRareOrUnique diff --git a/docs/data-and-memory.md b/docs/data-and-memory.md deleted file mode 100644 index 82f9a2b..0000000 --- a/docs/data-and-memory.md +++ /dev/null @@ -1,229 +0,0 @@ -# Nexus.Data & Nexus.Memory — The Data Pipeline - -## Overview - -Two-layer architecture: **Memory** reads raw bytes from the game process; **Data** interprets, classifies, and caches them. This separation keeps memory reading logic free of business rules and makes the data layer testable independently. - -``` -Game Process (RPM) - │ - ▼ -Nexus.Memory (raw reads, no business logic) - │ GameMemoryReader → hierarchical state tree - │ EntityList → red-black tree traversal - │ ComponentReader → ECS component extraction - │ - ▼ -Nexus.Data (interpretation, classification, caching) - │ MemoryPoller → two-tier event loop - │ EntityMapper → Memory.Entity → Core.EntitySnapshot - │ EntityClassifier → path + components → EntityCategory - │ GameStateEnricher → derived threat/danger metrics - │ - ▼ -GameDataCache (volatile references, lock-free) - │ - ▼ -Bot Systems (read-only consumers) -``` - ---- - -## GameDataCache — Single Source of Truth - -Thread-safe, volatile reference holder. Writer: MemoryPoller thread. Readers: bot systems. - -**Hot fields** (updated at 60Hz — 4 lightweight RPM calls): -- CameraMatrix (64 bytes) -- PlayerPosition (X, Y, Z) -- PlayerVitals (HP, mana, ES current/max) -- IsLoading, IsEscapeOpen - -**Cold fields** (updated at 10Hz — full hierarchical read): -- Entities, HostileMonsters, NearbyLoot -- Terrain (WalkabilitySnapshot) -- AreaHash, AreaLevel, CurrentAreaName -- Quest data (linked lists, UI groups, state entries) -- LatestState (complete GameState) - -**Slow fields** (updated at 1Hz): -- Character name -- Quest linked lists, quest state entries - -No locks — relies on volatile reference semantics for atomic swaps. - ---- - -## MemoryPoller — Two-Tier Event Loop - -Owns the memory-reading background thread. - -### Hot Tick (60Hz) - -4 pre-resolved RPM calls using cached addresses: -1. Camera matrix (64 bytes from cached address) -2. Player position (12 bytes: X, Y, Z) -3. Player vitals (24 bytes: HP, mana, ES) -4. Loading/escape state (pointer dereference + int) - -No allocations, no GC. Targeting ~3-5ms per tick. - -### Cold Tick (10Hz, every 6th hot tick) - -Full hierarchical read: -1. `GameMemoryReader.ReadSnapshot()` — cascades through state tree -2. Re-resolve hot addresses via `ResolveHotAddresses()` -3. `BuildGameState()` — map entities, filter lists, process quests -4. `GameStateEnricher.Enrich()` — compute derived fields -5. Update all cache fields - -### BuildGameState() Flow - -``` -ReadSnapshot() → GameStateSnapshot (raw) - │ - ├── Entity mapping: - │ for each entity in snapshot: - │ EntityMapper.MapEntity(entity, playerPos) → EntitySnapshot - │ - ├── Filter into: - │ - HostileMonsters (Category==Monster && IsAlive) - │ - NearbyLoot (Category==WorldItem) - │ - All entities - │ - ├── Quest processing: - │ - Filter active (StateId > 0) - │ - Resolve state text via QuestStateLookup - │ - Convert to QuestProgress, QuestInfo - │ - └── Returns GameState -``` - ---- - -## GameMemoryReader — Hierarchical State Tree - -Top-level orchestrator. Creates sub-readers on `Attach()`: - -``` -GameStates (top) -└── InGameState - ├── AreaInstance - │ ├── EntityList (MSVC std::map red-black tree) - │ ├── PlayerSkills (Actor component) - │ ├── QuestStates (dat file entries) - │ └── Terrain (walkability grid) - ├── UIElements (quest linked lists, UI tree) - └── WorldData (camera matrix) -``` - -Each `RemoteObject` caches its data and depends on parent for context. Single `Update()` call cascades through tree. - -### Infrastructure - -- **ProcessMemory**: P/Invoke wrapper for ReadProcessMemory. Tracks reads/sec and KB/sec. -- **MemoryContext**: Shared state — process handle, offsets, module base, pattern scanner. -- **ComponentReader**: Reads ECS components (Life, Render, Mods, etc.) from entities. -- **MsvcStringReader**: Reads MSVC std::wstring (SSO-aware: inline if capacity ≤ 8, heap pointer otherwise). -- **PatternScanner**: AOB scan for resolving base addresses. - ---- - -## EntityList — Tree Traversal - -Reads entities from AreaInstance's MSVC std::map (red-black tree, in-order traversal). - -**Tree node layout:** -``` -+0x00: left child ptr -+0x08: parent ptr -+0x10: right child ptr -+0x28: entity pointer -``` - -**Optimization**: Tree order is cached — re-walked only when entity count changes. - -**Per-entity reads:** -1. Path (EntityDetails → std::wstring) -2. Skip low-priority types (effects, terrain, critters — no components read) -3. Position (Render component: X, Y, Z) -4. Component lookup (STL hash map: name → index) -5. Component data: - - Targetable (bool flag) - - Mods/ObjectMagicProperties (rarity) - - Life (HP, dynamic — re-read every frame for monsters) - - Actor (action ID) - - WorldItem (inner entity for ground loot) - - AreaTransition (destination area) - -**Caching strategy:** -- Stable per entity: path, component list, targetable, rarity, transition name -- Dynamic (re-read every frame): monster HP, action ID - ---- - -## EntityClassifier — Path + Components → Category - -Single source of truth for entity classification. - -1. **Path-based** (primary): Parses `Metadata/[Category]/...` path segments -2. **Component override**: Monster, Chest, Shrine, Waypoint, AreaTransition, Portal, TownPortal, NPC, Player - -Output: `EntityCategory` (Core enum, 17 types) - ---- - -## EntityMapper — Memory.Entity → Core.EntitySnapshot - -Transforms raw memory data to enriched snapshots: - -``` -Memory.Entity (raw) - │ - ├── Copy: ID, path, metadata, position, Z, vitals, components, mods - ├── Classify: EntityClassifier.Classify(path, components) → EntityCategory - ├── Threat level: Rarity → MonsterThreatLevel - ├── Area name: AreaNameLookup.Resolve() for transitions - ├── Distance: Vector2.Distance(position, playerPos) - └── Alive state: HasVitals ? LifeCurrent > 0 : true - │ - ▼ -Core.EntitySnapshot (public, classified, enriched) -``` - ---- - -## GameStateEnricher — Derived Metrics - -Computed once per cold tick, before systems run. - -**NearestEnemies**: HostileMonsters sorted by distance to player. - -**ThreatMap**: -- TotalHostiles, CloseRange (<300u), MidRange (300-600u), FarRange (600-1200u) -- ClosestDistance, ThreatCentroid (position average), HasRareOrUnique - -**DangerLevel** — Weighted threat score: -``` -score = Σ (distance_weight × rarity_multiplier) - -Distance weights: <200u = 3×, <400u = 2×, else = 1× -Rarity multipliers: Unique=5×, Rare=3×, Magic=1.5×, White=1× - -Life override: HP < 30% → Critical, HP < 50% → High -Score thresholds: ≥15 = Critical, ≥8 = High, ≥4 = Medium, >0 = Low, 0 = Safe -``` - ---- - -## Key Architectural Patterns - -| Pattern | Implementation | -|---------|---------------| -| Lock-free cross-thread | Volatile references in GameDataCache; no locks needed | -| Two-tier polling | Hot (4 RPM, 60Hz) + Cold (full read, 10Hz) | -| Hierarchical caching | Each RemoteObject caches data, re-reads only on change | -| Entity caching | Stable data cached per entity/zone; dynamic data (HP) re-read per frame | -| Separation of concerns | Memory: raw bytes. Data: interpretation + classification | -| Area name resolution | AreaNameLookup loads areas.json, caches ID → display name | -| Area graph | BFS pathfinding for quest progression ordering | diff --git a/docs/engine-and-systems.md b/docs/engine-and-systems.md deleted file mode 100644 index ed0ad66..0000000 --- a/docs/engine-and-systems.md +++ /dev/null @@ -1,197 +0,0 @@ -# Nexus.Engine & Nexus.Systems — Bot Brain - -## BotEngine (Orchestrator) - -The main loop. Owns systems, navigation, profiles, and action execution. - -### Logic Loop (25Hz, background thread) - -``` -1. Wait for MemoryPoller to provide latest GameState -2. CheckCharacterProfile() → auto-load profile if character changed -3. GameStateEnricher.Enrich() → compute NearestEnemies, ThreatMap, DangerLevel -4. Clear ActionQueue -5. NavigationController.Update(state) → compute path, set DesiredDirection -6. Run all ISystem implementations in priority order -7. NavigationSystem submits MoveAction if DesiredDirection is set -8. ActionQueue.Resolve() → merge conflicts -9. ExecuteActions() → emit key/mouse/click commands via IInputController -``` - -### Action Execution - -| Action | Execution | -|--------|-----------| -| MoveAction | Direction → MovementKeyTracker → WASD key state changes (delta-based) | -| CastAction | SmoothMoveTo target + key press. Re-projects moving entities. Adds ±30-50px jitter. | -| FlaskAction | Direct key press | -| KeyAction | Press/Down/Up operations | -| ClickAction | Left/Right/Middle click at screen position | - -### MovementKeyTracker - -Converts world-space direction vectors to WASD keys for isometric camera: - -``` -1. Rotate direction 45° to align with isometric axes -2. sx = dir.X * cos(45°) - dir.Y * sin(45°) -3. sy = dir.X * sin(45°) + dir.Y * cos(45°) -4. W if sy > 0.3, S if sy < -0.3, D if sx > 0.3, A if sx < -0.3 -5. Only emit key changes (delta-based — no redundant KeyDown/KeyUp) -``` - -### Mouse Drift (Navigation) - -During navigation, lazily repositions the mouse toward enemy clusters: -- Projects enemy centroid ahead of player movement -- Applies ±25° angular offset for organic appearance -- Fires every 800-1500ms (randomized) - -### Safety - -- Releases all held keys when loading screen or escape menu detected -- CombatSystem's `ReleaseAllHeld()` called on state transitions - ---- - -## Systems - -### ThreatSystem (Priority 50) - -Emergency threat response. Runs first, only acts on elevated danger. - -| Danger | Response | -|--------|----------| -| Safe / Low | No action | -| Medium | No action (MovementSystem handles soft avoidance) | -| High | Flee toward safety (priority 50, allows casting) | -| Critical or point-blank (<150 units) | **Urgent flee (priority 5) — blocks all casting** | - -**Flee direction**: `Player.Position - ThreatCentroid`, validated against terrain via `FindWalkableDirection()`. - -### MovementSystem (Priority 100) - -Continuous soft avoidance via **inverse-square repulsion field**. - -For each hostile monster within SafeDistance (400 units): -``` -force += (playerPos - enemyPos) / distanceSquared * RepulsionWeight -``` -Normalizes sum, validates against terrain, submits as lower-priority MoveAction. - -Effect: Player gently drifts away from enemies without hard fleeing. - -### AreaProgressionSystem (Priority 199) - -High-level area traversal. Runs before NavigationSystem to take precedence. - -**State machine (7 phases):** -``` -Exploring → Looting → NavigatingToChest → InteractingChest → -NavigatingToTransition → Interacting → TalkingToNpc -``` - -**Exploration strategy:** -1. Check for elite enemies (Rare/Unique within 800u) → yield to combat -2. Check for quest chests → navigate and interact -3. Check for loot (if danger ≤ Low) → pick up within 600u -4. Once fully explored → find area transition matching quest target -5. In towns with active quest → talk to NPC - -**Quest integration**: Queries active quests for target areas. Prioritizes tracked quests, then lowest act, then shortest path. Blacklists failed transitions after 5s timeout. - -**Navigation delegation**: Uses `NavigationController.NavigateToEntity()` and `.Explore()`. Sets targets and yields until reached. - -### NavigationSystem (Priority 200) - -Ultra-thin passthrough. If `NavigationController.DesiredDirection` is set, submits a MoveAction. All actual pathfinding logic lives in NavigationController (see [pathfinding.md](pathfinding.md)). - -### CombatSystem (Priority 300) - -Skill rotation and target selection. Hot-swappable via CharacterProfile. - -**Rotation loop:** -``` -1. Check global cooldown (skip if recently cast) -2. For each skill in priority order: - a. Check per-skill cooldown - b. Match skill to memory via slot index (fallback to name) - c. If aura: cast once per zone - d. If damage: find target → submit CastAction -3. Release held keys for skills without valid targets -``` - -**Target selection pipeline:** -``` -1. Filter by TargetSelection (Nearest, Rarest, MagicPlus, RarePlus, UniqueOnly) -2. Filter by range (SkillProfile.RangeMin/RangeMax) -3. Filter by line-of-sight (terrain query) -4. Check MinMonstersInRange (AOE threshold) -5. Pick best: Rarest mode → prefer higher rarity then nearer; others → nearest -6. Project to screen coordinates -``` - -**Skill input types:** -- LeftClick/RightClick/MiddleClick: Direct click at target position -- KeyPress with MaintainPressed: Hold key continuously -- KeyPress normal: Single tap - -**Kiting/orbit (during global cooldown):** -- Computes enemy centroid -- Moves perpendicular to centroid (orbital movement) -- Applies radial bias to maintain ideal distance -- Flips orbit direction if terrain blocks path -- Persists orbit sign across ticks for smooth motion - -**Cooldown management:** -- Per-skill: `max(skill.CooldownMs, globalCd + 50)` for rotation -- MaintainPressed skills: use skill.CooldownMs directly -- Area reset: clears aura tracking, resets orbit - -### ResourceSystem (Priority 400) - -Flask automation based on life/mana thresholds. - -``` -if LifePercent < LifeFlaskThreshold (50%) && cooldown expired → FlaskAction -if ManaPercent < ManaFlaskThreshold (50%) && cooldown expired → FlaskAction -``` - -Flask cooldown: 4000ms default. Hot-swappable on character profile change. - -### LootSystem (Priority 500) - -Stub — disabled by default. Item pickup logic handled by AreaProgressionSystem's looting phase. - ---- - -## System Interaction Diagram - -``` -GameState (read-only, shared) - │ - ├─→ ThreatSystem ──→ MoveAction (priority 5 or 50) - │ [blocks casting if priority ≤ 10] - │ - ├─→ MovementSystem ──→ MoveAction (priority 100) - │ [soft repulsion, overridable] - │ - ├─→ AreaProgressionSystem ──→ NavigateTo/Explore commands - │ [drives NavigationController] - │ - ├─→ NavigationSystem ──→ MoveAction (priority 200) - │ [passthrough from NavigationController] - │ - ├─→ CombatSystem ──→ CastAction (priority 300) - │ [skill rotation + target selection] - │ - ├─→ ResourceSystem ──→ FlaskAction (priority 400) - │ [always passes through] - │ - └─→ ActionQueue.Resolve() - │ - ├── Highest MoveAction wins - ├── CastAction passes unless blocked by urgent flee - ├── FlaskAction always passes - └──→ ExecuteActions() → IInputController -``` diff --git a/docs/infrastructure.md b/docs/infrastructure.md deleted file mode 100644 index 9ced350..0000000 --- a/docs/infrastructure.md +++ /dev/null @@ -1,207 +0,0 @@ -# Infrastructure & Game Interaction Projects - -## Nexus.GameOffsets — Memory Layout Definitions - -Pure offset structs for POE2 game memory. No logic, no reading — just struct layouts. - -**Contents:** -- **Entities/**: `EntityStruct`, `EntityDetails`, `ComponentLookup`, `ComponentNameAndIndex`, `ItemStruct`, `EntityTreeNode` -- **Components/** (22 structs): Actor, Animated, Buffs, Chest, Life, Mods, Player, Positioned, Render, Stats, Targetable, Transitionable, WorldItem, etc. -- **States/**: `InGameState`, `AreaInstance`, `AreaLoading`, `ServerData`, `WorldData`, `Inventory`, `ImportantUiElements` -- **Natives/**: C++ STL memory layouts — `StdVector`, `StdMap`, `StdList`, `StdBucket`, `StdWString`, `StdTuple` - -**Key offsets:** -- Actor skills: 0xB00 (ActiveSkillsVector), 0xB18 (CooldownsVector) -- UIElement: 0x10 (Children), 0x98 (StringId), 0x180 (Flags), 0x448 (Text) -- Entity: 0x80 (ID), 0x84 (Flags), 0x08 (EntityDetails) - -**Dependencies**: None. Used by Memory and Data layers. - ---- - -## Nexus.Screen — Screen Capture, OCR & Detection - -Screen capture, OCR, image processing, grid/item detection, loot label detection. - -### Core Components - -| Class | Purpose | -|-------|---------| -| ScreenReader | Main facade — OCR, template matching, screenshot, diff OCR | -| IScreenCapture | Desktop duplication or GDI capture | -| IOcrEngine | Interface for OCR backends (Win native, EasyOCR, OneOCR, WinOCR) | -| PythonOcrBridge | Calls Python script via subprocess for EasyOCR/YOLO | - -### Grid & Item Detection - -| Class | Purpose | -|-------|---------| -| GridReader | Reads stash/inventory grids (12-col 70×70px or 24-col 35×35px) | -| GridHandler | Template matching for cell occupancy, item size detection | -| TemplateMatchHandler | NCC-based visual matching (find identical items in grid) | -| DetectGridHandler | Edge detection to find grid boundaries | - -### Detection Systems - -| Class | Purpose | -|-------|---------| -| EnemyDetector | YOLO/ONNX object detection for enemy positions | -| BossDetector | Boss-specific recognition | -| HudReader | HUD element OCR (HP bar, mana, buffs) | -| GameStateDetector | Main menu vs in-game state | -| ScreenReader.DetectLootLabels() | Three-pass loot detection (polygon, contour, yellow text) | - -### Frame Pipeline - -Pub-sub for screen frames: `FramePipeline` distributes captured frames to multiple `IFrameConsumer` implementations (GameState, Enemy, Boss detectors, Minimap, Navigation). - -**Used by**: Bot, Navigation, Inventory, Ui - ---- - -## Nexus.Game — Game Interaction - -Low-level game control — window focus, input sending, clipboard operations. - -| Class | Purpose | -|-------|---------| -| GameController | Main facade — focus, chat, input, shortcuts | -| InputSender | Win32 SendInput (scan codes), Bézier mouse movement, Ctrl+click | -| WindowManager | SetForegroundWindow (with alt-key trick), GetWindowRect | -| ClipboardHelper | System clipboard read/write | - -**Key operations:** -- `FocusWindow()` — SetForegroundWindow + alt-key trick (required for background processes) -- `CtrlRightClick()` — buying from seller stash -- `MoveMouse()` — Bézier curve smooth move -- `MoveMouseInstant()` — direct teleport (no interpolation) -- `TypeText()`, `SelectAll()`, `Paste()` — clipboard operations - -**Used by**: Inventory, Trade, Items, Navigation, Bot - ---- - -## Nexus.Log — Game Log Watcher - -Parses Client.txt game log at 200ms poll intervals. - -| Event | Pattern | -|-------|---------| -| AreaEntered | `[SCENE] Set Source [AreaName]` or `You have entered AreaName` | -| WhisperReceived | Incoming whisper messages | -| WhisperSent | Outgoing whisper messages | -| TradeAccepted | Trade completion | -| PartyJoined/Left | Party state changes | -| LineReceived | Raw log lines | - -`CurrentArea` detected from log tail on startup. Used by Bot (reset navigation on area change), Inventory (wait for area transitions), Navigation. - ---- - -## Nexus.Trade — Trade Daemon IPC - -Manages trade search monitoring via external Node.js Playwright daemon. - -### TradeDaemonBridge - -Spawns `node tools/trade-daemon/daemon.mjs`, communicates via stdin/stdout JSON. - -**Commands (→ daemon):** -- `start`, `addSearch`, `addDiamondSearch` -- `pauseSearch`, `clickTravel` -- `openScrapPage`, `reloadScrapPage`, `closeScrapPage` - -**Events (← daemon):** -- `newListings` → `NewListings(searchId, items[])` -- `diamondListings` → `DiamondListings(searchId, pricedItems[])` -- `wsClose` → websocket disconnection - -**Trade flow**: Website "Travel to Hideout" button → stash opens → Ctrl+right-click to buy → `/hideout` to go home → store items - ---- - -## Nexus.Items — Item Parsing - -Parse item text from clipboard (Ctrl+C) using Sidekick item parser library. - -| Class | Purpose | -|-------|---------| -| ItemReader | Move to item → Ctrl+C → read clipboard → parse | -| SidekickBootstrapper | Initialize Sidekick parser on first use | - -**Used by**: Bot (identify items during scraping) - ---- - -## Nexus.Inventory — Stash & Grid Management - -Scan player inventory, track item placement, deposit to stash. - -| Class | Purpose | -|-------|---------| -| InventoryManager | Main interface — scan, deposit, clear | -| InventoryTracker | Cell occupancy matrix + item metadata | -| StashCalibrator | Grid boundary calibration via edge detection | - -**Key operations:** -- `ScanInventory()` → screenshot + grid scan → populate tracker -- `DepositItemsToStash()` → find stash NPC → click items with Shift+Ctrl -- `DepositAllToOpenStash()` → scan → click first occupied → repeat -- `ClearToStash()` → scan → deposit all → return to hideout -- `EnsureAtOwnHideout()` → `/hideout` command if needed - -**Grid calibration (2560×1440):** -- Cell sizes: 70×70px (12-col) or 35×35px (24-col), all 840px wide -- Inventory (12×5): origin (1696, 788) -- Stash 12×12: origin (23, 169) or (23, 216) in folder - ---- - -## Nexus.Navigation — Minimap-Based Movement - -Real-time navigation using minimap image matching + pathfinding. Separate from Nexus.Pathfinding (which is grid-based A*). - -| Class | Purpose | -|-------|---------| -| NavigationExecutor | State machine: Capture → Process → Plan → Move → Stuck | -| MinimapCapture | Frame pipeline consumer — wall color detection, checkpoint detection | -| WorldMap | Position matching via cross-correlation, canvas stitching | -| StuckDetector | No-progress detection | -| WallColorTracker | Learns wall palette from initial spawn | - -**Flow**: Capture minimap → detect position via wall color stitching → pathfind → send WASD keys - ---- - -## Nexus.Bot — Top-Level Orchestration - -Central coordinator that wires everything together. - -| Class | Purpose | -|-------|---------| -| BotOrchestrator | DI container, state machine, frame pipeline management | -| TradeExecutor | Single trade flow (navigate → buy → deposit) | -| MappingExecutor | Map exploration (navigate + loot) | -| KulemakExecutor | Boss fight with arena mechanics | -| CraftingExecutor | Crafting bench operations | -| DiamondExecutor | Diamond trade handling | -| ScrapExecutor | Vendor scrapping | -| TradeQueue | FIFO queue of trade tasks | -| LinkManager | Trade search management | - -**Bot modes**: Trading, Mapping, Crafting (via BotMode enum) - ---- - -## Nexus.Ui — Avalonia Desktop Application - -Entry point executable. Avalonia 11.2 + CommunityToolkit.MVVM + FluentTheme. - -**App.xaml.cs** wires all DI: -- Services: ConfigStore, GameController, ScreenReader, ClientLogWatcher, TradeMonitor, InventoryManager -- Bot: FramePipelineService, LinkManager, TradeExecutor, TradeQueue, BotOrchestrator, ModPoolService -- ViewModels: Main, Debug, Settings, Mapping, Atlas, Crafting, Memory, Nexus, ObjectBrowser - -**Additional dependencies**: Vortice.Direct2D1 (overlay rendering), Microsoft.Extensions.DependencyInjection - -**Views**: MainWindow, DebugWindow, SettingsWindow, MappingWindow, etc. with MVVM bindings. diff --git a/docs/input.md b/docs/input.md deleted file mode 100644 index fa8ccb5..0000000 --- a/docs/input.md +++ /dev/null @@ -1,67 +0,0 @@ -# Nexus.Input — Input Controllers & Humanization - -## IInputController Implementations - -### SendInputController (Default, No Driver) - -Uses Win32 `SendInput` API with **KEYEVENTF_SCANCODE** flag. Games read hardware scan codes, not virtual key codes. - -- **KeyDown/KeyUp**: Raw keyboard scan code via SendInput struct -- **KeyPress**: Down → Sleep(holdMs) → Up with humanization -- **SmoothMoveTo**: Cubic Bézier curve interpolation (10-40 steps) with random perpendicular spread -- **MouseMoveTo**: Direct `SetCursorPos()` (instant teleport) -- **Clicks**: Smooth move to target → humanized delay → click - -### InterceptionInputController (Driver-Based) - -Uses Interception keyboard/mouse driver for lower-level control: -- Delegates to `KeyboardHook` and `MouseHook` via InputInterceptor COM library -- Same smooth movement and humanization as SendInput -- Returns false from `Initialize()` if driver not installed (graceful fallback) - -### SimInputController (Simulator) - -Implements `IInputController` but doesn't make Win32 calls. Instead: -- **WASD** → Tracks held state, converts to direction vector with 45° isometric rotation -- **Skills** → Queues skill casts to SimWorld via `QueueSkill()` -- **Mouse** → Tracks screen position, converts to world coords via inverse camera matrix -- **Visualization** → Maintains flash timers (0.15s) for InputOverlayRenderer - -## Scan Codes - -``` -Movement: W=0x11 A=0x1E S=0x1F D=0x20 -Skills: Q=0x10 E=0x12 R=0x13 T=0x14 -Numbers: 1=0x02 2=0x03 3=0x04 4=0x05 5=0x06 -Modifiers: LShift=0x2A LCtrl=0x1D LAlt=0x38 -Other: Space=0x39 Enter=0x1C Escape=0x01 Slash=0x35 -``` - -## Humanizer - -Anti-detection layer applied to all input operations. - -| Method | Purpose | -|--------|---------| -| GaussianDelay(baseMs) | Adds gaussian noise (Box-Muller transform), clamped to [50ms, 150ms] | -| JitterPosition(x, y) | Random pixel offset within ClickJitterRadius (3px) | -| ShouldThrottle() | Tracks actions in 60-second rolling window, blocks if APM > MaxApm (250) | -| RecordAction() | Enqueues timestamp for APM tracking | -| RandomizedInterval(baseMs) | Adds ±20% jitter to poll intervals | - -## MovementKeyTracker - -Converts normalized direction vectors to WASD key state for isometric camera: - -``` -Rotate direction 45°: - sx = dir.X * cos(45°) - dir.Y * sin(45°) - sy = dir.X * sin(45°) + dir.Y * cos(45°) - -Key mapping: - W if sy > 0.3, S if sy < -0.3 - D if sx > 0.3, A if sx < -0.3 - -Delta-based: only sends KeyDown/KeyUp when state changes. -Supports holding multiple keys (W+D for diagonal). -``` diff --git a/docs/pathfinding.md b/docs/pathfinding.md deleted file mode 100644 index 00bd003..0000000 --- a/docs/pathfinding.md +++ /dev/null @@ -1,153 +0,0 @@ -# Nexus.Pathfinding — Navigation & Exploration - -## Overview - -Two classes: **NavigationController** (state machine — decides *where* to go) and **PathFinder** (A* algorithm — decides *how* to get there). - ---- - -## NavigationController - -### Modes - -| Mode | Trigger | Behavior | -|------|---------|----------| -| Idle | `Stop()` | No movement | -| NavigatingToPosition | `NavigateTo(pos)` | Path to fixed world coordinates | -| NavigatingToEntity | `NavigateToEntity(id)` | Chase a moving entity (re-targets each tick) | -| Exploring | `Explore()` | BFS frontier exploration of unmapped terrain | - -### Update Loop (called every tick) - -``` -1. Area change detection → clear path, explored grid, stuck history -2. EnsureExploredGrid() → allocate/resize to match terrain (preserves old data on expansion) -3. MarkExplored(playerPos) → mark cells near player as visited (radius 150 grid cells) -4. ResolveGoal() → get target position based on mode -5. If no goal and Exploring → PickExploreTarget() via BFS -6. Reach detection → within WaypointReachedDistance (80u), clear goal or stop -7. Stuck detection → if < 30u movement in 60 ticks (~1s), repath or pick new target -8. Pathfinding → A* from player to goal (with explored grid bias in explore mode) -9. Waypoint advancement → advance index as player reaches each waypoint -10. Output → DesiredDirection (normalized vector to next waypoint) -``` - -### Explored Grid - -Parallel bool array matching terrain dimensions. Tracks which cells the player has visited. - -- **Mark radius**: 150 grid cells (~1630 world units) — circular region around player -- **Preservation**: On terrain expansion, overlapping explored data is copied to new grid -- **Offset-aware**: Uses same OffsetX/OffsetY as terrain for absolute grid coordinates - -### BFS Exploration (PickExploreTarget) - -When Exploring mode needs a new goal: - -1. **BFS frontier search** (up to 100,000 iterations) - - 8-directional BFS outward from player - - Finds nearest unexplored walkable cell - - Returns that cell as world coordinates - -2. **Random distant target** (if BFS finds nothing) - - 20 attempts at random directions, 1500-3500 world units away - - Pushes player toward terrain edges where expansion triggers - -3. **Edge fallback** (if random fails) - - Heads toward nearest terrain boundary (10 cells from edge) - - Guarantees continued exploration with infinite terrain - -4. **Exploration complete** (only if all fallbacks fail) - - Sets `IsExplorationComplete = true` - - Prevents expensive re-BFS every tick - - Reset on area change - -### Stuck Detection - -- **Window**: Last 60 positions (~1 second at 60Hz) -- **Threshold**: Must move at least 30 world units in window -- **Grace period**: 120 ticks (2 seconds) after picking new explore target -- **On stuck while exploring**: Mark failed goal as explored, pick new target, set grace period -- **On stuck otherwise**: Repath - -### Path Failure Handling - -- **Explored bias fallback**: If A* with explored grid bias fails, retry without bias (bias can make distant targets unreachable) -- **Cooldown**: 3 seconds before retrying after path failure (prevents CPU burn on impossible paths) - ---- - -## PathFinder — A* Implementation - -### Signature - -```csharp -public static List? FindPath( - WalkabilitySnapshot terrain, Vector2 start, Vector2 goal, float worldToGrid, - bool[]? exploredGrid, int exploredWidth, int exploredHeight, - int exploredOffsetX, int exploredOffsetY) -``` - -Returns world-coordinate waypoints or null if unreachable. - -### Movement Model - -- **8-directional grid**: Cardinal + diagonal -- **Costs**: Cardinal = 1.0, Diagonal = √2 ≈ 1.414 -- **Explored penalty**: ×1.5 multiplier for explored cells (biases paths through unexplored territory) - -### Heuristic - -``` -h = max(dx, dy) + 0.414 * min(dx, dy) -``` -Diagonal/Chebyshev-based. Admissible and consistent. - -### Algorithm - -1. **Snap to walkable**: If start/goal in wall, BFS search for nearest walkable cell (radius up to 20) -2. **A* search** (budget: 200,000 iterations): - - Priority queue ordered by f = g + h - - 8 neighbors per expansion - - **Corner-cut check**: Diagonals require at least one adjacent cardinal cell walkable - - **Explored grid bias**: Multiply step cost by 1.5 for explored cells - - Track `bestNode` (closest reachable) for fallback -3. **Path reconstruction**: Backtrack via cameFrom map -4. **Simplification**: Remove collinear waypoints, keep only turning points -5. **Fallback**: If goal unreachable but bestNode is meaningfully closer (within 80% of starting heuristic), path to closest reachable cell - -### Data Structures - -| Structure | Type | Purpose | -|-----------|------|---------| -| Open set | PriorityQueue<(int,int), float> | Nodes to expand, ordered by f-score | -| Closed set | HashSet<(int,int)> | Already expanded nodes | -| gScore | Dictionary<(int,int), float> | Best known cost to each node | -| cameFrom | Dictionary<(int,int), (int,int)> | Backtracking map | - ---- - -## Integration - -``` -AreaProgressionSystem - │ .Explore() / .NavigateTo() / .NavigateToEntity() - ▼ -NavigationController - │ .Update(GameState) → computes path, sets DesiredDirection - │ calls PathFinder.FindPath() for A* routing - ▼ -NavigationSystem - │ reads DesiredDirection → submits MoveAction - ▼ -ActionQueue → BotEngine → MovementKeyTracker → WASD keys -``` - -### Coordinate Systems - -| Space | Example | Conversion | -|-------|---------|------------| -| World | (1517, 4491) | Raw game units | -| Grid | (139, 413) | world × WorldToGrid (23/250) | -| Local grid | (139-ox, 413-oy) | grid - terrain offset | -| Screen | project via CameraMatrix | WorldToScreen.Project() | diff --git a/docs/simulator.md b/docs/simulator.md deleted file mode 100644 index 28a6d8c..0000000 --- a/docs/simulator.md +++ /dev/null @@ -1,180 +0,0 @@ -# Nexus.Simulator — Standalone Game World - -## Purpose - -Test bot systems (combat, navigation, threat assessment) without the real game. Replaces the memory-reading pipeline with a procedural game world. Bot systems run unmodified — they see identical `GameState` objects and emit actions to `IInputController`. - -## Architecture - -``` -SimWorld (game tick loop) - │ - ├── SimPoller (60Hz background thread) - │ ├── FlushToWorld() → transfer input to SimWorld - │ ├── Tick(dt) → advance simulation - │ ├── SimStateBuilder.Build() → SimWorld → GameState - │ └── Push to GameDataCache - │ - ├── SimInputController (captures bot actions) - │ ├── WASD → MoveDirection vector (45° isometric conversion) - │ ├── Skills → QueueSkill(scanCode, targetWorldPos) - │ ├── Mouse → track position, screen↔world conversion - │ └── Flash timers for input visualization - │ - ├── Bot Logic Thread (60Hz) - │ ├── GameStateEnricher.Enrich(state) - │ ├── All 6 systems: Threat, Movement, Navigation, Combat, Resource, Loot - │ ├── NavigationController.Update() - │ └── ExecuteActions() → SimInputController - │ - └── Render Thread (ImGui + Veldrid) - ├── TerrainRenderer (diamond cells, isometric) - ├── EntityRenderer (player, enemies, health bars) - ├── EffectRenderer (melee cones, AOE circles, projectile lines) - ├── PathRenderer (A* waypoints) - ├── InputOverlayRenderer (keyboard + mouse state) - └── DebugPanel (system toggles, stats, spawn controls) -``` - -## SimWorld — Game Loop - -### Tick (dt-based, 60Hz) - -``` -1. CheckAndExpandTerrain() → expand when player within 50 cells of edge -2. MovePlayer(dt) → WASD direction × speed × dt, collision with terrain -3. ProcessSkills() → dequeue skill casts, dispatch by scan code -4. UpdateProjectiles(dt) → move, check terrain/enemy collisions -5. UpdateEffects(dt) → decay visual effects (0.3s duration) -6. UpdateEnemies(dt) → AI state machine per enemy -7. UpdateRespawns(dt) → cull far enemies, spawn new groups -``` - -### Terrain - -- Procedural: all walkable with scattered obstacles (rock clusters, wall segments, pillars) -- 500×500 initial grid, `WorldToGrid = 23/250` -- **Infinite expansion**: Expands 250 cells per side when player within 50 cells of edge -- Preserves existing data via array copy with offset adjustment - -### Player - -- Position (Vector2), Health/Mana with regen (5 HP/s, 10 MP/s) -- Move speed: 400 world units/s -- Collision: slide-along-X / slide-along-Y fallback if direct move blocked - -### Skills - -| Scan Code | Type | Behavior | -|-----------|------|----------| -| Q (0x10), R (0x13) | AOE | Damage all enemies within 250u of target position | -| E (0x12), T (0x14) | Projectile | Spawn projectile, 1200 speed, 800 range, 80u hit radius | -| LMB, RMB | Melee | 150u cone, 120° angle from player toward target | - -Base damage: 200 per hit. Configurable via SimConfig. - -### Enemy AI - -``` -State machine per SimEnemy: - -Idle → wander randomly within 200u of spawn, new target every 2-5s - │ player within 600u (aggro range) - ▼ -Chasing → move toward player at 75% player speed - │ player within 100u (attack range) - ▼ -Attacking → stand still, deal 30 damage every 1.5s - │ player escapes attack range - ▼ back to Chasing - │ health ≤ 0 - ▼ -Dead → visible for 2s → queue for respawn (5s delay) -``` - -### Enemy Spawning - -- **Groups**: 3-7 enemies per spawn, leader keeps rolled rarity, rest are Normal -- **Rarity distribution**: 70% Normal, 20% Magic, 8% Rare, 2% Unique -- **HP multipliers**: Magic=1.5×, Rare=3×, Unique=5× base (200) -- **Spawn ring**: 800-2000 world units from player -- **Direction bias**: ±90° cone ahead of player's movement direction -- **Culling**: Remove enemies > 3000u from player -- **Population**: Maintain 25 enemies, spawn new groups as needed - -## Bridge Layer - -### SimPoller - -Replaces MemoryPoller. Background thread at 60Hz: -1. `FlushToWorld()` — transfer accumulated input -2. `world.Tick(dt)` — advance simulation (dt clamped to 0.1s max) -3. `SimStateBuilder.Build()` — convert to GameState -4. Push to GameDataCache (same fields as production) - -### SimStateBuilder - -Converts SimWorld state → GameState: -- Each SimEnemy → EntitySnapshot (with rarity, threat level, AI state, HP) -- SimPlayer → PlayerState (position, vitals, skills) -- Camera matrix: orthographic projection (12800×7200 world units → 2560×1440 screen) - -### SimInputController - -Implements IInputController, captures actions instead of sending Win32 input: -- WASD → direction vector (with 45° isometric inversion) -- Skills → `SimWorld.QueueSkill(scanCode, worldPos)` -- Mouse → screen position tracking, inverse camera transform for world coords -- Input visualization: flash timers for keyboard/mouse overlay - -## Rendering - -### ViewTransform (Isometric Camera) - -45° counter-clockwise rotation matching the game's camera: - -``` -World → Grid: gx = worldX × WorldToGrid -Grid → Screen: rx = (gx - gy) × cos(45°) - ry = -(gx + gy) × cos(45°) -Screen = canvasOrigin + viewOffset + (rx, ry) × zoom -``` - -### Renderers - -| Renderer | Draws | -|----------|-------| -| TerrainRenderer | Diamond cells (rotated grid), explored overlay, minimap | -| EntityRenderer | Player (green circle), enemies (colored by rarity), health/mana bars | -| EffectRenderer | Melee cones (red triangle fan), AOE circles (blue), projectile lines (cyan) | -| PathRenderer | Cyan waypoint lines and dots from A* path | -| InputOverlayRenderer | Keyboard (3 rows: 1-5, QWERT, ASDF) + mouse (L/R/M buttons) | -| DebugPanel | Pause/speed, player stats, enemy counts, system toggles, threat info | - -### VeldridImGuiRenderer - -Custom ImGui backend for Veldrid 4.9.0 + D3D11: -- HLSL shaders compiled at runtime via D3DCompiler P/Invoke -- Dynamic vertex/index buffers, font texture from ImGui atlas -- Alpha blending pipeline with scissor rect support - -## SimConfig - -``` -Terrain: 500×500, WorldToGrid=23/250, ExpandThreshold=50, ExpandAmount=250 -Player: Speed=400, HP=1000, MP=500, HPRegen=5/s, MPRegen=10/s -Enemies: Count=25, Aggro=600u, Attack=100u, Speed=75%, HP=200, Damage=30 -Spawning: Ring=800-2000u, Groups=3-7, Cull=3000u -Skills: Melee=150u/120°, AOE=250u, Projectile=1200speed/800range, Damage=200 -Rarity: Normal=70%, Magic=20%, Rare=8%, Unique=2% -Simulation: SpeedMultiplier=1×, Pauseable -``` - -## Running - -``` -dotnet run --project src/Nexus.Simulator -``` - -Dependencies: Core, Data, Systems, Pathfinding, ImGui.NET, Veldrid, Veldrid.StartupUtilities -Does NOT depend on: Memory, Input, Screen, Game, Bot, Ui, Trade diff --git a/docs/test.html b/docs/test.html deleted file mode 100644 index 87f1b56..0000000 --- a/docs/test.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - Hold Timer - - - -
- -
Last hold: 0 ms
-
Current hold: 0 ms
-
- - - - \ No newline at end of file diff --git a/imgui.ini b/imgui.ini deleted file mode 100644 index 9c98818..0000000 --- a/imgui.ini +++ /dev/null @@ -1,15 +0,0 @@ -[Window][Debug##Default] -Pos=60,60 -Size=400,400 -Collapsed=0 - -[Window][Simulator Controls] -Pos=29,51 -Size=432,649 -Collapsed=0 - -[Window][Simulator] -Pos=564,96 -Size=1023,810 -Collapsed=0 - diff --git a/src/Nexus.Bot/AtlasExecutor.cs b/src/Automata.Bot/AtlasExecutor.cs similarity index 98% rename from src/Nexus.Bot/AtlasExecutor.cs rename to src/Automata.Bot/AtlasExecutor.cs index 65ff5c0..2f4e307 100644 --- a/src/Nexus.Bot/AtlasExecutor.cs +++ b/src/Automata.Bot/AtlasExecutor.cs @@ -1,11 +1,11 @@ -using Nexus.Core; -using Nexus.Game; -using Nexus.Inventory; -using Nexus.Navigation; -using Nexus.Screen; +using Automata.Core; +using Automata.Game; +using Automata.Inventory; +using Automata.Navigation; +using Automata.Screen; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; /// /// Captures the full endgame atlas as a panorama image. diff --git a/src/Automata.Bot/Automata.Bot.csproj b/src/Automata.Bot/Automata.Bot.csproj new file mode 100644 index 0000000..77ae1f4 --- /dev/null +++ b/src/Automata.Bot/Automata.Bot.csproj @@ -0,0 +1,17 @@ + + + net8.0-windows10.0.19041.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Nexus.Bot/BotOrchestrator.cs b/src/Automata.Bot/BotOrchestrator.cs similarity index 99% rename from src/Nexus.Bot/BotOrchestrator.cs rename to src/Automata.Bot/BotOrchestrator.cs index ab2ca51..708f005 100644 --- a/src/Nexus.Bot/BotOrchestrator.cs +++ b/src/Automata.Bot/BotOrchestrator.cs @@ -1,13 +1,13 @@ -using Nexus.Core; -using Nexus.Game; -using Nexus.Inventory; -using Nexus.GameLog; -using Nexus.Navigation; -using Nexus.Screen; -using Nexus.Trade; +using Automata.Core; +using Automata.Game; +using Automata.Inventory; +using Automata.GameLog; +using Automata.Navigation; +using Automata.Screen; +using Automata.Trade; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; public class BotStatus { diff --git a/src/Nexus.Bot/CombatManager.cs b/src/Automata.Bot/CombatManager.cs similarity index 98% rename from src/Nexus.Bot/CombatManager.cs rename to src/Automata.Bot/CombatManager.cs index 44ce7cf..d494cde 100644 --- a/src/Nexus.Bot/CombatManager.cs +++ b/src/Automata.Bot/CombatManager.cs @@ -1,10 +1,10 @@ using System.Diagnostics; -using Nexus.Core; -using Nexus.Game; -using Nexus.Screen; +using Automata.Core; +using Automata.Game; +using Automata.Screen; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; /// /// Manages the attack state machine (click → hold) with mana monitoring and flask usage. diff --git a/src/Nexus.Bot/CraftingExecutor.cs b/src/Automata.Bot/CraftingExecutor.cs similarity index 98% rename from src/Nexus.Bot/CraftingExecutor.cs rename to src/Automata.Bot/CraftingExecutor.cs index e7add43..3ba22e0 100644 --- a/src/Nexus.Bot/CraftingExecutor.cs +++ b/src/Automata.Bot/CraftingExecutor.cs @@ -1,9 +1,9 @@ -using Nexus.Core; -using Nexus.Game; -using Nexus.Items; +using Automata.Core; +using Automata.Game; +using Automata.Items; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; public class CraftingExecutor { diff --git a/src/Nexus.Bot/DiamondExecutor.cs b/src/Automata.Bot/DiamondExecutor.cs similarity index 98% rename from src/Nexus.Bot/DiamondExecutor.cs rename to src/Automata.Bot/DiamondExecutor.cs index fd1ab7f..590a58f 100644 --- a/src/Nexus.Bot/DiamondExecutor.cs +++ b/src/Automata.Bot/DiamondExecutor.cs @@ -1,12 +1,12 @@ using System.Collections.Concurrent; -using Nexus.Core; -using Nexus.Game; -using Nexus.Inventory; -using Nexus.Screen; -using Nexus.Trade; +using Automata.Core; +using Automata.Game; +using Automata.Inventory; +using Automata.Screen; +using Automata.Trade; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; public class DiamondExecutor { diff --git a/src/Nexus.Bot/FlaskManager.cs b/src/Automata.Bot/FlaskManager.cs similarity index 96% rename from src/Nexus.Bot/FlaskManager.cs rename to src/Automata.Bot/FlaskManager.cs index 591a043..476b4b7 100644 --- a/src/Nexus.Bot/FlaskManager.cs +++ b/src/Automata.Bot/FlaskManager.cs @@ -1,9 +1,9 @@ using System.Diagnostics; -using Nexus.Game; -using Nexus.Screen; +using Automata.Game; +using Automata.Screen; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; /// /// Monitors life/mana and presses flask keys when they drop below thresholds. diff --git a/src/Nexus.Bot/GameExecutor.cs b/src/Automata.Bot/GameExecutor.cs similarity index 98% rename from src/Nexus.Bot/GameExecutor.cs rename to src/Automata.Bot/GameExecutor.cs index 4f6ebe6..1f7386e 100644 --- a/src/Nexus.Bot/GameExecutor.cs +++ b/src/Automata.Bot/GameExecutor.cs @@ -1,11 +1,11 @@ using System.Diagnostics; -using Nexus.Core; -using Nexus.Game; -using Nexus.Inventory; -using Nexus.Screen; +using Automata.Core; +using Automata.Game; +using Automata.Inventory; +using Automata.Screen; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; /// /// Base class for game executors that interact with the game world. diff --git a/src/Nexus.Bot/KulemakExecutor.cs b/src/Automata.Bot/KulemakExecutor.cs similarity index 99% rename from src/Nexus.Bot/KulemakExecutor.cs rename to src/Automata.Bot/KulemakExecutor.cs index e97088a..1abd402 100644 --- a/src/Nexus.Bot/KulemakExecutor.cs +++ b/src/Automata.Bot/KulemakExecutor.cs @@ -1,12 +1,12 @@ -using Nexus.Core; -using Nexus.Game; -using Nexus.GameLog; -using Nexus.Inventory; -using Nexus.Navigation; -using Nexus.Screen; +using Automata.Core; +using Automata.Game; +using Automata.GameLog; +using Automata.Inventory; +using Automata.Navigation; +using Automata.Screen; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; /// /// Kulemak-specific boss run executor: scripted 4-phase + ring fight, diff --git a/src/Nexus.Bot/MappingExecutor.cs b/src/Automata.Bot/MappingExecutor.cs similarity index 99% rename from src/Nexus.Bot/MappingExecutor.cs rename to src/Automata.Bot/MappingExecutor.cs index 88ed649..a8979ea 100644 --- a/src/Nexus.Bot/MappingExecutor.cs +++ b/src/Automata.Bot/MappingExecutor.cs @@ -1,13 +1,13 @@ using System.Diagnostics; -using Nexus.Core; -using Nexus.Game; -using Nexus.GameLog; -using Nexus.Inventory; -using Nexus.Navigation; -using Nexus.Screen; +using Automata.Core; +using Automata.Game; +using Automata.GameLog; +using Automata.Inventory; +using Automata.Navigation; +using Automata.Screen; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; /// /// Shared infrastructure for any map/boss activity: combat loop, WASD navigation, diff --git a/src/Nexus.Bot/ScrapExecutor.cs b/src/Automata.Bot/ScrapExecutor.cs similarity index 97% rename from src/Nexus.Bot/ScrapExecutor.cs rename to src/Automata.Bot/ScrapExecutor.cs index dc4f051..3631265 100644 --- a/src/Nexus.Bot/ScrapExecutor.cs +++ b/src/Automata.Bot/ScrapExecutor.cs @@ -1,11 +1,11 @@ -using Nexus.Core; -using Nexus.Game; -using Nexus.Inventory; -using Nexus.Screen; -using Nexus.Trade; +using Automata.Core; +using Automata.Game; +using Automata.Inventory; +using Automata.Screen; +using Automata.Trade; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; public class ScrapExecutor { diff --git a/src/Nexus.Bot/TradeExecutor.cs b/src/Automata.Bot/TradeExecutor.cs similarity index 97% rename from src/Nexus.Bot/TradeExecutor.cs rename to src/Automata.Bot/TradeExecutor.cs index acac3ce..dfb1a34 100644 --- a/src/Nexus.Bot/TradeExecutor.cs +++ b/src/Automata.Bot/TradeExecutor.cs @@ -1,11 +1,11 @@ -using Nexus.Core; -using Nexus.Game; -using Nexus.Inventory; -using Nexus.Screen; -using Nexus.Trade; +using Automata.Core; +using Automata.Game; +using Automata.Inventory; +using Automata.Screen; +using Automata.Trade; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; public class TradeExecutor { diff --git a/src/Nexus.Bot/TradeQueue.cs b/src/Automata.Bot/TradeQueue.cs similarity index 97% rename from src/Nexus.Bot/TradeQueue.cs rename to src/Automata.Bot/TradeQueue.cs index 7ae9387..5521247 100644 --- a/src/Nexus.Bot/TradeQueue.cs +++ b/src/Automata.Bot/TradeQueue.cs @@ -1,7 +1,7 @@ -using Nexus.Core; +using Automata.Core; using Serilog; -namespace Nexus.Bot; +namespace Automata.Bot; public class TradeQueue { diff --git a/src/Nexus.Core/Nexus.Core.csproj b/src/Automata.Core/Automata.Core.csproj similarity index 86% rename from src/Nexus.Core/Nexus.Core.csproj rename to src/Automata.Core/Automata.Core.csproj index 4aadc25..5aff918 100644 --- a/src/Nexus.Core/Nexus.Core.csproj +++ b/src/Automata.Core/Automata.Core.csproj @@ -8,6 +8,6 @@ - + diff --git a/src/Nexus.Core/ConfigStore.cs b/src/Automata.Core/ConfigStore.cs similarity index 99% rename from src/Nexus.Core/ConfigStore.cs rename to src/Automata.Core/ConfigStore.cs index d0fef6d..6c93261 100644 --- a/src/Nexus.Core/ConfigStore.cs +++ b/src/Automata.Core/ConfigStore.cs @@ -2,7 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Serilog; -namespace Nexus.Core; +namespace Automata.Core; public class SavedLink { diff --git a/src/Nexus.Core/Delays.cs b/src/Automata.Core/Delays.cs similarity index 92% rename from src/Nexus.Core/Delays.cs rename to src/Automata.Core/Delays.cs index 907aad0..01b9f16 100644 --- a/src/Nexus.Core/Delays.cs +++ b/src/Automata.Core/Delays.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Automata.Core; public static class Delays { diff --git a/src/Nexus.Core/Helpers.cs b/src/Automata.Core/Helpers.cs similarity index 95% rename from src/Nexus.Core/Helpers.cs rename to src/Automata.Core/Helpers.cs index 48b3c16..19f5205 100644 --- a/src/Nexus.Core/Helpers.cs +++ b/src/Automata.Core/Helpers.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Automata.Core; public static class Helpers { diff --git a/src/Nexus.Core/LinkManager.cs b/src/Automata.Core/LinkManager.cs similarity index 99% rename from src/Nexus.Core/LinkManager.cs rename to src/Automata.Core/LinkManager.cs index 32f0ec2..7915a9a 100644 --- a/src/Nexus.Core/LinkManager.cs +++ b/src/Automata.Core/LinkManager.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Core; +namespace Automata.Core; public class TradeLink { diff --git a/src/Nexus.Core/Logging.cs b/src/Automata.Core/Logging.cs similarity index 89% rename from src/Nexus.Core/Logging.cs rename to src/Automata.Core/Logging.cs index 67465d0..bd4f173 100644 --- a/src/Nexus.Core/Logging.cs +++ b/src/Automata.Core/Logging.cs @@ -1,14 +1,14 @@ using Serilog; using Serilog.Events; -namespace Nexus.Core; +namespace Automata.Core; public static class Logging { public static void Setup() { Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() + .MinimumLevel.Information() .WriteTo.Console( outputTemplate: "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}") .WriteTo.File("logs/poe2trade-.log", diff --git a/src/Nexus.Core/ModPoolService.cs b/src/Automata.Core/ModPoolService.cs similarity index 99% rename from src/Nexus.Core/ModPoolService.cs rename to src/Automata.Core/ModPoolService.cs index 4000c1f..4aeeddc 100644 --- a/src/Nexus.Core/ModPoolService.cs +++ b/src/Automata.Core/ModPoolService.cs @@ -2,7 +2,7 @@ using System.Net.Http; using System.Text.Json; using Serilog; -namespace Nexus.Core; +namespace Automata.Core; public class ModPoolService { diff --git a/src/Nexus.Core/Poe2ScoutClient.cs b/src/Automata.Core/Poe2ScoutClient.cs similarity index 98% rename from src/Nexus.Core/Poe2ScoutClient.cs rename to src/Automata.Core/Poe2ScoutClient.cs index 8422d9d..51c07ff 100644 --- a/src/Nexus.Core/Poe2ScoutClient.cs +++ b/src/Automata.Core/Poe2ScoutClient.cs @@ -1,7 +1,7 @@ using System.Text.Json; using Serilog; -namespace Nexus.Core; +namespace Automata.Core; public static class Poe2ScoutClient { diff --git a/src/Nexus.Core/RePoETypes.cs b/src/Automata.Core/RePoETypes.cs similarity index 98% rename from src/Nexus.Core/RePoETypes.cs rename to src/Automata.Core/RePoETypes.cs index 9034de7..d62f665 100644 --- a/src/Nexus.Core/RePoETypes.cs +++ b/src/Automata.Core/RePoETypes.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Nexus.Core; +namespace Automata.Core; public class RePoEMod { diff --git a/src/Nexus.Core/StashCalibration.cs b/src/Automata.Core/StashCalibration.cs similarity index 95% rename from src/Nexus.Core/StashCalibration.cs rename to src/Automata.Core/StashCalibration.cs index 98f1cda..587b8b6 100644 --- a/src/Nexus.Core/StashCalibration.cs +++ b/src/Automata.Core/StashCalibration.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Automata.Core; public class StashTabInfo { diff --git a/src/Nexus.Core/Types.cs b/src/Automata.Core/Types.cs similarity index 99% rename from src/Nexus.Core/Types.cs rename to src/Automata.Core/Types.cs index 972159c..69a4deb 100644 --- a/src/Nexus.Core/Types.cs +++ b/src/Automata.Core/Types.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Automata.Core; public record Region(int X, int Y, int Width, int Height); diff --git a/src/Nexus.Game/Nexus.Game.csproj b/src/Automata.Game/Automata.Game.csproj similarity index 80% rename from src/Nexus.Game/Nexus.Game.csproj rename to src/Automata.Game/Automata.Game.csproj index 2a268b4..fa49371 100644 --- a/src/Nexus.Game/Nexus.Game.csproj +++ b/src/Automata.Game/Automata.Game.csproj @@ -6,6 +6,6 @@ true - + diff --git a/src/Nexus.Game/ClipboardHelper.cs b/src/Automata.Game/ClipboardHelper.cs similarity index 99% rename from src/Nexus.Game/ClipboardHelper.cs rename to src/Automata.Game/ClipboardHelper.cs index dc2e3c4..c895916 100644 --- a/src/Nexus.Game/ClipboardHelper.cs +++ b/src/Automata.Game/ClipboardHelper.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace Nexus.Game; +namespace Automata.Game; /// /// Win32 clipboard access without WinForms dependency. diff --git a/src/Nexus.Game/GameController.cs b/src/Automata.Game/GameController.cs similarity index 98% rename from src/Nexus.Game/GameController.cs rename to src/Automata.Game/GameController.cs index abbc59c..5b82b3c 100644 --- a/src/Nexus.Game/GameController.cs +++ b/src/Automata.Game/GameController.cs @@ -1,7 +1,7 @@ -using Nexus.Core; +using Automata.Core; using Serilog; -namespace Nexus.Game; +namespace Automata.Game; public class GameController : IGameController { diff --git a/src/Nexus.Game/IGameController.cs b/src/Automata.Game/IGameController.cs similarity index 97% rename from src/Nexus.Game/IGameController.cs rename to src/Automata.Game/IGameController.cs index 5b3984d..9359f16 100644 --- a/src/Nexus.Game/IGameController.cs +++ b/src/Automata.Game/IGameController.cs @@ -1,4 +1,4 @@ -namespace Nexus.Game; +namespace Automata.Game; public interface IGameController { diff --git a/src/Nexus.Game/InputSender.cs b/src/Automata.Game/InputSender.cs similarity index 99% rename from src/Nexus.Game/InputSender.cs rename to src/Automata.Game/InputSender.cs index b2c6c22..4942aeb 100644 --- a/src/Nexus.Game/InputSender.cs +++ b/src/Automata.Game/InputSender.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.Core; +using Automata.Core; -namespace Nexus.Game; +namespace Automata.Game; public class InputSender { diff --git a/src/Nexus.Game/WindowManager.cs b/src/Automata.Game/WindowManager.cs similarity index 99% rename from src/Nexus.Game/WindowManager.cs rename to src/Automata.Game/WindowManager.cs index 168252d..f593cfb 100644 --- a/src/Nexus.Game/WindowManager.cs +++ b/src/Automata.Game/WindowManager.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; using Serilog; -namespace Nexus.Game; +namespace Automata.Game; public class WindowManager { diff --git a/src/Automata.Inventory/Automata.Inventory.csproj b/src/Automata.Inventory/Automata.Inventory.csproj new file mode 100644 index 0000000..bfb3ac5 --- /dev/null +++ b/src/Automata.Inventory/Automata.Inventory.csproj @@ -0,0 +1,13 @@ + + + net8.0-windows10.0.19041.0 + enable + enable + + + + + + + + diff --git a/src/Nexus.Inventory/IInventoryManager.cs b/src/Automata.Inventory/IInventoryManager.cs similarity index 94% rename from src/Nexus.Inventory/IInventoryManager.cs rename to src/Automata.Inventory/IInventoryManager.cs index 9817755..ee1a93d 100644 --- a/src/Nexus.Inventory/IInventoryManager.cs +++ b/src/Automata.Inventory/IInventoryManager.cs @@ -1,7 +1,7 @@ -using Nexus.Core; -using Nexus.Screen; +using Automata.Core; +using Automata.Screen; -namespace Nexus.Inventory; +namespace Automata.Inventory; public interface IInventoryManager { diff --git a/src/Nexus.Inventory/InventoryManager.cs b/src/Automata.Inventory/InventoryManager.cs similarity index 99% rename from src/Nexus.Inventory/InventoryManager.cs rename to src/Automata.Inventory/InventoryManager.cs index c68dc31..1e31c55 100644 --- a/src/Nexus.Inventory/InventoryManager.cs +++ b/src/Automata.Inventory/InventoryManager.cs @@ -1,10 +1,10 @@ -using Nexus.Core; -using Nexus.Game; -using Nexus.GameLog; -using Nexus.Screen; +using Automata.Core; +using Automata.Game; +using Automata.GameLog; +using Automata.Screen; using Serilog; -namespace Nexus.Inventory; +namespace Automata.Inventory; public class InventoryManager : IInventoryManager { diff --git a/src/Nexus.Inventory/InventoryTracker.cs b/src/Automata.Inventory/InventoryTracker.cs similarity index 98% rename from src/Nexus.Inventory/InventoryTracker.cs rename to src/Automata.Inventory/InventoryTracker.cs index 562fc63..ef33f4f 100644 --- a/src/Nexus.Inventory/InventoryTracker.cs +++ b/src/Automata.Inventory/InventoryTracker.cs @@ -1,8 +1,8 @@ -using Nexus.Core; -using Nexus.Screen; +using Automata.Core; +using Automata.Screen; using Serilog; -namespace Nexus.Inventory; +namespace Automata.Inventory; public class PlacedItem { diff --git a/src/Nexus.Inventory/StashCalibrator.cs b/src/Automata.Inventory/StashCalibrator.cs similarity index 98% rename from src/Nexus.Inventory/StashCalibrator.cs rename to src/Automata.Inventory/StashCalibrator.cs index 8c2e576..657f9d6 100644 --- a/src/Nexus.Inventory/StashCalibrator.cs +++ b/src/Automata.Inventory/StashCalibrator.cs @@ -1,9 +1,9 @@ -using Nexus.Core; -using Nexus.Game; -using Nexus.Screen; +using Automata.Core; +using Automata.Game; +using Automata.Screen; using Serilog; -namespace Nexus.Inventory; +namespace Automata.Inventory; public class StashCalibrator { diff --git a/src/Nexus.Items/Nexus.Items.csproj b/src/Automata.Items/Automata.Items.csproj similarity index 76% rename from src/Nexus.Items/Nexus.Items.csproj rename to src/Automata.Items/Automata.Items.csproj index 69f3196..014b17a 100644 --- a/src/Nexus.Items/Nexus.Items.csproj +++ b/src/Automata.Items/Automata.Items.csproj @@ -5,8 +5,8 @@ enable - - + + diff --git a/src/Nexus.Items/ItemReader.cs b/src/Automata.Items/ItemReader.cs similarity index 96% rename from src/Nexus.Items/ItemReader.cs rename to src/Automata.Items/ItemReader.cs index 06b9c37..7475780 100644 --- a/src/Nexus.Items/ItemReader.cs +++ b/src/Automata.Items/ItemReader.cs @@ -1,10 +1,10 @@ -using Nexus.Core; -using Nexus.Game; +using Automata.Core; +using Automata.Game; using Sidekick.Apis.Poe.Items; using Sidekick.Apis.Poe.Trade.Parser; using Serilog; -namespace Nexus.Items; +namespace Automata.Items; /// /// Reads item data by hovering and pressing Ctrl+C to copy item text to clipboard. diff --git a/src/Nexus.Items/SidekickBootstrapper.cs b/src/Automata.Items/SidekickBootstrapper.cs similarity index 98% rename from src/Nexus.Items/SidekickBootstrapper.cs rename to src/Automata.Items/SidekickBootstrapper.cs index 7cf81a5..8b3b215 100644 --- a/src/Nexus.Items/SidekickBootstrapper.cs +++ b/src/Automata.Items/SidekickBootstrapper.cs @@ -9,7 +9,7 @@ using Sidekick.Common.Settings; using Sidekick.Data; using Serilog; -namespace Nexus.Items; +namespace Automata.Items; /// /// Bootstraps a minimal Sidekick DI container for item parsing. diff --git a/src/Nexus.Items/SidekickSettingsStub.cs b/src/Automata.Items/SidekickSettingsStub.cs similarity index 98% rename from src/Nexus.Items/SidekickSettingsStub.cs rename to src/Automata.Items/SidekickSettingsStub.cs index 9dfa7fe..4a8a54b 100644 --- a/src/Nexus.Items/SidekickSettingsStub.cs +++ b/src/Automata.Items/SidekickSettingsStub.cs @@ -1,6 +1,6 @@ using Sidekick.Common.Settings; -namespace Nexus.Items; +namespace Automata.Items; /// /// Minimal ISettingsService returning defaults for Sidekick initialization. diff --git a/src/Nexus.Navigation/AtlasPanorama.cs b/src/Automata.Navigation/AtlasPanorama.cs similarity index 99% rename from src/Nexus.Navigation/AtlasPanorama.cs rename to src/Automata.Navigation/AtlasPanorama.cs index b4a90b8..354baa6 100644 --- a/src/Nexus.Navigation/AtlasPanorama.cs +++ b/src/Automata.Navigation/AtlasPanorama.cs @@ -1,10 +1,10 @@ using System.Diagnostics; using OpenCvSharp; -using Nexus.Core; -using Nexus.Screen; +using Automata.Core; +using Automata.Screen; using Serilog; -namespace Nexus.Navigation; +namespace Automata.Navigation; public record AtlasProgress(int TilesCaptured, int Row, string Phase); diff --git a/src/Nexus.Navigation/Nexus.Navigation.csproj b/src/Automata.Navigation/Automata.Navigation.csproj similarity index 73% rename from src/Nexus.Navigation/Nexus.Navigation.csproj rename to src/Automata.Navigation/Automata.Navigation.csproj index bd3c70c..29698e6 100644 --- a/src/Nexus.Navigation/Nexus.Navigation.csproj +++ b/src/Automata.Navigation/Automata.Navigation.csproj @@ -12,8 +12,8 @@ - - - + + + diff --git a/src/Nexus.Navigation/IconDetector.cs b/src/Automata.Navigation/IconDetector.cs similarity index 99% rename from src/Nexus.Navigation/IconDetector.cs rename to src/Automata.Navigation/IconDetector.cs index a466d36..9312879 100644 --- a/src/Nexus.Navigation/IconDetector.cs +++ b/src/Automata.Navigation/IconDetector.cs @@ -1,7 +1,7 @@ using OpenCvSharp; using Serilog; -namespace Nexus.Navigation; +namespace Automata.Navigation; /// /// Detects minimap icons (doors, checkpoints) via template matching. diff --git a/src/Nexus.Navigation/MinimapCapture.cs b/src/Automata.Navigation/MinimapCapture.cs similarity index 99% rename from src/Nexus.Navigation/MinimapCapture.cs rename to src/Automata.Navigation/MinimapCapture.cs index f0aae8e..e7e3093 100644 --- a/src/Nexus.Navigation/MinimapCapture.cs +++ b/src/Automata.Navigation/MinimapCapture.cs @@ -1,10 +1,10 @@ using OpenCvSharp; -using Nexus.Screen; +using Automata.Screen; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; using Size = OpenCvSharp.Size; -namespace Nexus.Navigation; +namespace Automata.Navigation; public class MinimapCapture : IFrameConsumer, IDisposable { diff --git a/src/Nexus.Navigation/NavigationExecutor.cs b/src/Automata.Navigation/NavigationExecutor.cs similarity index 99% rename from src/Nexus.Navigation/NavigationExecutor.cs rename to src/Automata.Navigation/NavigationExecutor.cs index da36c11..a179164 100644 --- a/src/Nexus.Navigation/NavigationExecutor.cs +++ b/src/Automata.Navigation/NavigationExecutor.cs @@ -1,11 +1,11 @@ using System.Diagnostics; using OpenCvSharp; -using Nexus.Core; -using Nexus.Game; -using Nexus.Screen; +using Automata.Core; +using Automata.Game; +using Automata.Screen; using Serilog; -namespace Nexus.Navigation; +namespace Automata.Navigation; public class NavigationExecutor : IDisposable { diff --git a/src/Nexus.Navigation/NavigationTypes.cs b/src/Automata.Navigation/NavigationTypes.cs similarity index 99% rename from src/Nexus.Navigation/NavigationTypes.cs rename to src/Automata.Navigation/NavigationTypes.cs index 884a833..65080af 100644 --- a/src/Nexus.Navigation/NavigationTypes.cs +++ b/src/Automata.Navigation/NavigationTypes.cs @@ -1,7 +1,7 @@ -using Nexus.Core; +using Automata.Core; using OpenCvSharp; -namespace Nexus.Navigation; +namespace Automata.Navigation; public enum MinimapMode { diff --git a/src/Nexus.Navigation/PathFinder.cs b/src/Automata.Navigation/PathFinder.cs similarity index 99% rename from src/Nexus.Navigation/PathFinder.cs rename to src/Automata.Navigation/PathFinder.cs index 2988fbd..9445e81 100644 --- a/src/Nexus.Navigation/PathFinder.cs +++ b/src/Automata.Navigation/PathFinder.cs @@ -1,7 +1,7 @@ using OpenCvSharp; using Serilog; -namespace Nexus.Navigation; +namespace Automata.Navigation; /// /// Last BFS result for visualization. diff --git a/src/Nexus.Navigation/PerspectiveCalibrator.cs b/src/Automata.Navigation/PerspectiveCalibrator.cs similarity index 99% rename from src/Nexus.Navigation/PerspectiveCalibrator.cs rename to src/Automata.Navigation/PerspectiveCalibrator.cs index 8d6da04..1f3ab8f 100644 --- a/src/Nexus.Navigation/PerspectiveCalibrator.cs +++ b/src/Automata.Navigation/PerspectiveCalibrator.cs @@ -1,9 +1,9 @@ using OpenCvSharp; -using Nexus.Core; -using Nexus.Screen; +using Automata.Core; +using Automata.Screen; using Serilog; -namespace Nexus.Navigation; +namespace Automata.Navigation; public record CalibrationResult(float BestFactor, double BestConfidence, Dictionary AllResults); diff --git a/src/Nexus.Navigation/StuckDetector.cs b/src/Automata.Navigation/StuckDetector.cs similarity index 96% rename from src/Nexus.Navigation/StuckDetector.cs rename to src/Automata.Navigation/StuckDetector.cs index 3168233..5dd02c5 100644 --- a/src/Nexus.Navigation/StuckDetector.cs +++ b/src/Automata.Navigation/StuckDetector.cs @@ -1,4 +1,4 @@ -namespace Nexus.Navigation; +namespace Automata.Navigation; /// /// Detects when the player hasn't moved significantly over a window of frames. diff --git a/src/Nexus.Navigation/WallColorTracker.cs b/src/Automata.Navigation/WallColorTracker.cs similarity index 99% rename from src/Nexus.Navigation/WallColorTracker.cs rename to src/Automata.Navigation/WallColorTracker.cs index d9e9896..a28d810 100644 --- a/src/Nexus.Navigation/WallColorTracker.cs +++ b/src/Automata.Navigation/WallColorTracker.cs @@ -1,7 +1,7 @@ using OpenCvSharp; using Serilog; -namespace Nexus.Navigation; +namespace Automata.Navigation; /// /// Tracks HSV distribution of confirmed wall pixels and computes an adaptive diff --git a/src/Nexus.Navigation/WorldMap.cs b/src/Automata.Navigation/WorldMap.cs similarity index 99% rename from src/Nexus.Navigation/WorldMap.cs rename to src/Automata.Navigation/WorldMap.cs index 922cbd2..4435fd5 100644 --- a/src/Nexus.Navigation/WorldMap.cs +++ b/src/Automata.Navigation/WorldMap.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using OpenCvSharp; using Serilog; -namespace Nexus.Navigation; +namespace Automata.Navigation; public class WorldMap : IDisposable { diff --git a/src/Nexus.Screen/Nexus.Screen.csproj b/src/Automata.Screen/Automata.Screen.csproj similarity index 91% rename from src/Nexus.Screen/Nexus.Screen.csproj rename to src/Automata.Screen/Automata.Screen.csproj index e2b7096..780d038 100644 --- a/src/Nexus.Screen/Nexus.Screen.csproj +++ b/src/Automata.Screen/Automata.Screen.csproj @@ -15,6 +15,6 @@ - + diff --git a/src/Nexus.Screen/BossDetector.cs b/src/Automata.Screen/BossDetector.cs similarity index 99% rename from src/Nexus.Screen/BossDetector.cs rename to src/Automata.Screen/BossDetector.cs index c0e80ff..2fb6f31 100644 --- a/src/Nexus.Screen/BossDetector.cs +++ b/src/Automata.Screen/BossDetector.cs @@ -1,8 +1,8 @@ using OpenCvSharp; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; -namespace Nexus.Screen; +namespace Automata.Screen; /// /// Detects bosses using YOLO running on a background thread. diff --git a/src/Nexus.Screen/DaemonTypes.cs b/src/Automata.Screen/DaemonTypes.cs similarity index 97% rename from src/Nexus.Screen/DaemonTypes.cs rename to src/Automata.Screen/DaemonTypes.cs index fd27194..f149805 100644 --- a/src/Nexus.Screen/DaemonTypes.cs +++ b/src/Automata.Screen/DaemonTypes.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Nexus.Screen; +namespace Automata.Screen; public class OcrWord { @@ -49,7 +49,7 @@ public class DiffOcrResponse { public string Text { get; set; } = ""; public List Lines { get; set; } = []; - public Nexus.Core.Region? Region { get; set; } + public Automata.Core.Region? Region { get; set; } } public class TemplateMatchResult diff --git a/src/Nexus.Screen/DesktopDuplication.cs b/src/Automata.Screen/DesktopDuplication.cs similarity index 98% rename from src/Nexus.Screen/DesktopDuplication.cs rename to src/Automata.Screen/DesktopDuplication.cs index 40b4e39..4d81bcf 100644 --- a/src/Nexus.Screen/DesktopDuplication.cs +++ b/src/Automata.Screen/DesktopDuplication.cs @@ -5,9 +5,9 @@ using SharpGen.Runtime; using Vortice.Direct3D; using Vortice.Direct3D11; using Vortice.DXGI; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; -namespace Nexus.Screen; +namespace Automata.Screen; public sealed class DesktopDuplication : IScreenCapture { diff --git a/src/Nexus.Screen/DetectGridHandler.cs b/src/Automata.Screen/DetectGridHandler.cs similarity index 98% rename from src/Nexus.Screen/DetectGridHandler.cs rename to src/Automata.Screen/DetectGridHandler.cs index 581076c..ab4b4f0 100644 --- a/src/Nexus.Screen/DetectGridHandler.cs +++ b/src/Automata.Screen/DetectGridHandler.cs @@ -1,10 +1,10 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; class DetectGridHandler { diff --git a/src/Nexus.Screen/DetectionTypes.cs b/src/Automata.Screen/DetectionTypes.cs similarity index 95% rename from src/Nexus.Screen/DetectionTypes.cs rename to src/Automata.Screen/DetectionTypes.cs index 4252710..2ada4bf 100644 --- a/src/Nexus.Screen/DetectionTypes.cs +++ b/src/Automata.Screen/DetectionTypes.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; public record DetectedEnemy( float Confidence, diff --git a/src/Nexus.Screen/DiffCropHandler.cs b/src/Automata.Screen/DiffCropHandler.cs similarity index 99% rename from src/Nexus.Screen/DiffCropHandler.cs rename to src/Automata.Screen/DiffCropHandler.cs index 4b3781a..acd300f 100644 --- a/src/Nexus.Screen/DiffCropHandler.cs +++ b/src/Automata.Screen/DiffCropHandler.cs @@ -1,10 +1,10 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; class DiffCropHandler { diff --git a/src/Nexus.Screen/EdgeCropHandler.cs b/src/Automata.Screen/EdgeCropHandler.cs similarity index 99% rename from src/Nexus.Screen/EdgeCropHandler.cs rename to src/Automata.Screen/EdgeCropHandler.cs index 301390f..6fbe46b 100644 --- a/src/Nexus.Screen/EdgeCropHandler.cs +++ b/src/Automata.Screen/EdgeCropHandler.cs @@ -1,10 +1,10 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; class EdgeCropHandler { diff --git a/src/Nexus.Screen/EnemyDetector.cs b/src/Automata.Screen/EnemyDetector.cs similarity index 98% rename from src/Nexus.Screen/EnemyDetector.cs rename to src/Automata.Screen/EnemyDetector.cs index 2e1406a..37727bf 100644 --- a/src/Nexus.Screen/EnemyDetector.cs +++ b/src/Automata.Screen/EnemyDetector.cs @@ -1,9 +1,9 @@ using OpenCvSharp; -using Nexus.Core; +using Automata.Core; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; -namespace Nexus.Screen; +namespace Automata.Screen; /// /// Detects enemies on screen using two-stage approach: diff --git a/src/Nexus.Screen/FramePipeline.cs b/src/Automata.Screen/FramePipeline.cs similarity index 97% rename from src/Nexus.Screen/FramePipeline.cs rename to src/Automata.Screen/FramePipeline.cs index b5e52cf..e8734e5 100644 --- a/src/Nexus.Screen/FramePipeline.cs +++ b/src/Automata.Screen/FramePipeline.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; public class FramePipeline : IDisposable { diff --git a/src/Nexus.Screen/FramePipelineService.cs b/src/Automata.Screen/FramePipelineService.cs similarity index 96% rename from src/Nexus.Screen/FramePipelineService.cs rename to src/Automata.Screen/FramePipelineService.cs index 42e5362..b213661 100644 --- a/src/Nexus.Screen/FramePipelineService.cs +++ b/src/Automata.Screen/FramePipelineService.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Screen; +namespace Automata.Screen; public class FramePipelineService : IDisposable { diff --git a/src/Nexus.Screen/FrameSaver.cs b/src/Automata.Screen/FrameSaver.cs similarity index 98% rename from src/Nexus.Screen/FrameSaver.cs rename to src/Automata.Screen/FrameSaver.cs index 3e7831c..2eb2c88 100644 --- a/src/Nexus.Screen/FrameSaver.cs +++ b/src/Automata.Screen/FrameSaver.cs @@ -1,8 +1,8 @@ using OpenCvSharp; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; -namespace Nexus.Screen; +namespace Automata.Screen; /// /// Saves full-screen frames as JPEGs for YOLO training data collection. diff --git a/src/Nexus.Screen/GameStateDetector.cs b/src/Automata.Screen/GameStateDetector.cs similarity index 95% rename from src/Nexus.Screen/GameStateDetector.cs rename to src/Automata.Screen/GameStateDetector.cs index 5061e17..89be1bd 100644 --- a/src/Nexus.Screen/GameStateDetector.cs +++ b/src/Automata.Screen/GameStateDetector.cs @@ -1,7 +1,7 @@ -using Nexus.Core; +using Automata.Core; using Serilog; -namespace Nexus.Screen; +namespace Automata.Screen; /// /// Classifies the current game UI state by probing known pixel positions on each frame. diff --git a/src/Nexus.Screen/GdiCapture.cs b/src/Automata.Screen/GdiCapture.cs similarity index 95% rename from src/Nexus.Screen/GdiCapture.cs rename to src/Automata.Screen/GdiCapture.cs index 0a6313c..ff72148 100644 --- a/src/Nexus.Screen/GdiCapture.cs +++ b/src/Automata.Screen/GdiCapture.cs @@ -3,9 +3,9 @@ using System.Drawing.Imaging; using System.Runtime.InteropServices; using OpenCvSharp; using OpenCvSharp.Extensions; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; -namespace Nexus.Screen; +namespace Automata.Screen; public sealed class GdiCapture : IScreenCapture { diff --git a/src/Nexus.Screen/GridHandler.cs b/src/Automata.Screen/GridHandler.cs similarity index 99% rename from src/Nexus.Screen/GridHandler.cs rename to src/Automata.Screen/GridHandler.cs index 7a5c3ba..6943bcd 100644 --- a/src/Nexus.Screen/GridHandler.cs +++ b/src/Automata.Screen/GridHandler.cs @@ -1,8 +1,8 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Drawing; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; public class GridHandler { diff --git a/src/Nexus.Screen/GridReader.cs b/src/Automata.Screen/GridReader.cs similarity index 98% rename from src/Nexus.Screen/GridReader.cs rename to src/Automata.Screen/GridReader.cs index 8be4fd0..0a9285d 100644 --- a/src/Nexus.Screen/GridReader.cs +++ b/src/Automata.Screen/GridReader.cs @@ -1,7 +1,7 @@ -using Nexus.Core; +using Automata.Core; using Serilog; -namespace Nexus.Screen; +namespace Automata.Screen; public class GridLayout { diff --git a/src/Nexus.Screen/HudReader.cs b/src/Automata.Screen/HudReader.cs similarity index 98% rename from src/Nexus.Screen/HudReader.cs rename to src/Automata.Screen/HudReader.cs index 2fe83ed..3e60e93 100644 --- a/src/Nexus.Screen/HudReader.cs +++ b/src/Automata.Screen/HudReader.cs @@ -1,9 +1,9 @@ using OpenCvSharp; -using Nexus.Core; +using Automata.Core; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; -namespace Nexus.Screen; +namespace Automata.Screen; public record HudSnapshot { diff --git a/src/Nexus.Screen/IFrameConsumer.cs b/src/Automata.Screen/IFrameConsumer.cs similarity index 73% rename from src/Nexus.Screen/IFrameConsumer.cs rename to src/Automata.Screen/IFrameConsumer.cs index ca96ebb..b7089c6 100644 --- a/src/Nexus.Screen/IFrameConsumer.cs +++ b/src/Automata.Screen/IFrameConsumer.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; public interface IFrameConsumer { diff --git a/src/Nexus.Screen/IOcrEngine.cs b/src/Automata.Screen/IOcrEngine.cs similarity index 83% rename from src/Nexus.Screen/IOcrEngine.cs rename to src/Automata.Screen/IOcrEngine.cs index a66bf7b..6822e5c 100644 --- a/src/Nexus.Screen/IOcrEngine.cs +++ b/src/Automata.Screen/IOcrEngine.cs @@ -1,6 +1,6 @@ using System.Drawing; -namespace Nexus.Screen; +namespace Automata.Screen; public interface IOcrEngine : IDisposable { diff --git a/src/Nexus.Screen/IScreenCapture.cs b/src/Automata.Screen/IScreenCapture.cs similarity index 69% rename from src/Nexus.Screen/IScreenCapture.cs rename to src/Automata.Screen/IScreenCapture.cs index f354f93..4e96887 100644 --- a/src/Nexus.Screen/IScreenCapture.cs +++ b/src/Automata.Screen/IScreenCapture.cs @@ -1,7 +1,7 @@ using OpenCvSharp; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; -namespace Nexus.Screen; +namespace Automata.Screen; public interface IScreenCapture : IDisposable { diff --git a/src/Nexus.Screen/IScreenReader.cs b/src/Automata.Screen/IScreenReader.cs similarity index 96% rename from src/Nexus.Screen/IScreenReader.cs rename to src/Automata.Screen/IScreenReader.cs index dbeaf98..3d5e808 100644 --- a/src/Nexus.Screen/IScreenReader.cs +++ b/src/Automata.Screen/IScreenReader.cs @@ -1,6 +1,6 @@ -using Nexus.Core; +using Automata.Core; -namespace Nexus.Screen; +namespace Automata.Screen; public interface IScreenReader : IDisposable { diff --git a/src/Nexus.Screen/ImagePreprocessor.cs b/src/Automata.Screen/ImagePreprocessor.cs similarity index 99% rename from src/Nexus.Screen/ImagePreprocessor.cs rename to src/Automata.Screen/ImagePreprocessor.cs index b935b75..4e374e6 100644 --- a/src/Nexus.Screen/ImagePreprocessor.cs +++ b/src/Automata.Screen/ImagePreprocessor.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Drawing; using OpenCvSharp; diff --git a/src/Nexus.Screen/ImageUtils.cs b/src/Automata.Screen/ImageUtils.cs similarity index 97% rename from src/Nexus.Screen/ImageUtils.cs rename to src/Automata.Screen/ImageUtils.cs index dde7e84..8cb346b 100644 --- a/src/Nexus.Screen/ImageUtils.cs +++ b/src/Automata.Screen/ImageUtils.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Drawing; using System.Drawing.Imaging; diff --git a/src/Nexus.Screen/LootDebugDetector.cs b/src/Automata.Screen/LootDebugDetector.cs similarity index 98% rename from src/Nexus.Screen/LootDebugDetector.cs rename to src/Automata.Screen/LootDebugDetector.cs index 0a5c7cb..47c2f4c 100644 --- a/src/Nexus.Screen/LootDebugDetector.cs +++ b/src/Automata.Screen/LootDebugDetector.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Screen; +namespace Automata.Screen; /// /// Debug-only: periodically captures the screen, runs loot label detection, diff --git a/src/Nexus.Screen/LootLabel.cs b/src/Automata.Screen/LootLabel.cs similarity index 99% rename from src/Nexus.Screen/LootLabel.cs rename to src/Automata.Screen/LootLabel.cs index 99e56eb..a3fd8d3 100644 --- a/src/Nexus.Screen/LootLabel.cs +++ b/src/Automata.Screen/LootLabel.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; /// /// A detected loot label on screen with its position and classified tier. diff --git a/src/Nexus.Screen/Ocr/EasyOcrEngine.cs b/src/Automata.Screen/Ocr/EasyOcrEngine.cs similarity index 96% rename from src/Nexus.Screen/Ocr/EasyOcrEngine.cs rename to src/Automata.Screen/Ocr/EasyOcrEngine.cs index 1a81fd8..47d6655 100644 --- a/src/Nexus.Screen/Ocr/EasyOcrEngine.cs +++ b/src/Automata.Screen/Ocr/EasyOcrEngine.cs @@ -1,6 +1,6 @@ using System.Drawing; -namespace Nexus.Screen.Ocr; +namespace Automata.Screen.Ocr; /// /// OCR engine wrapping the Python EasyOCR daemon. diff --git a/src/Nexus.Screen/Ocr/OcrEngineFactory.cs b/src/Automata.Screen/Ocr/OcrEngineFactory.cs similarity index 93% rename from src/Nexus.Screen/Ocr/OcrEngineFactory.cs rename to src/Automata.Screen/Ocr/OcrEngineFactory.cs index e4cd982..8ee2636 100644 --- a/src/Nexus.Screen/Ocr/OcrEngineFactory.cs +++ b/src/Automata.Screen/Ocr/OcrEngineFactory.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Screen.Ocr; +namespace Automata.Screen.Ocr; public static class OcrEngineFactory { diff --git a/src/Nexus.Screen/Ocr/OneOcrEngine.cs b/src/Automata.Screen/Ocr/OneOcrEngine.cs similarity index 99% rename from src/Nexus.Screen/Ocr/OneOcrEngine.cs rename to src/Automata.Screen/Ocr/OneOcrEngine.cs index dd9a5ec..a982024 100644 --- a/src/Nexus.Screen/Ocr/OneOcrEngine.cs +++ b/src/Automata.Screen/Ocr/OneOcrEngine.cs @@ -3,7 +3,7 @@ using System.Drawing.Imaging; using System.Runtime.InteropServices; using Serilog; -namespace Nexus.Screen.Ocr; +namespace Automata.Screen.Ocr; /// /// OCR engine using OneOCR (Windows 11 Snipping Tool's built-in engine). diff --git a/src/Nexus.Screen/Ocr/WinOcrEngine.cs b/src/Automata.Screen/Ocr/WinOcrEngine.cs similarity index 98% rename from src/Nexus.Screen/Ocr/WinOcrEngine.cs rename to src/Automata.Screen/Ocr/WinOcrEngine.cs index b1e7cd5..f9688f1 100644 --- a/src/Nexus.Screen/Ocr/WinOcrEngine.cs +++ b/src/Automata.Screen/Ocr/WinOcrEngine.cs @@ -7,7 +7,7 @@ using Windows.Storage.Streams; using BitmapDecoder = Windows.Graphics.Imaging.BitmapDecoder; using SdImageFormat = System.Drawing.Imaging.ImageFormat; -namespace Nexus.Screen.Ocr; +namespace Automata.Screen.Ocr; public sealed class WinOcrEngine : IOcrEngine { diff --git a/src/Nexus.Screen/OnnxYoloDetector.cs b/src/Automata.Screen/OnnxYoloDetector.cs similarity index 99% rename from src/Nexus.Screen/OnnxYoloDetector.cs rename to src/Automata.Screen/OnnxYoloDetector.cs index 0da8399..0ac27bd 100644 --- a/src/Nexus.Screen/OnnxYoloDetector.cs +++ b/src/Automata.Screen/OnnxYoloDetector.cs @@ -5,7 +5,7 @@ using OpenCvSharp; using OpenCvSharp.Dnn; using Serilog; -namespace Nexus.Screen; +namespace Automata.Screen; /// /// YOLO11 object detection via ONNX Runtime with CUDA GPU acceleration. diff --git a/src/Nexus.Screen/PythonDetectBridge.cs b/src/Automata.Screen/PythonDetectBridge.cs similarity index 99% rename from src/Nexus.Screen/PythonDetectBridge.cs rename to src/Automata.Screen/PythonDetectBridge.cs index 4249144..6a083a3 100644 --- a/src/Nexus.Screen/PythonDetectBridge.cs +++ b/src/Automata.Screen/PythonDetectBridge.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Diagnostics; using System.Text.Json; diff --git a/src/Nexus.Screen/PythonOcrBridge.cs b/src/Automata.Screen/PythonOcrBridge.cs similarity index 99% rename from src/Nexus.Screen/PythonOcrBridge.cs rename to src/Automata.Screen/PythonOcrBridge.cs index 544135f..ef206d9 100644 --- a/src/Nexus.Screen/PythonOcrBridge.cs +++ b/src/Automata.Screen/PythonOcrBridge.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Diagnostics; using System.Drawing; diff --git a/src/Nexus.Screen/ScreenCapture.cs b/src/Automata.Screen/ScreenCapture.cs similarity index 96% rename from src/Nexus.Screen/ScreenCapture.cs rename to src/Automata.Screen/ScreenCapture.cs index 4018ee6..8d64f61 100644 --- a/src/Nexus.Screen/ScreenCapture.cs +++ b/src/Automata.Screen/ScreenCapture.cs @@ -1,9 +1,9 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; static class ScreenCapture { diff --git a/src/Nexus.Screen/ScreenFrame.cs b/src/Automata.Screen/ScreenFrame.cs similarity index 93% rename from src/Nexus.Screen/ScreenFrame.cs rename to src/Automata.Screen/ScreenFrame.cs index 5377be0..7acceeb 100644 --- a/src/Nexus.Screen/ScreenFrame.cs +++ b/src/Automata.Screen/ScreenFrame.cs @@ -1,7 +1,7 @@ using OpenCvSharp; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; -namespace Nexus.Screen; +namespace Automata.Screen; public class ScreenFrame : IDisposable { diff --git a/src/Nexus.Screen/ScreenReader.cs b/src/Automata.Screen/ScreenReader.cs similarity index 99% rename from src/Nexus.Screen/ScreenReader.cs rename to src/Automata.Screen/ScreenReader.cs index 3a92ca7..c2457a7 100644 --- a/src/Nexus.Screen/ScreenReader.cs +++ b/src/Automata.Screen/ScreenReader.cs @@ -3,12 +3,12 @@ using System.Drawing.Imaging; using System.Runtime.InteropServices; using OpenCvSharp; using OpenCvSharp.Extensions; -using Nexus.Core; +using Automata.Core; using Serilog; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; using Size = OpenCvSharp.Size; -namespace Nexus.Screen; +namespace Automata.Screen; public class ScreenReader : IScreenReader { diff --git a/src/Nexus.Screen/SignalProcessing.cs b/src/Automata.Screen/SignalProcessing.cs similarity index 99% rename from src/Nexus.Screen/SignalProcessing.cs rename to src/Automata.Screen/SignalProcessing.cs index 84a810d..3f9fee9 100644 --- a/src/Nexus.Screen/SignalProcessing.cs +++ b/src/Automata.Screen/SignalProcessing.cs @@ -1,4 +1,4 @@ -namespace Nexus.Screen; +namespace Automata.Screen; static class SignalProcessing { diff --git a/src/Nexus.Screen/TemplateMatchHandler.cs b/src/Automata.Screen/TemplateMatchHandler.cs similarity index 98% rename from src/Nexus.Screen/TemplateMatchHandler.cs rename to src/Automata.Screen/TemplateMatchHandler.cs index 61fd1a7..d779cf1 100644 --- a/src/Nexus.Screen/TemplateMatchHandler.cs +++ b/src/Automata.Screen/TemplateMatchHandler.cs @@ -1,9 +1,9 @@ -namespace Nexus.Screen; +namespace Automata.Screen; using System.Drawing; using OpenCvSharp; using OpenCvSharp.Extensions; -using Region = Nexus.Core.Region; +using Region = Automata.Core.Region; class TemplateMatchHandler { diff --git a/src/Nexus.Trade/Nexus.Trade.csproj b/src/Automata.Trade/Automata.Trade.csproj similarity index 77% rename from src/Nexus.Trade/Nexus.Trade.csproj rename to src/Automata.Trade/Automata.Trade.csproj index 3266cd4..0d0f761 100644 --- a/src/Nexus.Trade/Nexus.Trade.csproj +++ b/src/Automata.Trade/Automata.Trade.csproj @@ -5,6 +5,6 @@ enable - + diff --git a/src/Nexus.Trade/ITradeMonitor.cs b/src/Automata.Trade/ITradeMonitor.cs similarity index 93% rename from src/Nexus.Trade/ITradeMonitor.cs rename to src/Automata.Trade/ITradeMonitor.cs index 4c34cc6..ef45711 100644 --- a/src/Nexus.Trade/ITradeMonitor.cs +++ b/src/Automata.Trade/ITradeMonitor.cs @@ -1,6 +1,6 @@ -using Nexus.Core; +using Automata.Core; -namespace Nexus.Trade; +namespace Automata.Trade; public interface ITradeMonitor : IAsyncDisposable { diff --git a/src/Nexus.Trade/Selectors.cs b/src/Automata.Trade/Selectors.cs similarity index 97% rename from src/Nexus.Trade/Selectors.cs rename to src/Automata.Trade/Selectors.cs index c724ccd..eb0345a 100644 --- a/src/Nexus.Trade/Selectors.cs +++ b/src/Automata.Trade/Selectors.cs @@ -1,4 +1,4 @@ -namespace Nexus.Trade; +namespace Automata.Trade; public static class Selectors { diff --git a/src/Nexus.Trade/TradeDaemonBridge.cs b/src/Automata.Trade/TradeDaemonBridge.cs similarity index 99% rename from src/Nexus.Trade/TradeDaemonBridge.cs rename to src/Automata.Trade/TradeDaemonBridge.cs index c51c163..df7c768 100644 --- a/src/Nexus.Trade/TradeDaemonBridge.cs +++ b/src/Automata.Trade/TradeDaemonBridge.cs @@ -2,10 +2,10 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Text.Json; using System.Text.Json.Serialization; -using Nexus.Core; +using Automata.Core; using Serilog; -namespace Nexus.Trade; +namespace Automata.Trade; public class TradeDaemonBridge : ITradeMonitor { diff --git a/src/Nexus.Ui/App.axaml b/src/Automata.Ui/App.axaml similarity index 92% rename from src/Nexus.Ui/App.axaml rename to src/Automata.Ui/App.axaml index cc27e59..a77fa7a 100644 --- a/src/Nexus.Ui/App.axaml +++ b/src/Automata.Ui/App.axaml @@ -1,7 +1,7 @@ diff --git a/src/Nexus.Ui/App.axaml.cs b/src/Automata.Ui/App.axaml.cs similarity index 89% rename from src/Nexus.Ui/App.axaml.cs rename to src/Automata.Ui/App.axaml.cs index 822483d..92a293f 100644 --- a/src/Nexus.Ui/App.axaml.cs +++ b/src/Automata.Ui/App.axaml.cs @@ -2,20 +2,20 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Microsoft.Extensions.DependencyInjection; -using Nexus.Bot; -using Nexus.Core; -using Nexus.Game; -using Nexus.GameLog; -using Nexus.Inventory; -using Nexus.Items; -using Nexus.Screen; -using Nexus.Screen.Ocr; -using Nexus.Trade; -using Nexus.Ui.Overlay; -using Nexus.Ui.ViewModels; -using Nexus.Ui.Views; +using Automata.Bot; +using Automata.Core; +using Automata.Game; +using Automata.GameLog; +using Automata.Inventory; +using Automata.Items; +using Automata.Screen; +using Automata.Screen.Ocr; +using Automata.Trade; +using Automata.Ui.Overlay; +using Automata.Ui.ViewModels; +using Automata.Ui.Views; -namespace Nexus.Ui; +namespace Automata.Ui; public partial class App : Application { @@ -70,7 +70,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); var provider = services.BuildServiceProvider(); @@ -97,7 +97,7 @@ public partial class App : Application mainVm.AtlasVm = provider.GetRequiredService(); mainVm.CraftingVm = provider.GetRequiredService(); mainVm.MemoryVm = provider.GetRequiredService(); - mainVm.NexusVm = provider.GetRequiredService(); + mainVm.RobotoVm = provider.GetRequiredService(); mainVm.BrowserVm = provider.GetRequiredService(); var window = new MainWindow { DataContext = mainVm }; @@ -112,7 +112,7 @@ public partial class App : Application { overlay.Shutdown(); mainVm.Shutdown(); - mainVm.NexusVm?.Shutdown(); + mainVm.RobotoVm?.Shutdown(); await bot.DisposeAsync(); }; } diff --git a/src/Nexus.Ui/Nexus.Ui.csproj b/src/Automata.Ui/Automata.Ui.csproj similarity index 76% rename from src/Nexus.Ui/Nexus.Ui.csproj rename to src/Automata.Ui/Automata.Ui.csproj index f79c27c..ce866f4 100644 --- a/src/Nexus.Ui/Nexus.Ui.csproj +++ b/src/Automata.Ui/Automata.Ui.csproj @@ -16,16 +16,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/src/Nexus.Ui/Converters/ValueConverters.cs b/src/Automata.Ui/Converters/ValueConverters.cs similarity index 98% rename from src/Nexus.Ui/Converters/ValueConverters.cs rename to src/Automata.Ui/Converters/ValueConverters.cs index fc43ef3..7550d71 100644 --- a/src/Nexus.Ui/Converters/ValueConverters.cs +++ b/src/Automata.Ui/Converters/ValueConverters.cs @@ -2,10 +2,10 @@ using System.Globalization; using Avalonia; using Avalonia.Data.Converters; using Avalonia.Media; -using Nexus.Core; -using Nexus.Ui.ViewModels; +using Automata.Core; +using Automata.Ui.ViewModels; -namespace Nexus.Ui.Converters; +namespace Automata.Ui.Converters; public class LogLevelToBrushConverter : IValueConverter { diff --git a/src/Nexus.Ui/Overlay/D2dNativeMethods.cs b/src/Automata.Ui/Overlay/D2dNativeMethods.cs similarity index 99% rename from src/Nexus.Ui/Overlay/D2dNativeMethods.cs rename to src/Automata.Ui/Overlay/D2dNativeMethods.cs index f37496b..f33e9f7 100644 --- a/src/Nexus.Ui/Overlay/D2dNativeMethods.cs +++ b/src/Automata.Ui/Overlay/D2dNativeMethods.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.Ui.Overlay; +namespace Automata.Ui.Overlay; /// Win32 P/Invoke for the D2D overlay window, DWM transparency, and frame timing. internal static partial class D2dNativeMethods diff --git a/src/Nexus.Ui/Overlay/D2dOverlay.cs b/src/Automata.Ui/Overlay/D2dOverlay.cs similarity index 98% rename from src/Nexus.Ui/Overlay/D2dOverlay.cs rename to src/Automata.Ui/Overlay/D2dOverlay.cs index 1bcd18e..5cc155b 100644 --- a/src/Nexus.Ui/Overlay/D2dOverlay.cs +++ b/src/Automata.Ui/Overlay/D2dOverlay.cs @@ -1,13 +1,13 @@ using System.Diagnostics; using System.Runtime; using System.Runtime.InteropServices; -using Nexus.Bot; -using Nexus.Ui.Overlay.Layers; -using Nexus.Memory; +using Automata.Bot; +using Automata.Ui.Overlay.Layers; +using Roboto.Memory; using Vortice.Mathematics; -using static Nexus.Ui.Overlay.D2dNativeMethods; +using static Automata.Ui.Overlay.D2dNativeMethods; -namespace Nexus.Ui.Overlay; +namespace Automata.Ui.Overlay; /// /// Fullscreen transparent overlay rendered with Direct2D on a dedicated thread. diff --git a/src/Nexus.Ui/Overlay/D2dRenderContext.cs b/src/Automata.Ui/Overlay/D2dRenderContext.cs similarity index 99% rename from src/Nexus.Ui/Overlay/D2dRenderContext.cs rename to src/Automata.Ui/Overlay/D2dRenderContext.cs index d0cd9df..c81fa3c 100644 --- a/src/Nexus.Ui/Overlay/D2dRenderContext.cs +++ b/src/Automata.Ui/Overlay/D2dRenderContext.cs @@ -6,7 +6,7 @@ using DWriteFactory = Vortice.DirectWrite.IDWriteFactory; using D2dFactoryType = Vortice.Direct2D1.FactoryType; using DwFactoryType = Vortice.DirectWrite.FactoryType; -namespace Nexus.Ui.Overlay; +namespace Automata.Ui.Overlay; public sealed class D2dRenderContext : IDisposable { diff --git a/src/Nexus.Ui/Overlay/IOverlayLayer.cs b/src/Automata.Ui/Overlay/IOverlayLayer.cs similarity index 91% rename from src/Nexus.Ui/Overlay/IOverlayLayer.cs rename to src/Automata.Ui/Overlay/IOverlayLayer.cs index bd210ac..b220771 100644 --- a/src/Nexus.Ui/Overlay/IOverlayLayer.cs +++ b/src/Automata.Ui/Overlay/IOverlayLayer.cs @@ -1,7 +1,7 @@ -using Nexus.Navigation; -using Nexus.Screen; +using Automata.Navigation; +using Automata.Screen; -namespace Nexus.Ui.Overlay; +namespace Automata.Ui.Overlay; public record OverlayState( IReadOnlyList Enemies, diff --git a/src/Nexus.Ui/Overlay/Layers/D2dDebugTextLayer.cs b/src/Automata.Ui/Overlay/Layers/D2dDebugTextLayer.cs similarity index 99% rename from src/Nexus.Ui/Overlay/Layers/D2dDebugTextLayer.cs rename to src/Automata.Ui/Overlay/Layers/D2dDebugTextLayer.cs index e9f3008..f5d6c7f 100644 --- a/src/Nexus.Ui/Overlay/Layers/D2dDebugTextLayer.cs +++ b/src/Automata.Ui/Overlay/Layers/D2dDebugTextLayer.cs @@ -3,7 +3,7 @@ using Vortice.Direct2D1; using Vortice.DirectWrite; using Vortice.Mathematics; -namespace Nexus.Ui.Overlay.Layers; +namespace Automata.Ui.Overlay.Layers; internal sealed class D2dDebugTextLayer : ID2dOverlayLayer, IDisposable { diff --git a/src/Nexus.Ui/Overlay/Layers/D2dEnemyBoxLayer.cs b/src/Automata.Ui/Overlay/Layers/D2dEnemyBoxLayer.cs similarity index 99% rename from src/Nexus.Ui/Overlay/Layers/D2dEnemyBoxLayer.cs rename to src/Automata.Ui/Overlay/Layers/D2dEnemyBoxLayer.cs index 2a29ace..13c0eb1 100644 --- a/src/Nexus.Ui/Overlay/Layers/D2dEnemyBoxLayer.cs +++ b/src/Automata.Ui/Overlay/Layers/D2dEnemyBoxLayer.cs @@ -3,7 +3,7 @@ using Vortice.Direct2D1; using Vortice.DirectWrite; using Vortice.Mathematics; -namespace Nexus.Ui.Overlay.Layers; +namespace Automata.Ui.Overlay.Layers; internal sealed class D2dEnemyBoxLayer : ID2dOverlayLayer, IDisposable { diff --git a/src/Nexus.Ui/Overlay/Layers/D2dEntityLabelLayer.cs b/src/Automata.Ui/Overlay/Layers/D2dEntityLabelLayer.cs similarity index 94% rename from src/Nexus.Ui/Overlay/Layers/D2dEntityLabelLayer.cs rename to src/Automata.Ui/Overlay/Layers/D2dEntityLabelLayer.cs index 19c40d4..b5fcf77 100644 --- a/src/Nexus.Ui/Overlay/Layers/D2dEntityLabelLayer.cs +++ b/src/Automata.Ui/Overlay/Layers/D2dEntityLabelLayer.cs @@ -1,10 +1,10 @@ using System.Drawing; using System.Numerics; -using Nexus.Ui.ViewModels; -using Nexus.Data; +using Automata.Ui.ViewModels; +using Roboto.Data; using Vortice.DirectWrite; -namespace Nexus.Ui.Overlay.Layers; +namespace Automata.Ui.Overlay.Layers; internal sealed class D2dEntityLabelLayer : ID2dOverlayLayer, IDisposable { @@ -23,10 +23,10 @@ internal sealed class D2dEntityLabelLayer : ID2dOverlayLayer, IDisposable public void Draw(D2dRenderContext ctx, OverlayState state) { - var data = NexusViewModel.OverlayData; + var data = RobotoViewModel.OverlayData; if (data is null || data.Entries.Length == 0) return; - var cache = NexusViewModel.SharedCache; + var cache = RobotoViewModel.SharedCache; if (cache is null) return; // Read camera and player position from centralized cache (updated at 60Hz) diff --git a/src/Nexus.Ui/Overlay/Layers/D2dHudInfoLayer.cs b/src/Automata.Ui/Overlay/Layers/D2dHudInfoLayer.cs similarity index 99% rename from src/Nexus.Ui/Overlay/Layers/D2dHudInfoLayer.cs rename to src/Automata.Ui/Overlay/Layers/D2dHudInfoLayer.cs index 9dfa043..6381856 100644 --- a/src/Nexus.Ui/Overlay/Layers/D2dHudInfoLayer.cs +++ b/src/Automata.Ui/Overlay/Layers/D2dHudInfoLayer.cs @@ -3,7 +3,7 @@ using Vortice.Direct2D1; using Vortice.DirectWrite; using Vortice.Mathematics; -namespace Nexus.Ui.Overlay.Layers; +namespace Automata.Ui.Overlay.Layers; internal sealed class D2dHudInfoLayer : ID2dOverlayLayer, IDisposable { diff --git a/src/Nexus.Ui/Overlay/Layers/D2dLootLabelLayer.cs b/src/Automata.Ui/Overlay/Layers/D2dLootLabelLayer.cs similarity index 98% rename from src/Nexus.Ui/Overlay/Layers/D2dLootLabelLayer.cs rename to src/Automata.Ui/Overlay/Layers/D2dLootLabelLayer.cs index d716eb2..3ebfdcd 100644 --- a/src/Nexus.Ui/Overlay/Layers/D2dLootLabelLayer.cs +++ b/src/Automata.Ui/Overlay/Layers/D2dLootLabelLayer.cs @@ -3,7 +3,7 @@ using Vortice.Direct2D1; using Vortice.DirectWrite; using Vortice.Mathematics; -namespace Nexus.Ui.Overlay.Layers; +namespace Automata.Ui.Overlay.Layers; internal sealed class D2dLootLabelLayer : ID2dOverlayLayer, IDisposable { diff --git a/src/Nexus.Ui/Program.cs b/src/Automata.Ui/Program.cs similarity index 94% rename from src/Nexus.Ui/Program.cs rename to src/Automata.Ui/Program.cs index 1afa025..171915b 100644 --- a/src/Nexus.Ui/Program.cs +++ b/src/Automata.Ui/Program.cs @@ -1,8 +1,8 @@ using System.Runtime.InteropServices; using Avalonia; -using Nexus.Core; +using Automata.Core; -namespace Nexus.Ui; +namespace Automata.Ui; class Program { diff --git a/src/Nexus.Ui/ViewModels/AtlasViewModel.cs b/src/Automata.Ui/ViewModels/AtlasViewModel.cs similarity index 98% rename from src/Nexus.Ui/ViewModels/AtlasViewModel.cs rename to src/Automata.Ui/ViewModels/AtlasViewModel.cs index b257fc4..f2acc8b 100644 --- a/src/Nexus.Ui/ViewModels/AtlasViewModel.cs +++ b/src/Automata.Ui/ViewModels/AtlasViewModel.cs @@ -3,11 +3,11 @@ using Avalonia.Media.Imaging; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.Bot; -using Nexus.Navigation; +using Automata.Bot; +using Automata.Navigation; using Serilog; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class AtlasViewModel : ObservableObject, IDisposable { diff --git a/src/Nexus.Ui/ViewModels/CraftingViewModel.cs b/src/Automata.Ui/ViewModels/CraftingViewModel.cs similarity index 99% rename from src/Nexus.Ui/ViewModels/CraftingViewModel.cs rename to src/Automata.Ui/ViewModels/CraftingViewModel.cs index 846b16a..e44b1d6 100644 --- a/src/Nexus.Ui/ViewModels/CraftingViewModel.cs +++ b/src/Automata.Ui/ViewModels/CraftingViewModel.cs @@ -3,12 +3,12 @@ using System.IO; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.Bot; -using Nexus.Core; -using Nexus.Items; +using Automata.Bot; +using Automata.Core; +using Automata.Items; using Serilog; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class CraftStepViewModel : ObservableObject { diff --git a/src/Nexus.Ui/ViewModels/DebugViewModel.cs b/src/Automata.Ui/ViewModels/DebugViewModel.cs similarity index 99% rename from src/Nexus.Ui/ViewModels/DebugViewModel.cs rename to src/Automata.Ui/ViewModels/DebugViewModel.cs index 6760970..43dfce5 100644 --- a/src/Nexus.Ui/ViewModels/DebugViewModel.cs +++ b/src/Automata.Ui/ViewModels/DebugViewModel.cs @@ -1,12 +1,12 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.Bot; -using Nexus.Core; -using Nexus.Game; -using Nexus.Screen; +using Automata.Bot; +using Automata.Core; +using Automata.Game; +using Automata.Screen; using Serilog; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class DebugViewModel : ObservableObject { diff --git a/src/Nexus.Ui/ViewModels/MainWindowViewModel.cs b/src/Automata.Ui/ViewModels/MainWindowViewModel.cs similarity index 98% rename from src/Nexus.Ui/ViewModels/MainWindowViewModel.cs rename to src/Automata.Ui/ViewModels/MainWindowViewModel.cs index 017179f..f75d7f5 100644 --- a/src/Nexus.Ui/ViewModels/MainWindowViewModel.cs +++ b/src/Automata.Ui/ViewModels/MainWindowViewModel.cs @@ -4,12 +4,12 @@ using System.Runtime.InteropServices; using Avalonia.Media.Imaging; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.Bot; -using Nexus.Core; -using Nexus.Navigation; +using Automata.Bot; +using Automata.Core; +using Automata.Navigation; using Serilog; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public class LogEntry { @@ -183,7 +183,7 @@ public partial class MainWindowViewModel : ObservableObject public AtlasViewModel? AtlasVm { get; set; } public CraftingViewModel? CraftingVm { get; set; } public MemoryViewModel? MemoryVm { get; set; } - public NexusViewModel? NexusVm { get; set; } + public RobotoViewModel? RobotoVm { get; set; } public ObjectBrowserViewModel? BrowserVm { get; set; } partial void OnBotModeChanged(BotMode value) diff --git a/src/Nexus.Ui/ViewModels/MappingViewModel.cs b/src/Automata.Ui/ViewModels/MappingViewModel.cs similarity index 97% rename from src/Nexus.Ui/ViewModels/MappingViewModel.cs rename to src/Automata.Ui/ViewModels/MappingViewModel.cs index 5ab9f3d..0514963 100644 --- a/src/Nexus.Ui/ViewModels/MappingViewModel.cs +++ b/src/Automata.Ui/ViewModels/MappingViewModel.cs @@ -2,11 +2,11 @@ using System.Collections.ObjectModel; using Timer = System.Timers.Timer; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; -using Nexus.Bot; -using Nexus.Core; -using Nexus.Screen; +using Automata.Bot; +using Automata.Core; +using Automata.Screen; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class MappingViewModel : ObservableObject, IDisposable { diff --git a/src/Nexus.Ui/ViewModels/MemoryViewModel.cs b/src/Automata.Ui/ViewModels/MemoryViewModel.cs similarity index 99% rename from src/Nexus.Ui/ViewModels/MemoryViewModel.cs rename to src/Automata.Ui/ViewModels/MemoryViewModel.cs index d16b4b1..86093a3 100644 --- a/src/Nexus.Ui/ViewModels/MemoryViewModel.cs +++ b/src/Automata.Ui/ViewModels/MemoryViewModel.cs @@ -6,10 +6,10 @@ using Avalonia.Platform; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.Memory; -using Nexus.Memory.Objects; +using Roboto.Memory; +using Roboto.Memory.Objects; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class MemoryNodeViewModel : ObservableObject { @@ -646,7 +646,7 @@ public partial class MemoryViewModel : ObservableObject { var withPos = snap.Entities.Count(e => e.HasPosition); var withComps = snap.Entities.Count(e => e.Components is not null); - var monsters = snap.Entities.Count(e => e.Type == Nexus.Memory.EntityType.Monster); + var monsters = snap.Entities.Count(e => e.Type == Roboto.Memory.EntityType.Monster); var knownComps = _reader?.Registry["components"].Count ?? 0; _entitySummary!.Set($"{snap.Entities.Count} total, {withComps} with comps, {knownComps} known, {monsters} monsters"); @@ -723,7 +723,7 @@ public partial class MemoryViewModel : ObservableObject /// from live memory and recurses. For collapsed nodes with children, ensures /// a placeholder exists so the expand arrow is visible. /// - private static void SyncUiNodeLazy(MemoryNodeViewModel vm, Nexus.Memory.Objects.UIElements uiElements) + private static void SyncUiNodeLazy(MemoryNodeViewModel vm, Roboto.Memory.Objects.UIElements uiElements) { var uiEl = vm.UiElement; if (uiEl is null || uiEl.Address == 0) return; diff --git a/src/Nexus.Ui/ViewModels/ModPoolViewModel.cs b/src/Automata.Ui/ViewModels/ModPoolViewModel.cs similarity index 93% rename from src/Nexus.Ui/ViewModels/ModPoolViewModel.cs rename to src/Automata.Ui/ViewModels/ModPoolViewModel.cs index e815105..9233ef1 100644 --- a/src/Nexus.Ui/ViewModels/ModPoolViewModel.cs +++ b/src/Automata.Ui/ViewModels/ModPoolViewModel.cs @@ -1,7 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; -using Nexus.Core; +using Automata.Core; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class AvailableModViewModel : ObservableObject { diff --git a/src/Nexus.Ui/ViewModels/ObjectBrowserViewModel.cs b/src/Automata.Ui/ViewModels/ObjectBrowserViewModel.cs similarity index 99% rename from src/Nexus.Ui/ViewModels/ObjectBrowserViewModel.cs rename to src/Automata.Ui/ViewModels/ObjectBrowserViewModel.cs index 5ccce1f..778ebc2 100644 --- a/src/Nexus.Ui/ViewModels/ObjectBrowserViewModel.cs +++ b/src/Automata.Ui/ViewModels/ObjectBrowserViewModel.cs @@ -2,9 +2,9 @@ using System.Collections.ObjectModel; using System.Text; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.Memory; +using Roboto.Memory; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class FieldRowViewModel : ObservableObject { @@ -409,7 +409,7 @@ public partial class ObjectBrowserViewModel : ObservableObject /// /// Builds a map of offset → label name for the given object type, based on known GameOffsets. /// - private static Dictionary BuildOffsetLabels(Nexus.Memory.GameOffsets o, string objectType) + private static Dictionary BuildOffsetLabels(GameOffsets o, string objectType) { // Normalize: strip namespace prefixes (e.g. "GameStates@InGameState" → "InGameState") var type = objectType; diff --git a/src/Nexus.Ui/ViewModels/ParsedModViewModel.cs b/src/Automata.Ui/ViewModels/ParsedModViewModel.cs similarity index 97% rename from src/Nexus.Ui/ViewModels/ParsedModViewModel.cs rename to src/Automata.Ui/ViewModels/ParsedModViewModel.cs index 9458f0c..18ae28f 100644 --- a/src/Nexus.Ui/ViewModels/ParsedModViewModel.cs +++ b/src/Automata.Ui/ViewModels/ParsedModViewModel.cs @@ -1,7 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using Sidekick.Data.Items; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class ParsedModViewModel : ObservableObject { diff --git a/src/Nexus.Ui/ViewModels/NexusViewModel.cs b/src/Automata.Ui/ViewModels/RobotoViewModel.cs similarity index 98% rename from src/Nexus.Ui/ViewModels/NexusViewModel.cs rename to src/Automata.Ui/ViewModels/RobotoViewModel.cs index 711db01..6bd17a1 100644 --- a/src/Nexus.Ui/ViewModels/NexusViewModel.cs +++ b/src/Automata.Ui/ViewModels/RobotoViewModel.cs @@ -3,17 +3,17 @@ using System.Numerics; using System.Runtime.InteropServices; using Avalonia; using Avalonia.Media.Imaging; -using Nexus.Memory; +using Roboto.Memory; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.GameLog; -using Nexus.Core; -using Nexus.Data; -using Nexus.Engine; -using Nexus.Input; -using Nexus.Pathfinding; +using Automata.GameLog; +using Roboto.Core; +using Roboto.Data; +using Roboto.Engine; +using Roboto.Input; +using Roboto.Navigation; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; /// /// Thread-safe snapshot read by the overlay layer each frame. @@ -79,7 +79,7 @@ public partial class EntityListItem : ObservableObject } } -public partial class NexusViewModel : ObservableObject, IDisposable +public partial class RobotoViewModel : ObservableObject, IDisposable { [LibraryImport("user32.dll")] private static partial short GetAsyncKeyState(int vKey); @@ -170,7 +170,7 @@ public partial class NexusViewModel : ObservableObject, IDisposable /// public static volatile GameDataCache? SharedCache; - public NexusViewModel(IClientLogWatcher logWatcher) + public RobotoViewModel(IClientLogWatcher logWatcher) { var config = new BotConfig(); var reader = new GameMemoryReader(); diff --git a/src/Nexus.Ui/ViewModels/SettingsViewModel.cs b/src/Automata.Ui/ViewModels/SettingsViewModel.cs similarity index 99% rename from src/Nexus.Ui/ViewModels/SettingsViewModel.cs rename to src/Automata.Ui/ViewModels/SettingsViewModel.cs index 8080cf6..e7d15f5 100644 --- a/src/Nexus.Ui/ViewModels/SettingsViewModel.cs +++ b/src/Automata.Ui/ViewModels/SettingsViewModel.cs @@ -4,12 +4,12 @@ using Avalonia.Media.Imaging; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.Bot; -using Nexus.Core; -using Nexus.Inventory; +using Automata.Bot; +using Automata.Core; +using Automata.Inventory; using Serilog; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class SettingsViewModel : ObservableObject { diff --git a/src/Nexus.Ui/ViewModels/SkillProfileViewModel.cs b/src/Automata.Ui/ViewModels/SkillProfileViewModel.cs similarity index 98% rename from src/Nexus.Ui/ViewModels/SkillProfileViewModel.cs rename to src/Automata.Ui/ViewModels/SkillProfileViewModel.cs index 5303369..6f685f6 100644 --- a/src/Nexus.Ui/ViewModels/SkillProfileViewModel.cs +++ b/src/Automata.Ui/ViewModels/SkillProfileViewModel.cs @@ -1,9 +1,9 @@ using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Nexus.Core; +using Roboto.Core; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class SkillProfileViewModel : ObservableObject { diff --git a/src/Nexus.Ui/ViewModels/StashTabViewModel.cs b/src/Automata.Ui/ViewModels/StashTabViewModel.cs similarity index 95% rename from src/Nexus.Ui/ViewModels/StashTabViewModel.cs rename to src/Automata.Ui/ViewModels/StashTabViewModel.cs index c9b7d1d..0685056 100644 --- a/src/Nexus.Ui/ViewModels/StashTabViewModel.cs +++ b/src/Automata.Ui/ViewModels/StashTabViewModel.cs @@ -1,8 +1,8 @@ using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; -using Nexus.Core; +using Automata.Core; -namespace Nexus.Ui.ViewModels; +namespace Automata.Ui.ViewModels; public partial class StashTabViewModel : ObservableObject { diff --git a/src/Nexus.Ui/Views/MainWindow.axaml b/src/Automata.Ui/Views/MainWindow.axaml similarity index 99% rename from src/Nexus.Ui/Views/MainWindow.axaml rename to src/Automata.Ui/Views/MainWindow.axaml index e9a5163..b47aa9e 100644 --- a/src/Nexus.Ui/Views/MainWindow.axaml +++ b/src/Automata.Ui/Views/MainWindow.axaml @@ -1,9 +1,9 @@ @@ -845,9 +845,9 @@ - - - + + + - - + + - + true/pm diff --git a/src/Nexus.Bot/Nexus.Bot.csproj b/src/Nexus.Bot/Nexus.Bot.csproj deleted file mode 100644 index d2d07a4..0000000 --- a/src/Nexus.Bot/Nexus.Bot.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - net8.0-windows10.0.19041.0 - enable - enable - - - - - - - - - - - - diff --git a/src/Nexus.Core/ActionExecutor.cs b/src/Nexus.Core/ActionExecutor.cs deleted file mode 100644 index 0325b67..0000000 --- a/src/Nexus.Core/ActionExecutor.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Numerics; - -namespace Nexus.Core; - -public static class ActionExecutor -{ - // Screen center (half of 2560x1440) - private const float ScreenCenterX = 1280f; - private const float ScreenCenterY = 720f; - - // How far ahead of the player (in screen pixels) the idle cursor sits - private const float IdleCursorDistance = 200f; - - public static void Execute(List resolved, IInputController input, - MovementKeyTracker moveTracker, MovementBlender blender, Vector2? playerPos = null, - Matrix4x4? camera = null) - { - if (!input.IsInitialized) return; - - var hasCast = false; - - // Filter out physically impossible key combos (same finger) - resolved = HandModel.Filter(resolved, - moveTracker.IsWHeld, moveTracker.IsAHeld, - moveTracker.IsSHeld, moveTracker.IsDHeld); - - // Discrete actions - foreach (var action in resolved) - { - switch (action) - { - case FlaskAction flask: - input.KeyPress(flask.FlaskScanCode); - break; - - case CastAction cast: - hasCast = true; - if (cast.TargetScreenPos.HasValue) - input.SmoothMoveTo((int)cast.TargetScreenPos.Value.X, (int)cast.TargetScreenPos.Value.Y); - input.KeyPress(cast.SkillScanCode); - break; - - case ClickAction click: - var cx = (int)click.ScreenPosition.X; - var cy = (int)click.ScreenPosition.Y; - switch (click.Type) - { - case ClickType.Left: input.LeftClick(cx, cy); break; - case ClickType.Right: input.RightClick(cx, cy); break; - case ClickType.Middle: input.MiddleClick(cx, cy); break; - } - break; - - case KeyAction key: - switch (key.Type) - { - case KeyActionType.Press: input.KeyPress(key.ScanCode); break; - case KeyActionType.Down: input.KeyDown(key.ScanCode); break; - case KeyActionType.Up: input.KeyUp(key.ScanCode); break; - } - break; - - case DodgeRollAction dodge: - input.SetDodgeDirection(dodge.Direction); - input.KeyPress(0x39); // Space bar - break; - } - } - - // Idle mouse tracking: when not casting, keep cursor ahead of player in movement direction. - // This prevents jarring jumps from target to target and gives smooth cursor flow. - if (!hasCast && blender.Direction is { } moveDir && camera.HasValue && playerPos.HasValue) - { - // Project a point slightly ahead of the player in the movement direction - var aheadWorld = playerPos.Value + moveDir * 300f; - var screenAhead = WorldToScreen.Project(aheadWorld, 0f, camera.Value); - if (screenAhead.HasValue) - { - // Clamp to reasonable screen bounds - var sx = Math.Clamp(screenAhead.Value.X, 100f, 2460f); - var sy = Math.Clamp(screenAhead.Value.Y, 100f, 1340f); - input.SmoothMoveTo((int)sx, (int)sy); - } - } - - // WASD movement (delta-based held keys) - moveTracker.Apply(input, blender.Direction, playerPos); - } -} diff --git a/src/Nexus.Core/HandModel.cs b/src/Nexus.Core/HandModel.cs deleted file mode 100644 index ed02730..0000000 --- a/src/Nexus.Core/HandModel.cs +++ /dev/null @@ -1,110 +0,0 @@ -namespace Nexus.Core; - -public enum Finger { Pinky, Ring, Middle, Index, Thumb } - -/// -/// Maps left-hand keys to physical fingers and filters out actions that would -/// require the same finger simultaneously. Dropped actions retry next tick (16ms). -/// Right hand (mouse) is unconstrained. -/// -public static class HandModel -{ - private static readonly Dictionary FingerMap = new() - { - // Pinky: 1, Q, A - [ScanCodes.Key1] = Finger.Pinky, - [ScanCodes.Q] = Finger.Pinky, - [ScanCodes.A] = Finger.Pinky, - - // Ring: 2, W, S - [ScanCodes.Key2] = Finger.Ring, - [ScanCodes.W] = Finger.Ring, - [ScanCodes.S] = Finger.Ring, - - // Middle: 3, E, D - [ScanCodes.Key3] = Finger.Middle, - [ScanCodes.E] = Finger.Middle, - [ScanCodes.D] = Finger.Middle, - - // Index: 4, 5, R, T, F - [ScanCodes.Key4] = Finger.Index, - [ScanCodes.Key5] = Finger.Index, - [ScanCodes.R] = Finger.Index, - [ScanCodes.T] = Finger.Index, - [ScanCodes.F] = Finger.Index, - - // Thumb: Space, LAlt - [ScanCodes.Space] = Finger.Thumb, - [ScanCodes.LAlt] = Finger.Thumb, - }; - - // Lower = higher priority when two actions compete for the same finger - private static int ActionTypePriority(BotAction a) => a switch - { - DodgeRollAction => 0, - FlaskAction => 1, - CastAction => 2, - KeyAction => 3, - _ => 4, - }; - - public static List Filter(List resolved, - bool wHeld, bool aHeld, bool sHeld, bool dHeld) - { - // Build occupied set from currently held WASD keys - var occupied = new HashSet(); - if (wHeld) occupied.Add(Finger.Ring); - if (aHeld) occupied.Add(Finger.Pinky); - if (sHeld) occupied.Add(Finger.Ring); - if (dHeld) occupied.Add(Finger.Middle); - - // Sort by action type priority (dodge > flask > cast > key) - resolved.Sort((a, b) => ActionTypePriority(a).CompareTo(ActionTypePriority(b))); - - var result = new List(resolved.Count); - - foreach (var action in resolved) - { - var scanCode = GetScanCode(action); - - // No scan code (ClickAction, ChatAction, WaitAction, MoveAction) → always pass - if (scanCode is null) - { - result.Add(action); - continue; - } - - // Key releases always pass — they free a finger - if (action is KeyAction { Type: KeyActionType.Up }) - { - result.Add(action); - continue; - } - - // No finger mapping for this scan code → pass (right-hand or unmapped key) - if (!FingerMap.TryGetValue(scanCode.Value, out var finger)) - { - result.Add(action); - continue; - } - - // Finger free → accept and mark occupied - if (occupied.Add(finger)) - { - result.Add(action); - } - // else: finger already occupied → drop, will retry next tick - } - - return result; - } - - private static ushort? GetScanCode(BotAction action) => action switch - { - DodgeRollAction => ScanCodes.Space, - FlaskAction f => f.FlaskScanCode, - CastAction c => c.SkillScanCode, - KeyAction k => k.ScanCode, - _ => null, - }; -} diff --git a/src/Nexus.Core/MovementBlender.cs b/src/Nexus.Core/MovementBlender.cs deleted file mode 100644 index d05a239..0000000 --- a/src/Nexus.Core/MovementBlender.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System.Numerics; - -namespace Nexus.Core; - -public readonly record struct MovementIntent( - int Layer, - Vector2 Direction, - float Override = 0f, - string? Source = null -); - -/// -/// Blends movement contributions from multiple systems using priority-layered attenuation. -/// Higher-priority layers (lower number) attenuate lower-priority ones via their Override factor. -/// Applies terrain validation once on the blended result. WASD hysteresis handles smoothing. -/// -public sealed class MovementBlender -{ - private readonly List _intents = new(); - - // Stuck detection - private Vector2 _lastResolvePos; - private int _stuckFrames; - private const int StuckFrameThreshold = 15; // ~250ms at 60Hz - private const int StuckRecoveryThreshold = 45; // ~750ms — try random direction to break free - private const float StuckMovePerFrame = 3f; // must move > 3 world units per frame to count as moving - private static readonly Random StuckRng = new(); - - // EMA smoothing to dampen terrain validation jitter. - // Snap decision based on INTENT change (pre-terrain), not terrain output — prevents - // terrain probe noise from bypassing the EMA via the snap threshold. - private Vector2? _smoothedDirection; - private const float SmoothingAlpha = 0.20f; // 20% new, 80% previous - - // Terrain validation cache — prevents re-probing within a small radius, - // breaking the position↔direction feedback loop that causes zigzag oscillation - private Vector2 _cachedTerrainInputDir; - private Vector2 _cachedTerrainResult; - private Vector2 _cachedTerrainPos; - private const float TerrainCacheRadius = 20f; // don't re-probe within 20 world units - - public Vector2? Direction { get; private set; } - public Vector2? RawDirection { get; private set; } - - /// True when layer 0 (critical flee) was submitted — blocks casting. - public bool IsUrgentFlee { get; private set; } - - /// True when the player hasn't moved for several frames — orbit/herd suppressed. - public bool IsStuck { get; private set; } - - /// Snapshot of intents from the last Resolve() call, for diagnostic logging. - public IReadOnlyList LastIntents => _lastIntents; - private List _lastIntents = new(); - - public void Submit(MovementIntent intent) => _intents.Add(intent); - - /// - /// Clears intents for a new frame. Called at the top of each logic tick. - /// - public void Clear() => _intents.Clear(); - - /// - /// Updates stuck detection based on player movement. Call BEFORE systems run - /// so that IsStuck is available for systems to check (e.g. MovementSystem suppresses orbit). - /// - public void UpdateStuckState(Vector2 playerPos) - { - var moved = Vector2.Distance(playerPos, _lastResolvePos); - if (moved < StuckMovePerFrame) - _stuckFrames++; - else - _stuckFrames = Math.Max(0, _stuckFrames - 3); // recover 3x faster than building up - _lastResolvePos = playerPos; - IsStuck = _stuckFrames > StuckFrameThreshold; - } - - /// - /// Blends all submitted intents and validates against terrain. - /// Applies EMA smoothing after terrain validation to dampen probe jitter. - /// - public void Resolve(WalkabilitySnapshot? terrain, Vector2 playerPos, float worldToGrid) - { - IsUrgentFlee = false; - - if (IsStuck) - { - // Drop orbit (L2) and herd (L4) — they don't help when stuck - _intents.RemoveAll(i => i.Layer == 2 || i.Layer == 4); - - // Drop flee (L0, L1) too — if we're stuck, flee is pointing into a wall. - // Let wall push and navigation guide us out instead. - _intents.RemoveAll(i => i.Layer <= 1); - - // After 750ms stuck, inject a random nudge at high priority to break free - if (_stuckFrames > StuckRecoveryThreshold) - { - var angle = StuckRng.NextDouble() * Math.PI * 2; - var nudge = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); - _intents.Add(new MovementIntent(0, nudge, 0.6f, "StuckEscape")); - // Reset counter so we try a new direction periodically - if (_stuckFrames % 30 == 0) - _stuckFrames = StuckRecoveryThreshold + 1; - } - } - - _lastIntents = new List(_intents); - - if (_intents.Count == 0) - { - RawDirection = null; - Direction = null; - return; - } - - // Check for urgent flee (layer 0) - foreach (var intent in _intents) - { - if (intent.Layer == 0) - { - IsUrgentFlee = true; - break; - } - } - - // Group by layer, sum within layer, track max override per layer - var layers = new SortedDictionary(); - foreach (var intent in _intents) - { - if (layers.TryGetValue(intent.Layer, out var existing)) - layers[intent.Layer] = (existing.Sum + intent.Direction, Math.Max(existing.MaxOverride, intent.Override)); - else - layers[intent.Layer] = (intent.Direction, intent.Override); - } - - // Blend across layers with priority-based attenuation - var attenuation = 1f; - var result = Vector2.Zero; - - foreach (var (_, (sum, maxOverride)) in layers) - { - result += sum * attenuation; - attenuation *= (1f - maxOverride); - } - - if (result.LengthSquared() < 0.0001f) - { - RawDirection = null; - Direction = null; - return; - } - - // Normalize the blended result - var rawDir = Vector2.Normalize(result); - - // Terrain validation with grid-cell caching. - // Re-probe only when the raw direction changes (>~14°) or the player enters a new grid cell. - // This prevents the feedback loop: direction jitter → zigzag movement → crosses cell boundary → more jitter. - if (terrain is not null) - { - var dirSimilar = Vector2.Dot(rawDir, _cachedTerrainInputDir) > 0.97f; - var nearbyPos = Vector2.DistanceSquared(playerPos, _cachedTerrainPos) < TerrainCacheRadius * TerrainCacheRadius; - - if (dirSimilar && nearbyPos) - { - rawDir = _cachedTerrainResult; - } - else - { - var preTerrainDir = rawDir; - rawDir = TerrainQuery.FindWalkableDirection(terrain, playerPos, rawDir, worldToGrid); - _cachedTerrainInputDir = preTerrainDir; - _cachedTerrainResult = rawDir; - _cachedTerrainPos = playerPos; - } - } - - RawDirection = rawDir; - - // EMA smoothing. Only snap (bypass smoothing) on urgent flee (L0), - // which needs instant response. All other direction changes (orbit flips, - // terrain jitter, waypoint changes) get smoothed to prevent oscillation. - if (_smoothedDirection.HasValue) - { - if (IsUrgentFlee) - { - // Emergency flee — snap immediately, no smoothing - } - else - { - var smoothed = Vector2.Lerp(_smoothedDirection.Value, rawDir, SmoothingAlpha); - if (smoothed.LengthSquared() > 0.0001f) - rawDir = Vector2.Normalize(smoothed); - } - } - - _smoothedDirection = rawDir; - Direction = rawDir; - } - - /// - /// Full reset — call on area change or loading screen. - /// - public void Reset() - { - _intents.Clear(); - _lastIntents.Clear(); - Direction = null; - RawDirection = null; - IsUrgentFlee = false; - IsStuck = false; - _stuckFrames = 0; - _lastResolvePos = Vector2.Zero; - _smoothedDirection = null; - _cachedTerrainPos = new Vector2(float.MinValue, float.MinValue); - } - - /// - /// Compact diagnostic string: lists active intents and final direction. - /// Example: "Orbit(L2,0.0) Navigation(L3,0.0) Herd(L4,0.2) → (0.71,-0.31)" - /// - public string DiagnosticSummary() - { - if (_lastIntents.Count == 0) - return "none"; - - var parts = new List(); - foreach (var intent in _lastIntents) - { - var dir = intent.Direction; - var mag = dir.Length(); - parts.Add($"{intent.Source ?? "?"}(L{intent.Layer},ovr={intent.Override:F1},mag={mag:F2})"); - } - - var dirStr = Direction.HasValue - ? $"({Direction.Value.X:F2},{Direction.Value.Y:F2})" - : "null"; - - var stuckStr = IsStuck ? " [STUCK]" : ""; - return string.Join(" + ", parts) + " → " + dirStr + stuckStr; - } -} diff --git a/src/Nexus.Core/MovementKeyTracker.cs b/src/Nexus.Core/MovementKeyTracker.cs deleted file mode 100644 index 9470e01..0000000 --- a/src/Nexus.Core/MovementKeyTracker.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System.Numerics; -using Serilog; - -namespace Nexus.Core; - -/// -/// Translates a movement direction vector into WASD key presses. -/// Applies 45° rotation to account for isometric camera (W+A = one world axis). -/// Tracks which keys are currently held and only sends changes (delta). -/// Enforces a minimum hold duration (55±10ms gaussian) on every key press. -/// -public sealed class MovementKeyTracker -{ - private bool _wHeld, _aHeld, _sHeld, _dHeld; - - public bool IsWHeld => _wHeld; - public bool IsAHeld => _aHeld; - public bool IsSHeld => _sHeld; - public bool IsDHeld => _dHeld; - private long _wDownAt, _aDownAt, _sDownAt, _dDownAt; - private int _wMinHold, _aMinHold, _sMinHold, _dMinHold; - private long _wUpAt, _aUpAt, _sUpAt, _dUpAt; - private int _wRepress, _aRepress, _sRepress, _dRepress; - private Vector2? _lastPlayerPos; - - private static readonly Random Rng = new(); - - // 45° rotation constants - private const float Cos45 = 0.70710678f; - private const float Sin45 = 0.70710678f; - - // Hysteresis: higher threshold to press, lower to release — prevents oscillation - private const float PressThreshold = 0.35f; - private const float ReleaseThreshold = 0.15f; - - /// - /// Apply a movement direction. Null or zero direction releases all keys. - /// Direction is in world space; we rotate 45° for the isometric camera before mapping to WASD. - /// Uses hysteresis to prevent key oscillation. - /// - public void Apply(IInputController input, Vector2? direction, Vector2? playerPos = null) - { - _lastPlayerPos = playerPos; - - bool wantW, wantA, wantS, wantD; - - if (direction is { } dir && dir.LengthSquared() > 0.001f) - { - // Rotate 45° for isometric camera alignment - var sx = dir.X * Cos45 - dir.Y * Sin45; - var sy = dir.X * Sin45 + dir.Y * Cos45; - - // Hysteresis: different thresholds for press vs release - wantW = _wHeld ? sy > ReleaseThreshold : sy > PressThreshold; - wantS = _sHeld ? sy < -ReleaseThreshold : sy < -PressThreshold; - wantD = _dHeld ? sx > ReleaseThreshold : sx > PressThreshold; - wantA = _aHeld ? sx < -ReleaseThreshold : sx < -PressThreshold; - } - else - { - wantW = wantA = wantS = wantD = false; - } - - var now = Environment.TickCount64; - SetKey(input, ScanCodes.W, ref _wHeld, ref _wDownAt, ref _wMinHold, ref _wUpAt, ref _wRepress, wantW, now, _lastPlayerPos); - SetKey(input, ScanCodes.A, ref _aHeld, ref _aDownAt, ref _aMinHold, ref _aUpAt, ref _aRepress, wantA, now, _lastPlayerPos); - SetKey(input, ScanCodes.S, ref _sHeld, ref _sDownAt, ref _sMinHold, ref _sUpAt, ref _sRepress, wantS, now, _lastPlayerPos); - SetKey(input, ScanCodes.D, ref _dHeld, ref _dDownAt, ref _dMinHold, ref _dUpAt, ref _dRepress, wantD, now, _lastPlayerPos); - } - - /// - /// Release all movement keys immediately (bypasses min hold — for shutdown/area change). - /// - public void ReleaseAll(IInputController input) - { - if (_wHeld) { input.KeyUp(ScanCodes.W); _wHeld = false; } - if (_aHeld) { input.KeyUp(ScanCodes.A); _aHeld = false; } - if (_sHeld) { input.KeyUp(ScanCodes.S); _sHeld = false; } - if (_dHeld) { input.KeyUp(ScanCodes.D); _dHeld = false; } - } - - private static string KeyName(ushort scanCode) => scanCode switch - { - 0x11 => "W", 0x1E => "A", 0x1F => "S", 0x20 => "D", _ => $"0x{scanCode:X2}" - }; - - private static void SetKey(IInputController input, ushort scanCode, - ref bool held, ref long downAt, ref int minHold, - ref long upAt, ref int repressDelay, bool want, long now, Vector2? pos) - { - if (want && !held) - { - // Enforce re-press cooldown after release - if (now - upAt < repressDelay) return; - - input.KeyDown(scanCode); - held = true; - downAt = now; - minHold = HoldMs(); - if (pos.HasValue) - Log.Information("[WASD] {Key} DOWN (minHold={MinHold}ms) pos=({X:F0},{Y:F0})", - KeyName(scanCode), minHold, pos.Value.X, pos.Value.Y); - else - Log.Information("[WASD] {Key} DOWN (minHold={MinHold}ms)", KeyName(scanCode), minHold); - } - else if (!want && held) - { - var elapsed = now - downAt; - if (elapsed < minHold) return; // enforce minimum hold - - input.KeyUp(scanCode); - held = false; - upAt = now; - repressDelay = RepressMs(); - if (pos.HasValue) - Log.Information("[WASD] {Key} UP (held={Elapsed}ms, min={MinHold}ms) pos=({X:F0},{Y:F0})", - KeyName(scanCode), elapsed, minHold, pos.Value.X, pos.Value.Y); - else - Log.Information("[WASD] {Key} UP (held={Elapsed}ms, min={MinHold}ms)", KeyName(scanCode), elapsed, minHold); - } - } - - /// Gaussian hold duration peaked at 55ms, range [44, 76]. - private static int HoldMs() - { - double u, v, s; - do - { - u = Rng.NextDouble() * 2.0 - 1.0; - v = Rng.NextDouble() * 2.0 - 1.0; - s = u * u + v * v; - } while (s >= 1.0 || s == 0.0); - var g = u * Math.Sqrt(-2.0 * Math.Log(s) / s); - return Math.Clamp((int)Math.Round(55.0 + g * 6.0), 44, 76); - } - - /// Gaussian re-press cooldown peaked at 40ms, range [25, 65]. - private static int RepressMs() - { - double u, v, s; - do - { - u = Rng.NextDouble() * 2.0 - 1.0; - v = Rng.NextDouble() * 2.0 - 1.0; - s = u * u + v * v; - } while (s >= 1.0 || s == 0.0); - var g = u * Math.Sqrt(-2.0 * Math.Log(s) / s); - return Math.Clamp((int)Math.Round(40.0 + g * 8.0), 25, 65); - } -} diff --git a/src/Nexus.Core/ProjectileSnapshot.cs b/src/Nexus.Core/ProjectileSnapshot.cs deleted file mode 100644 index 91f549c..0000000 --- a/src/Nexus.Core/ProjectileSnapshot.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Numerics; - -namespace Nexus.Core; - -public record ProjectileSnapshot -{ - public Vector2 Position { get; init; } - public Vector2 Direction { get; init; } - public float Speed { get; init; } - public float HitRadius { get; init; } - public float DistanceToPlayer { get; init; } - /// Seconds until impact. Null if projectile will miss. - public float? TimeToImpact { get; init; } - /// Closest distance the projectile's trajectory passes to the player center. - public float ClosestApproachDistance { get; init; } -} diff --git a/src/Nexus.Core/TerrainQuery.cs b/src/Nexus.Core/TerrainQuery.cs deleted file mode 100644 index 06e9809..0000000 --- a/src/Nexus.Core/TerrainQuery.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System.Numerics; - -namespace Nexus.Core; - -/// -/// Terrain line-of-sight and walkable direction queries on the walkability grid. -/// -public static class TerrainQuery -{ - /// - /// Bresenham line walk on the walkability grid. Returns false if any cell is unwalkable. - /// - public static bool HasLineOfSight(WalkabilitySnapshot terrain, Vector2 from, Vector2 to, float worldToGrid) - { - int x0 = (int)(from.X * worldToGrid); - int y0 = (int)(from.Y * worldToGrid); - int x1 = (int)(to.X * worldToGrid); - int y1 = (int)(to.Y * worldToGrid); - - int dx = Math.Abs(x1 - x0); - int dy = Math.Abs(y1 - y0); - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; - int err = dx - dy; - - while (true) - { - if (!terrain.IsWalkable(x0, y0)) - return false; - - if (x0 == x1 && y0 == y1) - break; - - int e2 = 2 * err; - if (e2 > -dy) { err -= dy; x0 += sx; } - if (e2 < dx) { err += dx; y0 += sy; } - } - - return true; - } - - /// - /// Validates a desired movement direction against terrain. If blocked, tries rotations - /// ±45°, ±90°, ±135°, 180° and returns the first clear direction. - /// Returns original direction as fallback (game engine will wall-slide). - /// - public static Vector2 FindWalkableDirection( - WalkabilitySnapshot terrain, Vector2 playerPos, Vector2 desiredDir, float worldToGrid, - float probeDistance = 60f) - { - if (IsDirectionClear(terrain, playerPos, desiredDir, worldToGrid, probeDistance)) - return desiredDir; - - // Try rotations: ±45°, ±90°, ±135°, 180° - ReadOnlySpan angles = [45f, -45f, 90f, -90f, 135f, -135f, 180f]; - - foreach (var angleDeg in angles) - { - var rotated = Rotate(desiredDir, angleDeg); - if (IsDirectionClear(terrain, playerPos, rotated, worldToGrid, probeDistance)) - return rotated; - } - - return desiredDir; - } - - private static bool IsDirectionClear( - WalkabilitySnapshot terrain, Vector2 origin, Vector2 dir, float worldToGrid, float distance) - { - // Check near (2-3 grid cells), mid, and far probes - var nearpoint = origin + dir * 30f; - var midpoint = origin + dir * (distance * 0.5f); - var endpoint = origin + dir * distance; - - int nx = (int)(nearpoint.X * worldToGrid); - int ny = (int)(nearpoint.Y * worldToGrid); - int mx = (int)(midpoint.X * worldToGrid); - int my = (int)(midpoint.Y * worldToGrid); - int ex = (int)(endpoint.X * worldToGrid); - int ey = (int)(endpoint.Y * worldToGrid); - - return terrain.IsWalkable(nx, ny) && terrain.IsWalkable(mx, my) && terrain.IsWalkable(ex, ey); - } - - /// - /// Probes 8 directions around the player for nearby walls. - /// Returns a normalized push-away vector, or Zero if no walls are close. - /// - public static Vector2 ComputeWallRepulsion(WalkabilitySnapshot terrain, Vector2 playerPos, float worldToGrid) - { - const float probeNear = 40f; // ~3-4 grid cells - const float probeFar = 100f; // ~9-10 grid cells - - var push = Vector2.Zero; - - for (var i = 0; i < 8; i++) - { - var angle = i * MathF.PI / 4f; - var dir = new Vector2(MathF.Cos(angle), MathF.Sin(angle)); - - // Near probe — strong push - var near = playerPos + dir * probeNear; - var nx = (int)(near.X * worldToGrid); - var ny = (int)(near.Y * worldToGrid); - if (!terrain.IsWalkable(nx, ny)) - { - push -= dir * 1.0f; - continue; // don't double-count - } - - // Far probe — gentle push - var far = playerPos + dir * probeFar; - var fx = (int)(far.X * worldToGrid); - var fy = (int)(far.Y * worldToGrid); - if (!terrain.IsWalkable(fx, fy)) - push -= dir * 0.4f; - } - - if (push.LengthSquared() < 0.0001f) - return Vector2.Zero; - - return Vector2.Normalize(push); - } - - /// - /// Predictive wall steering — casts rays ahead along the movement direction. - /// If forward is blocked but a side is clear, returns a lateral steering vector. - /// - public static Vector2 ComputeWallSteering( - WalkabilitySnapshot terrain, Vector2 playerPos, Vector2 moveDir, float worldToGrid) - { - if (moveDir.LengthSquared() < 0.0001f) - return Vector2.Zero; - - var dir = Vector2.Normalize(moveDir); - var leftDir = Rotate(dir, 30f); - var rightDir = Rotate(dir, -30f); - - ReadOnlySpan distances = [40f, 80f, 120f]; - - var forwardBlocked = false; - var leftClear = true; - var rightClear = true; - - foreach (var dist in distances) - { - var fwd = playerPos + dir * dist; - var fx = (int)(fwd.X * worldToGrid); - var fy = (int)(fwd.Y * worldToGrid); - if (!terrain.IsWalkable(fx, fy)) - forwardBlocked = true; - - var left = playerPos + leftDir * dist; - var lx = (int)(left.X * worldToGrid); - var ly = (int)(left.Y * worldToGrid); - if (!terrain.IsWalkable(lx, ly)) - leftClear = false; - - var right = playerPos + rightDir * dist; - var rx = (int)(right.X * worldToGrid); - var ry = (int)(right.Y * worldToGrid); - if (!terrain.IsWalkable(rx, ry)) - rightClear = false; - } - - if (!forwardBlocked) - return Vector2.Zero; - - // Steer toward the clear side - var lateral = new Vector2(-dir.Y, dir.X); // perpendicular (left) - if (leftClear && !rightClear) - return lateral; - if (rightClear && !leftClear) - return -lateral; - if (leftClear && rightClear) - return lateral; // default left when both clear - // Both blocked — push backward - return -dir; - } - - private static Vector2 Rotate(Vector2 v, float degrees) - { - float rad = degrees * MathF.PI / 180f; - float cos = MathF.Cos(rad); - float sin = MathF.Sin(rad); - return Vector2.Normalize(new Vector2(v.X * cos - v.Y * sin, v.X * sin + v.Y * cos)); - } -} diff --git a/src/Nexus.Core/ThreatAssessment.cs b/src/Nexus.Core/ThreatAssessment.cs deleted file mode 100644 index f7a195b..0000000 --- a/src/Nexus.Core/ThreatAssessment.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Numerics; - -namespace Nexus.Core; - -public enum ThreatCategory -{ - Ignore, // score ≤ 2 — not worth reacting to - Monitor, // score 2–6 — track but don't change behavior - Engage, // score 6–15 — fight, stay mobile - Flee, // score 15–25 — kite aggressively - Emergency, // score > 25 OR player HP critical — run, pop flasks -} - -public class ThreatEntry -{ - public uint EntityId { get; init; } - public Vector2 Position { get; set; } - public float DistanceToPlayer { get; set; } - public float ThreatScore { get; set; } - public float PerceivedDanger { get; set; } // normalized 0..1 - public ThreatCategory Category { get; set; } - public bool HasLineOfSight { get; set; } - public MonsterRarity Rarity { get; init; } - public float HpPercent { get; set; } - public bool IsAlive { get; set; } -} - -public class ThreatAssessment -{ - public List Entries { get; set; } = []; - - // Precomputed aggregates — consumed by steering, combat, state machine - public float ZoneThreatLevel { get; set; } // sum of all scores - public ThreatEntry? PrimaryTarget { get; set; } // best kill target - public ThreatEntry? MostDangerous { get; set; } // highest threat score - public Vector2 ThreatCentroid { get; set; } // score-weighted center - public Vector2 SafestDirection { get; set; } // away from centroid - public bool AnyEmergency { get; set; } - public bool ShouldFlee { get; set; } // zone threat > flee threshold - public bool AreaClear { get; set; } // no Monitor+ threats remain - public float ClosestDistance { get; set; } - - /// Continuous 0..1 flee weight for steering blend. - public float FleeWeight { get; set; } - - // Convenience — backward compatibility - public int CloseRange { get; set; } // < 300 - public int MidRange { get; set; } // 300–600 - public int FarRange { get; set; } // 600–1200 - public bool HasRareOrUnique { get; set; } -} diff --git a/src/Nexus.Core/WalkabilitySnapshot.cs b/src/Nexus.Core/WalkabilitySnapshot.cs deleted file mode 100644 index f3acd7a..0000000 --- a/src/Nexus.Core/WalkabilitySnapshot.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Nexus.Core; - -public record WalkabilitySnapshot -{ - public int Width { get; init; } - public int Height { get; init; } - public byte[] Data { get; init; } = []; - - /// - /// Absolute grid X coordinate of the top-left corner of Data. - /// Grid coord gx maps to local index gx - OffsetX. - /// - public int OffsetX { get; init; } - - /// - /// Absolute grid Y coordinate of the top-left corner of Data. - /// - public int OffsetY { get; init; } - - public bool IsWalkable(int gx, int gy) - { - var lx = gx - OffsetX; - var ly = gy - OffsetY; - if (lx < 0 || lx >= Width || ly < 0 || ly >= Height) - return false; - return Data[ly * Width + lx] != 0; - } -} diff --git a/src/Nexus.Data/GameStateEnricher.cs b/src/Nexus.Data/GameStateEnricher.cs deleted file mode 100644 index 2d68fe9..0000000 --- a/src/Nexus.Data/GameStateEnricher.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Numerics; -using Nexus.Core; - -namespace Nexus.Data; - -/// -/// Computes derived fields on GameState once per tick. -/// Threat scoring is now handled by ThreatSystem (runs as ISystem). -/// -public static class GameStateEnricher -{ - public static void Enrich(GameState state) - { - state.NearestEnemies = ComputeNearestEnemies(state.HostileMonsters); - state.GroundEffects = []; // stub until memory reads ground effects - } - - private static IReadOnlyList ComputeNearestEnemies(IReadOnlyList hostiles) - { - if (hostiles.Count == 0) return []; - - var sorted = new List(hostiles); - sorted.Sort((a, b) => a.DistanceToPlayer.CompareTo(b.DistanceToPlayer)); - return sorted; - } -} diff --git a/src/Nexus.Input/ScanCodes.cs b/src/Nexus.Input/ScanCodes.cs deleted file mode 100644 index aa2dbf9..0000000 --- a/src/Nexus.Input/ScanCodes.cs +++ /dev/null @@ -1,44 +0,0 @@ -// ScanCodes has moved to Nexus.Core. This file re-exports for backward compatibility. -// ReSharper disable once CheckNamespace -namespace Nexus.Input -{ - /// Re-exports Nexus.Core.ScanCodes for backward compatibility. - public static class ScanCodes - { - public const ushort W = Core.ScanCodes.W; - public const ushort A = Core.ScanCodes.A; - public const ushort S = Core.ScanCodes.S; - public const ushort D = Core.ScanCodes.D; - public const ushort Key1 = Core.ScanCodes.Key1; - public const ushort Key2 = Core.ScanCodes.Key2; - public const ushort Key3 = Core.ScanCodes.Key3; - public const ushort Key4 = Core.ScanCodes.Key4; - public const ushort Key5 = Core.ScanCodes.Key5; - public const ushort Key6 = Core.ScanCodes.Key6; - public const ushort Key7 = Core.ScanCodes.Key7; - public const ushort Key8 = Core.ScanCodes.Key8; - public const ushort Key9 = Core.ScanCodes.Key9; - public const ushort Key0 = Core.ScanCodes.Key0; - public const ushort LShift = Core.ScanCodes.LShift; - public const ushort RShift = Core.ScanCodes.RShift; - public const ushort LCtrl = Core.ScanCodes.LCtrl; - public const ushort LAlt = Core.ScanCodes.LAlt; - public const ushort Escape = Core.ScanCodes.Escape; - public const ushort Tab = Core.ScanCodes.Tab; - public const ushort Space = Core.ScanCodes.Space; - public const ushort Enter = Core.ScanCodes.Enter; - public const ushort Backspace = Core.ScanCodes.Backspace; - public const ushort F1 = Core.ScanCodes.F1; - public const ushort F2 = Core.ScanCodes.F2; - public const ushort F3 = Core.ScanCodes.F3; - public const ushort F4 = Core.ScanCodes.F4; - public const ushort F5 = Core.ScanCodes.F5; - public const ushort Q = Core.ScanCodes.Q; - public const ushort E = Core.ScanCodes.E; - public const ushort R = Core.ScanCodes.R; - public const ushort T = Core.ScanCodes.T; - public const ushort I = Core.ScanCodes.I; - public const ushort F = Core.ScanCodes.F; - public const ushort Slash = Core.ScanCodes.Slash; - } -} diff --git a/src/Nexus.Inventory/Nexus.Inventory.csproj b/src/Nexus.Inventory/Nexus.Inventory.csproj deleted file mode 100644 index 6d2333c..0000000 --- a/src/Nexus.Inventory/Nexus.Inventory.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - net8.0-windows10.0.19041.0 - enable - enable - - - - - - - - diff --git a/src/Nexus.Simulator/Bridge/SimInputController.cs b/src/Nexus.Simulator/Bridge/SimInputController.cs deleted file mode 100644 index 6a1f9e6..0000000 --- a/src/Nexus.Simulator/Bridge/SimInputController.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System.Numerics; -using Nexus.Core; -using Nexus.Simulator.World; -using Serilog; - -namespace Nexus.Simulator.Bridge; - -/// -/// Captures bot actions (key presses, mouse moves) and feeds them into SimWorld. -/// Implements IInputController so BotEngine systems can emit actions normally. -/// -public class SimInputController : IInputController -{ - private readonly SimWorld _world; - private readonly object _lock = new(); - - // WASD key states - private bool _wHeld, _aHeld, _sHeld, _dHeld; - - // Mouse position (screen coords captured from SmoothMoveTo/MouseMoveTo) - private Vector2 _mouseScreenPos = new(1280, 720); - - // Camera matrix for screen→world conversion - private Matrix4x4? _cameraMatrix; - - // Input visualization tracking - private readonly Dictionary _keyTimers = new(); - private readonly float[] _mouseTimers = new float[3]; - private const float FlashDuration = 0.3f; - - // Dodge roll - private Vector2? _pendingDodgeDirection; - - // Smooth mouse interpolation - private Vector2 _mouseMoveStartPos; - private Vector2 _mouseTargetPos; - private float _mouseMoveProgress = 1f; // 1 = arrived - private const float MouseMoveSpeed = 6f; // interpolation speed (higher = faster) - - public bool IsInitialized => true; - - /// - /// The bot's current mouse position in screen coordinates (for mock cursor rendering). - /// - public Vector2 MouseScreenPos - { - get { lock (_lock) return _mouseScreenPos; } - } - - public SimInputController(SimWorld world) - { - _world = world; - } - - public void SetCameraMatrix(Matrix4x4 camera) - { - _cameraMatrix = camera; - } - - /// - /// Called each sim tick to push accumulated inputs into SimWorld. - /// - public void FlushToWorld() - { - lock (_lock) - { - // Convert WASD to direction vector (with 45° isometric rotation inversion) - var dx = 0f; - var dy = 0f; - if (_wHeld) { dx += 0.70710678f; dy += 0.70710678f; } - if (_sHeld) { dx -= 0.70710678f; dy -= 0.70710678f; } - if (_dHeld) { dx += 0.70710678f; dy -= 0.70710678f; } - if (_aHeld) { dx -= 0.70710678f; dy += 0.70710678f; } - - var dir = new Vector2(dx, dy); - if (dir.LengthSquared() > 0.001f) - dir = Vector2.Normalize(dir); - - _world.MoveDirection = dir; - _world.MouseWorldPos = ScreenToWorld(_mouseScreenPos); - } - } - - private Vector2 ScreenToWorld(Vector2 screen) - { - if (_cameraMatrix is not { } cam) return _world.Player.Position; - if (!Matrix4x4.Invert(cam, out var inv)) return _world.Player.Position; - - // NDC from screen - var ndcX = screen.X / 1280f - 1f; - var ndcY = 1f - screen.Y / 720f; - - var worldNear = Vector4.Transform(new Vector4(ndcX, ndcY, 0, 1), inv); - if (MathF.Abs(worldNear.W) < 0.0001f) return _world.Player.Position; - return new Vector2(worldNear.X / worldNear.W, worldNear.Y / worldNear.W); - } - - // IInputController implementation — captures actions, no actual Win32 calls - - private static string KeyName(ushort sc) => sc switch - { - 0x11 => "W", 0x1E => "A", 0x1F => "S", 0x20 => "D", _ => $"0x{sc:X2}" - }; - - public void KeyDown(ushort scanCode) - { - lock (_lock) - { - switch (scanCode) - { - case 0x11: _wHeld = true; break; // W - case 0x1E: _aHeld = true; break; // A - case 0x1F: _sHeld = true; break; // S - case 0x20: _dHeld = true; break; // D - } - _keyTimers[scanCode] = float.MaxValue; - } - } - - public void KeyUp(ushort scanCode) - { - lock (_lock) - { - switch (scanCode) - { - case 0x11: _wHeld = false; break; - case 0x1E: _aHeld = false; break; - case 0x1F: _sHeld = false; break; - case 0x20: _dHeld = false; break; - } - _keyTimers.Remove(scanCode); - } - } - - public void SetDodgeDirection(Vector2 direction) - { - lock (_lock) { _pendingDodgeDirection = direction; } - } - - public void KeyPress(ushort scanCode, int holdMs = 50) - { - lock (_lock) - { - _keyTimers[scanCode] = FlashDuration; - } - - // Intercept dodge roll key (Space = 0x39) - if (scanCode == 0x39) - { - Vector2 dir; - lock (_lock) - { - dir = _pendingDodgeDirection ?? _world.MoveDirection; - _pendingDodgeDirection = null; - } - if (dir.LengthSquared() > 0.001f) - _world.QueueDodgeRoll(dir); - return; - } - - // Queue as skill cast - var target = ScreenToWorld(_mouseScreenPos); - _world.QueueSkill(scanCode, target); - } - - public void MouseMoveTo(int x, int y) - { - lock (_lock) { _mouseScreenPos = new Vector2(x, y); } - } - - public void SmoothMoveTo(int x, int y) - { - lock (_lock) - { - _mouseMoveStartPos = _mouseScreenPos; - _mouseTargetPos = new Vector2(x, y); - _mouseMoveProgress = 0f; - } - } - public void MouseMoveBy(int dx, int dy) - { - lock (_lock) { _mouseScreenPos += new Vector2(dx, dy); } - } - - public void LeftClick(int x, int y) - { - Log.Information("[Click] Left at ({X},{Y})", x, y); - lock (_lock) { _mouseTimers[0] = FlashDuration; } - MouseMoveTo(x, y); - var target = ScreenToWorld(new Vector2(x, y)); - _world.QueueSkill(0, target); - } - - public void RightClick(int x, int y) - { - Log.Information("[Click] Right at ({X},{Y})", x, y); - lock (_lock) { _mouseTimers[1] = FlashDuration; } - MouseMoveTo(x, y); - var target = ScreenToWorld(new Vector2(x, y)); - _world.QueueSkill(1, target); - } - - public void MiddleClick(int x, int y) - { - Log.Information("[Click] Middle at ({X},{Y})", x, y); - lock (_lock) { _mouseTimers[2] = FlashDuration; } - MouseMoveTo(x, y); - var target = ScreenToWorld(new Vector2(x, y)); - _world.QueueSkill(2, target); - } - - public void LeftDown() { lock (_lock) { _mouseTimers[0] = float.MaxValue; } } - public void LeftUp() { lock (_lock) { _mouseTimers[0] = 0; } } - public void RightDown() { lock (_lock) { _mouseTimers[1] = float.MaxValue; } } - public void RightUp() { lock (_lock) { _mouseTimers[1] = 0; } } - - /// - /// Decrement flash timers and interpolate mouse. Call once per frame with frame delta time. - /// - public void UpdateTimers(float dt) - { - lock (_lock) - { - // Key flash timers - var expired = new List(); - var updates = new List<(ushort key, float time)>(); - - foreach (var kvp in _keyTimers) - { - if (kvp.Value < 1000f) // not held (held = MaxValue) - { - var t = kvp.Value - dt; - if (t <= 0) expired.Add(kvp.Key); - else updates.Add((kvp.Key, t)); - } - } - - foreach (var k in expired) _keyTimers.Remove(k); - foreach (var (k, t) in updates) _keyTimers[k] = t; - - // Mouse button flash timers - for (var i = 0; i < 3; i++) - if (_mouseTimers[i] > 0 && _mouseTimers[i] < 1000f) - _mouseTimers[i] = MathF.Max(0, _mouseTimers[i] - dt); - - // Smooth mouse interpolation toward target - if (_mouseMoveProgress < 1f) - { - _mouseMoveProgress = MathF.Min(1f, _mouseMoveProgress + dt * MouseMoveSpeed); - // Ease-out: fast start, slow end - var t = 1f - MathF.Pow(1f - _mouseMoveProgress, 3f); - _mouseScreenPos = Vector2.Lerp(_mouseMoveStartPos, _mouseTargetPos, t); - } - } - } - - /// - /// Snapshot of currently active keys and mouse buttons for visualization. - /// - public InputSnapshot GetInputSnapshot() - { - lock (_lock) - { - var keys = new HashSet(); - foreach (var (k, t) in _keyTimers) - if (t > 0) keys.Add(k); - return new InputSnapshot(keys, _mouseTimers[0] > 0, _mouseTimers[1] > 0, _mouseTimers[2] > 0); - } - } -} - -public readonly struct InputSnapshot -{ - private readonly HashSet? _activeKeys; - private readonly bool _leftMouse, _rightMouse, _middleMouse; - - public InputSnapshot(HashSet activeKeys, bool left, bool right, bool middle) - { - _activeKeys = activeKeys; - _leftMouse = left; - _rightMouse = right; - _middleMouse = middle; - } - - public bool IsKeyActive(ushort scanCode) => _activeKeys?.Contains(scanCode) ?? false; - - public bool IsMouseActive(int button) => button switch - { - 0 => _leftMouse, - 1 => _rightMouse, - 2 => _middleMouse, - _ => false, - }; -} diff --git a/src/Nexus.Simulator/Bridge/SimPoller.cs b/src/Nexus.Simulator/Bridge/SimPoller.cs deleted file mode 100644 index d88218f..0000000 --- a/src/Nexus.Simulator/Bridge/SimPoller.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Numerics; -using Nexus.Core; -using Nexus.Data; -using Nexus.Simulator.Config; -using Nexus.Simulator.World; - -namespace Nexus.Simulator.Bridge; - -/// -/// Replaces MemoryPoller. Ticks SimWorld at 60Hz and pushes state to GameDataCache. -/// Runs on a background thread, just like the real MemoryPoller. -/// -public sealed class SimPoller : IDisposable -{ - private readonly SimWorld _world; - private readonly SimInputController _input; - private readonly GameDataCache _cache; - private readonly SimConfig _config; - - private Thread? _thread; - private volatile bool _running; - - public event Action? StateUpdated; - - public SimPoller(SimWorld world, SimInputController input, GameDataCache cache, SimConfig config) - { - _world = world; - _input = input; - _cache = cache; - _config = config; - } - - public void Start() - { - if (_running) return; - _running = true; - _thread = new Thread(PollLoop) - { - Name = "Nexus.SimPoller", - IsBackground = true, - }; - _thread.Start(); - } - - public void Stop() - { - _running = false; - _thread?.Join(2000); - _thread = null; - } - - private void PollLoop() - { - const double targetMs = 1000.0 / 60.0; - var sw = System.Diagnostics.Stopwatch.StartNew(); - var lastMs = sw.Elapsed.TotalMilliseconds; - - while (_running) - { - var nowMs = sw.Elapsed.TotalMilliseconds; - var dt = (float)((nowMs - lastMs) / 1000.0); - lastMs = nowMs; - - // Clamp dt to avoid spiral of death - dt = Math.Min(dt, 0.1f); - - // Flush bot inputs into world - _input.FlushToWorld(); - - // Tick the simulation - _world.Tick(dt); - - // Build GameState - var state = SimStateBuilder.Build(_world, dt); - var cam = SimStateBuilder.BuildCameraMatrix(_world.Player.Position); - - // Update input controller's camera for screen→world conversion - _input.SetCameraMatrix(cam); - - // Push to cache - _cache.CameraMatrix = new CameraMatrixData(cam); - _cache.PlayerPosition = new PlayerPositionData( - _world.Player.Position.X, _world.Player.Position.Y, 0f); - _cache.PlayerVitals = new PlayerVitalsData( - _world.Player.Health, _world.Player.MaxHealth, - _world.Player.Mana, _world.Player.MaxMana, - _world.Player.Es, _world.Player.MaxEs); - _cache.IsLoading = false; - _cache.IsEscapeOpen = false; - _cache.Entities = state.Entities; - _cache.HostileMonsters = state.HostileMonsters; - _cache.NearbyLoot = []; - _cache.Terrain = _world.Terrain; - _cache.AreaHash = 1; - _cache.AreaLevel = 1; - _cache.CurrentAreaName = "SimulatedArena"; - _cache.CharacterName = "SimPlayer"; - _cache.LatestState = state; - _cache.HotTickTimestamp = Environment.TickCount64; - _cache.ColdTickTimestamp = Environment.TickCount64; - - StateUpdated?.Invoke(); - - // Sleep for remainder of frame - var elapsed = sw.Elapsed.TotalMilliseconds - nowMs; - var sleep = targetMs - elapsed; - if (sleep > 1) - Thread.Sleep((int)sleep); - } - } - - public void Dispose() => Stop(); -} diff --git a/src/Nexus.Simulator/Bridge/SimStateBuilder.cs b/src/Nexus.Simulator/Bridge/SimStateBuilder.cs deleted file mode 100644 index 27bc482..0000000 --- a/src/Nexus.Simulator/Bridge/SimStateBuilder.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System.Numerics; -using Nexus.Core; -using Nexus.Simulator.World; - -namespace Nexus.Simulator.Bridge; - -/// -/// Converts SimWorld state into GameState for the bot systems. -/// -public static class SimStateBuilder -{ - private static long _tickNumber; - - public static GameState Build(SimWorld world, float dt) - { - _tickNumber++; - var player = world.Player; - - var entities = new List(); - var hostiles = new List(); - var nearbyLoot = new List(); - - foreach (var enemy in world.Enemies) - { - var snap = new EntitySnapshot - { - Id = enemy.Id, - Path = "Metadata/Monsters/SimEnemy", - Category = EntityCategory.Monster, - ThreatLevel = enemy.GetThreatLevel(), - Rarity = enemy.Rarity, - Position = enemy.Position, - Z = 0f, - DistanceToPlayer = Vector2.Distance(enemy.Position, player.Position), - IsAlive = enemy.IsAlive, - LifeCurrent = enemy.Health, - LifeTotal = enemy.MaxHealth, - IsTargetable = enemy.IsAlive, - ActionId = enemy.IsAttacking ? (short)1 : (short)0, - IsAttacking = enemy.IsAttacking, - IsMoving = enemy.AiState == EnemyAiState.Chasing, - }; - entities.Add(snap); - if (enemy.IsAlive) - hostiles.Add(snap); - } - - // Add area transition entity at dungeon exit so BotTick exit avoidance works - var exitSnap = new EntitySnapshot - { - Id = uint.MaxValue, - Path = "Metadata/Terrain/AreaTransition", - Category = EntityCategory.AreaTransition, - Position = world.EndWorldPos, - Z = 0f, - DistanceToPlayer = Vector2.Distance(world.EndWorldPos, player.Position), - IsAlive = false, - IsTargetable = true, - TransitionName = "DungeonExit", - }; - entities.Add(exitSnap); - - // Build loot snapshots - foreach (var item in world.Items) - { - var (rarity, isQuest) = item.Category switch - { - LootCategory.Magic => (MonsterRarity.Magic, false), - LootCategory.Rare => (MonsterRarity.Rare, false), - LootCategory.Unique => (MonsterRarity.Unique, false), - LootCategory.Quest => (MonsterRarity.White, true), - _ => (MonsterRarity.White, false), - }; - - var label = item.Category == LootCategory.Currency - ? $"Currency:{item.Label}" - : item.Label; - - var snap = new EntitySnapshot - { - Id = item.Id, - Path = "Metadata/MiscellaneousObjects/WorldItem", - Category = EntityCategory.WorldItem, - Rarity = rarity, - Position = item.Position, - Z = 0f, - DistanceToPlayer = Vector2.Distance(item.Position, player.Position), - IsAlive = true, - IsTargetable = true, - ItemBaseName = label, - IsQuestItem = isQuest, - }; - entities.Add(snap); - - // Only add filtered items (currency/rare/unique/quest) to NearbyLoot - if (item.Category is LootCategory.Currency or LootCategory.Rare - or LootCategory.Unique or LootCategory.Quest) - { - nearbyLoot.Add(snap); - } - } - - var cameraMatrix = BuildCameraMatrix(player.Position); - - var projectiles = BuildProjectileSnapshots(world, player.Position); - - return new GameState - { - TickNumber = _tickNumber, - DeltaTime = dt, - TimestampMs = Environment.TickCount64, - AreaHash = 1, - AreaLevel = 1, - CurrentAreaName = "SimulatedArena", - IsLoading = false, - IsEscapeOpen = false, - CameraMatrix = cameraMatrix, - Terrain = world.Terrain, - Entities = entities, - HostileMonsters = hostiles, - NearbyLoot = nearbyLoot, - EnemyProjectiles = projectiles, - Player = new PlayerState - { - CharacterName = "SimPlayer", - HasPosition = true, - Position = player.Position, - Z = 0f, - LifeCurrent = player.Health, - LifeTotal = player.MaxHealth, - ManaCurrent = player.Mana, - ManaTotal = player.MaxMana, - EsCurrent = player.Es, - EsTotal = player.MaxEs, - Skills = BuildSkillStates(), - IsRolling = player.IsRolling, - RollCooldownRemaining = player.RollCooldownRemaining, - }, - }; - } - - /// - /// Creates a top-down orthographic camera matrix that WorldToScreen.Project() can use. - /// WorldToScreen expects: sx = (clipX + 1) * 1280, sy = (1 - clipY) * 720 - /// So we need: clipX = (worldX - camX) / halfViewW, clipY = (worldY - camY) / halfViewH - /// - public static Matrix4x4 BuildCameraMatrix(Vector2 playerPos) - { - // View dimensions in world units that map to 2560x1440 screen - // At default zoom, ~5 world units per pixel → 2560*5 = 12800 world units wide - const float viewWidth = 12800f; - const float viewHeight = 7200f; - - // Orthographic projection centered on player - // WorldToScreen does: clip = Transform(world4, matrix), then divides by W - // For ortho: we want (worldX - playerX) / halfW → clipX, (worldY - playerY) / halfH → clipY - var halfW = viewWidth / 2f; - var halfH = viewHeight / 2f; - - // Row-major matrix that transforms (worldX, worldY, 0, 1) → (clipX, clipY, clipZ, 1) - return new Matrix4x4( - 1f / halfW, 0, 0, 0, - 0, 1f / halfH, 0, 0, - 0, 0, 1, 0, - -playerPos.X / halfW, -playerPos.Y / halfH, 0, 1); - } - - private const float PlayerRadius = 20f; - - private static List BuildProjectileSnapshots(SimWorld world, Vector2 playerPos) - { - var snapshots = new List(); - - foreach (var proj in world.Projectiles) - { - if (!proj.IsEnemyProjectile || proj.IsExpired) continue; - - var toPlayer = playerPos - proj.Position; - var dist = toPlayer.Length(); - - // Dot product: how far ahead of the projectile the player is (along travel direction) - var dot = Vector2.Dot(toPlayer, proj.Direction); - if (dot < 0) continue; // Moving away from player - - // Perpendicular distance from player center to projectile trajectory line - var closestDist = MathF.Abs(toPlayer.X * proj.Direction.Y - toPlayer.Y * proj.Direction.X); - - var collisionRadius = proj.HitRadius + PlayerRadius; - float? timeToImpact = null; - - if (closestDist < collisionRadius) - { - // Will hit — compute entry time via circle-line intersection - var discriminant = collisionRadius * collisionRadius - closestDist * closestDist; - var entryDist = dot - MathF.Sqrt(discriminant); - if (entryDist > 0) - timeToImpact = entryDist / proj.Speed; - else - timeToImpact = 0f; // Already overlapping - } - - snapshots.Add(new ProjectileSnapshot - { - Position = proj.Position, - Direction = proj.Direction, - Speed = proj.Speed, - HitRadius = proj.HitRadius, - DistanceToPlayer = dist, - TimeToImpact = timeToImpact, - ClosestApproachDistance = closestDist, - }); - } - - return snapshots; - } - - private static List BuildSkillStates() - { - return - [ - new SkillState { SlotIndex = 0, Name = "MeleeStrike", SkillBarSlot = 0, CanBeUsed = true, ChargesMax = 1, ChargesCurrent = 1 }, - new SkillState { SlotIndex = 1, Name = "PowerStrike", SkillBarSlot = 1, CanBeUsed = true, ChargesMax = 1, ChargesCurrent = 1 }, - new SkillState { SlotIndex = 3, Name = "WhirlingSlash", SkillBarSlot = 3, CanBeUsed = true, ChargesMax = 1, ChargesCurrent = 1, CooldownTimeMs = 500 }, - new SkillState { SlotIndex = 4, Name = "SpearThrow", SkillBarSlot = 4, CanBeUsed = true, ChargesMax = 1, ChargesCurrent = 1, CooldownTimeMs = 300 }, - ]; - } -} diff --git a/src/Nexus.Simulator/Config/SimConfig.cs b/src/Nexus.Simulator/Config/SimConfig.cs deleted file mode 100644 index 7ca9c8a..0000000 --- a/src/Nexus.Simulator/Config/SimConfig.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace Nexus.Simulator.Config; - -public class SimConfig -{ - // Terrain - public int TerrainWidth { get; set; } = 1500; - public int TerrainHeight { get; set; } = 1500; - public float WorldToGrid { get; set; } = 23f / 250f; - - // Dungeon generation - public int DungeonRoomCountMin { get; set; } = 10; - public int DungeonRoomCountMax { get; set; } = 18; - public int DungeonRoomSizeMin { get; set; } = 55; // grid cells (~600 world units) - public int DungeonRoomSizeMax { get; set; } = 110; // grid cells (~1200 world units) - public int DungeonCorridorWidth { get; set; } = 13; // grid cells (~140 world units) - public float DungeonEndReachDist { get; set; } = 150f; // world units - - // Player - public float PlayerMoveSpeed { get; set; } = 400f; - public int PlayerMaxHealth { get; set; } = 800; - public int PlayerMaxMana { get; set; } = 500; - public int PlayerMaxEs { get; set; } = 400; - public float PlayerHealthRegen { get; set; } = 8f; - public float PlayerManaRegen { get; set; } = 10f; - public float PlayerEsRegen { get; set; } = 30f; // ES recharge rate (per second, once recharging) - public float PlayerEsRechargeDelay { get; set; } = 2f; // Seconds after last damage before ES recharges - - // Enemies — melee - public int TargetEnemyCount { get; set; } = 50; - public float EnemyAggroRange { get; set; } = 600f; - public float EnemyMeleeAttackRange { get; set; } = 100f; - public float EnemyMoveSpeedFactor { get; set; } = 0.75f; - public int EnemyBaseHealth { get; set; } = 500; - public int EnemyMeleeBaseDamage { get; set; } = 60; - public float EnemyMeleeAttackCooldown { get; set; } = 1.2f; - - // Enemies — ranged - public float RangedEnemyChance { get; set; } = 0.30f; // 30% of enemies are ranged - public float EnemyRangedAttackRange { get; set; } = 500f; - public float EnemyRangedPreferredRange { get; set; } = 350f; - public int EnemyRangedBaseDamage { get; set; } = 40; - public float EnemyRangedAttackCooldown { get; set; } = 2.0f; - public float EnemyProjectileSpeed { get; set; } = 800f; - public float EnemyProjectileHitRadius { get; set; } = 40f; - - // Enemies — general - public float EnemyDespawnTime { get; set; } = 2f; - public float EnemyRespawnTime { get; set; } = 5f; - public float EnemyWanderRadius { get; set; } = 200f; - public float EnemySpawnMinDist { get; set; } = 800f; - public float EnemySpawnMaxDist { get; set; } = 2000f; - public float EnemyCullDist { get; set; } = 3000f; - public int EnemyGroupMin { get; set; } = 7; - public int EnemyGroupMax { get; set; } = 18; - public float EnemyGroupSpread { get; set; } = 120f; - - // Dodge roll - public float DodgeRollDistance { get; set; } = 100f; // world units traveled per roll - public float DodgeRollDuration { get; set; } = 0.25f; // 250ms - public float DodgeRollCooldown { get; set; } = 1.0f; // 1s between rolls - - // Player skills - public float MeleeRange { get; set; } = 350f; - public float MeleeConeAngle { get; set; } = 120f; - public float AoeRadius { get; set; } = 250f; - public float ProjectileSpeed { get; set; } = 1200f; - public float ProjectileRange { get; set; } = 800f; - public float ProjectileHitRadius { get; set; } = 80f; - public int SkillBaseDamage { get; set; } = 200; - - // Simulation - public float SpeedMultiplier { get; set; } = 1f; - public bool IsPaused { get; set; } - - // Rarity distribution (must sum to 1.0) - public float NormalChance { get; set; } = 0.70f; - public float MagicChance { get; set; } = 0.20f; - public float RareChance { get; set; } = 0.08f; - public float UniqueChance { get; set; } = 0.02f; -} diff --git a/src/Nexus.Simulator/Nexus.Simulator.csproj b/src/Nexus.Simulator/Nexus.Simulator.csproj deleted file mode 100644 index 9161be8..0000000 --- a/src/Nexus.Simulator/Nexus.Simulator.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - Exe - net8.0-windows10.0.19041.0 - enable - enable - true - - - - - - - - - - - - - - - - diff --git a/src/Nexus.Simulator/Program.cs b/src/Nexus.Simulator/Program.cs deleted file mode 100644 index 52a7d23..0000000 --- a/src/Nexus.Simulator/Program.cs +++ /dev/null @@ -1,231 +0,0 @@ -using System.Numerics; -using ImGuiNET; -using Nexus.Core; -using Nexus.Data; -using Nexus.Pathfinding; -using Nexus.Simulator.Bridge; -using Nexus.Simulator.Config; -using Nexus.Simulator.Rendering; -using Nexus.Simulator.World; -using Nexus.Systems; -using Serilog; -using Veldrid; -using Veldrid.Sdl2; -using Veldrid.StartupUtilities; - -Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}") - .CreateLogger(); - -// Parse --ticks N for headless auto-exit -int? maxTicks = null; -for (int i = 0; i < args.Length - 1; i++) - if (args[i] == "--ticks" && int.TryParse(args[i + 1], out var t)) - maxTicks = t; - -Log.Information("Nexus Simulator starting..."); - -// ── Configuration ── -var simConfig = new SimConfig(); -var botConfig = new BotConfig -{ - WorldToGrid = simConfig.WorldToGrid, - LogicTickRateHz = 60, -}; - -// ── Create simulation world ── -var world = new SimWorld(simConfig); -var cache = new GameDataCache(); -var input = new SimInputController(world); -var poller = new SimPoller(world, input, cache, simConfig); -var nav = new NavigationController(botConfig); - -// ── Create systems (same as BotEngine, minus AreaProgression) ── -var systems = SystemFactory.CreateSystems(botConfig, nav); - -// Apply a default profile with configured skills -var profile = new CharacterProfile -{ - Name = "SimPlayer", - Skills = - [ - new() { SlotIndex = 0, Label = "LMB", InputType = SkillInputType.LeftClick, Priority = 0, RangeMax = 350f }, - new() { SlotIndex = 1, Label = "RMB", InputType = SkillInputType.RightClick, Priority = 1, RangeMax = 350f, CooldownMs = 800 }, - new() { SlotIndex = 3, Label = "Q", InputType = SkillInputType.KeyPress, ScanCode = 0x10, Priority = 3, RangeMax = 350f, CooldownMs = 2000, MinMonstersInRange = 3 }, - new() { SlotIndex = 4, Label = "E", InputType = SkillInputType.KeyPress, ScanCode = 0x12, Priority = 4, RangeMax = 800f, CooldownMs = 1500 }, - ], - Combat = new CombatSettings - { - GlobalCooldownMs = 400, - AttackRange = 350f, - }, -}; - -// Apply profile to systems that need it -foreach (var sys in systems) -{ - if (sys is CombatSystem combat) combat.ApplyProfile(profile); - if (sys is ResourceSystem resource) resource.ApplyProfile(profile); -} - -// ── Start simulation poller ── -poller.Start(); - -// ── Explore the dungeon (not beeline to exit) ── -nav.Explore(); - -// ── Bot logic thread ── -var actionQueue = new ActionQueue(); -var movementBlender = new MovementBlender(); -var moveTracker = new MovementKeyTracker(); -var botRunning = true; -var lastStatusLogMs = 0L; -var botTickCount = 0; - -var botThread = new Thread(() => -{ - var sw = System.Diagnostics.Stopwatch.StartNew(); - var intervalMs = 1000.0 / botConfig.LogicTickRateHz; - - while (botRunning) - { - try - { - var state = cache.LatestState; - if (state is not null && !state.IsLoading && !state.IsEscapeOpen) - { - var resolved = BotTick.Run(state, systems, actionQueue, movementBlender, nav, botConfig); - ActionExecutor.Execute(resolved, input, moveTracker, movementBlender, state.Player.Position, state.CameraMatrix); - - // Check if dungeon end reached — regenerate and explore new dungeon - if (world.ReachedEnd) - { - world.RegenerateTerrain(); - nav.Explore(); - } - - botTickCount++; - if (maxTicks.HasValue && botTickCount >= maxTicks.Value) - { - botRunning = false; - break; - } - - // Periodic status log (every 2 seconds) - var nowMs = Environment.TickCount64; - if (nowMs - lastStatusLogMs >= 2000) - { - lastStatusLogMs = nowMs; - var p = state.Player; - var enemySnapshot = world.Enemies.ToArray(); - var melee = enemySnapshot.Count(e => e.IsAlive && !e.IsRanged); - var ranged = enemySnapshot.Count(e => e.IsAlive && e.IsRanged); - var actions = string.Join(",", resolved.Select(a => a switch - { - CastAction c => $"Cast({c.SkillScanCode:X2})", - FlaskAction => "Flask", - ClickAction => "Click", - KeyAction k => $"Key({k.ScanCode:X2})", - _ => a.GetType().Name, - })); - if (actions.Length == 0) actions = "none"; - - var ta = state.ThreatAssessment; - Log.Information( - "Status: HP={HP}/{MaxHP} ES={ES}/{MaxES} Mana={MP}/{MaxMP} " + - "Threat={Zone:F1}(fw={Fw:F2}) " + - "Enemies={Total}({Melee}m/{Ranged}r) Nav={NavMode} Actions=[{Actions}] " + - "Move=[{Blender}]", - p.LifeCurrent, p.LifeTotal, p.EsCurrent, p.EsTotal, p.ManaCurrent, p.ManaTotal, - ta.ZoneThreatLevel, ta.FleeWeight, - melee + ranged, melee, ranged, - nav.Mode, actions, movementBlender.DiagnosticSummary()); - } - } - } - catch (Exception ex) - { - Log.Debug(ex, "Bot logic error"); - } - - var elapsed = sw.Elapsed.TotalMilliseconds; - var sleep = intervalMs - (elapsed % intervalMs); - if (sleep > 1) - Thread.Sleep((int)sleep); - } -}) -{ - Name = "Nexus.BotLogic", - IsBackground = true, -}; -botThread.Start(); - -// ── Veldrid + ImGui window ── -var windowInfo = new WindowCreateInfo -{ - X = 100, - Y = 100, - WindowWidth = 1600, - WindowHeight = 1000, - WindowTitle = "Nexus Simulator", -}; - -var window = VeldridStartup.CreateWindow(ref windowInfo); -var gd = VeldridStartup.CreateGraphicsDevice(window, new GraphicsDeviceOptions -{ - PreferStandardClipSpaceYDirection = true, - PreferDepthRangeZeroToOne = true, - SyncToVerticalBlank = true, -}, GraphicsBackend.Direct3D11); - -var imguiRenderer = new VeldridImGuiRenderer( - gd, gd.MainSwapchain.Framebuffer.OutputDescription, - window.Width, window.Height); - -var renderer = new SimRenderer(simConfig, world, nav, systems, input); -var cl = gd.ResourceFactory.CreateCommandList(); - -window.Resized += () => -{ - gd.MainSwapchain.Resize((uint)window.Width, (uint)window.Height); - imguiRenderer.WindowResized(window.Width, window.Height); -}; - -// ── Main render loop ── -var renderSw = System.Diagnostics.Stopwatch.StartNew(); -var lastRenderMs = 0.0; - -while (window.Exists && botRunning) -{ - var nowMs = renderSw.Elapsed.TotalMilliseconds; - var deltaSeconds = (float)((nowMs - lastRenderMs) / 1000.0); - lastRenderMs = nowMs; - - var snapshot = window.PumpEvents(); - if (!window.Exists) break; - - imguiRenderer.Update(deltaSeconds, snapshot); - - // Update input flash timers & render sim world - input.UpdateTimers(deltaSeconds); - renderer.Render(cache.LatestState); - - cl.Begin(); - cl.SetFramebuffer(gd.MainSwapchain.Framebuffer); - cl.ClearColorTarget(0, new RgbaFloat(0.05f, 0.05f, 0.08f, 1f)); - imguiRenderer.Render(gd, cl); - cl.End(); - gd.SubmitCommands(cl); - gd.SwapBuffers(gd.MainSwapchain); -} - -// ── Cleanup ── -botRunning = false; -botThread.Join(2000); -poller.Dispose(); -cl.Dispose(); -imguiRenderer.Dispose(); -gd.Dispose(); -Log.Information("Nexus Simulator stopped."); - diff --git a/src/Nexus.Simulator/Rendering/DebugPanel.cs b/src/Nexus.Simulator/Rendering/DebugPanel.cs deleted file mode 100644 index d7a5012..0000000 --- a/src/Nexus.Simulator/Rendering/DebugPanel.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System.Numerics; -using ImGuiNET; -using Nexus.Core; -using Nexus.Pathfinding; -using Nexus.Simulator.Config; -using Nexus.Simulator.World; - -namespace Nexus.Simulator.Rendering; - -public class DebugPanel -{ - private readonly SimConfig _config; - private readonly SimWorld _world; - private readonly NavigationController _nav; - private readonly IReadOnlyList _systems; - - private int _spawnRarity; // 0=Normal, 1=Magic, 2=Rare, 3=Unique - - // Cached threat display — updated at ~4Hz to prevent visual flicker - private long _lastThreatUpdateMs; - private string _cachedZoneLine = ""; - private string _cachedDangerLine = ""; - private string _cachedRangeLine = ""; - private string _cachedClosestLine = ""; - private string _cachedTopThreatLine = "Top Threat: None"; - private string _cachedKillTargetLine = "Kill Target: None"; - - public DebugPanel(SimConfig config, SimWorld world, NavigationController nav, IReadOnlyList systems) - { - _config = config; - _world = world; - _nav = nav; - _systems = systems; - } - - public void Draw(GameState? state) - { - ImGui.Begin("Simulator Controls"); - - // Simulation controls - if (ImGui.CollapsingHeader("Simulation", ImGuiTreeNodeFlags.DefaultOpen)) - { - var paused = _config.IsPaused; - if (ImGui.Checkbox("Paused", ref paused)) - _config.IsPaused = paused; - ImGui.SameLine(); - if (ImGui.Button("Step")) - { - _config.IsPaused = false; - } - - var speed = _config.SpeedMultiplier; - if (ImGui.SliderFloat("Speed", ref speed, 0.1f, 4f, "%.1fx")) - _config.SpeedMultiplier = speed; - - if (ImGui.Button("Regenerate Terrain")) - _world.RegenerateTerrain(); - } - - // Player stats - if (ImGui.CollapsingHeader("Player", ImGuiTreeNodeFlags.DefaultOpen)) - { - var player = _world.Player; - ImGui.Text($"Position: ({player.Position.X:F0}, {player.Position.Y:F0})"); - - var hpPct = player.MaxHealth > 0 ? (float)player.Health / player.MaxHealth : 0; - ImGui.ProgressBar(hpPct, new Vector2(-1, 0), $"HP: {player.Health}/{player.MaxHealth}"); - - var manaPct = player.MaxMana > 0 ? (float)player.Mana / player.MaxMana : 0; - ImGui.ProgressBar(manaPct, new Vector2(-1, 0), $"MP: {player.Mana}/{player.MaxMana}"); - } - - // Enemy stats - if (ImGui.CollapsingHeader("Enemies", ImGuiTreeNodeFlags.DefaultOpen)) - { - var enemies = _world.Enemies.ToArray(); // snapshot — list mutated by SimPoller thread - var alive = enemies.Count(e => e.IsAlive); - var dead = enemies.Count(e => !e.IsAlive); - var chasing = enemies.Count(e => e.AiState == EnemyAiState.Chasing); - var attacking = enemies.Count(e => e.AiState == EnemyAiState.Attacking); - - ImGui.Text($"Total: {enemies.Length} Alive: {alive} Dead: {dead}"); - ImGui.Text($"Chasing: {chasing} Attacking: {attacking}"); - - ImGui.Separator(); - ImGui.Text("Spawn Enemy:"); - string[] rarities = ["Normal", "Magic", "Rare", "Unique"]; - ImGui.Combo("Rarity", ref _spawnRarity, rarities, rarities.Length); - if (ImGui.Button("Spawn at Player")) - { - var rarity = (MonsterRarity)_spawnRarity; - var offset = new Vector2(200, 0); - _world.SpawnEnemyAt(_world.Player.Position + offset, rarity); - } - } - - // Navigation - if (ImGui.CollapsingHeader("Navigation")) - { - ImGui.Text($"Mode: {_nav.Mode}"); - ImGui.Text($"Status: {_nav.Status}"); - ImGui.Text($"Direction: {(_nav.DesiredDirection.HasValue ? $"({_nav.DesiredDirection.Value.X:F2}, {_nav.DesiredDirection.Value.Y:F2})" : "none")}"); - - var path = _nav.CurrentPath; - ImGui.Text($"Path: {path?.Count ?? 0} waypoints"); - ImGui.Text($"Exploration complete: {_nav.IsExplorationComplete}"); - } - - // Systems - if (ImGui.CollapsingHeader("Systems")) - { - foreach (var sys in _systems) - { - var enabled = sys.IsEnabled; - if (ImGui.Checkbox(sys.Name, ref enabled)) - sys.IsEnabled = enabled; - } - } - - // Threat info — cached at ~4Hz to prevent visual flicker - if (state is not null && ImGui.CollapsingHeader("Threat", ImGuiTreeNodeFlags.DefaultOpen)) - { - var nowMs = Environment.TickCount64; - if (nowMs - _lastThreatUpdateMs >= 250) - { - _lastThreatUpdateMs = nowMs; - var ta = state.ThreatAssessment; - _cachedZoneLine = $"Zone Threat: {ta.ZoneThreatLevel:F1} Flee Weight: {ta.FleeWeight:F2}"; - _cachedDangerLine = $"Danger: {state.Danger} Flee: {ta.ShouldFlee} Emergency: {ta.AnyEmergency}"; - _cachedRangeLine = $"Close: {ta.CloseRange} Mid: {ta.MidRange} Far: {ta.FarRange}"; - _cachedClosestLine = $"Closest: {ta.ClosestDistance:F0} Rare/Unique: {ta.HasRareOrUnique}"; - _cachedTopThreatLine = ta.MostDangerous is { } top - ? $"Top Threat: #{top.EntityId} ({top.Rarity}) score={top.ThreatScore:F1} cat={top.Category}" - : "Top Threat: None"; - _cachedKillTargetLine = ta.PrimaryTarget is { } pt - ? $"Kill Target: #{pt.EntityId} ({pt.Rarity}) HP={pt.HpPercent:P0} dist={pt.DistanceToPlayer:F0}" - : "Kill Target: None"; - } - - ImGui.Text(_cachedZoneLine); - ImGui.Text(_cachedDangerLine); - ImGui.Text(_cachedRangeLine); - ImGui.Text(_cachedClosestLine); - ImGui.Text(_cachedTopThreatLine); - ImGui.Text(_cachedKillTargetLine); - } - - // Action queue - if (state is not null && ImGui.CollapsingHeader("State")) - { - ImGui.Text($"Tick: {state.TickNumber}"); - ImGui.Text($"Entities: {state.Entities.Count}"); - ImGui.Text($"Hostile: {state.HostileMonsters.Count}"); - ImGui.Text($"Nearest: {state.NearestEnemies.Count}"); - } - - ImGui.End(); - } -} diff --git a/src/Nexus.Simulator/Rendering/EffectRenderer.cs b/src/Nexus.Simulator/Rendering/EffectRenderer.cs deleted file mode 100644 index a402aeb..0000000 --- a/src/Nexus.Simulator/Rendering/EffectRenderer.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Numerics; -using ImGuiNET; -using Nexus.Simulator.World; - -namespace Nexus.Simulator.Rendering; - -public static class EffectRenderer -{ - public static void DrawEffects(ImDrawListPtr drawList, IReadOnlyList effects, ViewTransform vt) - { - foreach (var effect in effects) - { - var alpha = (byte)(255 * (1f - effect.Progress)); - var originScreen = vt.WorldToScreen(effect.Origin); - var targetScreen = vt.WorldToScreen(effect.TargetPosition); - - switch (effect.Type) - { - case SkillEffectType.Melee: - DrawMeleeCone(drawList, originScreen, targetScreen, - effect.Radius * vt.WorldScale, effect.ConeAngle, alpha); - break; - - case SkillEffectType.Aoe: - DrawAoeCircle(drawList, targetScreen, - effect.Radius * vt.WorldScale, alpha); - break; - - case SkillEffectType.Projectile: - DrawProjectileLine(drawList, originScreen, targetScreen, alpha); - break; - } - } - } - - public static void DrawProjectiles(ImDrawListPtr drawList, IReadOnlyList projectiles, ViewTransform vt) - { - foreach (var proj in projectiles) - { - var pos = vt.WorldToScreen(proj.Position); - var radius = proj.HitRadius * vt.WorldScale * 0.3f; - - if (proj.IsEnemyProjectile) - { - // Red/orange for enemy projectiles - drawList.AddCircleFilled(pos, Math.Max(3f, radius), 0xFF3344FF); - drawList.AddCircle(pos, Math.Max(4f, radius + 1), 0xFF0000FF); - } - else - { - // Cyan for player projectiles - drawList.AddCircleFilled(pos, Math.Max(3f, radius), 0xFF00DDFF); - drawList.AddCircle(pos, Math.Max(4f, radius + 1), 0xFF00AAFF); - } - } - } - - private static void DrawMeleeCone(ImDrawListPtr drawList, Vector2 origin, Vector2 target, - float radius, float coneAngle, byte alpha) - { - var dir = target - origin; - if (dir.LengthSquared() < 1f) return; - dir = Vector2.Normalize(dir); - - var halfAngle = coneAngle * MathF.PI / 360f; - var segments = 12; - var color = (uint)(alpha << 24) | 0x00FF4444; - - // Draw cone as triangle fan - var prevPoint = origin + Rotate(dir, -halfAngle) * radius; - for (var i = 1; i <= segments; i++) - { - var t = (float)i / segments; - var angle = -halfAngle + t * halfAngle * 2; - var point = origin + Rotate(dir, angle) * radius; - drawList.AddTriangleFilled(origin, prevPoint, point, color); - prevPoint = point; - } - } - - private static void DrawAoeCircle(ImDrawListPtr drawList, Vector2 center, float radius, byte alpha) - { - var fillColor = (uint)(alpha / 2 << 24) | 0x004488FF; - var borderColor = (uint)(alpha << 24) | 0x006688FF; - drawList.AddCircleFilled(center, radius, fillColor); - drawList.AddCircle(center, radius, borderColor, 0, 2f); - } - - private static void DrawProjectileLine(ImDrawListPtr drawList, Vector2 from, Vector2 to, byte alpha) - { - var color = (uint)(alpha << 24) | 0x0000DDFF; - drawList.AddLine(from, to, color, 2f); - } - - private static Vector2 Rotate(Vector2 v, float radians) - { - var cos = MathF.Cos(radians); - var sin = MathF.Sin(radians); - return new Vector2(v.X * cos - v.Y * sin, v.X * sin + v.Y * cos); - } -} diff --git a/src/Nexus.Simulator/Rendering/EntityRenderer.cs b/src/Nexus.Simulator/Rendering/EntityRenderer.cs deleted file mode 100644 index 1cd1ee5..0000000 --- a/src/Nexus.Simulator/Rendering/EntityRenderer.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System.Numerics; -using ImGuiNET; -using Nexus.Core; -using Nexus.Simulator.World; - -namespace Nexus.Simulator.Rendering; - -public static class EntityRenderer -{ - public static void DrawPlayer(ImDrawListPtr drawList, SimPlayer player, ViewTransform vt) - { - var screenPos = vt.WorldToScreen(player.Position); - - var radius = 8f; - if (player.IsRolling) - { - drawList.AddCircleFilled(screenPos, radius, 0xFFFFAA00); // Cyan when rolling - drawList.AddCircle(screenPos, radius + 1, 0xFFFFDD44); - // "R" centered in the circle - var textSize = ImGui.CalcTextSize("R"); - drawList.AddText(screenPos - textSize * 0.5f, 0xFF000000, "R"); - } - else - { - drawList.AddCircleFilled(screenPos, radius, 0xFF00FF00); // Green - drawList.AddCircle(screenPos, radius + 1, 0xFF00AA00); - } - - var barY = radius + 8; - - // ES bar (purple, above health) - if (player.MaxEs > 0) - { - DrawHealthBar(drawList, screenPos - new Vector2(15, barY), 30, 3, - player.Es, player.MaxEs, 0xFFFF8800); // Cyan/purple in ABGR - barY += 5; - } - - // Health bar - DrawHealthBar(drawList, screenPos - new Vector2(15, barY), 30, 4, - player.Health, player.MaxHealth, 0xFF00DD00); - barY += 6; - - // Mana bar - DrawHealthBar(drawList, screenPos - new Vector2(15, barY), 30, 3, - player.Mana, player.MaxMana, 0xFFDD6600); - } - - public static void DrawEnemies(ImDrawListPtr drawList, IReadOnlyList enemies, - ViewTransform vt, Vector2 canvasMin, Vector2 canvasMax) - { - foreach (var enemy in enemies) - { - var screenPos = vt.WorldToScreen(enemy.Position); - - // Cull off-screen - if (screenPos.X < canvasMin.X - 20 || screenPos.X > canvasMax.X + 20 || - screenPos.Y < canvasMin.Y - 20 || screenPos.Y > canvasMax.Y + 20) - continue; - - var (color, radius) = enemy.Rarity switch - { - MonsterRarity.Unique => (0xFF00AAFF, 10f), // Orange - MonsterRarity.Rare => (0xFF00FFFF, 8f), // Yellow - MonsterRarity.Magic => (0xFFFF8800, 6f), // Blue - _ => (0xFFCCCCCC, 5f), // White - }; - - if (!enemy.IsAlive) - { - color = 0xFF444444; - radius *= 0.7f; - } - - if (enemy.IsRanged) - { - // Diamond shape for ranged enemies - var s = radius * 1.2f; - var pts = new[] - { - screenPos + new Vector2(0, -s), - screenPos + new Vector2(s, 0), - screenPos + new Vector2(0, s), - screenPos + new Vector2(-s, 0), - }; - drawList.AddQuadFilled(pts[0], pts[1], pts[2], pts[3], color); - } - else - { - drawList.AddCircleFilled(screenPos, radius, color); - } - - if (enemy.AiState == EnemyAiState.Chasing) - drawList.AddCircle(screenPos, radius + 2, 0xFF0000FF); // Red ring when chasing - else if (enemy.AiState == EnemyAiState.Attacking) - drawList.AddCircle(screenPos, radius + 3, 0xFF0000FF, 0, 3f); - else if (enemy.AiState == EnemyAiState.Retreating) - drawList.AddCircle(screenPos, radius + 2, 0xFF00AAAA); // Teal ring when retreating - - // Health bar - if (enemy.IsAlive && enemy.Health < enemy.MaxHealth) - { - DrawHealthBar(drawList, screenPos - new Vector2(12, radius + 6), 24, 3, - enemy.Health, enemy.MaxHealth, 0xFF0000FF); - } - } - } - - public static void DrawItems(ImDrawListPtr drawList, IReadOnlyList items, - ViewTransform vt, Vector2 canvasMin, Vector2 canvasMax) - { - foreach (var item in items) - { - var screenPos = vt.WorldToScreen(item.Position); - - // Cull off-screen - if (screenPos.X < canvasMin.X - 60 || screenPos.X > canvasMax.X + 60 || - screenPos.Y < canvasMin.Y - 20 || screenPos.Y > canvasMax.Y + 20) - continue; - - var color = item.Category switch - { - LootCategory.Currency => 0xFF0000FF, // Red - LootCategory.Magic => 0xFFFF8800, // Blue - LootCategory.Rare => 0xFF00FFFF, // Yellow - LootCategory.Unique => 0xFF00AAFF, // Orange - LootCategory.Quest => 0xFF00FF00, // Green - _ => 0xFFCCCCCC, // White - }; - - var textSize = ImGui.CalcTextSize(item.Label); - var labelPos = screenPos - new Vector2(textSize.X * 0.5f, 16f); - - // Background - var pad = new Vector2(3, 1); - drawList.AddRectFilled(labelPos - pad, labelPos + textSize + pad, 0xBB000000); - - // Label text - drawList.AddText(labelPos, color, item.Label); - - // Small dot at item position - drawList.AddCircleFilled(screenPos, 3f, color); - } - } - - private static void DrawHealthBar(ImDrawListPtr drawList, Vector2 pos, float width, float height, - int current, int max, uint color) - { - if (max <= 0) return; - var pct = (float)current / max; - - // Background - drawList.AddRectFilled(pos, pos + new Vector2(width, height), 0xFF000000); - // Fill - drawList.AddRectFilled(pos, pos + new Vector2(width * pct, height), color); - // Border - drawList.AddRect(pos, pos + new Vector2(width, height), 0xFF666666); - } -} diff --git a/src/Nexus.Simulator/Rendering/InputOverlayRenderer.cs b/src/Nexus.Simulator/Rendering/InputOverlayRenderer.cs deleted file mode 100644 index 5a6a6ce..0000000 --- a/src/Nexus.Simulator/Rendering/InputOverlayRenderer.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System.Numerics; -using ImGuiNET; -using Nexus.Simulator.Bridge; - -namespace Nexus.Simulator.Rendering; - -/// -/// Draws a keyboard + mouse + mousepad overlay showing which keys/buttons are currently pressed. -/// Keys are white normally, yellow when active. -/// -public static class InputOverlayRenderer -{ - private const float KeySize = 26f; - private const float Gap = 2f; - private const float Stride = KeySize + Gap; - - // Colors (ABGR) - private const uint White = 0xFFFFFFFF; - private const uint Yellow = 0xFF00FFFF; - private const uint DarkBg = 0xFF1E1E1E; - private const uint ActiveBg = 0xFF1A5C8C; - private const uint Outline = 0xFF555555; - private const uint DimText = 0xFFAAAAAA; - private const uint ScrollBg = 0xFF333333; - private const uint CursorDot = 0xFF00DDFF; // Cyan dot for cursor position - private const uint CrosshairColor = 0x44FFFFFF; // Dim crosshair - private const float SpaceBarHeight = 20f; - - // Keyboard rows: (label, scanCode, column offset) - private static readonly (string L, ushort S, float C)[] Row0 = - [("1", 0x02, 0), ("2", 0x03, 1), ("3", 0x04, 2), ("4", 0x05, 3), ("5", 0x06, 4)]; - - private static readonly (string L, ushort S, float C)[] Row1 = - [("Q", 0x10, 0.25f), ("W", 0x11, 1.25f), ("E", 0x12, 2.25f), ("R", 0x13, 3.25f), ("T", 0x14, 4.25f)]; - - private static readonly (string L, ushort S, float C)[] Row2 = - [("A", 0x1E, 0.5f), ("S", 0x1F, 1.5f), ("D", 0x20, 2.5f), ("F", 0x21, 3.5f)]; - - // Screen dimensions the bot thinks it has - private const float ScreenW = 2560f; - private const float ScreenH = 1440f; - - public static void Draw(ImDrawListPtr drawList, InputSnapshot input, Vector2 mouseScreenPos, - Vector2 canvasOrigin, Vector2 canvasSize) - { - var padSize = 80f; - var mouseH = 64f; - var kbH = 3 * Stride + SpaceBarHeight + Gap; - var totalH = kbH + 6 + mouseH + 6 + padSize; - var origin = canvasOrigin + new Vector2(15, canvasSize.Y - totalH - 15); - - // Keyboard - DrawKeyRow(drawList, origin, Row0, 0, input); - DrawKeyRow(drawList, origin, Row1, 1, input); - DrawKeyRow(drawList, origin, Row2, 2, input); - DrawSpaceBar(drawList, origin + new Vector2(0, 3 * Stride), input); - - // Mouse to the right of keyboard - var kbW = 4.25f * Stride + KeySize; - var mouseOrigin = origin + new Vector2(kbW + 12, (kbH - mouseH) / 2); - DrawMouse(drawList, input, mouseOrigin); - - // Mousepad below keyboard + mouse row - var padOrigin = origin + new Vector2(0, kbH + 6); - DrawMousepad(drawList, mouseScreenPos, input, padOrigin, kbW + 12 + 44); - } - - private static void DrawKeyRow(ImDrawListPtr drawList, Vector2 origin, - (string L, ushort S, float C)[] keys, int row, InputSnapshot input) - { - foreach (var (label, scan, col) in keys) - { - var pos = origin + new Vector2(col * Stride, row * Stride); - var on = input.IsKeyActive(scan); - - drawList.AddRectFilled(pos, pos + new Vector2(KeySize), on ? ActiveBg : DarkBg, 3f); - drawList.AddRect(pos, pos + new Vector2(KeySize), on ? Yellow : Outline, 3f); - - var ts = ImGui.CalcTextSize(label); - drawList.AddText(pos + (new Vector2(KeySize) - ts) * 0.5f, on ? Yellow : White, label); - } - } - - private static void DrawSpaceBar(ImDrawListPtr drawList, Vector2 origin, InputSnapshot input) - { - const ushort spaceScan = 0x39; - var on = input.IsKeyActive(spaceScan); - - // Wide bar spanning roughly the ASDF row width - var pos = origin + new Vector2(0.5f * Stride, 0); - var size = new Vector2(3.5f * Stride, SpaceBarHeight); - - drawList.AddRectFilled(pos, pos + size, on ? ActiveBg : DarkBg, 3f); - drawList.AddRect(pos, pos + size, on ? Yellow : Outline, 3f); - } - - private static void DrawMouse(ImDrawListPtr drawList, InputSnapshot input, Vector2 o) - { - const float w = 44, h = 64, hw = w / 2, bh = 26; - - // Body - drawList.AddRectFilled(o, o + new Vector2(w, h), DarkBg, 10f); - drawList.AddRect(o, o + new Vector2(w, h), Outline, 10f); - - // Left button highlight - var lOn = input.IsMouseActive(0); - if (lOn) - drawList.AddRectFilled(o + new Vector2(2, 2), o + new Vector2(hw - 1, bh), ActiveBg, 4f); - - // Right button highlight - var rOn = input.IsMouseActive(1); - if (rOn) - drawList.AddRectFilled(o + new Vector2(hw + 1, 2), o + new Vector2(w - 2, bh), ActiveBg, 4f); - - // Dividers - drawList.AddLine(o + new Vector2(hw, 2), o + new Vector2(hw, bh), Outline); - drawList.AddLine(o + new Vector2(4, bh), o + new Vector2(w - 4, bh), Outline); - - // Scroll wheel - var mOn = input.IsMouseActive(2); - var sw = new Vector2(8, 14); - var sp = o + new Vector2((w - sw.X) / 2, (bh - sw.Y) / 2); - drawList.AddRectFilled(sp, sp + sw, mOn ? ActiveBg : ScrollBg, 3f); - drawList.AddRect(sp, sp + sw, mOn ? Yellow : Outline, 3f); - - // Labels - DrawCentered(drawList, "L", o + new Vector2(hw / 2, bh / 2), lOn ? Yellow : DimText); - DrawCentered(drawList, "R", o + new Vector2(hw + hw / 2, bh / 2), rOn ? Yellow : DimText); - } - - private static void DrawMousepad(ImDrawListPtr drawList, Vector2 mouseScreenPos, - InputSnapshot input, Vector2 origin, float width) - { - // Mousepad: maps the bot's 2560x1440 screen space to a small rectangle - var aspect = ScreenH / ScreenW; - var padW = width; - var padH = padW * aspect; - - // Background - drawList.AddRectFilled(origin, origin + new Vector2(padW, padH), DarkBg, 4f); - drawList.AddRect(origin, origin + new Vector2(padW, padH), Outline, 4f); - - // Crosshair at center - var center = origin + new Vector2(padW, padH) * 0.5f; - drawList.AddLine(center - new Vector2(8, 0), center + new Vector2(8, 0), CrosshairColor); - drawList.AddLine(center - new Vector2(0, 8), center + new Vector2(0, 8), CrosshairColor); - - // Map mouse screen position to pad coordinates - var nx = Math.Clamp(mouseScreenPos.X / ScreenW, 0f, 1f); - var ny = Math.Clamp(mouseScreenPos.Y / ScreenH, 0f, 1f); - var dotPos = origin + new Vector2(nx * padW, ny * padH); - - // Cursor dot — changes color on click - var anyClick = input.IsMouseActive(0) || input.IsMouseActive(1) || input.IsMouseActive(2); - var dotColor = anyClick ? Yellow : CursorDot; - drawList.AddCircleFilled(dotPos, 4f, dotColor); - drawList.AddCircle(dotPos, 5f, anyClick ? Yellow : Outline); - - // Coordinates text - var coordText = $"{mouseScreenPos.X:F0},{mouseScreenPos.Y:F0}"; - var ts = ImGui.CalcTextSize(coordText); - drawList.AddText(origin + new Vector2(padW - ts.X - 4, padH - ts.Y - 2), DimText, coordText); - } - - private static void DrawCentered(ImDrawListPtr drawList, string text, Vector2 center, uint color) - { - var ts = ImGui.CalcTextSize(text); - drawList.AddText(center - ts * 0.5f, color, text); - } -} diff --git a/src/Nexus.Simulator/Rendering/PathRenderer.cs b/src/Nexus.Simulator/Rendering/PathRenderer.cs deleted file mode 100644 index 314309b..0000000 --- a/src/Nexus.Simulator/Rendering/PathRenderer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Numerics; -using ImGuiNET; -using Nexus.Pathfinding; - -namespace Nexus.Simulator.Rendering; - -public static class PathRenderer -{ - public static void Draw(ImDrawListPtr drawList, NavigationController nav, ViewTransform vt) - { - var path = nav.CurrentPath; - if (path is null || path.Count < 2) return; - - for (var i = 0; i < path.Count - 1; i++) - { - var a = vt.WorldToScreen(path[i]); - var b = vt.WorldToScreen(path[i + 1]); - drawList.AddLine(a, b, 0xFFFFFF00, 2f); // Cyan - } - - // Draw waypoint dots - foreach (var wp in path) - { - var pos = vt.WorldToScreen(wp); - drawList.AddCircleFilled(pos, 3f, 0xFFFFFF00); - } - } -} diff --git a/src/Nexus.Simulator/Rendering/SimRenderer.cs b/src/Nexus.Simulator/Rendering/SimRenderer.cs deleted file mode 100644 index 541f242..0000000 --- a/src/Nexus.Simulator/Rendering/SimRenderer.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System.Numerics; -using ImGuiNET; -using Nexus.Core; -using Nexus.Pathfinding; -using Nexus.Simulator.Bridge; -using Nexus.Simulator.Config; -using Nexus.Simulator.World; - -namespace Nexus.Simulator.Rendering; - -/// -/// Main renderer: draws the 45°-rotated isometric game world viewport using ImGui draw lists. -/// -public class SimRenderer -{ - private readonly SimConfig _config; - private readonly SimWorld _world; - private readonly NavigationController _nav; - private readonly SimInputController _input; - private readonly DebugPanel _debugPanel; - - // Camera - private Vector2 _viewOffset; - private float _zoom = 2f; // pixels per grid cell - private const float C = 0.70710678f; - - public SimRenderer(SimConfig config, SimWorld world, NavigationController nav, - IReadOnlyList systems, SimInputController input) - { - _config = config; - _world = world; - _nav = nav; - _input = input; - _debugPanel = new DebugPanel(config, world, nav, systems); - - // Center view on player - CenterOnPlayer(); - } - - public void Render(GameState? state) - { - // Debug panel (side window) - _debugPanel.Draw(state); - - // Main viewport - ImGui.SetNextWindowPos(Vector2.Zero, ImGuiCond.FirstUseEver); - ImGui.SetNextWindowSize(new Vector2(1200, 900), ImGuiCond.FirstUseEver); - ImGui.Begin("Simulator", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse); - - var canvasOrigin = ImGui.GetCursorScreenPos(); - var canvasSize = ImGui.GetContentRegionAvail(); - - // Handle input - HandleInput(canvasOrigin, canvasSize); - - // Center camera on player - CenterOnPlayer(canvasSize); - - var drawList = ImGui.GetWindowDrawList(); - var vt = new ViewTransform(canvasOrigin, _viewOffset, _zoom, _config.WorldToGrid); - - // Clip to canvas - drawList.PushClipRect(canvasOrigin, canvasOrigin + canvasSize); - - // 1. Terrain - TerrainRenderer.Draw(drawList, _world.Terrain, vt, canvasSize, - _nav.ExploredGrid, _nav.ExploredWidth, _nav.ExploredHeight, - _nav.ExploredOffsetX, _nav.ExploredOffsetY, - _world.StartWorldPos, _world.EndWorldPos); - - // 2. Path - PathRenderer.Draw(drawList, _nav, vt); - - // 3. Effects — snapshot shared lists to avoid concurrent modification from SimPoller thread - var effects = _world.ActiveEffects.ToArray(); - var projectiles = _world.Projectiles.ToArray(); - var enemies = _world.Enemies.ToArray(); - var items = _world.Items.ToArray(); - - EffectRenderer.DrawEffects(drawList, effects, vt); - EffectRenderer.DrawProjectiles(drawList, projectiles, vt); - - // 4. Items (ground loot labels) - EntityRenderer.DrawItems(drawList, items, vt, canvasOrigin, canvasOrigin + canvasSize); - - // 5. Enemies - EntityRenderer.DrawEnemies(drawList, enemies, vt, canvasOrigin, canvasOrigin + canvasSize); - - // 6. Player - EntityRenderer.DrawPlayer(drawList, _world.Player, vt); - - // 7. Monitor viewport outline — shows what a 2560x1440 monitor would see - DrawMonitorBounds(drawList, vt); - - // 8. Mock cursor — shows where the bot's mouse is pointing in the world - DrawMockCursor(drawList, vt); - - drawList.PopClipRect(); - - // Minimap (bottom-right corner, top-down view) - var minimapSize = 150f; - var minimapOrigin = canvasOrigin + canvasSize - new Vector2(minimapSize + 10, minimapSize + 10); - var playerGridPos = _world.Player.Position * _config.WorldToGrid; - var startGridPos = _world.StartWorldPos * _config.WorldToGrid; - var endGridPos = _world.EndWorldPos * _config.WorldToGrid; - TerrainRenderer.DrawMinimap(drawList, _world.Terrain, playerGridPos, minimapOrigin, minimapSize, - startGridPos, endGridPos); - - // HUD text - DrawHud(drawList, canvasOrigin, state); - - // Input overlay (keyboard + mouse + mousepad) - InputOverlayRenderer.Draw(drawList, _input.GetInputSnapshot(), _input.MouseScreenPos, - canvasOrigin, canvasSize); - - ImGui.End(); - } - - private void CenterOnPlayer(Vector2? canvasSize = null) - { - var cs = canvasSize ?? new Vector2(1200, 900); - var gx = _world.Player.Position.X * _config.WorldToGrid; - var gy = _world.Player.Position.Y * _config.WorldToGrid; - // Rotated grid position - var rx = (gx - gy) * C; - var ry = -(gx + gy) * C; - _viewOffset = cs * 0.5f - new Vector2(rx, ry) * _zoom; - } - - private void HandleInput(Vector2 canvasOrigin, Vector2 canvasSize) - { - if (!ImGui.IsWindowHovered()) return; - - var io = ImGui.GetIO(); - - // Scroll to zoom (works in rotated space — no change needed) - if (io.MouseWheel != 0) - { - var mousePos = io.MousePos - canvasOrigin; - var worldBeforeZoom = (mousePos - _viewOffset) / _zoom; - - _zoom *= io.MouseWheel > 0 ? 1.15f : 1f / 1.15f; - _zoom = Math.Clamp(_zoom, 0.2f, 20f); - - _viewOffset = mousePos - worldBeforeZoom * _zoom; - } - } - - /// - /// Draws an axis-aligned rectangle centered on the player showing the approximate - /// field of view of a 2560x1440 monitor. Scales with sim zoom. - /// - private void DrawMonitorBounds(ImDrawListPtr drawList, ViewTransform vt) - { - // Approximate visible world area on a real monitor (~1400x788 world units at 16:9). - // Aggro range (600) sits comfortably inside. - const float halfW = 700f; - const float halfH = 394f; - - var center = vt.WorldToScreen(_world.Player.Position); - var scale = vt.WorldScale; - var sw = halfW * scale; - var sh = halfH * scale; - - var tl = center + new Vector2(-sw, -sh); - var br = center + new Vector2(sw, sh); - - const uint color = 0x44AAAAAA; - drawList.AddRect(tl, br, color); - } - - private void DrawMockCursor(ImDrawListPtr drawList, ViewTransform vt) - { - // Convert the bot's mouse screen position to world, then to our viewport - var mouseWorld = _world.MouseWorldPos; - var screenPos = vt.WorldToScreen(mouseWorld); - - const float s = 12f; // cursor size - var col = 0xFFFFFFFF; // white - var shadow = 0xAA000000; // black shadow - - // Arrow cursor shape (triangle + tail) - var tip = screenPos; - var left = tip + new Vector2(0, s); - var right = tip + new Vector2(s * 0.55f, s * 0.75f); - var mid = tip + new Vector2(s * 0.2f, s * 0.65f); - var tailEnd = tip + new Vector2(s * 0.55f, s * 1.1f); - var tailRight = tip + new Vector2(s * 0.4f, s * 0.95f); - - // Shadow (offset by 1px) - var off = new Vector2(1, 1); - drawList.AddTriangleFilled(tip + off, left + off, right + off, shadow); - drawList.AddTriangleFilled(mid + off, tailEnd + off, tailRight + off, shadow); - - // Cursor body - drawList.AddTriangleFilled(tip, left, right, col); - drawList.AddTriangleFilled(mid, tailEnd, tailRight, col); - - // Outline - drawList.AddTriangle(tip, left, right, 0xFF000000); - } - - private void DrawHud(ImDrawListPtr drawList, Vector2 canvasOrigin, GameState? state) - { - var textPos = canvasOrigin + new Vector2(10, 10); - var color = 0xFFFFFFFF; - - drawList.AddText(textPos, color, $"Nav: {_nav.Mode} - {_nav.Status}"); - textPos.Y += 16; - - if (state is not null) - { - var ta = state.ThreatAssessment; - drawList.AddText(textPos, color, $"Threat: {ta.ZoneThreatLevel:F0} (fw={ta.FleeWeight:F1}) Enemies: {state.HostileMonsters.Count}"); - textPos.Y += 16; - } - - drawList.AddText(textPos, color, $"Zoom: {_zoom:F1}x Tick: {_world.TickNumber}"); - } -} diff --git a/src/Nexus.Simulator/Rendering/TerrainRenderer.cs b/src/Nexus.Simulator/Rendering/TerrainRenderer.cs deleted file mode 100644 index edf1ea1..0000000 --- a/src/Nexus.Simulator/Rendering/TerrainRenderer.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.Numerics; -using ImGuiNET; -using Nexus.Core; - -namespace Nexus.Simulator.Rendering; - -public static class TerrainRenderer -{ - /// - /// Draws the walkability grid as rotated diamond cells on the ImGui draw list. - /// Only draws cells visible in the current viewport for performance. - /// - public static void Draw(ImDrawListPtr drawList, WalkabilitySnapshot terrain, - ViewTransform vt, Vector2 canvasSize, - bool[]? exploredGrid = null, int exploredWidth = 0, int exploredHeight = 0, - int exploredOffsetX = 0, int exploredOffsetY = 0, - Vector2? startWorldPos = null, Vector2? endWorldPos = null) - { - var cellSize = vt.Zoom; - if (cellSize < 0.5f) return; - - var ox = terrain.OffsetX; - var oy = terrain.OffsetY; - - // Compute visible grid bounds from screen corners (inverse transform) - var c0 = vt.ScreenToGrid(0, 0); - var c1 = vt.ScreenToGrid(canvasSize.X, 0); - var c2 = vt.ScreenToGrid(canvasSize.X, canvasSize.Y); - var c3 = vt.ScreenToGrid(0, canvasSize.Y); - - var minGx = (int)MathF.Floor(Min4(c0.X, c1.X, c2.X, c3.X)); - var maxGx = (int)MathF.Ceiling(Max4(c0.X, c1.X, c2.X, c3.X)); - var minGy = (int)MathF.Floor(Min4(c0.Y, c1.Y, c2.Y, c3.Y)); - var maxGy = (int)MathF.Ceiling(Max4(c0.Y, c1.Y, c2.Y, c3.Y)); - - // Clamp to terrain bounds - minGx = Math.Max(ox, minGx); - maxGx = Math.Min(ox + terrain.Width - 1, maxGx); - minGy = Math.Max(oy, minGy); - maxGy = Math.Min(oy + terrain.Height - 1, maxGy); - - // Skip cells if too many - var step = 1; - if (cellSize < 2f) step = 4; - else if (cellSize < 4f) step = 2; - - for (var gy = minGy; gy <= maxGy; gy += step) - for (var gx = minGx; gx <= maxGx; gx += step) - { - var w = terrain.IsWalkable(gx, gy); - uint color; - - if (!w) - { - color = 0xFF1A1A2E; // Dark wall - } - else - { - var elx = gx - exploredOffsetX; - var ely = gy - exploredOffsetY; - var explored = exploredGrid is not null - && elx >= 0 && elx < exploredWidth && ely >= 0 && ely < exploredHeight - && exploredGrid[ely * exploredWidth + elx]; - - color = explored ? 0xFF3D3D5C : 0xFF2A2A3F; - } - - // Draw diamond (rotated grid cell) - var p0 = vt.GridToScreen(gx, gy); // top - var p1 = vt.GridToScreen(gx + step, gy); // right - var p2 = vt.GridToScreen(gx + step, gy + step); // bottom - var p3 = vt.GridToScreen(gx, gy + step); // left - drawList.AddQuadFilled(p0, p1, p2, p3, color); - } - - // Draw start marker (green circle) - if (startWorldPos.HasValue) - { - var sp = vt.WorldToScreen(startWorldPos.Value); - var r = Math.Max(6f, vt.Zoom * 3f); - drawList.AddCircleFilled(sp, r, 0xFF00FF00); // green - drawList.AddCircle(sp, r, 0xFF00AA00, 0, 2f); - } - - // Draw end marker (red circle) - if (endWorldPos.HasValue) - { - var ep = vt.WorldToScreen(endWorldPos.Value); - var r = Math.Max(6f, vt.Zoom * 3f); - drawList.AddCircleFilled(ep, r, 0xFF0000FF); // red (ABGR) - drawList.AddCircle(ep, r, 0xFF0000AA, 0, 2f); - } - } - - /// - /// Draws a minimap in the corner (top-down, no rotation). - /// - public static void DrawMinimap(ImDrawListPtr drawList, WalkabilitySnapshot terrain, - Vector2 playerGridPos, Vector2 minimapOrigin, float minimapSize, - Vector2? startGridPos = null, Vector2? endGridPos = null) - { - var scaleX = minimapSize / terrain.Width; - var scaleY = minimapSize / terrain.Height; - var scale = Math.Min(scaleX, scaleY); - - // Background - drawList.AddRectFilled(minimapOrigin, - minimapOrigin + new Vector2(terrain.Width * scale, terrain.Height * scale), - 0xFF0A0A15); - - // Draw walkable cells (sampled) — local coords - var step = Math.Max(1, terrain.Width / 200); - for (var ly = 0; ly < terrain.Height; ly += step) - for (var lx = 0; lx < terrain.Width; lx += step) - { - if (!terrain.IsWalkable(lx + terrain.OffsetX, ly + terrain.OffsetY)) continue; - var px = minimapOrigin.X + lx * scale; - var py = minimapOrigin.Y + ly * scale; - drawList.AddRectFilled( - new Vector2(px, py), - new Vector2(px + scale * step, py + scale * step), - 0xFF2A2A3F); - } - - // Start marker (green dot) - if (startGridPos.HasValue) - { - var startLocal = startGridPos.Value - new Vector2(terrain.OffsetX, terrain.OffsetY); - var startPx = minimapOrigin + startLocal * scale; - drawList.AddCircleFilled(startPx, 3f, 0xFF00FF00); // green - } - - // End marker (red dot) - if (endGridPos.HasValue) - { - var endLocal = endGridPos.Value - new Vector2(terrain.OffsetX, terrain.OffsetY); - var endPx = minimapOrigin + endLocal * scale; - drawList.AddCircleFilled(endPx, 3f, 0xFF0000FF); // red - } - - // Player dot (cyan) — convert absolute grid pos to local - var playerLocalPos = playerGridPos - new Vector2(terrain.OffsetX, terrain.OffsetY); - var playerPx = minimapOrigin + playerLocalPos * scale; - drawList.AddCircleFilled(playerPx, 3f, 0xFFFFFF00); // cyan (ABGR) - - // Border - drawList.AddRect(minimapOrigin, - minimapOrigin + new Vector2(terrain.Width * scale, terrain.Height * scale), - 0xFF666666); - } - - private static float Min4(float a, float b, float c, float d) - => MathF.Min(MathF.Min(a, b), MathF.Min(c, d)); - - private static float Max4(float a, float b, float c, float d) - => MathF.Max(MathF.Max(a, b), MathF.Max(c, d)); -} diff --git a/src/Nexus.Simulator/Rendering/VeldridImGuiRenderer.cs b/src/Nexus.Simulator/Rendering/VeldridImGuiRenderer.cs deleted file mode 100644 index 28d01cc..0000000 --- a/src/Nexus.Simulator/Rendering/VeldridImGuiRenderer.cs +++ /dev/null @@ -1,460 +0,0 @@ -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using ImGuiNET; -using Veldrid; - -namespace Nexus.Simulator.Rendering; - -/// -/// Minimal ImGui renderer for Veldrid 4.9.0 on D3D11. -/// Based on the canonical veldrid-imgui integration. -/// -public sealed class VeldridImGuiRenderer : IDisposable -{ - private readonly GraphicsDevice _gd; - - // GPU resources - private DeviceBuffer _vertexBuffer = null!; - private DeviceBuffer _indexBuffer = null!; - private DeviceBuffer _projMatrixBuffer = null!; - private Texture _fontTexture = null!; - private TextureView _fontTextureView = null!; - private ResourceSet _fontResourceSet = null!; - private ResourceLayout _projSamplerLayout = null!; - private ResourceLayout _textureLayout = null!; - private Pipeline _pipeline = null!; - private Shader[] _shaders = null!; - private ResourceSet _projSamplerSet = null!; - - private int _windowWidth; - private int _windowHeight; - private readonly IntPtr _fontAtlasId = (IntPtr)1; - - // Per-texture resource sets - private readonly Dictionary _resourceSets = new(); - - public VeldridImGuiRenderer(GraphicsDevice gd, OutputDescription outputDescription, int width, int height) - { - _gd = gd; - _windowWidth = width; - _windowHeight = height; - - var context = ImGui.CreateContext(); - ImGui.SetCurrentContext(context); - - var io = ImGui.GetIO(); - io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset; - io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard; - io.DisplaySize = new Vector2(width, height); - io.DisplayFramebufferScale = Vector2.One; - - CreateDeviceResources(gd, outputDescription); - } - - public void WindowResized(int width, int height) - { - _windowWidth = width; - _windowHeight = height; - ImGui.GetIO().DisplaySize = new Vector2(width, height); - } - - public void Update(float deltaSeconds, InputSnapshot snapshot) - { - var io = ImGui.GetIO(); - io.DisplaySize = new Vector2(_windowWidth, _windowHeight); - io.DeltaTime = deltaSeconds > 0 ? deltaSeconds : 1f / 60f; - - UpdateInput(snapshot); - - ImGui.NewFrame(); - } - - public void Render(GraphicsDevice gd, CommandList cl) - { - ImGui.Render(); - RenderImDrawData(ImGui.GetDrawData(), gd, cl); - } - - private void CreateDeviceResources(GraphicsDevice gd, OutputDescription outputDescription) - { - var factory = gd.ResourceFactory; - - _vertexBuffer = factory.CreateBuffer(new BufferDescription(10000, BufferUsage.VertexBuffer | BufferUsage.Dynamic)); - _indexBuffer = factory.CreateBuffer(new BufferDescription(2000, BufferUsage.IndexBuffer | BufferUsage.Dynamic)); - _projMatrixBuffer = factory.CreateBuffer(new BufferDescription(64, BufferUsage.UniformBuffer | BufferUsage.Dynamic)); - - // Create shaders - _shaders = CreateShaders(gd); - - // Resource layouts - _projSamplerLayout = factory.CreateResourceLayout(new ResourceLayoutDescription( - new ResourceLayoutElementDescription("ProjectionMatrixBuffer", ResourceKind.UniformBuffer, ShaderStages.Vertex), - new ResourceLayoutElementDescription("MainSampler", ResourceKind.Sampler, ShaderStages.Fragment))); - - _textureLayout = factory.CreateResourceLayout(new ResourceLayoutDescription( - new ResourceLayoutElementDescription("MainTexture", ResourceKind.TextureReadOnly, ShaderStages.Fragment))); - - // Pipeline - var vertexLayout = new VertexLayoutDescription( - new VertexElementDescription("in_position", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Float2), - new VertexElementDescription("in_texCoord", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Float2), - new VertexElementDescription("in_color", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Byte4_Norm)); - - var pipelineDesc = new GraphicsPipelineDescription - { - BlendState = new BlendStateDescription - { - AttachmentStates = [ - new BlendAttachmentDescription - { - BlendEnabled = true, - SourceColorFactor = BlendFactor.SourceAlpha, - DestinationColorFactor = BlendFactor.InverseSourceAlpha, - ColorFunction = BlendFunction.Add, - SourceAlphaFactor = BlendFactor.One, - DestinationAlphaFactor = BlendFactor.InverseSourceAlpha, - AlphaFunction = BlendFunction.Add, - }, - ], - }, - DepthStencilState = new DepthStencilStateDescription(false, false, ComparisonKind.Always), - RasterizerState = new RasterizerStateDescription( - FaceCullMode.None, PolygonFillMode.Solid, FrontFace.Clockwise, - true, true), - PrimitiveTopology = PrimitiveTopology.TriangleList, - ResourceLayouts = [_projSamplerLayout, _textureLayout], - ShaderSet = new ShaderSetDescription([vertexLayout], _shaders), - Outputs = outputDescription, - }; - - _pipeline = factory.CreateGraphicsPipeline(ref pipelineDesc); - - // Cached resource set for projection + sampler - _projSamplerSet = factory.CreateResourceSet(new ResourceSetDescription( - _projSamplerLayout, _projMatrixBuffer, gd.PointSampler)); - - // Font texture (MUST be after _textureLayout is created) - RecreateFontDeviceTexture(gd); - } - - private void RecreateFontDeviceTexture(GraphicsDevice gd) - { - var io = ImGui.GetIO(); - io.Fonts.GetTexDataAsRGBA32(out IntPtr pixels, out int width, out int height, out int bytesPerPixel); - - _fontTexture?.Dispose(); - _fontTextureView?.Dispose(); - _fontResourceSet?.Dispose(); - - _fontTexture = gd.ResourceFactory.CreateTexture(TextureDescription.Texture2D( - (uint)width, (uint)height, 1, 1, - PixelFormat.R8_G8_B8_A8_UNorm, TextureUsage.Sampled)); - _fontTexture.Name = "ImGui.NET Font Texture"; - - gd.UpdateTexture(_fontTexture, pixels, (uint)(bytesPerPixel * width * height), 0, 0, 0, - (uint)width, (uint)height, 1, 0, 0); - - _fontTextureView = gd.ResourceFactory.CreateTextureView(_fontTexture); - - io.Fonts.SetTexID(_fontAtlasId); - io.Fonts.ClearTexData(); - - _fontResourceSet = gd.ResourceFactory.CreateResourceSet(new ResourceSetDescription( - _textureLayout, _fontTextureView)); - _resourceSets[_fontAtlasId] = _fontResourceSet; - } - - private Shader[] CreateShaders(GraphicsDevice gd) - { - if (gd.BackendType != GraphicsBackend.Direct3D11) - throw new NotSupportedException($"Backend {gd.BackendType} not supported. Use Direct3D11."); - - var vertexShaderBytes = CompileHlsl(VertexShaderHlsl, "main", "vs_5_0"); - var fragmentShaderBytes = CompileHlsl(FragmentShaderHlsl, "main", "ps_5_0"); - - var vertexShader = gd.ResourceFactory.CreateShader(new ShaderDescription( - ShaderStages.Vertex, vertexShaderBytes, "main")); - var fragmentShader = gd.ResourceFactory.CreateShader(new ShaderDescription( - ShaderStages.Fragment, fragmentShaderBytes, "main")); - - return [vertexShader, fragmentShader]; - } - - private void RenderImDrawData(ImDrawDataPtr drawData, GraphicsDevice gd, CommandList cl) - { - if (drawData.CmdListsCount == 0) return; - - // Resize vertex/index buffers if needed - var totalVtxSize = (uint)(drawData.TotalVtxCount * Unsafe.SizeOf()); - var totalIdxSize = (uint)(drawData.TotalIdxCount * sizeof(ushort)); - - if (totalVtxSize == 0 || totalIdxSize == 0) return; - - if (totalVtxSize > _vertexBuffer.SizeInBytes) - { - _vertexBuffer.Dispose(); - _vertexBuffer = gd.ResourceFactory.CreateBuffer(new BufferDescription( - totalVtxSize * 2, BufferUsage.VertexBuffer | BufferUsage.Dynamic)); - } - - if (totalIdxSize > _indexBuffer.SizeInBytes) - { - _indexBuffer.Dispose(); - _indexBuffer = gd.ResourceFactory.CreateBuffer(new BufferDescription( - totalIdxSize * 2, BufferUsage.IndexBuffer | BufferUsage.Dynamic)); - } - - // Upload vertex/index data - uint vtxOffset = 0, idxOffset = 0; - for (var i = 0; i < drawData.CmdListsCount; i++) - { - var cmdList = drawData.CmdLists[i]; - var vtxSize = (uint)(cmdList.VtxBuffer.Size * Unsafe.SizeOf()); - var idxSize = (uint)(cmdList.IdxBuffer.Size * sizeof(ushort)); - cl.UpdateBuffer(_vertexBuffer, vtxOffset, cmdList.VtxBuffer.Data, vtxSize); - cl.UpdateBuffer(_indexBuffer, idxOffset, cmdList.IdxBuffer.Data, idxSize); - vtxOffset += vtxSize; - idxOffset += idxSize; - } - - // Update projection matrix - var mvp = Matrix4x4.CreateOrthographicOffCenter( - drawData.DisplayPos.X, - drawData.DisplayPos.X + drawData.DisplaySize.X, - drawData.DisplayPos.Y + drawData.DisplaySize.Y, - drawData.DisplayPos.Y, - -1f, 1f); - cl.UpdateBuffer(_projMatrixBuffer, 0, ref mvp); - - cl.SetVertexBuffer(0, _vertexBuffer); - cl.SetIndexBuffer(_indexBuffer, IndexFormat.UInt16); - cl.SetPipeline(_pipeline); - cl.SetGraphicsResourceSet(0, _projSamplerSet); - - // Draw - var clipOff = drawData.DisplayPos; - var clipScale = drawData.FramebufferScale; - vtxOffset = 0; - idxOffset = 0; - for (var n = 0; n < drawData.CmdListsCount; n++) - { - var cmdList = drawData.CmdLists[n]; - for (var cmdI = 0; cmdI < cmdList.CmdBuffer.Size; cmdI++) - { - var pcmd = cmdList.CmdBuffer[cmdI]; - - if (pcmd.TextureId != IntPtr.Zero) - { - if (_resourceSets.TryGetValue(pcmd.TextureId, out var rs)) - cl.SetGraphicsResourceSet(1, rs); - } - - var clipX = (uint)Math.Max(0, (pcmd.ClipRect.X - clipOff.X) * clipScale.X); - var clipY = (uint)Math.Max(0, (pcmd.ClipRect.Y - clipOff.Y) * clipScale.Y); - var clipW = (uint)Math.Max(0, (pcmd.ClipRect.Z - pcmd.ClipRect.X) * clipScale.X); - var clipH = (uint)Math.Max(0, (pcmd.ClipRect.W - pcmd.ClipRect.Y) * clipScale.Y); - - if (clipW == 0 || clipH == 0) continue; - - cl.SetScissorRect(0, clipX, clipY, clipW, clipH); - - cl.DrawIndexed(pcmd.ElemCount, 1, - pcmd.IdxOffset + idxOffset, - (int)(pcmd.VtxOffset + vtxOffset / (uint)Unsafe.SizeOf()), - 0); - } - vtxOffset += (uint)(cmdList.VtxBuffer.Size * Unsafe.SizeOf()); - idxOffset += (uint)cmdList.IdxBuffer.Size; - } - } - - private void UpdateInput(InputSnapshot snapshot) - { - var io = ImGui.GetIO(); - - io.MousePos = snapshot.MousePosition; - - io.MouseDown[0] = snapshot.IsMouseDown(MouseButton.Left); - io.MouseDown[1] = snapshot.IsMouseDown(MouseButton.Right); - io.MouseDown[2] = snapshot.IsMouseDown(MouseButton.Middle); - io.MouseWheel = snapshot.WheelDelta; - - foreach (var ke in snapshot.KeyEvents) - { - var imguiKey = ConvertKey(ke.Key); - if (imguiKey != ImGuiKey.None) - io.AddKeyEvent(imguiKey, ke.Down); - } - - foreach (var c in snapshot.KeyCharPresses) - io.AddInputCharacter(c); - } - - private static ImGuiKey ConvertKey(Key key) => key switch - { - Key.Tab => ImGuiKey.Tab, - Key.Left => ImGuiKey.LeftArrow, - Key.Right => ImGuiKey.RightArrow, - Key.Up => ImGuiKey.UpArrow, - Key.Down => ImGuiKey.DownArrow, - Key.Home => ImGuiKey.Home, - Key.End => ImGuiKey.End, - Key.Delete => ImGuiKey.Delete, - Key.BackSpace => ImGuiKey.Backspace, - Key.Enter => ImGuiKey.Enter, - Key.Escape => ImGuiKey.Escape, - Key.Space => ImGuiKey.Space, - Key.A => ImGuiKey.A, - Key.C => ImGuiKey.C, - Key.V => ImGuiKey.V, - Key.X => ImGuiKey.X, - Key.Y => ImGuiKey.Y, - Key.Z => ImGuiKey.Z, - _ => ImGuiKey.None, - }; - - // ── HLSL Shader Sources ── - - private const string VertexShaderHlsl = @" -cbuffer ProjectionMatrixBuffer : register(b0) -{ - float4x4 ProjectionMatrix; -}; - -struct VS_INPUT -{ - float2 pos : TEXCOORD0; - float2 uv : TEXCOORD1; - float4 col : TEXCOORD2; -}; - -struct PS_INPUT -{ - float4 pos : SV_POSITION; - float4 col : COLOR0; - float2 uv : TEXCOORD0; -}; - -PS_INPUT main(VS_INPUT input) -{ - PS_INPUT output; - output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.0, 1.0)); - output.col = input.col; - output.uv = input.uv; - return output; -} -"; - - private const string FragmentShaderHlsl = @" -Texture2D MainTexture : register(t0); -SamplerState MainSampler : register(s0); - -struct PS_INPUT -{ - float4 pos : SV_POSITION; - float4 col : COLOR0; - float2 uv : TEXCOORD0; -}; - -float4 main(PS_INPUT input) : SV_Target -{ - float4 texColor = MainTexture.Sample(MainSampler, input.uv); - return input.col * texColor; -} -"; - - // ── D3DCompiler P/Invoke ── - - [DllImport("d3dcompiler_47.dll", CallingConvention = CallingConvention.StdCall)] - private static extern int D3DCompile( - [MarshalAs(UnmanagedType.LPStr)] string pSrcData, - nint srcDataSize, - [MarshalAs(UnmanagedType.LPStr)] string? pSourceName, - IntPtr pDefines, - IntPtr pInclude, - [MarshalAs(UnmanagedType.LPStr)] string pEntrypoint, - [MarshalAs(UnmanagedType.LPStr)] string pTarget, - uint flags1, - uint flags2, - out IntPtr ppCode, - out IntPtr ppErrorMsgs); - - // ID3DBlob vtable offsets - private static IntPtr BlobGetBufferPointer(IntPtr blob) - { - var vtable = Marshal.ReadIntPtr(blob); - var fn = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size); - return Marshal.GetDelegateForFunctionPointer(fn)(blob); - } - - private static nint BlobGetBufferSize(IntPtr blob) - { - var vtable = Marshal.ReadIntPtr(blob); - var fn = Marshal.ReadIntPtr(vtable, 4 * IntPtr.Size); - return Marshal.GetDelegateForFunctionPointer(fn)(blob); - } - - private static void BlobRelease(IntPtr blob) - { - var vtable = Marshal.ReadIntPtr(blob); - var fn = Marshal.ReadIntPtr(vtable, 2 * IntPtr.Size); - Marshal.GetDelegateForFunctionPointer(fn)(blob); - } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate IntPtr BlobBufferPointerFn(IntPtr self); - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate nint BlobBufferSizeFn(IntPtr self); - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate int BlobReleaseFn(IntPtr self); - - private static byte[] CompileHlsl(string source, string entryPoint, string target) - { - var hr = D3DCompile(source, (nint)source.Length, null, IntPtr.Zero, IntPtr.Zero, - entryPoint, target, 0, 0, out var codeBlob, out var errorBlob); - - if (hr != 0 || codeBlob == IntPtr.Zero) - { - var error = "Unknown shader compilation error"; - if (errorBlob != IntPtr.Zero) - { - var errorPtr = BlobGetBufferPointer(errorBlob); - error = Marshal.PtrToStringAnsi(errorPtr) ?? error; - BlobRelease(errorBlob); - } - throw new InvalidOperationException($"HLSL compilation failed: {error}"); - } - - if (errorBlob != IntPtr.Zero) - BlobRelease(errorBlob); - - var bufferPtr = BlobGetBufferPointer(codeBlob); - var bufferSize = (int)BlobGetBufferSize(codeBlob); - var result = new byte[bufferSize]; - Marshal.Copy(bufferPtr, result, 0, bufferSize); - BlobRelease(codeBlob); - return result; - } - - public void Dispose() - { - _vertexBuffer?.Dispose(); - _indexBuffer?.Dispose(); - _projMatrixBuffer?.Dispose(); - _fontTexture?.Dispose(); - _fontTextureView?.Dispose(); - _fontResourceSet?.Dispose(); - _projSamplerSet?.Dispose(); - _pipeline?.Dispose(); - _projSamplerLayout?.Dispose(); - _textureLayout?.Dispose(); - if (_shaders is not null) - foreach (var shader in _shaders) - shader?.Dispose(); - foreach (var rs in _resourceSets.Values) - rs?.Dispose(); - } -} diff --git a/src/Nexus.Simulator/Rendering/ViewTransform.cs b/src/Nexus.Simulator/Rendering/ViewTransform.cs deleted file mode 100644 index 4d947cb..0000000 --- a/src/Nexus.Simulator/Rendering/ViewTransform.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Numerics; - -namespace Nexus.Simulator.Rendering; - -/// -/// Encapsulates the 45° rotated isometric camera transform. -/// Grid coordinates are rotated so that WASD maps to screen up/left/down/right. -/// -public readonly struct ViewTransform -{ - private readonly Vector2 _canvasOrigin; - private readonly Vector2 _viewOffset; - private readonly float _zoom; - private readonly float _worldToGrid; - private const float C = 0.70710678f; // cos(45°) = 1/√2 - - public ViewTransform(Vector2 canvasOrigin, Vector2 viewOffset, float zoom, float worldToGrid) - { - _canvasOrigin = canvasOrigin; - _viewOffset = viewOffset; - _zoom = zoom; - _worldToGrid = worldToGrid; - } - - public float Zoom => _zoom; - public float WorldToGrid => _worldToGrid; - - /// - /// Scale factor from world units to screen pixels (distance-preserving rotation). - /// - public float WorldScale => _worldToGrid * _zoom; - - /// - /// Convert world position to screen position. - /// - public Vector2 WorldToScreen(Vector2 worldPos) - { - var gx = worldPos.X * _worldToGrid; - var gy = worldPos.Y * _worldToGrid; - return GridToScreen(gx, gy); - } - - /// - /// Convert absolute grid position to screen position. - /// - public Vector2 GridToScreen(float gx, float gy) - { - var rx = (gx - gy) * C; - var ry = -(gx + gy) * C; - return _canvasOrigin + _viewOffset + new Vector2(rx, ry) * _zoom; - } - - /// - /// Convert screen-relative position (relative to canvas origin) to absolute grid coords. - /// - public Vector2 ScreenToGrid(float sx, float sy) - { - var rx = (sx - _viewOffset.X) / _zoom; - var ry = (sy - _viewOffset.Y) / _zoom; - return new Vector2((rx - ry) * C, -(rx + ry) * C); - } -} diff --git a/src/Nexus.Simulator/World/SimEnemy.cs b/src/Nexus.Simulator/World/SimEnemy.cs deleted file mode 100644 index 2984869..0000000 --- a/src/Nexus.Simulator/World/SimEnemy.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Numerics; -using Nexus.Core; - -namespace Nexus.Simulator.World; - -public enum EnemyAiState -{ - Idle, - Chasing, - Attacking, - Retreating, // Ranged enemy backing away to maintain distance - Dead, -} - -public enum EnemyType -{ - Melee, - Ranged, -} - -public class SimEnemy -{ - private static uint _nextId = 1000; - - public uint Id { get; } - public Vector2 Position { get; set; } - public int Health { get; set; } - public int MaxHealth { get; set; } - public MonsterRarity Rarity { get; set; } - public EnemyType Type { get; set; } - public EnemyAiState AiState { get; set; } = EnemyAiState.Idle; - public float MoveSpeed { get; set; } - - // Damage (scaled by rarity) - public int AttackDamage { get; set; } - - // Timers - public float AttackCooldownRemaining { get; set; } - public float DespawnTimer { get; set; } - public float RespawnTimer { get; set; } - - // Ranged-specific - public float PreferredRange { get; set; } // Distance ranged enemies try to maintain - public float AttackRange { get; set; } // Max attack range (melee=100, ranged=500) - - // Wander - public Vector2 WanderTarget { get; set; } - public float WanderTimer { get; set; } - public Vector2 SpawnPosition { get; set; } - - public bool IsAlive => Health > 0; - public bool IsAttacking => AiState == EnemyAiState.Attacking; - public bool IsRanged => Type == EnemyType.Ranged; - - public SimEnemy(Vector2 position, MonsterRarity rarity, EnemyType type, - int baseHealth, int baseDamage, float moveSpeed) - { - Id = Interlocked.Increment(ref _nextId); - Position = position; - SpawnPosition = position; - Rarity = rarity; - Type = type; - MoveSpeed = moveSpeed; - - var hpMultiplier = rarity switch - { - MonsterRarity.Magic => 1.5f, - MonsterRarity.Rare => 3f, - MonsterRarity.Unique => 5f, - _ => 1f, - }; - MaxHealth = (int)(baseHealth * hpMultiplier); - Health = MaxHealth; - - var dmgMultiplier = rarity switch - { - MonsterRarity.Magic => 1.5f, - MonsterRarity.Rare => 2.5f, - MonsterRarity.Unique => 4f, - _ => 1f, - }; - AttackDamage = (int)(baseDamage * dmgMultiplier); - } - - public void TakeDamage(int damage) - { - if (!IsAlive) return; - Health = Math.Max(0, Health - damage); - if (Health <= 0) - { - AiState = EnemyAiState.Dead; - DespawnTimer = 2f; - } - } - - public MonsterThreatLevel GetThreatLevel() => Rarity switch - { - MonsterRarity.Magic => MonsterThreatLevel.Magic, - MonsterRarity.Rare => MonsterThreatLevel.Rare, - MonsterRarity.Unique => MonsterThreatLevel.Unique, - _ => MonsterThreatLevel.Normal, - }; -} diff --git a/src/Nexus.Simulator/World/SimItem.cs b/src/Nexus.Simulator/World/SimItem.cs deleted file mode 100644 index ceda3e4..0000000 --- a/src/Nexus.Simulator/World/SimItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Numerics; - -namespace Nexus.Simulator.World; - -public enum LootCategory { Currency, Normal, Magic, Rare, Unique, Quest } - -public class SimItem -{ - private static uint _nextId = 50000; - - public uint Id { get; } = Interlocked.Increment(ref _nextId); - public Vector2 Position { get; init; } - public LootCategory Category { get; init; } - public string Label { get; init; } = ""; -} diff --git a/src/Nexus.Simulator/World/SimPlayer.cs b/src/Nexus.Simulator/World/SimPlayer.cs deleted file mode 100644 index 26947a1..0000000 --- a/src/Nexus.Simulator/World/SimPlayer.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System.Numerics; - -namespace Nexus.Simulator.World; - -public class SimPlayer -{ - public Vector2 Position { get; set; } - public int Health { get; set; } - public int MaxHealth { get; set; } - public int Mana { get; set; } - public int MaxMana { get; set; } - public int Es { get; set; } - public int MaxEs { get; set; } - public float MoveSpeed { get; set; } - public float HealthRegen { get; set; } - public float ManaRegen { get; set; } - public float EsRegen { get; set; } - public float EsRechargeDelay { get; set; } - - // Dodge roll state - public bool IsRolling { get; set; } - public Vector2 RollDirection { get; set; } - public float RollElapsed { get; set; } - public float RollDuration { get; set; } - public float RollCooldownRemaining { get; set; } - - // Accumulate fractional regen - private float _healthRegenAccum; - private float _manaRegenAccum; - private float _esRegenAccum; - private float _timeSinceLastDamage; - - public SimPlayer(int maxHealth, int maxMana, int maxEs, - float moveSpeed, float healthRegen, float manaRegen, - float esRegen, float esRechargeDelay) - { - MaxHealth = maxHealth; - MaxMana = maxMana; - MaxEs = maxEs; - Health = maxHealth; - Mana = maxMana; - Es = maxEs; - MoveSpeed = moveSpeed; - HealthRegen = healthRegen; - ManaRegen = manaRegen; - EsRegen = esRegen; - EsRechargeDelay = esRechargeDelay; - _timeSinceLastDamage = esRechargeDelay; // Start with ES recharging - } - - public void StartDodgeRoll(Vector2 direction, float duration) - { - if (IsRolling || RollCooldownRemaining > 0) return; - IsRolling = true; - RollDirection = Vector2.Normalize(direction); - RollElapsed = 0f; - RollDuration = duration; - } - - public void Update(float dt) - { - // Tick dodge cooldown - if (RollCooldownRemaining > 0) - RollCooldownRemaining = MathF.Max(0, RollCooldownRemaining - dt); - - _timeSinceLastDamage += dt; - - // Health regen (always active) - _healthRegenAccum += HealthRegen * dt; - if (_healthRegenAccum >= 1f) - { - var amount = (int)_healthRegenAccum; - Health = Math.Min(MaxHealth, Health + amount); - _healthRegenAccum -= amount; - } - - // Mana regen - _manaRegenAccum += ManaRegen * dt; - if (_manaRegenAccum >= 1f) - { - var amount = (int)_manaRegenAccum; - Mana = Math.Min(MaxMana, Mana + amount); - _manaRegenAccum -= amount; - } - - // ES recharge (after delay since last damage) - if (_timeSinceLastDamage >= EsRechargeDelay && Es < MaxEs) - { - _esRegenAccum += EsRegen * dt; - if (_esRegenAccum >= 1f) - { - var amount = (int)_esRegenAccum; - Es = Math.Min(MaxEs, Es + amount); - _esRegenAccum -= amount; - } - } - } - - public void TakeDamage(int damage) - { - _timeSinceLastDamage = 0f; - _esRegenAccum = 0f; - - // ES absorbs damage first - if (Es > 0) - { - if (damage <= Es) - { - Es -= damage; - return; - } - damage -= Es; - Es = 0; - } - - Health = Math.Max(0, Health - damage); - } - - public bool IsAlive => Health > 0; - - /// Effective HP percentage considering both ES and Life. - public float EffectiveHpPercent - { - get - { - var totalMax = MaxHealth + MaxEs; - if (totalMax == 0) return 0f; - return (float)(Health + Es) / totalMax * 100f; - } - } -} diff --git a/src/Nexus.Simulator/World/SimProjectile.cs b/src/Nexus.Simulator/World/SimProjectile.cs deleted file mode 100644 index 5b783f2..0000000 --- a/src/Nexus.Simulator/World/SimProjectile.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Numerics; - -namespace Nexus.Simulator.World; - -public class SimProjectile -{ - public Vector2 Position { get; set; } - public Vector2 Direction { get; set; } - public float Speed { get; set; } - public float MaxRange { get; set; } - public float HitRadius { get; set; } - public int Damage { get; set; } - public float DistanceTraveled { get; set; } - public bool IsExpired { get; set; } - public bool IsEnemyProjectile { get; set; } - - public SimProjectile(Vector2 origin, Vector2 direction, float speed, float maxRange, float hitRadius, int damage, - bool isEnemyProjectile = false) - { - Position = origin; - Direction = Vector2.Normalize(direction); - Speed = speed; - MaxRange = maxRange; - HitRadius = hitRadius; - Damage = damage; - IsEnemyProjectile = isEnemyProjectile; - } - - public void Update(float dt) - { - var step = Speed * dt; - Position += Direction * step; - DistanceTraveled += step; - if (DistanceTraveled >= MaxRange) - IsExpired = true; - } -} diff --git a/src/Nexus.Simulator/World/SimSkillEffect.cs b/src/Nexus.Simulator/World/SimSkillEffect.cs deleted file mode 100644 index 073a51f..0000000 --- a/src/Nexus.Simulator/World/SimSkillEffect.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Numerics; - -namespace Nexus.Simulator.World; - -public enum SkillEffectType -{ - Melee, - Aoe, - Projectile, -} - -public class SimSkillEffect -{ - public SkillEffectType Type { get; set; } - public Vector2 Origin { get; set; } - public Vector2 TargetPosition { get; set; } - public float Radius { get; set; } - public float ConeAngle { get; set; } - public int Damage { get; set; } - public float Duration { get; set; } = 0.3f; - public float Elapsed { get; set; } - public bool Applied { get; set; } - - public bool IsExpired => Elapsed >= Duration; - public float Progress => Duration > 0 ? Elapsed / Duration : 1f; -} diff --git a/src/Nexus.Simulator/World/SimWorld.cs b/src/Nexus.Simulator/World/SimWorld.cs deleted file mode 100644 index b12ae1e..0000000 --- a/src/Nexus.Simulator/World/SimWorld.cs +++ /dev/null @@ -1,760 +0,0 @@ -using System.Numerics; -using Nexus.Core; -using Nexus.Simulator.Config; -using Serilog; - -namespace Nexus.Simulator.World; - -public class SimWorld -{ - private readonly SimConfig _config; - private readonly Random _rng = new(); - - public SimPlayer Player { get; } - public List Enemies { get; } = []; - public List Projectiles { get; } = []; - public List ActiveEffects { get; } = []; - public List Items { get; } = []; - public WalkabilitySnapshot Terrain { get; private set; } - public long TickNumber { get; private set; } - public Vector2 StartWorldPos { get; private set; } - public Vector2 EndWorldPos { get; private set; } - public bool ReachedEnd { get; private set; } - - private int _dungeonSeed; - - // Pending respawns - private readonly List<(float timer, MonsterRarity rarity)> _respawnQueue = []; - - // Queued actions from SimInputController - public Vector2 MoveDirection { get; set; } - public Vector2 MouseWorldPos { get; set; } - private readonly Queue<(ushort scanCode, Vector2 targetWorldPos)> _skillQueue = new(); - - // Dodge roll - private Vector2? _pendingDodgeDirection; - private Vector2 _lastFacingDirection = Vector2.UnitX; - - public SimWorld(SimConfig config) - { - _config = config; - _dungeonSeed = _rng.Next(); - - var dungeon = TerrainGenerator.GenerateDungeon( - config.TerrainWidth, config.TerrainHeight, _dungeonSeed, - config.DungeonRoomCountMin, config.DungeonRoomCountMax, - config.DungeonRoomSizeMin, config.DungeonRoomSizeMax, - config.DungeonCorridorWidth, config.WorldToGrid); - - Terrain = dungeon.Terrain; - StartWorldPos = dungeon.StartWorldPos; - EndWorldPos = dungeon.EndWorldPos; - - Player = new SimPlayer( - config.PlayerMaxHealth, config.PlayerMaxMana, config.PlayerMaxEs, - config.PlayerMoveSpeed, config.PlayerHealthRegen, config.PlayerManaRegen, - config.PlayerEsRegen, config.PlayerEsRechargeDelay) - { - Position = dungeon.StartWorldPos, - }; - - // Spawn enemies inside dungeon rooms - SpawnEnemiesInRooms(dungeon.Rooms); - } - - public void RegenerateTerrain() - { - _dungeonSeed = _rng.Next(); - var dungeon = TerrainGenerator.GenerateDungeon( - _config.TerrainWidth, _config.TerrainHeight, _dungeonSeed, - _config.DungeonRoomCountMin, _config.DungeonRoomCountMax, - _config.DungeonRoomSizeMin, _config.DungeonRoomSizeMax, - _config.DungeonCorridorWidth, _config.WorldToGrid); - - Terrain = dungeon.Terrain; - StartWorldPos = dungeon.StartWorldPos; - EndWorldPos = dungeon.EndWorldPos; - ReachedEnd = false; - - Player.Position = dungeon.StartWorldPos; - Player.Health = Player.MaxHealth; - Player.Mana = Player.MaxMana; - Player.Es = Player.MaxEs; - Enemies.Clear(); - Projectiles.Clear(); - ActiveEffects.Clear(); - Items.Clear(); - _respawnQueue.Clear(); - SpawnEnemiesInRooms(dungeon.Rooms); - Log.Information("Dungeon regenerated (seed={Seed}), {Rooms} rooms", _dungeonSeed, dungeon.Rooms.Count); - } - - public void QueueSkill(ushort scanCode, Vector2 targetWorldPos) - { - _skillQueue.Enqueue((scanCode, targetWorldPos)); - } - - public void QueueDodgeRoll(Vector2 direction) - { - _pendingDodgeDirection = direction; - } - - public void Tick(float dt) - { - if (_config.IsPaused) return; - - dt *= _config.SpeedMultiplier; - TickNumber++; - - // 0. Check if player reached dungeon end - if (!ReachedEnd && Vector2.Distance(Player.Position, EndWorldPos) < _config.DungeonEndReachDist) - { - ReachedEnd = true; - Log.Information("Reached dungeon end!"); - } - - // 0.5. Start queued dodge roll - if (_pendingDodgeDirection.HasValue && !Player.IsRolling && Player.RollCooldownRemaining <= 0) - { - Player.StartDodgeRoll(_pendingDodgeDirection.Value, _config.DodgeRollDuration); - _pendingDodgeDirection = null; - } - else - { - _pendingDodgeDirection = null; - } - - // 1. Move player - MovePlayer(dt); - - // 2. Process queued skills - ProcessSkills(); - - // 3. Update projectiles (player + enemy) - UpdateProjectiles(dt); - - // 4. Update skill effects - UpdateEffects(dt); - - // 5. Update enemy AI - UpdateEnemies(dt); - - // 6. Process respawn queue - UpdateRespawns(dt); - - // 7. Pickup items near player - PickupNearbyItems(); - - // 8. Player regen - Player.Update(dt); - } - - // Pre-computed 8 cardinal directions for fallback movement - private static readonly Vector2[] Cardinals = - [ - new(1, 0), new(-1, 0), new(0, 1), new(0, -1), - Vector2.Normalize(new(1, 1)), Vector2.Normalize(new(1, -1)), - Vector2.Normalize(new(-1, 1)), Vector2.Normalize(new(-1, -1)), - ]; - - private void MovePlayer(float dt) - { - // Dodge roll overrides normal movement - if (Player.IsRolling) - { - MovePlayerDodgeRoll(dt); - return; - } - - if (MoveDirection.LengthSquared() < 0.001f) return; - - var dir = Vector2.Normalize(MoveDirection); - _lastFacingDirection = dir; // Track for stationary dodge - var step = Player.MoveSpeed * dt; - - // Try full direction - if (TryMove(dir, step)) return; - - // Try axis-aligned slides - if (MathF.Abs(dir.X) > 0.01f && TryMove(new Vector2(dir.X > 0 ? 1 : -1, 0), step)) return; - if (MathF.Abs(dir.Y) > 0.01f && TryMove(new Vector2(0, dir.Y > 0 ? 1 : -1), step)) return; - - // Try perpendicular directions (wall slide along corners) - var perp1 = new Vector2(-dir.Y, dir.X); - var perp2 = new Vector2(dir.Y, -dir.X); - if (TryMove(perp1, step * 0.5f)) return; - if (TryMove(perp2, step * 0.5f)) return; - - // All preferred directions failed — try all 8 cardinals at reduced step - // Sort by dot product with desired direction (prefer closest to intended direction) - foreach (var cardinal in Cardinals) - { - if (TryMove(cardinal, step * 0.5f)) return; - } - - // Absolute last resort: try tiny steps in all 8 directions to nudge out - foreach (var cardinal in Cardinals) - { - if (TryMove(cardinal, step * 0.15f)) return; - } - } - - /// - /// Ease-out dodge roll: v(t) = (2D/T) * (1 - t/T). - /// Peak speed at start = 2D/T, decelerates to 0 over duration T. - /// Total distance traveled = D (integral of v over [0,T]). - /// - private void MovePlayerDodgeRoll(float dt) - { - Player.RollElapsed += dt; - var t = Player.RollElapsed; - var T = Player.RollDuration; - var D = _config.DodgeRollDistance; - - if (t >= T) - { - // Roll complete - Player.IsRolling = false; - Player.RollCooldownRemaining = _config.DodgeRollCooldown; - return; - } - - // Quadratic ease-out speed: peaks at 2D/T, drops to 0 - var speed = (2f * D / T) * (1f - t / T); - var step = speed * dt; - var dir = Player.RollDirection; - - // Try full direction, then wall-slide fallback - if (!TryMove(dir, step)) - { - // Try axis-aligned slides - if (MathF.Abs(dir.X) > 0.01f && TryMove(new Vector2(dir.X > 0 ? 1 : -1, 0), step)) { } - else if (MathF.Abs(dir.Y) > 0.01f && TryMove(new Vector2(0, dir.Y > 0 ? 1 : -1), step)) { } - else - { - // Completely blocked — end roll early - Player.IsRolling = false; - Player.RollCooldownRemaining = _config.DodgeRollCooldown; - } - } - } - - private bool TryMove(Vector2 dir, float step) - { - var newPos = Player.Position + dir * step; - var gx = (int)(newPos.X * _config.WorldToGrid); - var gy = (int)(newPos.Y * _config.WorldToGrid); - if (!Terrain.IsWalkable(gx, gy)) return false; - Player.Position = newPos; - return true; - } - - private void ProcessSkills() - { - while (_skillQueue.TryDequeue(out var skill)) - { - var type = GetSkillType(skill.scanCode); - var targetPos = skill.targetWorldPos; - - switch (type) - { - case SkillEffectType.Melee: - ProcessMeleeSkill(targetPos); - break; - case SkillEffectType.Aoe: - ProcessAoeSkill(targetPos); - break; - case SkillEffectType.Projectile: - ProcessProjectileSkill(targetPos); - break; - } - } - } - - private SkillEffectType GetSkillType(ushort scanCode) - { - return scanCode switch - { - 0x10 => SkillEffectType.Aoe, // Q - 0x12 => SkillEffectType.Projectile, // E - 0x13 => SkillEffectType.Aoe, // R - 0x14 => SkillEffectType.Projectile, // T - _ => SkillEffectType.Melee, - }; - } - - private void ProcessMeleeSkill(Vector2 targetPos) - { - var dir = targetPos - Player.Position; - if (dir.LengthSquared() > 0) - dir = Vector2.Normalize(dir); - else - dir = Vector2.UnitX; - - var effect = new SimSkillEffect - { - Type = SkillEffectType.Melee, - Origin = Player.Position, - TargetPosition = targetPos, - Radius = _config.MeleeRange, - ConeAngle = _config.MeleeConeAngle, - Damage = _config.SkillBaseDamage, - }; - ActiveEffects.Add(effect); - - // Apply melee damage immediately - var halfAngle = _config.MeleeConeAngle * MathF.PI / 360f; - foreach (var enemy in Enemies) - { - if (!enemy.IsAlive) continue; - var toEnemy = enemy.Position - Player.Position; - var dist = toEnemy.Length(); - if (dist > _config.MeleeRange) continue; - - if (dist > 0) - { - var angle = MathF.Acos(Vector2.Dot(Vector2.Normalize(toEnemy), dir)); - if (angle > halfAngle) continue; - } - - enemy.TakeDamage(_config.SkillBaseDamage); - if (!enemy.IsAlive) - { - Log.Information("Kill: {Rarity} {Type} #{Id}", enemy.Rarity, enemy.Type, enemy.Id); - SpawnLoot(enemy); - } - } - } - - private void ProcessAoeSkill(Vector2 targetPos) - { - var effect = new SimSkillEffect - { - Type = SkillEffectType.Aoe, - Origin = Player.Position, - TargetPosition = targetPos, - Radius = _config.AoeRadius, - Damage = _config.SkillBaseDamage, - }; - ActiveEffects.Add(effect); - - foreach (var enemy in Enemies) - { - if (!enemy.IsAlive) continue; - if (Vector2.Distance(enemy.Position, targetPos) <= _config.AoeRadius) - { - enemy.TakeDamage(_config.SkillBaseDamage); - if (!enemy.IsAlive) - { - Log.Information("Kill: {Rarity} {Type} #{Id}", enemy.Rarity, enemy.Type, enemy.Id); - SpawnLoot(enemy); - } - } - } - } - - private void ProcessProjectileSkill(Vector2 targetPos) - { - var dir = targetPos - Player.Position; - if (dir.LengthSquared() < 1f) dir = Vector2.UnitX; - - var projectile = new SimProjectile( - Player.Position, dir, - _config.ProjectileSpeed, _config.ProjectileRange, - _config.ProjectileHitRadius, _config.SkillBaseDamage); - - Projectiles.Add(projectile); - - ActiveEffects.Add(new SimSkillEffect - { - Type = SkillEffectType.Projectile, - Origin = Player.Position, - TargetPosition = targetPos, - Radius = _config.ProjectileHitRadius, - Damage = _config.SkillBaseDamage, - Duration = _config.ProjectileRange / _config.ProjectileSpeed, - }); - } - - private void UpdateProjectiles(float dt) - { - for (var i = Projectiles.Count - 1; i >= 0; i--) - { - var proj = Projectiles[i]; - proj.Update(dt); - - // Check terrain collision - var gx = (int)(proj.Position.X * _config.WorldToGrid); - var gy = (int)(proj.Position.Y * _config.WorldToGrid); - if (!Terrain.IsWalkable(gx, gy)) - { - proj.IsExpired = true; - } - - if (!proj.IsExpired) - { - if (proj.IsEnemyProjectile) - { - // Enemy projectile hits player - if (Vector2.Distance(Player.Position, proj.Position) <= proj.HitRadius) - { - Player.TakeDamage(proj.Damage); - Log.Information("Damage: -{Dmg} projectile HP={HP}/{MaxHP} ES={ES}/{MaxES}", - proj.Damage, Player.Health, Player.MaxHealth, Player.Es, Player.MaxEs); - if (!Player.IsAlive) - Log.Warning("PLAYER DIED to projectile"); - proj.IsExpired = true; - } - } - else - { - // Player projectile hits enemies - foreach (var enemy in Enemies) - { - if (!enemy.IsAlive) continue; - if (Vector2.Distance(enemy.Position, proj.Position) <= proj.HitRadius) - { - enemy.TakeDamage(proj.Damage); - if (!enemy.IsAlive) - { - Log.Information("Kill: {Rarity} {Type} #{Id}", enemy.Rarity, enemy.Type, enemy.Id); - SpawnLoot(enemy); - } - proj.IsExpired = true; - break; - } - } - } - } - - if (proj.IsExpired) - Projectiles.RemoveAt(i); - } - } - - private void UpdateEffects(float dt) - { - for (var i = ActiveEffects.Count - 1; i >= 0; i--) - { - ActiveEffects[i].Elapsed += dt; - if (ActiveEffects[i].IsExpired) - ActiveEffects.RemoveAt(i); - } - } - - private void UpdateEnemies(float dt) - { - for (var i = Enemies.Count - 1; i >= 0; i--) - { - var enemy = Enemies[i]; - - if (enemy.AiState == EnemyAiState.Dead) - { - enemy.DespawnTimer -= dt; - if (enemy.DespawnTimer <= 0) - { - _respawnQueue.Add((_config.EnemyRespawnTime, enemy.Rarity)); - Enemies.RemoveAt(i); - } - continue; - } - - // Attack cooldown - if (enemy.AttackCooldownRemaining > 0) - enemy.AttackCooldownRemaining -= dt; - - var dist = Vector2.Distance(enemy.Position, Player.Position); - - if (!Player.IsAlive) - { - enemy.AiState = EnemyAiState.Idle; - UpdateWander(enemy, dt); - continue; - } - - if (enemy.IsRanged) - UpdateRangedEnemy(enemy, dist, dt); - else - UpdateMeleeEnemy(enemy, dist, dt); - } - } - - private void UpdateMeleeEnemy(SimEnemy enemy, float dist, float dt) - { - if (dist <= enemy.AttackRange) - { - // In melee range — attack - enemy.AiState = EnemyAiState.Attacking; - if (enemy.AttackCooldownRemaining <= 0) - { - Player.TakeDamage(enemy.AttackDamage); - Log.Information("Damage: -{Dmg} melee ({Rarity}) HP={HP}/{MaxHP} ES={ES}/{MaxES}", - enemy.AttackDamage, enemy.Rarity, Player.Health, Player.MaxHealth, Player.Es, Player.MaxEs); - if (!Player.IsAlive) - Log.Warning("PLAYER DIED to melee {Rarity} #{Id}", enemy.Rarity, enemy.Id); - enemy.AttackCooldownRemaining = _config.EnemyMeleeAttackCooldown; - } - } - else if (dist <= _config.EnemyAggroRange) - { - // Chase player - enemy.AiState = EnemyAiState.Chasing; - MoveToward(enemy, Player.Position, enemy.MoveSpeed, dt); - } - else - { - enemy.AiState = EnemyAiState.Idle; - UpdateWander(enemy, dt); - } - } - - private void UpdateRangedEnemy(SimEnemy enemy, float dist, float dt) - { - if (dist > _config.EnemyAggroRange) - { - // Out of aggro range — idle wander - enemy.AiState = EnemyAiState.Idle; - UpdateWander(enemy, dt); - return; - } - - if (dist <= enemy.AttackRange && enemy.AttackCooldownRemaining <= 0) - { - // In range and ready to fire — shoot projectile - enemy.AiState = EnemyAiState.Attacking; - FireEnemyProjectile(enemy); - enemy.AttackCooldownRemaining = _config.EnemyRangedAttackCooldown; - return; - } - - // Too close — retreat to preferred range - if (dist < enemy.PreferredRange * 0.7f) - { - enemy.AiState = EnemyAiState.Retreating; - MoveAwayFrom(enemy, Player.Position, enemy.MoveSpeed, dt); - return; - } - - // Too far — close in to attack range - if (dist > enemy.AttackRange) - { - enemy.AiState = EnemyAiState.Chasing; - MoveToward(enemy, Player.Position, enemy.MoveSpeed * 0.8f, dt); - return; - } - - // At good range, waiting for cooldown — strafe laterally - enemy.AiState = EnemyAiState.Chasing; - var toPlayer = Vector2.Normalize(Player.Position - enemy.Position); - var strafe = new Vector2(-toPlayer.Y, toPlayer.X); // perpendicular - // Alternate strafe direction based on enemy ID - if (enemy.Id % 2 == 0) strafe = -strafe; - MoveToward(enemy, enemy.Position + strafe * 100f, enemy.MoveSpeed * 0.5f, dt); - } - - private void FireEnemyProjectile(SimEnemy enemy) - { - var dir = Player.Position - enemy.Position; - if (dir.LengthSquared() < 1f) return; - - var proj = new SimProjectile( - enemy.Position, dir, - _config.EnemyProjectileSpeed, - _config.EnemyRangedAttackRange * 1.5f, - _config.EnemyProjectileHitRadius, - enemy.AttackDamage, - isEnemyProjectile: true); - - Projectiles.Add(proj); - } - - private void MoveToward(SimEnemy enemy, Vector2 target, float speed, float dt) - { - var dir = target - enemy.Position; - if (dir.LengthSquared() < 1f) return; - dir = Vector2.Normalize(dir); - - var newPos = enemy.Position + dir * speed * dt; - var gx = (int)(newPos.X * _config.WorldToGrid); - var gy = (int)(newPos.Y * _config.WorldToGrid); - if (Terrain.IsWalkable(gx, gy)) - enemy.Position = newPos; - } - - private void MoveAwayFrom(SimEnemy enemy, Vector2 target, float speed, float dt) - { - var dir = enemy.Position - target; - if (dir.LengthSquared() < 1f) return; - dir = Vector2.Normalize(dir); - - var newPos = enemy.Position + dir * speed * dt; - var gx = (int)(newPos.X * _config.WorldToGrid); - var gy = (int)(newPos.Y * _config.WorldToGrid); - if (Terrain.IsWalkable(gx, gy)) - enemy.Position = newPos; - } - - private void UpdateWander(SimEnemy enemy, float dt) - { - enemy.WanderTimer -= dt; - if (enemy.WanderTimer <= 0) - { - var angle = _rng.NextSingle() * MathF.Tau; - var dist2 = _rng.NextSingle() * _config.EnemyWanderRadius; - enemy.WanderTarget = enemy.SpawnPosition + new Vector2(MathF.Cos(angle), MathF.Sin(angle)) * dist2; - enemy.WanderTimer = 2f + _rng.NextSingle() * 3f; - } - - if (Vector2.Distance(enemy.Position, enemy.WanderTarget) > 10f) - MoveToward(enemy, enemy.WanderTarget, enemy.MoveSpeed * 0.3f, dt); - } - - private void UpdateRespawns(float dt) - { - for (var i = _respawnQueue.Count - 1; i >= 0; i--) - { - var (timer, rarity) = _respawnQueue[i]; - timer -= dt; - if (timer <= 0) - { - // Respawn at random walkable position in dungeon - var pos = TerrainGenerator.FindRandomWalkable(Terrain, _rng); - if (pos.HasValue) - { - var gridToWorld = 1f / _config.WorldToGrid; - SpawnEnemyAt(new Vector2(pos.Value.x * gridToWorld, pos.Value.y * gridToWorld), rarity); - } - _respawnQueue.RemoveAt(i); - } - else - { - _respawnQueue[i] = (timer, rarity); - } - } - } - - public void SpawnEnemyAt(Vector2 worldPos, MonsterRarity rarity) - { - var type = _rng.NextSingle() < _config.RangedEnemyChance ? EnemyType.Ranged : EnemyType.Melee; - var baseDmg = type == EnemyType.Ranged ? _config.EnemyRangedBaseDamage : _config.EnemyMeleeBaseDamage; - - var enemy = new SimEnemy(worldPos, rarity, type, - _config.EnemyBaseHealth, baseDmg, - Player.MoveSpeed * _config.EnemyMoveSpeedFactor) - { - WanderTarget = worldPos, - WanderTimer = _rng.NextSingle() * 3f, - AttackRange = type == EnemyType.Ranged ? _config.EnemyRangedAttackRange : _config.EnemyMeleeAttackRange, - PreferredRange = type == EnemyType.Ranged ? _config.EnemyRangedPreferredRange : 0f, - }; - Enemies.Add(enemy); - } - - private void SpawnEnemiesInRooms(List<(int x, int y, int w, int h)> rooms) - { - var gridToWorld = 1f / _config.WorldToGrid; - var totalSpawned = 0; - - foreach (var (rx, ry, rw, rh) in rooms) - { - var roomCenter = new Vector2((rx + rw / 2f) * gridToWorld, (ry + rh / 2f) * gridToWorld); - - // Skip the start room — don't spawn enemies right on the player - if (Vector2.Distance(roomCenter, StartWorldPos) < 50f) continue; - - var groupSize = _rng.Next(_config.EnemyGroupMin, _config.EnemyGroupMax + 1); - for (var i = 0; i < groupSize && totalSpawned < _config.TargetEnemyCount; i++) - { - var lx = rx + _rng.Next(rw); - var ly = ry + _rng.Next(rh); - if (!Terrain.IsWalkable(lx, ly)) continue; - var pos = new Vector2(lx * gridToWorld, ly * gridToWorld); - var rarity = i == 0 ? RollRarity() : MonsterRarity.White; - SpawnEnemyAt(pos, rarity); - totalSpawned++; - } - } - } - - private MonsterRarity RollRarity() - { - var roll = _rng.NextSingle(); - if (roll < _config.UniqueChance) return MonsterRarity.Unique; - if (roll < _config.UniqueChance + _config.RareChance) return MonsterRarity.Rare; - if (roll < _config.UniqueChance + _config.RareChance + _config.MagicChance) return MonsterRarity.Magic; - return MonsterRarity.White; - } - - // --- Loot --- - - private void SpawnLoot(SimEnemy enemy) - { - var (dropChance, minItems, maxItems) = enemy.Rarity switch - { - MonsterRarity.Unique => (1f, 3, 5), - MonsterRarity.Rare => (0.8f, 2, 3), - MonsterRarity.Magic => (0.5f, 1, 2), - _ => (0.3f, 1, 1), - }; - - if (_rng.NextSingle() > dropChance) return; - - var count = _rng.Next(minItems, maxItems + 1); - for (var i = 0; i < count; i++) - { - var category = RollLootCategory(); - var offset = new Vector2( - (_rng.NextSingle() - 0.5f) * 60f, - (_rng.NextSingle() - 0.5f) * 60f); - - Items.Add(new SimItem - { - Position = enemy.Position + offset, - Category = category, - Label = PickLootName(category), - }); - } - } - - private LootCategory RollLootCategory() - { - var roll = _rng.NextSingle(); - if (roll < 0.15f) return LootCategory.Currency; - if (roll < 0.50f) return LootCategory.Normal; - if (roll < 0.75f) return LootCategory.Magic; - if (roll < 0.90f) return LootCategory.Rare; - if (roll < 0.95f) return LootCategory.Unique; - return LootCategory.Quest; - } - - private static readonly string[] CurrencyNames = ["Gold", "Scroll of Wisdom", "Exalted Orb", "Chaos Orb", "Transmutation Orb"]; - private static readonly string[] NormalNames = ["Iron Sword", "Leather Cap", "Bone Shield", "Short Bow", "Cloth Robe"]; - private static readonly string[] MagicNames = ["Runic Hatchet", "Serpent Wand", "Chain Gloves", "Wolf Pelt", "Jade Amulet"]; - private static readonly string[] RareNames = ["Dread Edge", "Soul Render", "Viper Strike", "Storm Circlet", "Blood Greaves"]; - private static readonly string[] UniqueNames = ["Headhunter", "Mageblood", "Ashes of the Stars", "Aegis Aurora"]; - private static readonly string[] QuestNames = ["Ancient Tablet", "Relic Shard", "Quest Gem", "Sealed Letter"]; - - private string PickLootName(LootCategory category) - { - var pool = category switch - { - LootCategory.Currency => CurrencyNames, - LootCategory.Normal => NormalNames, - LootCategory.Magic => MagicNames, - LootCategory.Rare => RareNames, - LootCategory.Unique => UniqueNames, - LootCategory.Quest => QuestNames, - _ => NormalNames, - }; - return pool[_rng.Next(pool.Length)]; - } - - private void PickupNearbyItems() - { - for (var i = Items.Count - 1; i >= 0; i--) - { - if (Vector2.Distance(Items[i].Position, Player.Position) < 80f) - Items.RemoveAt(i); - } - } -} diff --git a/src/Nexus.Simulator/World/TerrainGenerator.cs b/src/Nexus.Simulator/World/TerrainGenerator.cs deleted file mode 100644 index 0c7dc1e..0000000 --- a/src/Nexus.Simulator/World/TerrainGenerator.cs +++ /dev/null @@ -1,496 +0,0 @@ -using System.Numerics; -using Nexus.Core; - -namespace Nexus.Simulator.World; - -public record DungeonResult( - WalkabilitySnapshot Terrain, - Vector2 StartWorldPos, - Vector2 EndWorldPos, - List<(int x, int y, int w, int h)> Rooms); - -public static class TerrainGenerator -{ - // Permutation table for Perlin noise (fixed seed for deterministic terrain) - private static readonly int[] Perm; - - static TerrainGenerator() - { - var p = new int[256]; - for (var i = 0; i < 256; i++) p[i] = i; - var rng = new Random(42); - for (var i = 255; i > 0; i--) - { - var j = rng.Next(i + 1); - (p[i], p[j]) = (p[j], p[i]); - } - Perm = new int[512]; - for (var i = 0; i < 512; i++) Perm[i] = p[i & 255]; - } - - public static DungeonResult GenerateDungeon(int width, int height, int seed, - int roomCountMin = 8, int roomCountMax = 15, - int roomSizeMin = 6, int roomSizeMax = 20, - int corridorWidth = 3, float worldToGrid = 23f / 250f) - { - var rng = new Random(seed); - var data = new byte[width * height]; // all walls by default - - // 1. Place rooms - var rooms = new List<(int x, int y, int w, int h)>(); - var targetCount = rng.Next(roomCountMin, roomCountMax + 1); - - for (var attempt = 0; attempt < targetCount * 50 && rooms.Count < targetCount; attempt++) - { - var rw = rng.Next(roomSizeMin, roomSizeMax + 1); - var rh = rng.Next(roomSizeMin, roomSizeMax + 1); - // Place rooms in the central third of the grid so they stay close together - var margin = width / 3; - var rx = rng.Next(margin, width - rw - margin); - var ry = rng.Next(margin, height - rh - margin); - - // Reject only if >50% overlap with an existing room (allow touching/partial overlap) - var overlaps = false; - foreach (var (ox, oy, ow, oh) in rooms) - { - var overlapX = Math.Max(0, Math.Min(rx + rw, ox + ow) - Math.Max(rx, ox)); - var overlapY = Math.Max(0, Math.Min(ry + rh, oy + oh) - Math.Max(ry, oy)); - var overlapArea = overlapX * overlapY; - var smallerArea = Math.Min(rw * rh, ow * oh); - if (overlapArea > smallerArea * 0.5f) - { - overlaps = true; - break; - } - } - if (overlaps) continue; - - rooms.Add((rx, ry, rw, rh)); - - // Carve room - for (var ly = ry; ly < ry + rh; ly++) - for (var lx = rx; lx < rx + rw; lx++) - data[ly * width + lx] = 1; - } - - if (rooms.Count < 2) - { - // Fallback: ensure at least 2 rooms - rooms.Clear(); - var r1 = (x: width / 4, y: height / 4, w: roomSizeMax, h: roomSizeMax); - var r2 = (x: width * 3 / 4 - roomSizeMax, y: height * 3 / 4 - roomSizeMax, w: roomSizeMax, h: roomSizeMax); - rooms.Add(r1); - rooms.Add(r2); - for (var ly = r1.y; ly < r1.y + r1.h; ly++) - for (var lx = r1.x; lx < r1.x + r1.w; lx++) - data[ly * width + lx] = 1; - for (var ly = r2.y; ly < r2.y + r2.h; ly++) - for (var lx = r2.x; lx < r2.x + r2.w; lx++) - data[ly * width + lx] = 1; - } - - // 2. Build MST using Prim's algorithm on room center distances - var roomCount = rooms.Count; - var inMst = new bool[roomCount]; - var mstEdges = new List<(int a, int b)>(); - inMst[0] = true; - - while (mstEdges.Count < roomCount - 1) - { - var bestDist = float.MaxValue; - var bestA = -1; - var bestB = -1; - - for (var a = 0; a < roomCount; a++) - { - if (!inMst[a]) continue; - for (var b = 0; b < roomCount; b++) - { - if (inMst[b]) continue; - var (ax, ay, aw, ah) = rooms[a]; - var (bx, by, bw, bh) = rooms[b]; - var dx = (ax + aw / 2f) - (bx + bw / 2f); - var dy = (ay + ah / 2f) - (by + bh / 2f); - var dist = dx * dx + dy * dy; - if (dist < bestDist) - { - bestDist = dist; - bestA = a; - bestB = b; - } - } - } - - if (bestB < 0) break; // disconnected (shouldn't happen) - inMst[bestB] = true; - mstEdges.Add((bestA, bestB)); - } - - // Add 1-2 extra edges for loops - var extraEdges = Math.Min(2, roomCount / 3); - for (var i = 0; i < extraEdges; i++) - { - var a = rng.Next(roomCount); - var b = rng.Next(roomCount); - if (a != b && !mstEdges.Contains((a, b)) && !mstEdges.Contains((b, a))) - mstEdges.Add((a, b)); - } - - // 3. Carve L-shaped corridors between connected rooms - foreach (var (a, b) in mstEdges) - { - var (ax, ay, aw, ah) = rooms[a]; - var (bx, by, bw, bh) = rooms[b]; - var cx1 = ax + aw / 2; - var cy1 = ay + ah / 2; - var cx2 = bx + bw / 2; - var cy2 = by + bh / 2; - - // Carve horizontal then vertical, with widened corner - CarveCorridor(data, width, height, cx1, cy1, cx2, cy1, corridorWidth); - CarveCorridor(data, width, height, cx2, cy1, cx2, cy2, corridorWidth); - - // Widen the corner with a square patch so the bot doesn't clip walls - var cornerR = corridorWidth; - for (var dy2 = -cornerR; dy2 <= cornerR; dy2++) - for (var dx2 = -cornerR; dx2 <= cornerR; dx2++) - { - var px = cx2 + dx2; - var py = cy1 + dy2; - if (px >= 0 && px < width && py >= 0 && py < height) - data[py * width + px] = 1; - } - } - - // 4. Build adjacency list from MST edges for BFS - var adj = new List[roomCount]; - for (var i = 0; i < roomCount; i++) adj[i] = []; - foreach (var (a, b) in mstEdges) - { - adj[a].Add(b); - adj[b].Add(a); - } - - // 5. Select start room: closest to grid center - var gridCx = width / 2f; - var gridCy = height / 2f; - var startRoom = 0; - var bestStartDist = float.MaxValue; - for (var i = 0; i < roomCount; i++) - { - var (rx, ry, rw, rh) = rooms[i]; - var dx = (rx + rw / 2f) - gridCx; - var dy = (ry + rh / 2f) - gridCy; - var d = dx * dx + dy * dy; - if (d < bestStartDist) { bestStartDist = d; startRoom = i; } - } - - // 6. Select end room: farthest from start via BFS graph distance - var bfsDist = new int[roomCount]; - Array.Fill(bfsDist, -1); - bfsDist[startRoom] = 0; - var bfsQueue = new Queue(); - bfsQueue.Enqueue(startRoom); - var endRoom = startRoom; - var maxDist = 0; - - while (bfsQueue.Count > 0) - { - var cur = bfsQueue.Dequeue(); - foreach (var nb in adj[cur]) - { - if (bfsDist[nb] >= 0) continue; - bfsDist[nb] = bfsDist[cur] + 1; - if (bfsDist[nb] > maxDist) { maxDist = bfsDist[nb]; endRoom = nb; } - bfsQueue.Enqueue(nb); - } - } - - // 7. Flood-fill connectivity check from start room — add corridors if any room unreachable - EnsureConnectivity(data, width, height, rooms, startRoom, corridorWidth); - - var terrain = new WalkabilitySnapshot { Width = width, Height = height, Data = data }; - - // Convert room centers to world positions - var g2w = 1f / worldToGrid; - var (sx, sy, sw, sh) = rooms[startRoom]; - var startWorld = new Vector2((sx + sw / 2f) * g2w, (sy + sh / 2f) * g2w); - var (ex, ey, ew, eh) = rooms[endRoom]; - var endWorld = new Vector2((ex + ew / 2f) * g2w, (ey + eh / 2f) * g2w); - - return new DungeonResult(terrain, startWorld, endWorld, rooms); - } - - private static void CarveCorridor(byte[] data, int width, int height, - int x1, int y1, int x2, int y2, int thickness) - { - var half = thickness / 2; - - if (y1 == y2) - { - // Horizontal - var minX = Math.Min(x1, x2); - var maxX = Math.Max(x1, x2); - for (var x = minX; x <= maxX; x++) - for (var dy = -half; dy <= half; dy++) - { - var y = y1 + dy; - if (x >= 0 && x < width && y >= 0 && y < height) - data[y * width + x] = 1; - } - } - else - { - // Vertical - var minY = Math.Min(y1, y2); - var maxY = Math.Max(y1, y2); - for (var y = minY; y <= maxY; y++) - for (var dx = -half; dx <= half; dx++) - { - var x = x1 + dx; - if (x >= 0 && x < width && y >= 0 && y < height) - data[y * width + x] = 1; - } - } - } - - private static void EnsureConnectivity(byte[] data, int width, int height, - List<(int x, int y, int w, int h)> rooms, int startRoom, int corridorWidth) - { - // Flood-fill from start room center - var (sx, sy, sw, sh) = rooms[startRoom]; - var seedX = sx + sw / 2; - var seedY = sy + sh / 2; - - var visited = new bool[width * height]; - var queue = new Queue<(int x, int y)>(); - queue.Enqueue((seedX, seedY)); - visited[seedY * width + seedX] = true; - - while (queue.Count > 0) - { - var (cx, cy) = queue.Dequeue(); - int[] dx = [-1, 1, 0, 0]; - int[] dy = [0, 0, -1, 1]; - for (var d = 0; d < 4; d++) - { - var nx = cx + dx[d]; - var ny = cy + dy[d]; - if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; - var idx = ny * width + nx; - if (visited[idx] || data[idx] == 0) continue; - visited[idx] = true; - queue.Enqueue((nx, ny)); - } - } - - // Check each room — if its center isn't reachable, carve a corridor to start - for (var i = 0; i < rooms.Count; i++) - { - if (i == startRoom) continue; - var (rx, ry, rw, rh) = rooms[i]; - var rcx = rx + rw / 2; - var rcy = ry + rh / 2; - if (visited[rcy * width + rcx]) continue; - - // Carve corridor from this room to start room - CarveCorridor(data, width, height, rcx, rcy, seedX, rcy, corridorWidth); - CarveCorridor(data, width, height, seedX, rcy, seedX, seedY, corridorWidth); - } - } - - public static WalkabilitySnapshot Generate(int width, int height, int? seed = null) - { - var data = new byte[width * height]; - FillNoiseRegion(data, width, height, 0, 0, 0, 0, width, height); - - // Clear spawn area at center - FillCircle(data, width, height, width / 2, height / 2, 20, 1); - - return new WalkabilitySnapshot { Width = width, Height = height, Data = data }; - } - - /// - /// Expands the terrain by adding cells on each side. Old data is preserved. - /// New regions use the same Perlin noise with absolute coordinates for seamless tiling. - /// - public static WalkabilitySnapshot Expand(WalkabilitySnapshot old, int left, int right, int top, int bottom, Random? rng = null) - { - var newW = old.Width + left + right; - var newH = old.Height + top + bottom; - var data = new byte[newW * newH]; - var newOffsetX = old.OffsetX - left; - var newOffsetY = old.OffsetY - top; - - // Fill entire new grid with noise (absolute coordinates ensure seamless expansion) - FillNoiseRegion(data, newW, newH, newOffsetX, newOffsetY, 0, 0, newW, newH); - - // Overwrite the old region with preserved data - for (var y = 0; y < old.Height; y++) - Array.Copy(old.Data, y * old.Width, data, (y + top) * newW + left, old.Width); - - return new WalkabilitySnapshot - { - Width = newW, - Height = newH, - Data = data, - OffsetX = newOffsetX, - OffsetY = newOffsetY, - }; - } - - /// - /// Fills a region of the data array using Perlin noise at absolute grid coordinates. - /// Combines corridor noise (abs of fractal noise — zero crossings form paths) - /// with room noise (low-frequency — creates larger open areas). - /// - private static void FillNoiseRegion(byte[] data, int dataWidth, int dataHeight, - int offsetX, int offsetY, int regionX, int regionY, int regionW, int regionH) - { - // Corridor noise: abs(fractal) creates paths along zero crossings - const float corridorScale = 0.04f; - const float corridorThreshold = 0.28f; // Width of corridors (wider = more open) - - // Room noise: low-frequency blobs create open areas - const float roomScale = 0.015f; - const float roomThreshold = 0.10f; // Lower = larger rooms - - // Detail noise: adds roughness to walls - const float detailScale = 0.12f; - const float detailWeight = 0.03f; - - for (var ly = regionY; ly < regionY + regionH; ly++) - for (var lx = regionX; lx < regionX + regionW; lx++) - { - if (lx < 0 || lx >= dataWidth || ly < 0 || ly >= dataHeight) continue; - - float ax = lx + offsetX; - float ay = ly + offsetY; - - // Corridor network: abs creates linear paths along noise zero-crossings - var corridor = MathF.Abs(FractalNoise(ax * corridorScale, ay * corridorScale, 3)); - - // Rooms: large smooth blobs (offset coordinates to decorrelate from corridors) - var room = FractalNoise(ax * roomScale + 500f, ay * roomScale + 500f, 2); - - // Detail: roughen edges - var detail = Perlin(ax * detailScale + 200f, ay * detailScale + 200f) * detailWeight; - - // Walkable if on a corridor path OR inside a room - var walkable = (corridor + detail) < corridorThreshold || room > roomThreshold; - - data[ly * dataWidth + lx] = walkable ? (byte)1 : (byte)0; - } - } - - /// - /// Multi-octave fractal Brownian motion noise. - /// - private static float FractalNoise(float x, float y, int octaves) - { - var value = 0f; - var amplitude = 1f; - var freq = 1f; - var maxAmp = 0f; - - for (var i = 0; i < octaves; i++) - { - value += Perlin(x * freq, y * freq) * amplitude; - maxAmp += amplitude; - amplitude *= 0.5f; - freq *= 2f; - } - - return value / maxAmp; - } - - /// - /// Classic 2D Perlin noise. Returns values roughly in [-1, 1]. - /// - private static float Perlin(float x, float y) - { - var xi = (int)MathF.Floor(x) & 255; - var yi = (int)MathF.Floor(y) & 255; - var xf = x - MathF.Floor(x); - var yf = y - MathF.Floor(y); - - var u = Fade(xf); - var v = Fade(yf); - - var aa = Perm[Perm[xi] + yi]; - var ab = Perm[Perm[xi] + yi + 1]; - var ba = Perm[Perm[xi + 1] + yi]; - var bb = Perm[Perm[xi + 1] + yi + 1]; - - var x1 = Lerp(Grad(aa, xf, yf), Grad(ba, xf - 1, yf), u); - var x2 = Lerp(Grad(ab, xf, yf - 1), Grad(bb, xf - 1, yf - 1), u); - return Lerp(x1, x2, v); - } - - private static float Fade(float t) => t * t * t * (t * (t * 6 - 15) + 10); - - private static float Lerp(float a, float b, float t) => a + t * (b - a); - - private static float Grad(int hash, float x, float y) - { - var h = hash & 3; - var u = h < 2 ? x : y; - var v = h < 2 ? y : x; - return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); - } - - private static void FillCircle(byte[] data, int width, int height, int cx, int cy, int radius, byte value) - { - var r2 = radius * radius; - for (var dy = -radius; dy <= radius; dy++) - for (var dx = -radius; dx <= radius; dx++) - { - if (dx * dx + dy * dy > r2) continue; - var x = cx + dx; - var y = cy + dy; - if (x >= 0 && x < width && y >= 0 && y < height) - data[y * width + x] = value; - } - } - - /// - /// Finds a walkable position near the center. Returns absolute grid coords. - /// - public static (int x, int y) FindSpawnPosition(WalkabilitySnapshot terrain) - { - var cx = terrain.OffsetX + terrain.Width / 2; - var cy = terrain.OffsetY + terrain.Height / 2; - - for (var r = 0; r < Math.Max(terrain.Width, terrain.Height); r++) - { - for (var dx = -r; dx <= r; dx++) - for (var dy = -r; dy <= r; dy++) - { - if (Math.Abs(dx) != r && Math.Abs(dy) != r) continue; - var x = cx + dx; - var y = cy + dy; - if (terrain.IsWalkable(x, y)) - return (x, y); - } - } - - return (cx, cy); - } - - /// - /// Finds a random walkable position. Returns absolute grid coords. - /// - public static (int x, int y)? FindRandomWalkable(WalkabilitySnapshot terrain, Random rng, int maxAttempts = 200) - { - for (var i = 0; i < maxAttempts; i++) - { - var lx = rng.Next(terrain.Width); - var ly = rng.Next(terrain.Height); - var ax = lx + terrain.OffsetX; - var ay = ly + terrain.OffsetY; - if (terrain.IsWalkable(ax, ay)) - return (ax, ay); - } - return null; - } -} diff --git a/src/Nexus.Systems/AreaProgressionSystem.cs b/src/Nexus.Systems/AreaProgressionSystem.cs deleted file mode 100644 index 933c365..0000000 --- a/src/Nexus.Systems/AreaProgressionSystem.cs +++ /dev/null @@ -1,611 +0,0 @@ -using System.Numerics; -using Nexus.Core; -using Nexus.Data; -using Nexus.Pathfinding; -using Serilog; - -namespace Nexus.Systems; - -public sealed class AreaProgressionSystem : ISystem -{ - private enum Phase { Exploring, Looting, NavigatingToChest, InteractingChest, NavigatingToTransition, Interacting, TalkingToNpc } - - private readonly AreaGraph _graph; - private readonly NavigationController _nav; - private readonly BotConfig _config; - - private readonly HashSet _visitedAreas = new(StringComparer.OrdinalIgnoreCase); - private readonly HashSet _blacklistedTransitions = new(); - private string? _currentAreaId; - private string? _targetAreaId; - private string? _targetTransitionName; - private uint _targetTransitionEntityId; - private Phase _phase = Phase.Exploring; - private uint _lastAreaHash; - private long _lastTransitionCheckMs; - private long _interactStartMs; - private uint _lootTargetEntityId; - private long _lootStartMs; - private uint _chestTargetEntityId; - private long _chestInteractStartMs; - private readonly HashSet _interactedChests = new(); - private bool _currentAreaExplored; - private bool _questDriven; - - public int Priority => SystemPriority.Navigation - 1; - public string Name => "Progression"; - public bool IsEnabled { get; set; } = true; - - public string? CurrentAreaId => _currentAreaId; - public string? TargetAreaId => _targetAreaId; - public IReadOnlySet VisitedAreas => _visitedAreas; - public string PhaseName => _phase.ToString(); - public string? TargetTransitionName => _targetTransitionName; - public bool IsLootingActive => _phase == Phase.Looting; - public bool IsQuestDriven => _questDriven; - public string? QuestTargetName => _targetAreaId is not null ? _graph.GetById(_targetAreaId)?.Name : null; - - public AreaProgressionSystem(BotConfig config, NavigationController nav, AreaGraph graph) - { - _config = config; - _nav = nav; - _graph = graph; - } - - public void Update(GameState state, ActionQueue actions, MovementBlender movement) - { - // Need area name from log watcher - if (state.CurrentAreaName is null) return; - - // Resolve current area - var node = _graph.GetByName(state.CurrentAreaName); - if (node is null) return; - - var prevAreaId = _currentAreaId; - _currentAreaId = node.Id; - _visitedAreas.Add(node.Id); - - var areaChanged = prevAreaId != _currentAreaId; - - if (areaChanged) - { - Log.Information("Progression: entered {Area} ({Id})", node.Name, node.Id); - - // Auto-mark all earlier areas (same act, lower order) as visited - foreach (var earlierId in _graph.GetEarlierAreas(_currentAreaId)) - _visitedAreas.Add(earlierId); - - // Force target recalculation - _targetAreaId = null; - } - - // Detect area hash change → reset navigation/phase state - if (state.AreaHash != _lastAreaHash) - { - _lastAreaHash = state.AreaHash; - _targetTransitionEntityId = 0; - _lootTargetEntityId = 0; - _chestTargetEntityId = 0; - _phase = Phase.Exploring; - _blacklistedTransitions.Clear(); - _interactedChests.Clear(); - } - - // Resolve target BEFORE deciding whether to explore — we need to know - // if the current area IS the target (affects exploration decision). - if (_targetAreaId is null) - { - _targetAreaId = FindQuestTarget(state); - _questDriven = _targetAreaId is not null; - _targetAreaId ??= _graph.FindNextTarget(_currentAreaId, _visitedAreas); - - if (_targetAreaId is null) - { - Log.Information("Progression: all reachable areas visited, no quest targets"); - IsEnabled = false; - return; - } - - var path = _graph.FindAreaPath(_currentAreaId, _targetAreaId); - if (path is null || path.Count < 2) - { - // Current area IS the target — no hop needed, just explore it - if (string.Equals(_currentAreaId, _targetAreaId, StringComparison.OrdinalIgnoreCase)) - { - Log.Information("Progression: arrived at target {Target}, exploring", - _graph.GetById(_targetAreaId)?.Name); - _targetTransitionName = null; - } - else - { - Log.Warning("Progression: no path from {From} to {To}", _currentAreaId, _targetAreaId); - _targetAreaId = null; - return; - } - } - else - { - var firstHop = path[1]; - var hopNode = _graph.GetById(firstHop); - _targetTransitionName = hopNode?.Name; - Log.Information("Progression: targeting {Target} via {Hop} ({Source})", - _graph.GetById(_targetAreaId)?.Name, _targetTransitionName, - _questDriven ? "quest" : "order"); - } - } - - // Now decide exploration — target is resolved so we know if we're at the destination - if (areaChanged) - { - var isAtTarget = string.Equals(_currentAreaId, _targetAreaId, StringComparison.OrdinalIgnoreCase); - // Towns: skip exploration. Pass-through areas (quest-driven, not final target): skip. - // Final target or non-quest areas: explore. - _currentAreaExplored = node.IsTown || (_questDriven && !isAtTarget); - - if (_currentAreaExplored) - Log.Debug("Progression: skipping exploration (town={Town}, questPassThrough={Pass})", - node.IsTown, _questDriven && !isAtTarget); - } - - // If we're in town and a quest targets this town, talk to NPC - if (node.IsTown && HasQuestInThisArea(state)) - { - UpdateTalkingToNpc(state, actions); - return; - } - - switch (_phase) - { - case Phase.Exploring: - UpdateExploring(state, actions); - break; - case Phase.Looting: - UpdateLooting(state, actions); - break; - case Phase.NavigatingToChest: - UpdateNavigatingToChest(state, actions); - break; - case Phase.InteractingChest: - UpdateInteractingChest(state, actions); - break; - case Phase.NavigatingToTransition: - UpdateNavigatingToTransition(state, actions); - break; - case Phase.Interacting: - UpdateInteracting(state, actions); - break; - case Phase.TalkingToNpc: - UpdateTalkingToNpc(state, actions); - break; - } - } - - private void UpdateExploring(GameState state, ActionQueue actions) - { - // ── Check 1: Yield for combat — stop exploring to clear nearby monsters ── - if (HasNearbyHostiles(state)) - { - if (_nav.Mode != NavMode.Idle) - _nav.Stop(); - return; - } - - // ── Check 2: Quest chest interaction ── - foreach (var e in state.Entities) - { - if (e.Path is null || !e.IsTargetable) continue; - if (!e.Path.Contains("QuestChestBase", StringComparison.OrdinalIgnoreCase)) continue; - if (_interactedChests.Contains(e.Id)) continue; - - _chestTargetEntityId = e.Id; - _chestInteractStartMs = state.TimestampMs; - Log.Information("Progression: found quest chest {Id} (dist={Dist:F0})", e.Id, e.DistanceToPlayer); - - if (e.DistanceToPlayer < 150f) - { - _phase = Phase.InteractingChest; - _nav.Stop(); - } - else - { - _phase = Phase.NavigatingToChest; - _nav.NavigateToEntity(e.Id); - } - return; - } - - // ── Check 3: Loot pickup when safe ── - const float LootPickupRange = 600f; - if (state.Danger <= DangerLevel.Low && state.NearbyLoot.Count > 0) - { - EntitySnapshot? nearestLoot = null; - foreach (var item in state.NearbyLoot) - { - if (item.DistanceToPlayer > LootPickupRange) continue; - if (nearestLoot is null || item.DistanceToPlayer < nearestLoot.DistanceToPlayer) - nearestLoot = item; - } - if (nearestLoot is not null) - { - _lootTargetEntityId = nearestLoot.Id; - _lootStartMs = state.TimestampMs; - _phase = Phase.Looting; - _nav.NavigateToEntity(nearestLoot.Id); - Log.Debug("Progression: looting item {Id} (dist={Dist:F0})", nearestLoot.Id, nearestLoot.DistanceToPlayer); - return; - } - } - - // If the current area hasn't been fully explored yet, keep exploring before - // looking for the exit transition. This prevents the bot from entering a - // dead-end area (like Mud Burrow) and immediately leaving without clearing it. - if (!_currentAreaExplored) - { - if (_nav.IsExplorationComplete) - { - _currentAreaExplored = true; - Log.Information("Progression: area exploration complete, now looking for transition to {Target}", - _targetTransitionName); - } - else - { - if (_nav.Mode != NavMode.Exploring) - _nav.Explore(); - return; // Don't scan for transitions yet - } - } - - // Throttle entity scanning to once per second - var now = state.TimestampMs; - if (now - _lastTransitionCheckMs < 1000) return; - _lastTransitionCheckMs = now; - - // Find the nearest targetable matching transition that isn't blacklisted - EntitySnapshot? best = null; - foreach (var e in state.Entities) - { - if (e.Category != EntityCategory.AreaTransition) continue; - if (!e.IsTargetable) continue; - if (!string.Equals(e.TransitionName, _targetTransitionName, StringComparison.OrdinalIgnoreCase)) continue; - if (_blacklistedTransitions.Contains(e.Id)) continue; - - if (best is null || e.DistanceToPlayer < best.DistanceToPlayer) - best = e; - } - - if (best is not null) - { - _targetTransitionEntityId = best.Id; - Log.Information("Progression: found transition to {Target} (entity {Id}, dist={Dist:F0})", - _targetTransitionName, best.Id, best.DistanceToPlayer); - - if (best.DistanceToPlayer < 150f) - { - _phase = Phase.Interacting; - _interactStartMs = state.TimestampMs; - _nav.Stop(); - } - else - { - _phase = Phase.NavigatingToTransition; - _nav.NavigateToEntity(best.Id); - } - return; - } - - // No transition found yet — explore - if (_nav.Mode != NavMode.Exploring) - _nav.Explore(); - } - - private void UpdateLooting(GameState state, ActionQueue actions) - { - // Danger spike — abandon loot, resume exploring - if (state.Danger >= DangerLevel.Medium) - { - Log.Debug("Progression: danger spike, abandoning loot"); - _lootTargetEntityId = 0; - _phase = Phase.Exploring; - return; - } - - // Elite appeared — yield to combat (will hit elite check in UpdateExploring next tick) - const float EliteEngagementRange = 800f; - foreach (var m in state.HostileMonsters) - { - if (m.Rarity >= MonsterRarity.Rare && m.DistanceToPlayer < EliteEngagementRange) - { - Log.Debug("Progression: elite appeared while looting, yielding"); - _lootTargetEntityId = 0; - _phase = Phase.Exploring; - _nav.Stop(); - return; - } - } - - // Timeout — entity unreachable - if (state.TimestampMs - _lootStartMs > 5000) - { - Log.Debug("Progression: loot pickup timeout on {Id}", _lootTargetEntityId); - _lootTargetEntityId = 0; - _phase = Phase.Exploring; - return; - } - - // Find the target loot entity - EntitySnapshot? target = null; - foreach (var item in state.NearbyLoot) - { - if (item.Id == _lootTargetEntityId) { target = item; break; } - } - - if (target is null) - { - // Target gone (picked up or despawned) — chain to next nearby loot - const float LootPickupRange = 600f; - EntitySnapshot? next = null; - foreach (var item in state.NearbyLoot) - { - if (item.DistanceToPlayer > LootPickupRange) continue; - if (next is null || item.DistanceToPlayer < next.DistanceToPlayer) - next = item; - } - - if (next is not null) - { - _lootTargetEntityId = next.Id; - _lootStartMs = state.TimestampMs; - _nav.NavigateToEntity(next.Id); - Log.Debug("Progression: chaining to next loot {Id} (dist={Dist:F0})", next.Id, next.DistanceToPlayer); - } - else - { - _lootTargetEntityId = 0; - _phase = Phase.Exploring; - Log.Debug("Progression: no more loot, resuming exploration"); - } - return; - } - - // Close enough — click to pick up - if (target.DistanceToPlayer < 100f) - { - if (state.CameraMatrix.HasValue) - { - var screenPos = WorldToScreen.Project(target.Position, state.Player.Z, state.CameraMatrix.Value); - if (screenPos.HasValue) - actions.Submit(new ClickAction(SystemPriority.Navigation - 1, screenPos.Value, ClickType.Left)); - } - return; - } - - // Otherwise NavigateToEntity handles approach (already set when entering phase) - } - - private void UpdateNavigatingToChest(GameState state, ActionQueue actions) - { - foreach (var e in state.Entities) - { - if (e.Id != _chestTargetEntityId) continue; - - if (e.DistanceToPlayer < 150f) - { - _phase = Phase.InteractingChest; - _chestInteractStartMs = state.TimestampMs; - _nav.Stop(); - return; - } - - // Entity still exists, keep navigating - return; - } - - // Entity disappeared — give up on this chest - Log.Debug("Progression: quest chest entity lost, resuming exploration"); - _interactedChests.Add(_chestTargetEntityId); - _chestTargetEntityId = 0; - _phase = Phase.Exploring; - } - - private void UpdateInteractingChest(GameState state, ActionQueue actions) - { - foreach (var e in state.Entities) - { - if (e.Id != _chestTargetEntityId) continue; - - if (!e.IsTargetable) - { - // Chest opened — done - Log.Information("Progression: quest chest {Id} opened", e.Id); - _interactedChests.Add(_chestTargetEntityId); - _chestTargetEntityId = 0; - _phase = Phase.Exploring; - return; - } - - if (state.CameraMatrix.HasValue) - { - var screenPos = WorldToScreen.Project(e.Position, state.Player.Z, state.CameraMatrix.Value); - if (screenPos.HasValue) - { - actions.Submit(new ClickAction(SystemPriority.Navigation - 1, screenPos.Value, ClickType.Left)); - return; - } - } - - // Can't project — walk closer - if (e.DistanceToPlayer > 100f) - { - _phase = Phase.NavigatingToChest; - _nav.NavigateToEntity(e.Id); - return; - } - return; - } - - // Entity gone or timeout - if (state.TimestampMs - _chestInteractStartMs > 5000) - { - Log.Warning("Progression: quest chest interaction timeout on {Id}", _chestTargetEntityId); - _interactedChests.Add(_chestTargetEntityId); - _chestTargetEntityId = 0; - _phase = Phase.Exploring; - } - } - - private void UpdateNavigatingToTransition(GameState state, ActionQueue actions) - { - // Hostiles nearby — abort transition, go fight - if (HasNearbyHostiles(state)) - { - Log.Debug("Progression: hostiles near transition, aborting to fight"); - _targetTransitionEntityId = 0; - _phase = Phase.Exploring; - _nav.Stop(); - return; - } - - // Check if the entity is still visible and close enough - foreach (var e in state.Entities) - { - if (e.Id != _targetTransitionEntityId) continue; - - if (e.DistanceToPlayer < 150f) - { - _phase = Phase.Interacting; - _interactStartMs = state.TimestampMs; - _nav.Stop(); - return; - } - - // Entity still exists, keep navigating - return; - } - - // Entity disappeared from view — go back to exploring - Log.Debug("Progression: transition entity lost, returning to explore"); - _targetTransitionEntityId = 0; - _phase = Phase.Exploring; - _nav.Explore(); - } - - private void UpdateInteracting(GameState state, ActionQueue actions) - { - // Hostiles nearby — abort transition, go fight - if (HasNearbyHostiles(state)) - { - Log.Debug("Progression: hostiles near exit, aborting click to fight"); - _targetTransitionEntityId = 0; - _phase = Phase.Exploring; - return; - } - - // Project entity to screen and click - foreach (var e in state.Entities) - { - if (e.Id != _targetTransitionEntityId) continue; - - if (state.CameraMatrix.HasValue) - { - var screenPos = WorldToScreen.Project(e.Position, state.Player.Z, state.CameraMatrix.Value); - if (screenPos.HasValue) - { - actions.Submit(new ClickAction(SystemPriority.Navigation - 1, screenPos.Value, ClickType.Left)); - return; - } - } - - // Can't project — walk closer - if (e.DistanceToPlayer > 100f) - { - _phase = Phase.NavigatingToTransition; - _nav.NavigateToEntity(e.Id); - return; - } - return; - } - - // Entity gone or interaction failed — blacklist this transition and try another - if (state.TimestampMs - _interactStartMs > 5000) - { - Log.Warning("Progression: interaction timeout on entity {Id}, blacklisting and trying next", - _targetTransitionEntityId); - _blacklistedTransitions.Add(_targetTransitionEntityId); - _targetTransitionEntityId = 0; - _phase = Phase.Exploring; - _nav.Explore(); - } - } - - private string? FindQuestTarget(GameState state) - { - var candidates = state.Quests - .Where(q => q.TargetAreas is { Count: > 0 }) - .SelectMany(q => q.TargetAreas!.Select(a => new { Quest = q, Area = a })) - .Where(x => x.Area.Id is not null - && !string.Equals(x.Area.Id, _currentAreaId, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (candidates.Count == 0) return null; - - // Prefer tracked quests, then lowest act, then quests with shortest path - var best = candidates - .OrderByDescending(x => x.Quest.IsTracked) - .ThenBy(x => x.Area.Act) - .ThenBy(x => x.Quest.PathToTarget?.Count ?? 999) - .First(); - - return best.Area.Id; - } - - private bool HasNearbyHostiles(GameState state) - { - const float EliteEngagementRange = 800f; - var combatRange = _config.CombatEngagementRange; - foreach (var m in state.HostileMonsters) - { - if (!m.IsAlive) continue; - var range = m.Rarity >= MonsterRarity.Rare ? EliteEngagementRange : combatRange; - if (m.DistanceToPlayer >= range) continue; - if (state.Terrain is { } t && - !TerrainQuery.HasLineOfSight(t, state.Player.Position, m.Position, _config.WorldToGrid)) - continue; - return true; - } - return false; - } - - private bool HasQuestInThisArea(GameState state) - { - return state.Quests.Any(q => q.TargetAreas?.Any(a => - string.Equals(a.Id, _currentAreaId, StringComparison.OrdinalIgnoreCase)) == true); - } - - private void UpdateTalkingToNpc(GameState state, ActionQueue actions) - { - // Find nearest targetable NPC - EntitySnapshot? npc = null; - foreach (var e in state.Entities) - { - if (e.Category != EntityCategory.Npc || !e.IsTargetable) continue; - if (npc is null || e.DistanceToPlayer < npc.DistanceToPlayer) - npc = e; - } - - if (npc is null) return; - - if (npc.DistanceToPlayer < 150f) - { - if (state.CameraMatrix.HasValue) - { - var screenPos = WorldToScreen.Project(npc.Position, state.Player.Z, state.CameraMatrix.Value); - if (screenPos.HasValue) - actions.Submit(new ClickAction(Priority, screenPos.Value, ClickType.Left)); - } - } - else - { - _nav.NavigateToEntity(npc.Id); - } - } -} diff --git a/src/Nexus.Systems/BotTick.cs b/src/Nexus.Systems/BotTick.cs deleted file mode 100644 index 78f8de2..0000000 --- a/src/Nexus.Systems/BotTick.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Numerics; -using Nexus.Core; -using Nexus.Data; -using Nexus.Pathfinding; - -namespace Nexus.Systems; - -public static class BotTick -{ - public static List Run( - GameState state, - List systems, - ActionQueue actionQueue, - MovementBlender movementBlender, - NavigationController nav, - BotConfig config) - { - GameStateEnricher.Enrich(state); - - actionQueue.Clear(); - movementBlender.Clear(); - - // Update stuck detection BEFORE systems run so IsStuck is current - if (state.Player.HasPosition) - movementBlender.UpdateStuckState(state.Player.Position); - - nav.Update(state); - - foreach (var sys in systems) - if (sys.IsEnabled) - sys.Update(state, actionQueue, movementBlender); - - // Wall repulsion — push away from nearby walls to prevent getting stuck - // Layer 5: never dropped by stuck detection (unlike L2 orbit) - if (state.Terrain is { } terrain && state.Player.HasPosition) - { - var wallPush = TerrainQuery.ComputeWallRepulsion(terrain, state.Player.Position, config.WorldToGrid); - if (wallPush.LengthSquared() > 0.0001f) - { - // Boost wall push when stuck - var mag = movementBlender.IsStuck ? 1.0f : 0.6f; - var ovr = movementBlender.IsStuck ? 0.8f : 0.5f; - movementBlender.Submit(new MovementIntent(5, wallPush * mag, ovr, "WallPush")); - } - } - - // Combat engagement: when visible enemies are nearby and we're not fleeing, - // navigate toward them instead of the exit. If fleeing, use normal nav to support escape. - var shouldEngage = false; - if (state.ThreatAssessment is not { ShouldFlee: true }) - { - var enemyCentroid = Vector2.Zero; - var enemyCount = 0; - foreach (var m in state.HostileMonsters) - { - if (!m.IsAlive || m.DistanceToPlayer >= config.CombatEngagementRange) continue; - if (state.Terrain is { } los && - !TerrainQuery.HasLineOfSight(los, state.Player.Position, m.Position, config.WorldToGrid)) - continue; - enemyCentroid += m.Position; - enemyCount++; - } - - if (enemyCount > 0 && state.Player.HasPosition) - { - shouldEngage = true; - enemyCentroid /= enemyCount; - var toEnemies = enemyCentroid - state.Player.Position; - if (toEnemies.LengthSquared() > 1f) - movementBlender.Submit(new MovementIntent(3, Vector2.Normalize(toEnemies), 0f, "Navigation")); - } - } - - if (!shouldEngage && nav.DesiredDirection.HasValue) - movementBlender.Submit(new MovementIntent(3, nav.DesiredDirection.Value, 0f, "Navigation")); - - // Predictive wall steering — look ahead along movement direction and steer around walls - if (!shouldEngage && state.Terrain is { } t2 && state.Player.HasPosition && nav.DesiredDirection.HasValue) - { - var steer = TerrainQuery.ComputeWallSteering(t2, state.Player.Position, - nav.DesiredDirection.Value, config.WorldToGrid); - if (steer.LengthSquared() > 0.0001f) - movementBlender.Submit(new MovementIntent(4, steer, 0.4f, "WallSteer")); - } - - // Exit avoidance: when fighting near area transitions, push away to prevent - // accidentally walking into them (they auto-trigger on proximity) - const float ExitAvoidRange = 300f; - if (shouldEngage && state.Player.HasPosition) - { - foreach (var e in state.Entities) - { - if (e.Category != EntityCategory.AreaTransition) continue; - var away = state.Player.Position - e.Position; - var dist = away.Length(); - if (dist >= ExitAvoidRange || dist < 0.1f) continue; - var strength = 1.0f - (dist / ExitAvoidRange); - movementBlender.Submit(new MovementIntent(6, Vector2.Normalize(away) * strength, 0.9f, "ExitAvoid")); - break; - } - } - - movementBlender.Resolve(state.Terrain, state.Player.Position, config.WorldToGrid); - var resolved = actionQueue.Resolve(); - - // Block casting during emergency flee or dodge roll — focus on escaping - if (state.ThreatAssessment is { AnyEmergency: true } || state.Player.IsRolling) - resolved.RemoveAll(a => a is CastAction); - - return resolved; - } -} diff --git a/src/Nexus.Systems/DodgeSystem.cs b/src/Nexus.Systems/DodgeSystem.cs deleted file mode 100644 index 6bc781c..0000000 --- a/src/Nexus.Systems/DodgeSystem.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Numerics; -using Nexus.Core; - -namespace Nexus.Systems; - -/// -/// Detects threatening enemy projectiles and triggers dodge rolls perpendicular to their trajectory. -/// Priority 75 — between Threat (50) and Movement (100). -/// -public class DodgeSystem : ISystem -{ - public int Priority => SystemPriority.Dodge; - public string Name => "Dodge"; - public bool IsEnabled { get; set; } = true; - - public float WorldToGrid { get; set; } = 23f / 250f; - - // Reaction window — only dodge projectiles arriving within this time - private const float ReactionWindow = 0.4f; - - // Only dodge projectiles whose closest approach is within this distance - private const float DodgeThreshold = 80f; - - // Minimum time between dodge decisions (prevents flip-flopping when multiple projectiles arrive) - private float _decisionCooldown; - private const float DecisionCooldownDuration = 0.1f; - - public void Update(GameState state, ActionQueue actions, MovementBlender movement) - { - var dt = state.DeltaTime; - if (_decisionCooldown > 0) - { - _decisionCooldown -= dt; - return; - } - - // Skip if already rolling, on cooldown, or no projectiles - if (state.Player.IsRolling) return; - if (state.Player.RollCooldownRemaining > 0) return; - if (state.EnemyProjectiles.Count == 0) return; - if (!state.Player.HasPosition) return; - - // Find most urgent threatening projectile - ProjectileSnapshot? urgent = null; - foreach (var proj in state.EnemyProjectiles) - { - if (proj.TimeToImpact is not { } tti) continue; // Will miss - if (tti > ReactionWindow) continue; // Too far away to react - if (proj.ClosestApproachDistance > DodgeThreshold) continue; - - if (urgent is null || tti < urgent.TimeToImpact) - urgent = proj; - } - - if (urgent is null) return; - - // Compute dodge direction: perpendicular to projectile trajectory - var projDir = urgent.Direction; - var perp1 = new Vector2(-projDir.Y, projDir.X); // Left perpendicular - var perp2 = new Vector2(projDir.Y, -projDir.X); // Right perpendicular - - // Choose the side that moves us AWAY from the projectile trajectory - var toPlayer = state.Player.Position - urgent.Position; - var dodgeDir = Vector2.Dot(toPlayer, perp1) > 0 ? perp1 : perp2; - - // Terrain validation: check if dodge destination is walkable - var dodgeDist = 100f; // approximate roll distance - var playerPos = state.Player.Position; - var dest1 = playerPos + dodgeDir * dodgeDist; - var gx1 = (int)(dest1.X * WorldToGrid); - var gy1 = (int)(dest1.Y * WorldToGrid); - - if (state.Terrain is { } terrain && !terrain.IsWalkable(gx1, gy1)) - { - // Try the other perpendicular - dodgeDir = -dodgeDir; - var dest2 = playerPos + dodgeDir * dodgeDist; - var gx2 = (int)(dest2.X * WorldToGrid); - var gy2 = (int)(dest2.Y * WorldToGrid); - - if (!terrain.IsWalkable(gx2, gy2)) - return; // Both sides blocked — can't dodge - } - - // Submit dodge action - actions.Submit(new DodgeRollAction(SystemPriority.Dodge, dodgeDir)); - - // Also submit movement bias so other systems know we want to go this way - movement.Submit(new MovementIntent(1, dodgeDir, 0.8f, "Dodge")); - - _decisionCooldown = DecisionCooldownDuration; - } -} diff --git a/src/Nexus.Systems/LootSystem.cs b/src/Nexus.Systems/LootSystem.cs deleted file mode 100644 index 0e4ec9f..0000000 --- a/src/Nexus.Systems/LootSystem.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Numerics; -using Nexus.Core; - -namespace Nexus.Systems; - -public class LootSystem : ISystem -{ - public int Priority => SystemPriority.Loot; - public string Name => "Loot"; - public bool IsEnabled { get; set; } = true; - - private const float LootRange = 400f; - private const float SafeRange = 600f; - - public void Update(GameState state, ActionQueue actions, MovementBlender movement) - { - if (state.NearbyLoot.Count == 0) return; - - // Don't loot if hostiles are nearby - foreach (var hostile in state.HostileMonsters) - { - if (hostile.IsAlive && hostile.DistanceToPlayer < SafeRange) - return; - } - - // Find nearest loot within range - EntitySnapshot? nearest = null; - var nearestDist = float.MaxValue; - - foreach (var loot in state.NearbyLoot) - { - if (loot.DistanceToPlayer < nearestDist && loot.DistanceToPlayer < LootRange) - { - nearest = loot; - nearestDist = loot.DistanceToPlayer; - } - } - - if (nearest is null) return; - - // Steer toward the item — L3 same as navigation so it replaces explore direction - var dir = nearest.Position - state.Player.Position; - if (dir.LengthSquared() > 1f) - movement.Submit(new MovementIntent(3, Vector2.Normalize(dir), 0f, "Loot")); - } -} diff --git a/src/Nexus.Systems/MovementSystem.cs b/src/Nexus.Systems/MovementSystem.cs deleted file mode 100644 index f0048a4..0000000 --- a/src/Nexus.Systems/MovementSystem.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Numerics; -using Nexus.Core; - -namespace Nexus.Systems; - -/// -/// Proximity-aware positioning: when enemies are within safe distance, applies a blend of -/// radial push (away from centroid) and tangential orbit (perpendicular to centroid). -/// The tangential component makes the bot circle around enemies instead of running backward. -/// Closer enemies produce stronger push-out, but always with orbit mixed in. -/// -public class MovementSystem : ISystem -{ - public int Priority => SystemPriority.Movement; - public string Name => "Movement"; - public bool IsEnabled { get; set; } = true; - - public float SafeDistance { get; set; } = 400f; - public float RepulsionWeight { get; set; } = 0.5f; - - /// World-to-grid conversion factor for terrain queries. - public float WorldToGrid { get; set; } = 23f / 250f; - - /// Minimum distance before radial push kicks in hard. - public float MinComfortDistance { get; set; } = 150f; - - private int _orbitSign = 1; - - public void Update(GameState state, ActionQueue actions, MovementBlender movement) - { - if (!state.Player.HasPosition) return; - if (state.HostileMonsters.Count == 0) return; - - var playerPos = state.Player.Position; - - // Compute weighted centroid and closest distance of nearby hostiles (LOS only) - var centroid = Vector2.Zero; - var count = 0; - var closestDist = float.MaxValue; - - foreach (var monster in state.HostileMonsters) - { - if (!monster.IsAlive) continue; - if (monster.DistanceToPlayer > SafeDistance) continue; - if (state.Terrain is { } t && - !TerrainQuery.HasLineOfSight(t, playerPos, monster.Position, WorldToGrid)) - continue; - - centroid += monster.Position; - count++; - if (monster.DistanceToPlayer < closestDist) - closestDist = monster.DistanceToPlayer; - } - - if (count == 0) return; - centroid /= count; - - var toCentroid = centroid - playerPos; - var dist = toCentroid.Length(); - if (dist < 1f) return; - - var centroidDir = toCentroid / dist; - - // Tangential component — perpendicular to centroid direction (orbit) - var tangent = new Vector2(-centroidDir.Y, centroidDir.X) * _orbitSign; - - // Radial component — push away from centroid, strength based on proximity - // Close < MinComfort: strong push out (don't let enemies stack on us) - // MinComfort..SafeDistance*0.6: moderate push out (keep distance) - // SafeDistance*0.6..0.8: pure orbit (sweet spot) - // SafeDistance*0.8+: gentle pull inward to maintain engagement - float radialStrength; - if (closestDist < MinComfortDistance) - radialStrength = -0.6f; // too close — strong push outward - else if (closestDist < SafeDistance * 0.4f) - radialStrength = -0.35f; // close — firm push outward - else if (closestDist < SafeDistance * 0.6f) - radialStrength = -0.15f; // moderate — gentle push outward - else if (closestDist > SafeDistance * 0.8f) - radialStrength = 0.3f; // at edge — pull inward to maintain engagement - else - radialStrength = 0f; // sweet spot — pure orbit - - // Blend: mostly tangent (circling) with radial bias - var result = tangent + centroidDir * radialStrength; - - if (result.LengthSquared() < 0.0001f) return; - - // Override: attenuate navigation (layer 3) when actively orbiting enemies. - // Keep moderate so navigation can still guide past walls. - float orbitOverride = closestDist < SafeDistance * 0.7f ? 0.5f : 0.3f; - - // Suppress orbit when blender detects stuck — let navigation guide out - if (movement.IsStuck) - return; - - movement.Submit(new MovementIntent(2, Vector2.Normalize(result) * RepulsionWeight, orbitOverride, "Orbit")); - } -} diff --git a/src/Nexus.Systems/SystemFactory.cs b/src/Nexus.Systems/SystemFactory.cs deleted file mode 100644 index 47dddfe..0000000 --- a/src/Nexus.Systems/SystemFactory.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Nexus.Core; -using Nexus.Data; -using Nexus.Pathfinding; - -namespace Nexus.Systems; - -public static class SystemFactory -{ - public static List CreateSystems(BotConfig config, NavigationController nav, - bool includeAreaProgression = false) - { - var systems = new List(); - - if (includeAreaProgression) - systems.Add(new AreaProgressionSystem(config, nav, AreaGraph.Load())); - - systems.Add(new ThreatSystem { WorldToGrid = config.WorldToGrid }); - systems.Add(new DodgeSystem { WorldToGrid = config.WorldToGrid }); - systems.Add(new MovementSystem - { - SafeDistance = config.SafeDistance, - RepulsionWeight = config.RepulsionWeight, - WorldToGrid = config.WorldToGrid, - }); - systems.Add(new NavigationSystem - { - WorldToGrid = config.WorldToGrid, - WaypointReachedDistance = config.WaypointReachedDistance, - }); - systems.Add(new CombatSystem(config)); - systems.Add(new ResourceSystem(config)); - systems.Add(new LootSystem()); - - return systems; - } -} diff --git a/src/Nexus.Systems/ThreatSystem.cs b/src/Nexus.Systems/ThreatSystem.cs deleted file mode 100644 index bb8188f..0000000 --- a/src/Nexus.Systems/ThreatSystem.cs +++ /dev/null @@ -1,453 +0,0 @@ -using System.Numerics; -using Nexus.Core; -using Serilog; - -namespace Nexus.Systems; - -/// -/// Per-entity threat scoring with continuous flee weights. -/// Runs first (priority 50). Builds ThreatAssessment on GameState, -/// then submits flee movement intents when warranted. -/// -public class ThreatSystem : ISystem -{ - public int Priority => SystemPriority.Threat; - public string Name => "Threat"; - public bool IsEnabled { get; set; } = true; - - public float WorldToGrid { get; set; } = 23f / 250f; - - // ── Config ── - public float MaxThreatRange { get; set; } = 900f; - public float PointBlankRange { get; set; } = 120f; - public float FleeThreshold { get; set; } = 180f; - public float PanicThreshold { get; set; } = 350f; - - // Weights for score composition - private const float W_Distance = 1.0f; - private const float W_Rarity = 1.0f; - private const float W_PackSize = 0.3f; - - // Score decay for smooth transitions - private float _smoothedZoneThreat; - private const float ZoneThreatUpAlpha = 0.12f; // ramp up over ~8 ticks (~130ms) - private const float ZoneThreatDownAlpha = 0.04f; // decay over ~25 ticks (~400ms) - - // Top-threat debounce — locked entity must lose for N consecutive ticks before switching - private uint? _lockedTopThreatId; - private int _loseStreak; - private const int TopThreatDebounce = 20; // ~330ms at 60Hz - - // Kill-target debounce - private uint? _lockedKillTargetId; - private int _killLoseStreak; - private const int KillTargetDebounce = 15; // ~250ms - - // Flee commitment — once flee starts, hold for minimum duration to prevent oscillation - private int _fleeCommitTicks; - private const int FleeCommitDuration = 60; // ~1 second at 60Hz - - // Logging - private ThreatCategory _prevMaxCategory = ThreatCategory.Ignore; - private uint? _prevTopThreatId; - - public void Update(GameState state, ActionQueue actions, MovementBlender movement) - { - if (!state.Player.HasPosition) return; - - var playerPos = state.Player.Position; - var playerHpFactor = 1f + (1f - state.Player.LifePercent / 100f) * 1.5f; - - // ── 1. Score each hostile ── - var entries = new List(state.HostileMonsters.Count); - - foreach (var monster in state.HostileMonsters) - { - if (!monster.IsAlive) continue; - - var entry = ScoreEntity(monster, playerPos, playerHpFactor, state); - entries.Add(entry); - } - - // ── 2. Pack context pass — count nearby allies per monster (capped) ── - for (var i = 0; i < entries.Count; i++) - { - var nearby = 0; - for (var j = 0; j < entries.Count; j++) - { - if (i == j) continue; - if (Vector2.DistanceSquared(entries[i].Position, entries[j].Position) < 600f * 600f) - nearby++; - } - // Cap pack bonus at 5 allies so 50-mob groups don't dominate the score - entries[i].ThreatScore += Math.Min(nearby, 5) * W_PackSize; - } - - // ── 3. Classify each entry ── - var anyEmergency = false; - ThreatEntry? rawTopThreat = null; - ThreatEntry? lockedEntry = null; - var closestDist = 0f; - var closestFound = false; - - foreach (var entry in entries) - { - entry.Category = Classify(entry, state.Player); - entry.PerceivedDanger = Math.Clamp(entry.ThreatScore / PanicThreshold, 0f, 1f); - - if (entry.Category == ThreatCategory.Emergency) - anyEmergency = true; - - if (rawTopThreat is null || entry.ThreatScore > rawTopThreat.ThreatScore - || (entry.ThreatScore == rawTopThreat.ThreatScore && entry.EntityId < rawTopThreat.EntityId)) - rawTopThreat = entry; - - if (_lockedTopThreatId.HasValue && entry.EntityId == _lockedTopThreatId.Value) - lockedEntry = entry; - - if (!closestFound || entry.DistanceToPlayer < closestDist) - { - closestDist = entry.DistanceToPlayer; - closestFound = true; - } - } - - // Debounce top-threat: locked entity stays until it's clearly outclassed for N ticks - ThreatEntry? mostDangerous; - if (lockedEntry is null) - { - // Locked entity is dead/despawned — accept raw winner immediately - mostDangerous = rawTopThreat; - _lockedTopThreatId = rawTopThreat?.EntityId; - _loseStreak = 0; - } - else if (rawTopThreat is not null && rawTopThreat.EntityId != _lockedTopThreatId) - { - // Only count as "losing" if the locked entry is significantly behind (>30%) - var significantlyBehind = rawTopThreat.ThreatScore > lockedEntry.ThreatScore * 1.3f; - if (significantlyBehind) - _loseStreak++; - // Still count slow drift if locked is behind at all, but at half rate - else if (rawTopThreat.ThreatScore > lockedEntry.ThreatScore) - _loseStreak = Math.Max(0, _loseStreak); // don't reset, just don't increment - else - _loseStreak = Math.Max(0, _loseStreak - 1); // locked is actually winning, cool down - - if (_loseStreak >= TopThreatDebounce) - { - mostDangerous = rawTopThreat; - _lockedTopThreatId = rawTopThreat.EntityId; - _loseStreak = 0; - } - else - { - mostDangerous = lockedEntry; - } - } - else - { - // Locked entity is still the raw winner — cool down streak - mostDangerous = lockedEntry; - _loseStreak = Math.Max(0, _loseStreak - 2); // cool down faster when winning - } - - // ── Log top threat changes ── - var newTopId = mostDangerous?.EntityId; - if (newTopId != _prevTopThreatId) - { - if (mostDangerous is not null) - Log.Information("TopThreat: #{Id} ({Rarity}) score={Score:F1} cat={Cat} dist={Dist:F0} hp={Hp:P0} (prev=#{Prev})", - mostDangerous.EntityId, mostDangerous.Rarity, mostDangerous.ThreatScore, - mostDangerous.Category, mostDangerous.DistanceToPlayer, mostDangerous.HpPercent, - _prevTopThreatId?.ToString() ?? "none"); - else - Log.Information("TopThreat: cleared (prev=#{Prev})", _prevTopThreatId?.ToString() ?? "none"); - - _prevTopThreatId = newTopId; - } - - // ── 4. Aggregate into ThreatAssessment ── - var rawZone = 0f; - foreach (var e in entries) - rawZone += e.ThreatScore; - - // Smooth zone threat — fast up, slow down - if (rawZone >= _smoothedZoneThreat) - _smoothedZoneThreat += (rawZone - _smoothedZoneThreat) * ZoneThreatUpAlpha; - else - _smoothedZoneThreat += (rawZone - _smoothedZoneThreat) * ZoneThreatDownAlpha; - - var centroid = ComputeThreatCentroid(entries, playerPos); - var safest = playerPos - centroid; - if (safest.LengthSquared() < 0.0001f) safest = Vector2.UnitY; - safest = Vector2.Normalize(safest); - - // Hysteresis on flee transition — require score to drop 15% below threshold to de-escalate - var wasFleeing = _prevMaxCategory >= ThreatCategory.Flee; - var fleeOffThreshold = wasFleeing ? FleeThreshold * 0.85f : FleeThreshold; - var rawShouldFlee = _smoothedZoneThreat > fleeOffThreshold || anyEmergency; - - // Flee commitment: once triggered, hold for minimum duration to prevent oscillation - if (rawShouldFlee) - _fleeCommitTicks = FleeCommitDuration; - else if (_fleeCommitTicks > 0) - _fleeCommitTicks--; - var shouldFlee = _fleeCommitTicks > 0; - var areaClear = entries.TrueForAll(e => e.Category < ThreatCategory.Monitor); - - // Range band counts (backward compat) - int close = 0, mid = 0, far = 0; - bool hasRareOrUnique = false; - foreach (var e in entries) - { - if (e.DistanceToPlayer < 300f) close++; - else if (e.DistanceToPlayer < 600f) mid++; - else if (e.DistanceToPlayer < 1200f) far++; - if (e.Rarity >= MonsterRarity.Rare) hasRareOrUnique = true; - } - - var assessment = new ThreatAssessment - { - Entries = entries, - ZoneThreatLevel = _smoothedZoneThreat, - PrimaryTarget = SelectPrimaryTarget(entries), - MostDangerous = mostDangerous, - ThreatCentroid = centroid, - SafestDirection = safest, - AnyEmergency = anyEmergency, - ShouldFlee = shouldFlee, - AreaClear = areaClear, - ClosestDistance = closestDist, - FleeWeight = Math.Clamp(_smoothedZoneThreat / PanicThreshold, 0f, 1f), - CloseRange = close, - MidRange = mid, - FarRange = far, - HasRareOrUnique = hasRareOrUnique, - }; - - // Debounce kill target — same lose-streak pattern - var rawKillTarget = assessment.PrimaryTarget; - var lockedKillEntry = _lockedKillTargetId.HasValue - ? entries.FirstOrDefault(e => e.EntityId == _lockedKillTargetId.Value - && e.Category >= ThreatCategory.Engage && e.HasLineOfSight) - : null; - - if (lockedKillEntry is null) - { - _lockedKillTargetId = rawKillTarget?.EntityId; - _killLoseStreak = 0; - } - else if (rawKillTarget is not null && rawKillTarget.EntityId != _lockedKillTargetId) - { - _killLoseStreak++; - if (_killLoseStreak >= KillTargetDebounce) - { - _lockedKillTargetId = rawKillTarget.EntityId; - _killLoseStreak = 0; - } - else - { - assessment.PrimaryTarget = lockedKillEntry; - } - } - else - { - _killLoseStreak = 0; - } - - state.ThreatAssessment = assessment; - - // Backward compat — keep DangerLevel for consumers that still read it - state.Danger = ToDangerLevel(assessment); - state.Threats = new ThreatMap - { - TotalHostiles = entries.Count, - CloseRange = close, - MidRange = mid, - FarRange = far, - ClosestDistance = closestDist, - ThreatCentroid = centroid, - HasRareOrUnique = hasRareOrUnique, - }; - - // ── 5. Log zone-level transitions with hysteresis ── - // Each threshold has a lower de-escalation point (15% below) to prevent bouncing - var zoneCat = ClassifyZone(_smoothedZoneThreat, anyEmergency, shouldFlee, _prevMaxCategory); - - if (zoneCat != _prevMaxCategory) - { - if (zoneCat >= ThreatCategory.Flee) - Log.Warning("Threat: {Prev} -> {Cur} (zone={Zone:F1}, closest={Dist:F0}, hostiles={Count})", - _prevMaxCategory, zoneCat, _smoothedZoneThreat, closestDist, entries.Count); - else - Log.Debug("Threat: {Prev} -> {Cur} (zone={Zone:F1})", - _prevMaxCategory, zoneCat, _smoothedZoneThreat); - _prevMaxCategory = zoneCat; - } - - // ── 6. Movement ── - // No raw flee intents — all movement goes through pathfinding (NavigationController) - // which routes around walls. ThreatAssessment.ShouldFlee/SafestDirection are available - // for BotTick to adjust navigation target if needed. - } - - // ── Per-entity scoring ── - - private ThreatEntry ScoreEntity(EntitySnapshot monster, Vector2 playerPos, float playerHpFactor, GameState state) - { - var dist = monster.DistanceToPlayer; - var score = 0f; - - // Distance — nonlinear: spikes sharply as enemies close in - var distFactor = DistanceFactor(dist); - score += distFactor * W_Distance; - - // Rarity — scaled by distance so far-away rares/uniques don't dominate - var rarityBase = monster.Rarity switch - { - MonsterRarity.Unique => 3f, - MonsterRarity.Rare => 2f, - MonsterRarity.Magic => 1.3f, - _ => 1f, - }; - // At max range: rarity contributes 20% of its base; at close range: 100% - var rarityScale = 0.2f + 0.8f * Math.Clamp(distFactor / 10f, 0f, 1f); - score += rarityBase * rarityScale * W_Rarity; - - // LOS — de-weight monsters behind walls - var hasLos = true; - if (state.Terrain is { } terrain) - { - hasLos = TerrainQuery.HasLineOfSight(terrain, playerPos, monster.Position, WorldToGrid); - if (!hasLos) score *= 0.05f; - } - - // Low HP monsters are less threatening - var hpPct = monster.LifeTotal > 0 ? (float)monster.LifeCurrent / monster.LifeTotal : 1f; - if (hpPct < 0.1f) score *= 0.3f; - - // Player HP context — same monster is scarier when you're low - score *= playerHpFactor; - - return new ThreatEntry - { - EntityId = monster.Id, - Position = monster.Position, - DistanceToPlayer = dist, - ThreatScore = score, - HasLineOfSight = hasLos, - Rarity = monster.Rarity, - HpPercent = hpPct, - IsAlive = true, - }; - } - - private float DistanceFactor(float dist) - { - if (dist > MaxThreatRange) return 0f; - var t = 1f - dist / MaxThreatRange; - return t * t * 10f; // inverse square: 10 @ 0, 2.5 @ 450, 0 @ 900 - } - - // ── Classification ── - - private ThreatCategory Classify(ThreatEntry entry, PlayerState player) - { - // Emergency overrides — certain conditions always trigger - if (player.LifePercent < 25f && entry.ThreatScore > 8f) - return ThreatCategory.Emergency; - - return entry.ThreatScore switch - { - > 20f => ThreatCategory.Flee, - > 8f => ThreatCategory.Engage, - > 3f => ThreatCategory.Monitor, - > 0f => ThreatCategory.Ignore, - _ => ThreatCategory.Ignore, - }; - } - - // ── Centroid (score-weighted) ── - - private static Vector2 ComputeThreatCentroid(List entries, Vector2 playerPos) - { - var totalWeight = 0f; - var weighted = Vector2.Zero; - - foreach (var e in entries) - { - if (e.Category < ThreatCategory.Monitor) continue; - weighted += e.Position * e.ThreatScore; - totalWeight += e.ThreatScore; - } - - return totalWeight > 0f ? weighted / totalWeight : playerPos; - } - - // ── Target selection (kill priority, not raw threat) ── - - private static ThreatEntry? SelectPrimaryTarget(List entries) - { - ThreatEntry? best = null; - var bestScore = float.MinValue; - - foreach (var e in entries) - { - if (e.Category < ThreatCategory.Engage) continue; - if (!e.HasLineOfSight) continue; - - var score = 0f; - - // Prefer low HP targets — finish them off - score += (1f - e.HpPercent) * 3f; - - // Prefer closer targets - score += (1f - Math.Clamp(e.DistanceToPlayer / 800f, 0f, 1f)) * 2f; - - // Prefer dangerous rarity - if (e.Rarity >= MonsterRarity.Rare) score += 2f; - if (e.Rarity == MonsterRarity.Unique) score += 3f; - - if (score > bestScore) - { - bestScore = score; - best = e; - } - } - - return best; - } - - // ── Backward compat ── - - private static ThreatCategory ClassifyZone(float zone, bool anyEmergency, bool shouldFlee, ThreatCategory prev) - { - if (anyEmergency) return ThreatCategory.Emergency; - if (shouldFlee) return ThreatCategory.Flee; - - // Escalation thresholds / de-escalation thresholds (15% gap) - // Engage: up at 100, down at 85 - // Monitor: up at 30, down at 25 - // Ignore: up at 5 - return prev switch - { - ThreatCategory.Engage when zone >= 85f => ThreatCategory.Engage, - ThreatCategory.Monitor when zone >= 100f => ThreatCategory.Engage, - ThreatCategory.Monitor when zone >= 25f => ThreatCategory.Monitor, - _ when zone >= 100f => ThreatCategory.Engage, - _ when zone >= 30f => ThreatCategory.Monitor, - _ when zone > 5f => ThreatCategory.Ignore, - _ => ThreatCategory.Ignore, - }; - } - - private static DangerLevel ToDangerLevel(ThreatAssessment a) - { - if (a.AnyEmergency) return DangerLevel.Critical; - if (a.ShouldFlee) return DangerLevel.Critical; - if (a.FleeWeight > 0.5f) return DangerLevel.High; - if (a.FleeWeight > 0.15f) return DangerLevel.Medium; - if (a.ZoneThreatLevel > 5f) return DangerLevel.Low; - return DangerLevel.Safe; - } -} diff --git a/src/Nexus.Core/ActionQueue.cs b/src/Roboto.Core/ActionQueue.cs similarity index 71% rename from src/Nexus.Core/ActionQueue.cs rename to src/Roboto.Core/ActionQueue.cs index 997b4fd..0d3a4fd 100644 --- a/src/Nexus.Core/ActionQueue.cs +++ b/src/Roboto.Core/ActionQueue.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public class ActionQueue { @@ -33,11 +33,12 @@ public class ActionQueue } /// - /// Resolve conflicts and return the final action list. - /// Movement is handled by MovementBlender — only non-movement actions remain here. + /// Resolve conflicts and return the final action list: /// 1. FlaskActions always pass through - /// 2. Get highest-priority CastAction - /// 3. All other actions pass through + /// 2. Get highest-priority MoveAction + CastAction + /// 3. Urgent move (priority ≤ 10) → include move, BLOCK cast (flee) + /// 4. Normal → include both cast + move + /// 5. All other actions pass through /// public List Resolve() { @@ -50,9 +51,21 @@ public class ActionQueue resolved.Add(action); } + var bestMove = GetHighestPriority(); var bestCast = GetHighestPriority(); - if (bestCast is not null) + + if (bestMove is not null) + { + resolved.Add(bestMove); + + // Urgent flee (priority ≤ 10) blocks casting + if (bestMove.Priority > 10 && bestCast is not null) + resolved.Add(bestCast); + } + else if (bestCast is not null) + { resolved.Add(bestCast); + } // Pass through everything else (Key, Click, Chat, Wait) except types already handled foreach (var action in _actions) diff --git a/src/Nexus.Core/Actions.cs b/src/Roboto.Core/Actions.cs similarity index 90% rename from src/Nexus.Core/Actions.cs rename to src/Roboto.Core/Actions.cs index 35bf7d7..9bd6e4b 100644 --- a/src/Nexus.Core/Actions.cs +++ b/src/Roboto.Core/Actions.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace Nexus.Core; +namespace Roboto.Core; public enum ClickType { Left, Right, Middle } public enum KeyActionType { Press, Down, Up } @@ -25,5 +25,3 @@ public record FlaskAction(int Priority, ushort FlaskScanCode) : BotAction(Priori public record ChatAction(int Priority, string Message) : BotAction(Priority); public record WaitAction(int Priority, int DurationMs) : BotAction(Priority); - -public record DodgeRollAction(int Priority, Vector2 Direction) : BotAction(Priority); diff --git a/src/Nexus.Core/BotConfig.cs b/src/Roboto.Core/BotConfig.cs similarity index 72% rename from src/Nexus.Core/BotConfig.cs rename to src/Roboto.Core/BotConfig.cs index bc304c0..230c502 100644 --- a/src/Nexus.Core/BotConfig.cs +++ b/src/Roboto.Core/BotConfig.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public class BotConfig { @@ -7,16 +7,13 @@ public class BotConfig public int MemoryPollRateHz { get; set; } = 30; // Movement - public float SafeDistance { get; set; } = 500f; - public float RepulsionWeight { get; set; } = 0.5f; + public float SafeDistance { get; set; } = 400f; + public float RepulsionWeight { get; set; } = 1.5f; public float WaypointReachedDistance { get; set; } = 80f; // Navigation public float WorldToGrid { get; set; } = 23f / 250f; - // Combat engagement — suppress navigation when enemies are within this range - public float CombatEngagementRange { get; set; } = 600f; - // Loot public float LootPickupRange { get; set; } = 600f; diff --git a/src/Nexus.Core/Buff.cs b/src/Roboto.Core/Buff.cs similarity index 89% rename from src/Nexus.Core/Buff.cs rename to src/Roboto.Core/Buff.cs index 85aab5a..e80ba65 100644 --- a/src/Nexus.Core/Buff.cs +++ b/src/Roboto.Core/Buff.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public record Buff { diff --git a/src/Nexus.Core/CharacterProfile.cs b/src/Roboto.Core/CharacterProfile.cs similarity index 98% rename from src/Nexus.Core/CharacterProfile.cs rename to src/Roboto.Core/CharacterProfile.cs index b592d32..8518c32 100644 --- a/src/Nexus.Core/CharacterProfile.cs +++ b/src/Roboto.Core/CharacterProfile.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public class CharacterProfile { diff --git a/src/Nexus.Core/CombatSettings.cs b/src/Roboto.Core/CombatSettings.cs similarity index 93% rename from src/Nexus.Core/CombatSettings.cs rename to src/Roboto.Core/CombatSettings.cs index a511432..02cf2a9 100644 --- a/src/Nexus.Core/CombatSettings.cs +++ b/src/Roboto.Core/CombatSettings.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public class CombatSettings { diff --git a/src/Nexus.Core/EntitySnapshot.cs b/src/Roboto.Core/EntitySnapshot.cs similarity index 98% rename from src/Nexus.Core/EntitySnapshot.cs rename to src/Roboto.Core/EntitySnapshot.cs index 4e4900d..7ede015 100644 --- a/src/Nexus.Core/EntitySnapshot.cs +++ b/src/Roboto.Core/EntitySnapshot.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace Nexus.Core; +namespace Roboto.Core; public enum EntityCategory { diff --git a/src/Nexus.Core/Enums.cs b/src/Roboto.Core/Enums.cs similarity index 86% rename from src/Nexus.Core/Enums.cs rename to src/Roboto.Core/Enums.cs index 57915a7..1f8821c 100644 --- a/src/Nexus.Core/Enums.cs +++ b/src/Roboto.Core/Enums.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public enum DangerLevel { @@ -12,7 +12,6 @@ public enum DangerLevel public static class SystemPriority { public const int Threat = 50; - public const int Dodge = 75; public const int Movement = 100; public const int Navigation = 200; public const int Combat = 300; diff --git a/src/Nexus.Core/FlaskSettings.cs b/src/Roboto.Core/FlaskSettings.cs similarity index 93% rename from src/Nexus.Core/FlaskSettings.cs rename to src/Roboto.Core/FlaskSettings.cs index 39dbf6d..8b7da24 100644 --- a/src/Nexus.Core/FlaskSettings.cs +++ b/src/Roboto.Core/FlaskSettings.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public class FlaskSettings { diff --git a/src/Nexus.Core/FlaskState.cs b/src/Roboto.Core/FlaskState.cs similarity index 91% rename from src/Nexus.Core/FlaskState.cs rename to src/Roboto.Core/FlaskState.cs index 3576f0e..6a4c074 100644 --- a/src/Nexus.Core/FlaskState.cs +++ b/src/Roboto.Core/FlaskState.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public record FlaskState { diff --git a/src/Nexus.Core/GameState.cs b/src/Roboto.Core/GameState.cs similarity index 84% rename from src/Nexus.Core/GameState.cs rename to src/Roboto.Core/GameState.cs index f1de0b4..37b4fca 100644 --- a/src/Nexus.Core/GameState.cs +++ b/src/Roboto.Core/GameState.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace Nexus.Core; +namespace Roboto.Core; public class GameState { @@ -27,11 +27,8 @@ public class GameState /// In-progress quests from the quest linked list with target areas and paths. public IReadOnlyList Quests { get; set; } = []; - public IReadOnlyList EnemyProjectiles { get; set; } = []; - - // Derived (computed once per tick by GameStateEnricher / ThreatSystem) + // Derived (computed once per tick by GameStateEnricher) public ThreatMap Threats { get; set; } = new(); - public ThreatAssessment ThreatAssessment { get; set; } = new(); public IReadOnlyList NearestEnemies { get; set; } = []; public IReadOnlyList GroundEffects { get; set; } = []; } diff --git a/src/Nexus.Core/GroundEffect.cs b/src/Roboto.Core/GroundEffect.cs similarity index 92% rename from src/Nexus.Core/GroundEffect.cs rename to src/Roboto.Core/GroundEffect.cs index f14f752..ab7f3f8 100644 --- a/src/Nexus.Core/GroundEffect.cs +++ b/src/Roboto.Core/GroundEffect.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace Nexus.Core; +namespace Roboto.Core; public record GroundEffect { diff --git a/src/Nexus.Core/IInputController.cs b/src/Roboto.Core/IInputController.cs similarity index 62% rename from src/Nexus.Core/IInputController.cs rename to src/Roboto.Core/IInputController.cs index 6b92156..24f9b86 100644 --- a/src/Nexus.Core/IInputController.cs +++ b/src/Roboto.Core/IInputController.cs @@ -1,6 +1,4 @@ -using System.Numerics; - -namespace Nexus.Core; +namespace Roboto.Core; public interface IInputController { @@ -18,10 +16,4 @@ public interface IInputController void LeftUp(); void RightDown(); void RightUp(); - - /// - /// Sets the direction for the next dodge roll. Called before KeyPress(0x21). - /// Default no-op for real input controllers (direction comes from game state). - /// - void SetDodgeDirection(Vector2 direction) { } } diff --git a/src/Nexus.Core/ISystem.cs b/src/Roboto.Core/ISystem.cs similarity index 52% rename from src/Nexus.Core/ISystem.cs rename to src/Roboto.Core/ISystem.cs index 7b03dbe..05b4564 100644 --- a/src/Nexus.Core/ISystem.cs +++ b/src/Roboto.Core/ISystem.cs @@ -1,9 +1,9 @@ -namespace Nexus.Core; +namespace Roboto.Core; public interface ISystem { int Priority { get; } string Name { get; } bool IsEnabled { get; set; } - void Update(GameState state, ActionQueue actions, MovementBlender movement); + void Update(GameState state, ActionQueue actions); } diff --git a/src/Nexus.Core/PlayerState.cs b/src/Roboto.Core/PlayerState.cs similarity index 89% rename from src/Nexus.Core/PlayerState.cs rename to src/Roboto.Core/PlayerState.cs index f9e02fa..1db0729 100644 --- a/src/Nexus.Core/PlayerState.cs +++ b/src/Roboto.Core/PlayerState.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace Nexus.Core; +namespace Roboto.Core; public record PlayerState { @@ -31,8 +31,4 @@ public record PlayerState // Skill slots (populated by memory when available) public IReadOnlyList Skills { get; init; } = []; - - // Dodge roll state - public bool IsRolling { get; init; } - public float RollCooldownRemaining { get; init; } } diff --git a/src/Nexus.Core/ProfileManager.cs b/src/Roboto.Core/ProfileManager.cs similarity index 99% rename from src/Nexus.Core/ProfileManager.cs rename to src/Roboto.Core/ProfileManager.cs index 868d2d5..3130296 100644 --- a/src/Nexus.Core/ProfileManager.cs +++ b/src/Roboto.Core/ProfileManager.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Nexus.Core; +namespace Roboto.Core; public class ProfileConfig { diff --git a/src/Nexus.Core/QuestInfo.cs b/src/Roboto.Core/QuestInfo.cs similarity index 96% rename from src/Nexus.Core/QuestInfo.cs rename to src/Roboto.Core/QuestInfo.cs index c62de53..1f75fc2 100644 --- a/src/Nexus.Core/QuestInfo.cs +++ b/src/Roboto.Core/QuestInfo.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public class QuestInfo { diff --git a/src/Nexus.Core/QuestProgress.cs b/src/Roboto.Core/QuestProgress.cs similarity index 96% rename from src/Nexus.Core/QuestProgress.cs rename to src/Roboto.Core/QuestProgress.cs index 3929f78..56a76cc 100644 --- a/src/Nexus.Core/QuestProgress.cs +++ b/src/Roboto.Core/QuestProgress.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public record QuestProgress { diff --git a/src/Roboto.Core/Roboto.Core.csproj b/src/Roboto.Core/Roboto.Core.csproj new file mode 100644 index 0000000..b104a1a --- /dev/null +++ b/src/Roboto.Core/Roboto.Core.csproj @@ -0,0 +1,7 @@ + + + net8.0-windows10.0.19041.0 + enable + enable + + diff --git a/src/Nexus.Core/SkillProfile.cs b/src/Roboto.Core/SkillProfile.cs similarity index 99% rename from src/Nexus.Core/SkillProfile.cs rename to src/Roboto.Core/SkillProfile.cs index 481d0d3..0c6a890 100644 --- a/src/Nexus.Core/SkillProfile.cs +++ b/src/Roboto.Core/SkillProfile.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public enum SkillInputType { KeyPress, LeftClick, RightClick, MiddleClick } diff --git a/src/Nexus.Core/SkillState.cs b/src/Roboto.Core/SkillState.cs similarity index 97% rename from src/Nexus.Core/SkillState.cs rename to src/Roboto.Core/SkillState.cs index 2205d36..6168749 100644 --- a/src/Nexus.Core/SkillState.cs +++ b/src/Roboto.Core/SkillState.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public record SkillState { diff --git a/src/Nexus.Core/TargetSelection.cs b/src/Roboto.Core/TargetSelection.cs similarity index 82% rename from src/Nexus.Core/TargetSelection.cs rename to src/Roboto.Core/TargetSelection.cs index 02d3309..fbcc872 100644 --- a/src/Nexus.Core/TargetSelection.cs +++ b/src/Roboto.Core/TargetSelection.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; public enum TargetSelection { diff --git a/src/Roboto.Core/TerrainQuery.cs b/src/Roboto.Core/TerrainQuery.cs new file mode 100644 index 0000000..df403b6 --- /dev/null +++ b/src/Roboto.Core/TerrainQuery.cs @@ -0,0 +1,88 @@ +using System.Numerics; + +namespace Roboto.Core; + +/// +/// Terrain line-of-sight and walkable direction queries on the walkability grid. +/// +public static class TerrainQuery +{ + /// + /// Bresenham line walk on the walkability grid. Returns false if any cell is unwalkable. + /// + public static bool HasLineOfSight(WalkabilitySnapshot terrain, Vector2 from, Vector2 to, float worldToGrid) + { + int x0 = (int)(from.X * worldToGrid); + int y0 = (int)(from.Y * worldToGrid); + int x1 = (int)(to.X * worldToGrid); + int y1 = (int)(to.Y * worldToGrid); + + int dx = Math.Abs(x1 - x0); + int dy = Math.Abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + int err = dx - dy; + + while (true) + { + if (!terrain.IsWalkable(x0, y0)) + return false; + + if (x0 == x1 && y0 == y1) + break; + + int e2 = 2 * err; + if (e2 > -dy) { err -= dy; x0 += sx; } + if (e2 < dx) { err += dx; y0 += sy; } + } + + return true; + } + + /// + /// Validates a desired movement direction against terrain. If blocked, tries rotations + /// ±45°, ±90°, ±135°, 180° and returns the first clear direction. + /// Returns original direction as fallback (game engine will wall-slide). + /// + public static Vector2 FindWalkableDirection( + WalkabilitySnapshot terrain, Vector2 playerPos, Vector2 desiredDir, float worldToGrid, + float probeDistance = 200f) + { + if (IsDirectionClear(terrain, playerPos, desiredDir, worldToGrid, probeDistance)) + return desiredDir; + + // Try rotations: ±45°, ±90°, ±135°, 180° + ReadOnlySpan angles = [45f, -45f, 90f, -90f, 135f, -135f, 180f]; + + foreach (var angleDeg in angles) + { + var rotated = Rotate(desiredDir, angleDeg); + if (IsDirectionClear(terrain, playerPos, rotated, worldToGrid, probeDistance)) + return rotated; + } + + return desiredDir; + } + + private static bool IsDirectionClear( + WalkabilitySnapshot terrain, Vector2 origin, Vector2 dir, float worldToGrid, float distance) + { + var endpoint = origin + dir * distance; + var midpoint = origin + dir * (distance * 0.5f); + + int mx = (int)(midpoint.X * worldToGrid); + int my = (int)(midpoint.Y * worldToGrid); + int ex = (int)(endpoint.X * worldToGrid); + int ey = (int)(endpoint.Y * worldToGrid); + + return terrain.IsWalkable(mx, my) && terrain.IsWalkable(ex, ey); + } + + private static Vector2 Rotate(Vector2 v, float degrees) + { + float rad = degrees * MathF.PI / 180f; + float cos = MathF.Cos(rad); + float sin = MathF.Sin(rad); + return Vector2.Normalize(new Vector2(v.X * cos - v.Y * sin, v.X * sin + v.Y * cos)); + } +} diff --git a/src/Nexus.Core/ThreatMap.cs b/src/Roboto.Core/ThreatMap.cs similarity index 80% rename from src/Nexus.Core/ThreatMap.cs rename to src/Roboto.Core/ThreatMap.cs index 4d65d9c..79e0214 100644 --- a/src/Nexus.Core/ThreatMap.cs +++ b/src/Roboto.Core/ThreatMap.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace Nexus.Core; +namespace Roboto.Core; public class ThreatMap { @@ -8,7 +8,7 @@ public class ThreatMap public int CloseRange { get; init; } // < 300 units public int MidRange { get; init; } // 300–600 public int FarRange { get; init; } // 600–1200 - public float ClosestDistance { get; init; } + public float ClosestDistance { get; init; } = float.MaxValue; public Vector2 ThreatCentroid { get; init; } public bool HasRareOrUnique { get; init; } } diff --git a/src/Nexus.Core/UiQuestInfo.cs b/src/Roboto.Core/UiQuestInfo.cs similarity index 93% rename from src/Nexus.Core/UiQuestInfo.cs rename to src/Roboto.Core/UiQuestInfo.cs index 6c0aa1c..e120874 100644 --- a/src/Nexus.Core/UiQuestInfo.cs +++ b/src/Roboto.Core/UiQuestInfo.cs @@ -1,4 +1,4 @@ -namespace Nexus.Core; +namespace Roboto.Core; /// /// Active quest info as displayed in the game UI. diff --git a/src/Roboto.Core/WalkabilitySnapshot.cs b/src/Roboto.Core/WalkabilitySnapshot.cs new file mode 100644 index 0000000..bf4dfb0 --- /dev/null +++ b/src/Roboto.Core/WalkabilitySnapshot.cs @@ -0,0 +1,15 @@ +namespace Roboto.Core; + +public record WalkabilitySnapshot +{ + public int Width { get; init; } + public int Height { get; init; } + public byte[] Data { get; init; } = []; + + public bool IsWalkable(int x, int y) + { + if (x < 0 || x >= Width || y < 0 || y >= Height) + return false; + return Data[y * Width + x] != 0; + } +} diff --git a/src/Nexus.Core/WorldToScreen.cs b/src/Roboto.Core/WorldToScreen.cs similarity index 97% rename from src/Nexus.Core/WorldToScreen.cs rename to src/Roboto.Core/WorldToScreen.cs index 96e5a76..33f12d0 100644 --- a/src/Nexus.Core/WorldToScreen.cs +++ b/src/Roboto.Core/WorldToScreen.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace Nexus.Core; +namespace Roboto.Core; public static class WorldToScreen { diff --git a/src/Nexus.Data/AreaGraph.cs b/src/Roboto.Data/AreaGraph.cs similarity index 99% rename from src/Nexus.Data/AreaGraph.cs rename to src/Roboto.Data/AreaGraph.cs index c7d57f5..4e5aaf4 100644 --- a/src/Nexus.Data/AreaGraph.cs +++ b/src/Roboto.Data/AreaGraph.cs @@ -1,6 +1,6 @@ using System.Text.Json; -namespace Nexus.Data; +namespace Roboto.Data; public record AreaNode( string Id, diff --git a/src/Nexus.Data/AreaNameLookup.cs b/src/Roboto.Data/AreaNameLookup.cs similarity index 98% rename from src/Nexus.Data/AreaNameLookup.cs rename to src/Roboto.Data/AreaNameLookup.cs index 34bb3ae..7e7ca68 100644 --- a/src/Nexus.Data/AreaNameLookup.cs +++ b/src/Roboto.Data/AreaNameLookup.cs @@ -1,6 +1,6 @@ using System.Text.Json; -namespace Nexus.Data; +namespace Roboto.Data; /// /// Resolves area IDs (e.g. "G1_4") to display names (e.g. "The Grelwood") diff --git a/src/Nexus.Data/EntityClassifier.cs b/src/Roboto.Data/EntityClassifier.cs similarity index 98% rename from src/Nexus.Data/EntityClassifier.cs rename to src/Roboto.Data/EntityClassifier.cs index cebf3e5..c390fed 100644 --- a/src/Nexus.Data/EntityClassifier.cs +++ b/src/Roboto.Data/EntityClassifier.cs @@ -1,6 +1,6 @@ -using Nexus.Core; +using Roboto.Core; -namespace Nexus.Data; +namespace Roboto.Data; /// /// Classifies entities from path + component data into EntityCategory. diff --git a/src/Nexus.Data/EntityMapper.cs b/src/Roboto.Data/EntityMapper.cs similarity index 96% rename from src/Nexus.Data/EntityMapper.cs rename to src/Roboto.Data/EntityMapper.cs index 6234a1e..d1edf12 100644 --- a/src/Nexus.Data/EntityMapper.cs +++ b/src/Roboto.Data/EntityMapper.cs @@ -1,8 +1,8 @@ using System.Numerics; -using Nexus.Core; -using MemEntity = Nexus.Memory.Entity; +using Roboto.Core; +using MemEntity = Roboto.Memory.Entity; -namespace Nexus.Data; +namespace Roboto.Data; /// /// Maps raw Memory.Entity → Core.EntitySnapshot. Single source of truth for entity mapping. diff --git a/src/Nexus.Data/GameDataCache.cs b/src/Roboto.Data/GameDataCache.cs similarity index 98% rename from src/Nexus.Data/GameDataCache.cs rename to src/Roboto.Data/GameDataCache.cs index 5859878..0ca85c0 100644 --- a/src/Nexus.Data/GameDataCache.cs +++ b/src/Roboto.Data/GameDataCache.cs @@ -1,8 +1,8 @@ using System.Numerics; -using Nexus.Core; -using Nexus.Memory; +using Roboto.Core; +using Roboto.Memory; -namespace Nexus.Data; +namespace Roboto.Data; /// /// Immutable snapshot of player position for lock-free cross-thread reads. diff --git a/src/Roboto.Data/GameStateEnricher.cs b/src/Roboto.Data/GameStateEnricher.cs new file mode 100644 index 0000000..3112e60 --- /dev/null +++ b/src/Roboto.Data/GameStateEnricher.cs @@ -0,0 +1,105 @@ +using System.Numerics; +using Roboto.Core; + +namespace Roboto.Data; + +/// +/// Computes all derived fields on GameState once per tick. +/// Static methods, no allocations beyond the sorted list. +/// +public static class GameStateEnricher +{ + public static void Enrich(GameState state) + { + state.NearestEnemies = ComputeNearestEnemies(state.HostileMonsters); + state.Threats = ComputeThreatMap(state.HostileMonsters); + state.Danger = ComputeDangerLevel(state); + state.GroundEffects = []; // stub until memory reads ground effects + } + + private static IReadOnlyList ComputeNearestEnemies(IReadOnlyList hostiles) + { + if (hostiles.Count == 0) return []; + + var sorted = new List(hostiles); + sorted.Sort((a, b) => a.DistanceToPlayer.CompareTo(b.DistanceToPlayer)); + return sorted; + } + + private static ThreatMap ComputeThreatMap(IReadOnlyList hostiles) + { + if (hostiles.Count == 0) return new ThreatMap(); + + int close = 0, mid = 0, far = 0; + float closest = float.MaxValue; + var weightedSum = Vector2.Zero; + bool hasRareOrUnique = false; + + foreach (var m in hostiles) + { + var d = m.DistanceToPlayer; + if (d < closest) closest = d; + + if (d < 300f) close++; + else if (d < 600f) mid++; + else if (d < 1200f) far++; + + weightedSum += m.Position; + + if (m.ThreatLevel is MonsterThreatLevel.Rare or MonsterThreatLevel.Unique) + hasRareOrUnique = true; + } + + return new ThreatMap + { + TotalHostiles = hostiles.Count, + CloseRange = close, + MidRange = mid, + FarRange = far, + ClosestDistance = closest, + ThreatCentroid = weightedSum / hostiles.Count, + HasRareOrUnique = hasRareOrUnique, + }; + } + + /// + /// Computes danger using a weighted threat score. + /// Close enemies count more, rares/uniques escalate significantly. + /// + private static DangerLevel ComputeDangerLevel(GameState state) + { + if (state.Player.LifePercent < 30f) return DangerLevel.Critical; + if (state.Player.LifePercent < 50f) return DangerLevel.High; + + // Weighted threat score: proximity × rarity multiplier + var threatScore = 0f; + foreach (var m in state.HostileMonsters) + { + var d = m.DistanceToPlayer; + if (d > 800f) continue; + + // Distance weight: closer = more dangerous + float distWeight; + if (d < 200f) distWeight = 3f; + else if (d < 400f) distWeight = 2f; + else distWeight = 1f; + + // Rarity multiplier + var rarityMul = m.Rarity switch + { + MonsterRarity.Unique => 5f, + MonsterRarity.Rare => 3f, + MonsterRarity.Magic => 1.5f, + _ => 1f, + }; + + threatScore += distWeight * rarityMul; + } + + if (threatScore >= 15f) return DangerLevel.Critical; + if (threatScore >= 8f) return DangerLevel.High; + if (threatScore >= 4f) return DangerLevel.Medium; + if (threatScore > 0f) return DangerLevel.Low; + return DangerLevel.Safe; + } +} diff --git a/src/Nexus.Data/MemoryPoller.cs b/src/Roboto.Data/MemoryPoller.cs similarity index 99% rename from src/Nexus.Data/MemoryPoller.cs rename to src/Roboto.Data/MemoryPoller.cs index 8fda25e..ef46ffe 100644 --- a/src/Nexus.Data/MemoryPoller.cs +++ b/src/Roboto.Data/MemoryPoller.cs @@ -1,10 +1,10 @@ using System.Diagnostics; using System.Numerics; -using Nexus.Memory; -using Nexus.Core; +using Roboto.Memory; +using Roboto.Core; using Serilog; -namespace Nexus.Data; +namespace Roboto.Data; /// /// Owns the memory read thread. Runs a two-tier loop: @@ -28,7 +28,7 @@ public sealed class MemoryPoller : IDisposable private nint _playerLifeAddr; private nint _inGameStateAddr; private nint _controllerAddr; - private Nexus.Memory.GameOffsets? _offsets; + private Roboto.Memory.GameOffsets? _offsets; private ProcessMemory? _mem; private int _hotHz; @@ -64,7 +64,7 @@ public sealed class MemoryPoller : IDisposable _thread = new Thread(PollLoop) { - Name = "Nexus.MemoryPoller", + Name = "Roboto.MemoryPoller", IsBackground = true, }; _thread.Start(); diff --git a/src/Nexus.Systems/Nexus.Systems.csproj b/src/Roboto.Data/Roboto.Data.csproj similarity index 61% rename from src/Nexus.Systems/Nexus.Systems.csproj rename to src/Roboto.Data/Roboto.Data.csproj index 0980613..38208e6 100644 --- a/src/Nexus.Systems/Nexus.Systems.csproj +++ b/src/Roboto.Data/Roboto.Data.csproj @@ -8,8 +8,7 @@ - - - + + diff --git a/src/Nexus.GameOffsets/Components/Actor.cs b/src/Roboto.GameOffsets/Components/Actor.cs similarity index 90% rename from src/Nexus.GameOffsets/Components/Actor.cs rename to src/Roboto.GameOffsets/Components/Actor.cs index a933bcd..e95c6cb 100644 --- a/src/Nexus.GameOffsets/Components/Actor.cs +++ b/src/Roboto.GameOffsets/Components/Actor.cs @@ -1,4 +1,4 @@ -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// /// Actor component offsets — confirmed from ExileCore2. diff --git a/src/Nexus.GameOffsets/Components/ActorDeployedEntity.cs b/src/Roboto.GameOffsets/Components/ActorDeployedEntity.cs similarity index 89% rename from src/Nexus.GameOffsets/Components/ActorDeployedEntity.cs rename to src/Roboto.GameOffsets/Components/ActorDeployedEntity.cs index 648cf78..cfd3811 100644 --- a/src/Nexus.GameOffsets/Components/ActorDeployedEntity.cs +++ b/src/Roboto.GameOffsets/Components/ActorDeployedEntity.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// A deployed entity (totem, mine, etc.). [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/Nexus.GameOffsets/Components/ActorSkill.cs b/src/Roboto.GameOffsets/Components/ActorSkill.cs similarity index 96% rename from src/Nexus.GameOffsets/Components/ActorSkill.cs rename to src/Roboto.GameOffsets/Components/ActorSkill.cs index 1c27db5..5000be5 100644 --- a/src/Nexus.GameOffsets/Components/ActorSkill.cs +++ b/src/Roboto.GameOffsets/Components/ActorSkill.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// /// An entry in the ActiveSkills vector: shared_ptr pair (0x10 bytes). diff --git a/src/Nexus.GameOffsets/Components/ActorSkillCooldown.cs b/src/Roboto.GameOffsets/Components/ActorSkillCooldown.cs similarity index 92% rename from src/Nexus.GameOffsets/Components/ActorSkillCooldown.cs rename to src/Roboto.GameOffsets/Components/ActorSkillCooldown.cs index 819224f..d0f242c 100644 --- a/src/Nexus.GameOffsets/Components/ActorSkillCooldown.cs +++ b/src/Roboto.GameOffsets/Components/ActorSkillCooldown.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// /// Cooldown state for a skill. Entries in Actor+0xB18 vector. diff --git a/src/Nexus.GameOffsets/Components/ActorVaalSkill.cs b/src/Roboto.GameOffsets/Components/ActorVaalSkill.cs similarity index 91% rename from src/Nexus.GameOffsets/Components/ActorVaalSkill.cs rename to src/Roboto.GameOffsets/Components/ActorVaalSkill.cs index 93c08fc..3cdd359 100644 --- a/src/Nexus.GameOffsets/Components/ActorVaalSkill.cs +++ b/src/Roboto.GameOffsets/Components/ActorVaalSkill.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Vaal soul tracking. [StructLayout(LayoutKind.Explicit, Pack = 1)] diff --git a/src/Nexus.GameOffsets/Components/Animated.cs b/src/Roboto.GameOffsets/Components/Animated.cs similarity index 90% rename from src/Nexus.GameOffsets/Components/Animated.cs rename to src/Roboto.GameOffsets/Components/Animated.cs index 2425ff4..3766d1a 100644 --- a/src/Nexus.GameOffsets/Components/Animated.cs +++ b/src/Roboto.GameOffsets/Components/Animated.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Animated component — reference to the animated entity. [StructLayout(LayoutKind.Explicit, Size = 0x300)] diff --git a/src/Nexus.GameOffsets/Components/Buffs.cs b/src/Roboto.GameOffsets/Components/Buffs.cs similarity index 90% rename from src/Nexus.GameOffsets/Components/Buffs.cs rename to src/Roboto.GameOffsets/Components/Buffs.cs index db335f9..761392d 100644 --- a/src/Nexus.GameOffsets/Components/Buffs.cs +++ b/src/Roboto.GameOffsets/Components/Buffs.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Buffs component — active status effects. [StructLayout(LayoutKind.Explicit, Size = 0x178)] diff --git a/src/Nexus.GameOffsets/Components/Charges.cs b/src/Roboto.GameOffsets/Components/Charges.cs similarity index 93% rename from src/Nexus.GameOffsets/Components/Charges.cs rename to src/Roboto.GameOffsets/Components/Charges.cs index 4f84081..c2231b2 100644 --- a/src/Nexus.GameOffsets/Components/Charges.cs +++ b/src/Roboto.GameOffsets/Components/Charges.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Charges component — flask/skill charges. [StructLayout(LayoutKind.Explicit, Size = 0x20)] diff --git a/src/Nexus.GameOffsets/Components/Chest.cs b/src/Roboto.GameOffsets/Components/Chest.cs similarity index 94% rename from src/Nexus.GameOffsets/Components/Chest.cs rename to src/Roboto.GameOffsets/Components/Chest.cs index 688fffa..b86d397 100644 --- a/src/Nexus.GameOffsets/Components/Chest.cs +++ b/src/Roboto.GameOffsets/Components/Chest.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Chest component. [StructLayout(LayoutKind.Explicit, Size = 0x170)] diff --git a/src/Nexus.GameOffsets/Components/ComponentHeader.cs b/src/Roboto.GameOffsets/Components/ComponentHeader.cs similarity index 87% rename from src/Nexus.GameOffsets/Components/ComponentHeader.cs rename to src/Roboto.GameOffsets/Components/ComponentHeader.cs index 2db11b0..f0f6910 100644 --- a/src/Nexus.GameOffsets/Components/ComponentHeader.cs +++ b/src/Roboto.GameOffsets/Components/ComponentHeader.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Common header at the start of every component. [StructLayout(LayoutKind.Explicit, Size = 0x10)] diff --git a/src/Nexus.GameOffsets/Components/Life.cs b/src/Roboto.GameOffsets/Components/Life.cs similarity index 95% rename from src/Nexus.GameOffsets/Components/Life.cs rename to src/Roboto.GameOffsets/Components/Life.cs index 2b4e4f1..f85275b 100644 --- a/src/Nexus.GameOffsets/Components/Life.cs +++ b/src/Roboto.GameOffsets/Components/Life.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Life component — contains Health, Mana, and ES vitals. [StructLayout(LayoutKind.Explicit, Size = 0x268)] diff --git a/src/Nexus.GameOffsets/Components/Mods.cs b/src/Roboto.GameOffsets/Components/Mods.cs similarity index 91% rename from src/Nexus.GameOffsets/Components/Mods.cs rename to src/Roboto.GameOffsets/Components/Mods.cs index 20e901b..9184a09 100644 --- a/src/Nexus.GameOffsets/Components/Mods.cs +++ b/src/Roboto.GameOffsets/Components/Mods.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Mods component — ModsAndObjectMagicProperties inline at +0x00. Rarity at +0x94. [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 0x1A0)] diff --git a/src/Nexus.GameOffsets/Components/Player.cs b/src/Roboto.GameOffsets/Components/Player.cs similarity index 87% rename from src/Nexus.GameOffsets/Components/Player.cs rename to src/Roboto.GameOffsets/Components/Player.cs index 74c0e35..7fa7af5 100644 --- a/src/Nexus.GameOffsets/Components/Player.cs +++ b/src/Roboto.GameOffsets/Components/Player.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Player component — name, XP, level. [StructLayout(LayoutKind.Explicit, Size = 0x208)] diff --git a/src/Nexus.GameOffsets/Components/Positioned.cs b/src/Roboto.GameOffsets/Components/Positioned.cs similarity index 90% rename from src/Nexus.GameOffsets/Components/Positioned.cs rename to src/Roboto.GameOffsets/Components/Positioned.cs index 21da95a..be35bda 100644 --- a/src/Nexus.GameOffsets/Components/Positioned.cs +++ b/src/Roboto.GameOffsets/Components/Positioned.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Positioned component — reaction (friendly/hostile/neutral). [StructLayout(LayoutKind.Explicit, Size = 0x1E8)] diff --git a/src/Nexus.GameOffsets/Components/Render.cs b/src/Roboto.GameOffsets/Components/Render.cs similarity index 93% rename from src/Nexus.GameOffsets/Components/Render.cs rename to src/Roboto.GameOffsets/Components/Render.cs index c7f868d..7301451 100644 --- a/src/Nexus.GameOffsets/Components/Render.cs +++ b/src/Roboto.GameOffsets/Components/Render.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Render component — world position, bounds, terrain height. [StructLayout(LayoutKind.Explicit, Size = 0x1B0)] diff --git a/src/Nexus.GameOffsets/Components/Shrine.cs b/src/Roboto.GameOffsets/Components/Shrine.cs similarity index 88% rename from src/Nexus.GameOffsets/Components/Shrine.cs rename to src/Roboto.GameOffsets/Components/Shrine.cs index 45968ab..680efe4 100644 --- a/src/Nexus.GameOffsets/Components/Shrine.cs +++ b/src/Roboto.GameOffsets/Components/Shrine.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Shrine component. [StructLayout(LayoutKind.Explicit, Size = 0x28)] diff --git a/src/Nexus.GameOffsets/Components/StateMachine.cs b/src/Roboto.GameOffsets/Components/StateMachine.cs similarity index 86% rename from src/Nexus.GameOffsets/Components/StateMachine.cs rename to src/Roboto.GameOffsets/Components/StateMachine.cs index 2ce904b..d912bf7 100644 --- a/src/Nexus.GameOffsets/Components/StateMachine.cs +++ b/src/Roboto.GameOffsets/Components/StateMachine.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// StateMachine component — entity state management. [StructLayout(LayoutKind.Explicit, Size = 0x178)] diff --git a/src/Nexus.GameOffsets/Components/Stats.cs b/src/Roboto.GameOffsets/Components/Stats.cs similarity index 91% rename from src/Nexus.GameOffsets/Components/Stats.cs rename to src/Roboto.GameOffsets/Components/Stats.cs index c6b43db..5f74b43 100644 --- a/src/Nexus.GameOffsets/Components/Stats.cs +++ b/src/Roboto.GameOffsets/Components/Stats.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Stats component — item stats, weapon index, shapeshift. [StructLayout(LayoutKind.Explicit, Size = 0x180)] diff --git a/src/Nexus.GameOffsets/Components/Targetable.cs b/src/Roboto.GameOffsets/Components/Targetable.cs similarity index 94% rename from src/Nexus.GameOffsets/Components/Targetable.cs rename to src/Roboto.GameOffsets/Components/Targetable.cs index 3dfdb4e..bc74585 100644 --- a/src/Nexus.GameOffsets/Components/Targetable.cs +++ b/src/Roboto.GameOffsets/Components/Targetable.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Targetable component — whether entity can be targeted/highlighted. [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 0x58)] diff --git a/src/Nexus.GameOffsets/Components/Transitionable.cs b/src/Roboto.GameOffsets/Components/Transitionable.cs similarity index 89% rename from src/Nexus.GameOffsets/Components/Transitionable.cs rename to src/Roboto.GameOffsets/Components/Transitionable.cs index 79f4f24..25ec8d9 100644 --- a/src/Nexus.GameOffsets/Components/Transitionable.cs +++ b/src/Roboto.GameOffsets/Components/Transitionable.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// Transitionable component — area transition state. [StructLayout(LayoutKind.Explicit, Size = 0x128)] diff --git a/src/Nexus.GameOffsets/Components/TriggerableBlockage.cs b/src/Roboto.GameOffsets/Components/TriggerableBlockage.cs similarity index 90% rename from src/Nexus.GameOffsets/Components/TriggerableBlockage.cs rename to src/Roboto.GameOffsets/Components/TriggerableBlockage.cs index 90d1b82..4268448 100644 --- a/src/Nexus.GameOffsets/Components/TriggerableBlockage.cs +++ b/src/Roboto.GameOffsets/Components/TriggerableBlockage.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// TriggerableBlockage component — door/gate blocked state. [StructLayout(LayoutKind.Explicit, Size = 0x38)] diff --git a/src/Nexus.GameOffsets/Components/WorldItem.cs b/src/Roboto.GameOffsets/Components/WorldItem.cs similarity index 91% rename from src/Nexus.GameOffsets/Components/WorldItem.cs rename to src/Roboto.GameOffsets/Components/WorldItem.cs index cf462e8..ed39760 100644 --- a/src/Nexus.GameOffsets/Components/WorldItem.cs +++ b/src/Roboto.GameOffsets/Components/WorldItem.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Components; +namespace Roboto.GameOffsets.Components; /// WorldItem component — contains pointer to inner item entity at +0x28 (TBD from diagnostic). [StructLayout(LayoutKind.Explicit, Size = 0x30)] diff --git a/src/Nexus.GameOffsets/Entities/Entity.cs b/src/Roboto.GameOffsets/Entities/Entity.cs similarity index 96% rename from src/Nexus.GameOffsets/Entities/Entity.cs rename to src/Roboto.GameOffsets/Entities/Entity.cs index d2ebfa5..c6b7a00 100644 --- a/src/Nexus.GameOffsets/Entities/Entity.cs +++ b/src/Roboto.GameOffsets/Entities/Entity.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.Entities; +namespace Roboto.GameOffsets.Entities; /// Item struct — wraps an entity pointer for inventory items. [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/Nexus.GameOffsets/Entities/EntityTreeNode.cs b/src/Roboto.GameOffsets/Entities/EntityTreeNode.cs similarity index 97% rename from src/Nexus.GameOffsets/Entities/EntityTreeNode.cs rename to src/Roboto.GameOffsets/Entities/EntityTreeNode.cs index a37cd6e..9b9e0cd 100644 --- a/src/Nexus.GameOffsets/Entities/EntityTreeNode.cs +++ b/src/Roboto.GameOffsets/Entities/EntityTreeNode.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Entities; +namespace Roboto.GameOffsets.Entities; /// MSVC std::map tree node for entity storage. Red-black tree node layout. [StructLayout(LayoutKind.Explicit, Size = 0x30)] diff --git a/src/Nexus.GameOffsets/Natives/StdBucket.cs b/src/Roboto.GameOffsets/Natives/StdBucket.cs similarity index 87% rename from src/Nexus.GameOffsets/Natives/StdBucket.cs rename to src/Roboto.GameOffsets/Natives/StdBucket.cs index ee515de..94f2b84 100644 --- a/src/Nexus.GameOffsets/Natives/StdBucket.cs +++ b/src/Roboto.GameOffsets/Natives/StdBucket.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Natives; +namespace Roboto.GameOffsets.Natives; /// Bucket structure used in hash containers. [StructLayout(LayoutKind.Explicit, Size = 0x20)] diff --git a/src/Nexus.GameOffsets/Natives/StdList.cs b/src/Roboto.GameOffsets/Natives/StdList.cs similarity index 94% rename from src/Nexus.GameOffsets/Natives/StdList.cs rename to src/Roboto.GameOffsets/Natives/StdList.cs index 97dc432..a1284c7 100644 --- a/src/Nexus.GameOffsets/Natives/StdList.cs +++ b/src/Roboto.GameOffsets/Natives/StdList.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Natives; +namespace Roboto.GameOffsets.Natives; /// MSVC std::list layout: head node pointer + size. [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/Nexus.GameOffsets/Natives/StdMap.cs b/src/Roboto.GameOffsets/Natives/StdMap.cs similarity index 96% rename from src/Nexus.GameOffsets/Natives/StdMap.cs rename to src/Roboto.GameOffsets/Natives/StdMap.cs index fac819a..6195ad1 100644 --- a/src/Nexus.GameOffsets/Natives/StdMap.cs +++ b/src/Roboto.GameOffsets/Natives/StdMap.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Natives; +namespace Roboto.GameOffsets.Natives; /// MSVC std::map layout: head node pointer + size. [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/Nexus.GameOffsets/Natives/StdTuple.cs b/src/Roboto.GameOffsets/Natives/StdTuple.cs similarity index 92% rename from src/Nexus.GameOffsets/Natives/StdTuple.cs rename to src/Roboto.GameOffsets/Natives/StdTuple.cs index 6d55f8a..2258e00 100644 --- a/src/Nexus.GameOffsets/Natives/StdTuple.cs +++ b/src/Roboto.GameOffsets/Natives/StdTuple.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Natives; +namespace Roboto.GameOffsets.Natives; /// 2D tuple (e.g., terrain dimensions). [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/Nexus.GameOffsets/Natives/StdVector.cs b/src/Roboto.GameOffsets/Natives/StdVector.cs similarity index 90% rename from src/Nexus.GameOffsets/Natives/StdVector.cs rename to src/Roboto.GameOffsets/Natives/StdVector.cs index 3e3028f..beeb591 100644 --- a/src/Nexus.GameOffsets/Natives/StdVector.cs +++ b/src/Roboto.GameOffsets/Natives/StdVector.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Natives; +namespace Roboto.GameOffsets.Natives; /// MSVC std::vector layout: begin/end/capacity pointers. [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/Nexus.GameOffsets/Natives/StdWString.cs b/src/Roboto.GameOffsets/Natives/StdWString.cs similarity index 95% rename from src/Nexus.GameOffsets/Natives/StdWString.cs rename to src/Roboto.GameOffsets/Natives/StdWString.cs index cc38300..0587dfb 100644 --- a/src/Nexus.GameOffsets/Natives/StdWString.cs +++ b/src/Roboto.GameOffsets/Natives/StdWString.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.Natives; +namespace Roboto.GameOffsets.Natives; /// MSVC std::wstring (basic_string<wchar_t>) with SSO. [StructLayout(LayoutKind.Explicit, Size = 0x20)] diff --git a/src/Nexus.GameOffsets/Natives/Util.cs b/src/Roboto.GameOffsets/Natives/Util.cs similarity index 86% rename from src/Nexus.GameOffsets/Natives/Util.cs rename to src/Roboto.GameOffsets/Natives/Util.cs index 2cd77ef..e4972ca 100644 --- a/src/Nexus.GameOffsets/Natives/Util.cs +++ b/src/Roboto.GameOffsets/Natives/Util.cs @@ -1,4 +1,4 @@ -namespace Nexus.GameOffsets.Natives; +namespace Roboto.GameOffsets.Natives; public static class Util { diff --git a/src/Nexus.GameOffsets/Nexus.GameOffsets.csproj b/src/Roboto.GameOffsets/Roboto.GameOffsets.csproj similarity index 100% rename from src/Nexus.GameOffsets/Nexus.GameOffsets.csproj rename to src/Roboto.GameOffsets/Roboto.GameOffsets.csproj diff --git a/src/Nexus.GameOffsets/States/AreaInstance.cs b/src/Roboto.GameOffsets/States/AreaInstance.cs similarity index 97% rename from src/Nexus.GameOffsets/States/AreaInstance.cs rename to src/Roboto.GameOffsets/States/AreaInstance.cs index 7c6afe4..6e02ac2 100644 --- a/src/Nexus.GameOffsets/States/AreaInstance.cs +++ b/src/Roboto.GameOffsets/States/AreaInstance.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.States; +namespace Roboto.GameOffsets.States; /// AreaInstance (IngameData) — current area data, entities, terrain. [StructLayout(LayoutKind.Explicit, Size = 0xCD0)] diff --git a/src/Nexus.GameOffsets/States/AreaLoading.cs b/src/Roboto.GameOffsets/States/AreaLoading.cs similarity index 94% rename from src/Nexus.GameOffsets/States/AreaLoading.cs rename to src/Roboto.GameOffsets/States/AreaLoading.cs index af34573..6774c68 100644 --- a/src/Nexus.GameOffsets/States/AreaLoading.cs +++ b/src/Roboto.GameOffsets/States/AreaLoading.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.States; +namespace Roboto.GameOffsets.States; /// AreaLoading state — loading screen info. [StructLayout(LayoutKind.Explicit, Size = 0xE58)] diff --git a/src/Nexus.GameOffsets/States/ImportantUiElements.cs b/src/Roboto.GameOffsets/States/ImportantUiElements.cs similarity index 95% rename from src/Nexus.GameOffsets/States/ImportantUiElements.cs rename to src/Roboto.GameOffsets/States/ImportantUiElements.cs index ff5e83e..b636ed3 100644 --- a/src/Nexus.GameOffsets/States/ImportantUiElements.cs +++ b/src/Roboto.GameOffsets/States/ImportantUiElements.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.States; +namespace Roboto.GameOffsets.States; /// Important UI element pointers within InGameState. [StructLayout(LayoutKind.Explicit, Size = 0x740)] diff --git a/src/Nexus.GameOffsets/States/InGameState.cs b/src/Roboto.GameOffsets/States/InGameState.cs similarity index 96% rename from src/Nexus.GameOffsets/States/InGameState.cs rename to src/Roboto.GameOffsets/States/InGameState.cs index f3a657f..ff49244 100644 --- a/src/Nexus.GameOffsets/States/InGameState.cs +++ b/src/Roboto.GameOffsets/States/InGameState.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.GameOffsets.States; +namespace Roboto.GameOffsets.States; /// InGameState — the main in-game state containing all sub-structures. [StructLayout(LayoutKind.Explicit, Size = 0x310)] diff --git a/src/Nexus.GameOffsets/States/Inventory.cs b/src/Roboto.GameOffsets/States/Inventory.cs similarity index 95% rename from src/Nexus.GameOffsets/States/Inventory.cs rename to src/Roboto.GameOffsets/States/Inventory.cs index b0bb0a1..81d0acc 100644 --- a/src/Nexus.GameOffsets/States/Inventory.cs +++ b/src/Roboto.GameOffsets/States/Inventory.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.States; +namespace Roboto.GameOffsets.States; /// Pre-inventory wrapper — contains the actual inventory pointer. [StructLayout(LayoutKind.Explicit, Size = 0x10)] diff --git a/src/Nexus.GameOffsets/States/ServerData.cs b/src/Roboto.GameOffsets/States/ServerData.cs similarity index 91% rename from src/Nexus.GameOffsets/States/ServerData.cs rename to src/Roboto.GameOffsets/States/ServerData.cs index 705d811..de1e35c 100644 --- a/src/Nexus.GameOffsets/States/ServerData.cs +++ b/src/Roboto.GameOffsets/States/ServerData.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Natives; -namespace Nexus.GameOffsets.States; +namespace Roboto.GameOffsets.States; /// ServerData — player server-side data. [StructLayout(LayoutKind.Explicit, Size = 0x58)] diff --git a/src/Nexus.GameOffsets/States/WorldData.cs b/src/Roboto.GameOffsets/States/WorldData.cs similarity index 97% rename from src/Nexus.GameOffsets/States/WorldData.cs rename to src/Roboto.GameOffsets/States/WorldData.cs index 80a131b..badd798 100644 --- a/src/Nexus.GameOffsets/States/WorldData.cs +++ b/src/Roboto.GameOffsets/States/WorldData.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; using System.Numerics; -namespace Nexus.GameOffsets.States; +namespace Roboto.GameOffsets.States; /// WorldData — world area details and camera. [StructLayout(LayoutKind.Explicit, Size = 0xA8)] diff --git a/src/Nexus.Input/Humanizer.cs b/src/Roboto.Input/Humanizer.cs similarity index 98% rename from src/Nexus.Input/Humanizer.cs rename to src/Roboto.Input/Humanizer.cs index 668b73d..36a56ab 100644 --- a/src/Nexus.Input/Humanizer.cs +++ b/src/Roboto.Input/Humanizer.cs @@ -1,6 +1,6 @@ -using Nexus.Core; +using Roboto.Core; -namespace Nexus.Input; +namespace Roboto.Input; public sealed class Humanizer { diff --git a/src/Nexus.Input/InterceptionInputController.cs b/src/Roboto.Input/InterceptionInputController.cs similarity index 81% rename from src/Nexus.Input/InterceptionInputController.cs rename to src/Roboto.Input/InterceptionInputController.cs index 33bbde4..e4e1ff1 100644 --- a/src/Nexus.Input/InterceptionInputController.cs +++ b/src/Roboto.Input/InterceptionInputController.cs @@ -1,9 +1,9 @@ using System.Runtime.InteropServices; using InputInterceptorNS; -using Nexus.Core; +using Roboto.Core; using Serilog; -namespace Nexus.Input; +namespace Roboto.Input; public sealed partial class InterceptionInputController : IInputController, IDisposable { @@ -73,12 +73,10 @@ public sealed partial class InterceptionInputController : IInputController, IDis if (_humanizer is not null) { if (_humanizer.ShouldThrottle()) return; + holdMs = _humanizer.GaussianDelay(holdMs); _humanizer.RecordAction(); } - var hold = HoldMs(); - Log.Information("[Key] 0x{ScanCode:X2} DOWN (hold={HoldMs}ms)", scanCode, hold); - _keyboard?.SimulateKeyPress((KeyCode)scanCode, hold); - Log.Information("[Key] 0x{ScanCode:X2} UP", scanCode); + _keyboard?.SimulateKeyPress((KeyCode)scanCode, holdMs); } public void MouseMoveTo(int x, int y) @@ -152,10 +150,7 @@ public sealed partial class InterceptionInputController : IInputController, IDis } SmoothMoveTo(x, y); Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10); - var hold = HoldMs(); - Log.Information("[Click] Left DOWN (hold={HoldMs}ms)", hold); - _mouse?.SimulateLeftButtonClick(hold); - Log.Information("[Click] Left UP"); + _mouse?.SimulateLeftButtonClick(_humanizer?.GaussianDelay(50) ?? 50); } public void RightClick(int x, int y) @@ -169,10 +164,7 @@ public sealed partial class InterceptionInputController : IInputController, IDis } SmoothMoveTo(x, y); Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10); - var hold = HoldMs(); - Log.Information("[Click] Right DOWN (hold={HoldMs}ms)", hold); - _mouse?.SimulateRightButtonClick(hold); - Log.Information("[Click] Right UP"); + _mouse?.SimulateRightButtonClick(_humanizer?.GaussianDelay(50) ?? 50); } public void MiddleClick(int x, int y) @@ -186,24 +178,7 @@ public sealed partial class InterceptionInputController : IInputController, IDis } SmoothMoveTo(x, y); Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10); - var hold = HoldMs(); - Log.Information("[Click] Middle DOWN (hold={HoldMs}ms)", hold); - _mouse?.SimulateMiddleButtonClick(hold); - Log.Information("[Click] Middle UP"); - } - - /// Gaussian hold duration peaked at 55ms, range [44, 76]. - private static int HoldMs() - { - double u, v, s; - do - { - u = Rng.NextDouble() * 2.0 - 1.0; - v = Rng.NextDouble() * 2.0 - 1.0; - s = u * u + v * v; - } while (s >= 1.0 || s == 0.0); - var g = u * Math.Sqrt(-2.0 * Math.Log(s) / s); - return Math.Clamp((int)Math.Round(55.0 + g * 6.0), 44, 76); + _mouse?.SimulateMiddleButtonClick(_humanizer?.GaussianDelay(50) ?? 50); } public void LeftDown() diff --git a/src/Nexus.Input/Nexus.Input.csproj b/src/Roboto.Input/Roboto.Input.csproj similarity index 89% rename from src/Nexus.Input/Nexus.Input.csproj rename to src/Roboto.Input/Roboto.Input.csproj index a8e735e..d216727 100644 --- a/src/Nexus.Input/Nexus.Input.csproj +++ b/src/Roboto.Input/Roboto.Input.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Nexus.Core/ScanCodes.cs b/src/Roboto.Input/ScanCodes.cs similarity index 94% rename from src/Nexus.Core/ScanCodes.cs rename to src/Roboto.Input/ScanCodes.cs index 16c1069..d0432ba 100644 --- a/src/Nexus.Core/ScanCodes.cs +++ b/src/Roboto.Input/ScanCodes.cs @@ -1,7 +1,7 @@ -namespace Nexus.Core; +namespace Roboto.Input; /// -/// Hardware scan codes for keyboard input. +/// Hardware scan codes for keyboard input via Interception driver. /// public static class ScanCodes { diff --git a/src/Nexus.Input/SendInputController.cs b/src/Roboto.Input/SendInputController.cs similarity index 86% rename from src/Nexus.Input/SendInputController.cs rename to src/Roboto.Input/SendInputController.cs index b9b3873..242cb31 100644 --- a/src/Nexus.Input/SendInputController.cs +++ b/src/Roboto.Input/SendInputController.cs @@ -1,8 +1,8 @@ using System.Runtime.InteropServices; -using Nexus.Core; +using Roboto.Core; using Serilog; -namespace Nexus.Input; +namespace Roboto.Input; /// /// Fallback input controller using Win32 SendInput with KEYEVENTF_SCANCODE. @@ -46,15 +46,13 @@ public sealed partial class SendInputController : IInputController if (_humanizer is not null) { if (_humanizer.ShouldThrottle()) return; + holdMs = _humanizer.GaussianDelay(holdMs); _humanizer.RecordAction(); } - var hold = HoldMs(); - Log.Information("[Key] 0x{ScanCode:X2} DOWN (hold={HoldMs}ms)", scanCode, hold); KeyDown(scanCode); - Thread.Sleep(hold); + Thread.Sleep(holdMs); KeyUp(scanCode); - Log.Information("[Key] 0x{ScanCode:X2} UP", scanCode); } // ── Mouse movement ── @@ -115,7 +113,7 @@ public sealed partial class SendInputController : IInputController } SmoothMoveTo(x, y); Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10); - MouseClick(MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, HoldMs()); + MouseClick(MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, _humanizer?.GaussianDelay(50) ?? 50); } public void RightClick(int x, int y) @@ -129,7 +127,7 @@ public sealed partial class SendInputController : IInputController } SmoothMoveTo(x, y); Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10); - MouseClick(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, HoldMs()); + MouseClick(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, _humanizer?.GaussianDelay(50) ?? 50); } public void MiddleClick(int x, int y) @@ -143,7 +141,7 @@ public sealed partial class SendInputController : IInputController } SmoothMoveTo(x, y); Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10); - MouseClick(MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, HoldMs()); + MouseClick(MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, _humanizer?.GaussianDelay(50) ?? 50); } public void LeftDown() @@ -172,38 +170,13 @@ public sealed partial class SendInputController : IInputController // ── Private helpers ── - /// Gaussian hold duration peaked at 55ms, range [44, 76]. - private static int HoldMs() - { - double u, v, s; - do - { - u = Rng.NextDouble() * 2.0 - 1.0; - v = Rng.NextDouble() * 2.0 - 1.0; - s = u * u + v * v; - } while (s >= 1.0 || s == 0.0); - var g = u * Math.Sqrt(-2.0 * Math.Log(s) / s); - return Math.Clamp((int)Math.Round(55.0 + g * 6.0), 44, 76); - } - - private static string ClickName(uint downFlag) => downFlag switch - { - MOUSEEVENTF_LEFTDOWN => "Left", - MOUSEEVENTF_RIGHTDOWN => "Right", - MOUSEEVENTF_MIDDLEDOWN => "Middle", - _ => "?" - }; - private void MouseClick(uint downFlag, uint upFlag, int holdMs) { - var name = ClickName(downFlag); - Log.Information("[Click] {Button} DOWN (hold={HoldMs}ms)", name, holdMs); var down = MakeMouseInput(downFlag); var up = MakeMouseInput(upFlag); SendInput(1, [down], INPUT_SIZE); Thread.Sleep(holdMs); SendInput(1, [up], INPUT_SIZE); - Log.Information("[Click] {Button} UP", name); } private static double EaseInOutQuad(double t) => diff --git a/src/Nexus.Input/interception.dll b/src/Roboto.Input/interception.dll similarity index 100% rename from src/Nexus.Input/interception.dll rename to src/Roboto.Input/interception.dll diff --git a/src/Nexus.Memory/Diagnostics/MemoryDiagnostics.cs b/src/Roboto.Memory/Diagnostics/MemoryDiagnostics.cs similarity index 99% rename from src/Nexus.Memory/Diagnostics/MemoryDiagnostics.cs rename to src/Roboto.Memory/Diagnostics/MemoryDiagnostics.cs index 41e9204..95c9df4 100644 --- a/src/Nexus.Memory/Diagnostics/MemoryDiagnostics.cs +++ b/src/Roboto.Memory/Diagnostics/MemoryDiagnostics.cs @@ -3,10 +3,10 @@ using System.Drawing.Imaging; using System.Globalization; using System.Runtime.InteropServices; using System.Text; -using Nexus.Memory.Objects; +using Roboto.Memory.Objects; using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Diagnostic and scan methods extracted from GameMemoryReader. @@ -7583,7 +7583,7 @@ public sealed class MemoryDiagnostics var modsPtr = _ctx.Memory.ReadPointer(iFirst + modsIdx * 8); if (modsPtr != 0) { - var mods = _ctx.Memory.Read(modsPtr); + var mods = _ctx.Memory.Read(modsPtr); sb.AppendLine($" Mods.Rarity: {mods.Rarity}"); } } diff --git a/src/Nexus.Memory/Diagnostics/QuestNameLookup.cs b/src/Roboto.Memory/Diagnostics/QuestNameLookup.cs similarity index 98% rename from src/Nexus.Memory/Diagnostics/QuestNameLookup.cs rename to src/Roboto.Memory/Diagnostics/QuestNameLookup.cs index cae14b4..3cda3e9 100644 --- a/src/Nexus.Memory/Diagnostics/QuestNameLookup.cs +++ b/src/Roboto.Memory/Diagnostics/QuestNameLookup.cs @@ -1,7 +1,7 @@ using System.Text.Json; using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Loads quest name mappings from a JSON file (generated by tools/dump_quest_names.py). diff --git a/src/Nexus.Memory/Files/DatFile.cs b/src/Roboto.Memory/Files/DatFile.cs similarity index 99% rename from src/Nexus.Memory/Files/DatFile.cs rename to src/Roboto.Memory/Files/DatFile.cs index e220a22..a3fde7c 100644 --- a/src/Nexus.Memory/Files/DatFile.cs +++ b/src/Roboto.Memory/Files/DatFile.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Generic wrapper for a single .dat table. Bulk-reads all rows in one RPM call, diff --git a/src/Nexus.Memory/Files/FileRootScanner.cs b/src/Roboto.Memory/Files/FileRootScanner.cs similarity index 99% rename from src/Nexus.Memory/Files/FileRootScanner.cs rename to src/Roboto.Memory/Files/FileRootScanner.cs index 12beda1..7287154 100644 --- a/src/Nexus.Memory/Files/FileRootScanner.cs +++ b/src/Roboto.Memory/Files/FileRootScanner.cs @@ -1,7 +1,7 @@ using System.Text; using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Info about a discovered .dat file in memory: first record address, row size, and row count. diff --git a/src/Nexus.Memory/Files/FilesContainer.cs b/src/Roboto.Memory/Files/FilesContainer.cs similarity index 99% rename from src/Nexus.Memory/Files/FilesContainer.cs rename to src/Roboto.Memory/Files/FilesContainer.cs index 07c37c9..f94957c 100644 --- a/src/Nexus.Memory/Files/FilesContainer.cs +++ b/src/Roboto.Memory/Files/FilesContainer.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Facade for all in-memory .dat file access. Owns the diff --git a/src/Nexus.Memory/Files/IDatRowParser.cs b/src/Roboto.Memory/Files/IDatRowParser.cs similarity index 97% rename from src/Nexus.Memory/Files/IDatRowParser.cs rename to src/Roboto.Memory/Files/IDatRowParser.cs index be9a8fb..e72cbea 100644 --- a/src/Nexus.Memory/Files/IDatRowParser.cs +++ b/src/Roboto.Memory/Files/IDatRowParser.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Stateless parser for a single .dat row schema. One implementation per .dat file type. diff --git a/src/Nexus.Memory/Files/MapPinRow.cs b/src/Roboto.Memory/Files/MapPinRow.cs similarity index 98% rename from src/Nexus.Memory/Files/MapPinRow.cs rename to src/Roboto.Memory/Files/MapPinRow.cs index 92e44e3..995c7a2 100644 --- a/src/Nexus.Memory/Files/MapPinRow.cs +++ b/src/Roboto.Memory/Files/MapPinRow.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Parsed row from MapPins.dat. diff --git a/src/Nexus.Memory/Files/QuestRow.cs b/src/Roboto.Memory/Files/QuestRow.cs similarity index 98% rename from src/Nexus.Memory/Files/QuestRow.cs rename to src/Roboto.Memory/Files/QuestRow.cs index 7d907f2..321e9b4 100644 --- a/src/Nexus.Memory/Files/QuestRow.cs +++ b/src/Roboto.Memory/Files/QuestRow.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Parsed row from Quest.dat. Fields mirror the in-memory Quest object layout. diff --git a/src/Nexus.Memory/Files/QuestStateRow.cs b/src/Roboto.Memory/Files/QuestStateRow.cs similarity index 99% rename from src/Nexus.Memory/Files/QuestStateRow.cs rename to src/Roboto.Memory/Files/QuestStateRow.cs index 991d816..555b725 100644 --- a/src/Nexus.Memory/Files/QuestStateRow.cs +++ b/src/Roboto.Memory/Files/QuestStateRow.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Parsed row from QuestStates.dat (208 bytes per row). diff --git a/src/Nexus.Memory/Files/WorldAreaRow.cs b/src/Roboto.Memory/Files/WorldAreaRow.cs similarity index 99% rename from src/Nexus.Memory/Files/WorldAreaRow.cs rename to src/Roboto.Memory/Files/WorldAreaRow.cs index cb47a02..ef95113 100644 --- a/src/Nexus.Memory/Files/WorldAreaRow.cs +++ b/src/Roboto.Memory/Files/WorldAreaRow.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Parsed row from WorldAreas.dat (same layout as AreaTemplate offsets). diff --git a/src/Nexus.Memory/GameMemoryReader.cs b/src/Roboto.Memory/GameMemoryReader.cs similarity index 99% rename from src/Nexus.Memory/GameMemoryReader.cs rename to src/Roboto.Memory/GameMemoryReader.cs index 9fb6667..acf3743 100644 --- a/src/Nexus.Memory/GameMemoryReader.cs +++ b/src/Roboto.Memory/GameMemoryReader.cs @@ -1,8 +1,8 @@ using System.Numerics; -using Nexus.Memory.Objects; +using Roboto.Memory.Objects; using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; public class GameMemoryReader : IDisposable { diff --git a/src/Nexus.Memory/GameOffsets.cs b/src/Roboto.Memory/GameOffsets.cs similarity index 99% rename from src/Nexus.Memory/GameOffsets.cs rename to src/Roboto.Memory/GameOffsets.cs index ac0828c..283783d 100644 --- a/src/Nexus.Memory/GameOffsets.cs +++ b/src/Roboto.Memory/GameOffsets.cs @@ -3,7 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Reads/writes int as hex strings ("0x1A8") or plain numbers (424). diff --git a/src/Nexus.Memory/GameStateReader.cs b/src/Roboto.Memory/GameStateReader.cs similarity index 99% rename from src/Nexus.Memory/GameStateReader.cs rename to src/Roboto.Memory/GameStateReader.cs index a2d536a..71ab564 100644 --- a/src/Nexus.Memory/GameStateReader.cs +++ b/src/Roboto.Memory/GameStateReader.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Resolves GameState → Controller → InGameState, reads state slots, loading/escape state. diff --git a/src/Nexus.Memory/Infrastructure/ComponentReader.cs b/src/Roboto.Memory/Infrastructure/ComponentReader.cs similarity index 99% rename from src/Nexus.Memory/Infrastructure/ComponentReader.cs rename to src/Roboto.Memory/Infrastructure/ComponentReader.cs index eea5517..2da856d 100644 --- a/src/Nexus.Memory/Infrastructure/ComponentReader.cs +++ b/src/Roboto.Memory/Infrastructure/ComponentReader.cs @@ -1,9 +1,9 @@ using System.Text; -using Nexus.GameOffsets.Components; -using Nexus.GameOffsets.Natives; +using Roboto.GameOffsets.Components; +using Roboto.GameOffsets.Natives; using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Reads entity components via ECS: component list discovery, vitals, position, component lookup. diff --git a/src/Nexus.Memory/Infrastructure/MemoryContext.cs b/src/Roboto.Memory/Infrastructure/MemoryContext.cs similarity index 98% rename from src/Nexus.Memory/Infrastructure/MemoryContext.cs rename to src/Roboto.Memory/Infrastructure/MemoryContext.cs index 9baee9e..dc42940 100644 --- a/src/Nexus.Memory/Infrastructure/MemoryContext.cs +++ b/src/Roboto.Memory/Infrastructure/MemoryContext.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Shared state for all memory reader classes. Holds the process handle, offsets, registry, diff --git a/src/Nexus.Memory/Infrastructure/MemoryProfiler.cs b/src/Roboto.Memory/Infrastructure/MemoryProfiler.cs similarity index 98% rename from src/Nexus.Memory/Infrastructure/MemoryProfiler.cs rename to src/Roboto.Memory/Infrastructure/MemoryProfiler.cs index 390a8e8..e7d5626 100644 --- a/src/Nexus.Memory/Infrastructure/MemoryProfiler.cs +++ b/src/Roboto.Memory/Infrastructure/MemoryProfiler.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Thread-static section profiler for memory reads. When enabled, attributes each diff --git a/src/Nexus.Memory/Infrastructure/MsvcStringReader.cs b/src/Roboto.Memory/Infrastructure/MsvcStringReader.cs similarity index 99% rename from src/Nexus.Memory/Infrastructure/MsvcStringReader.cs rename to src/Roboto.Memory/Infrastructure/MsvcStringReader.cs index 110dcf0..c1b0014 100644 --- a/src/Nexus.Memory/Infrastructure/MsvcStringReader.cs +++ b/src/Roboto.Memory/Infrastructure/MsvcStringReader.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Reads MSVC std::string and std::wstring from process memory. diff --git a/src/Nexus.Memory/Infrastructure/Native.cs b/src/Roboto.Memory/Infrastructure/Native.cs similarity index 98% rename from src/Nexus.Memory/Infrastructure/Native.cs rename to src/Roboto.Memory/Infrastructure/Native.cs index 105c02e..1294226 100644 --- a/src/Nexus.Memory/Infrastructure/Native.cs +++ b/src/Roboto.Memory/Infrastructure/Native.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Nexus.Memory; +namespace Roboto.Memory; internal static partial class Native { diff --git a/src/Nexus.Memory/Infrastructure/ObjectRegistry.cs b/src/Roboto.Memory/Infrastructure/ObjectRegistry.cs similarity index 99% rename from src/Nexus.Memory/Infrastructure/ObjectRegistry.cs rename to src/Roboto.Memory/Infrastructure/ObjectRegistry.cs index 92f8cb5..020ed1c 100644 --- a/src/Nexus.Memory/Infrastructure/ObjectRegistry.cs +++ b/src/Roboto.Memory/Infrastructure/ObjectRegistry.cs @@ -1,7 +1,7 @@ using System.Text.Json; using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Persistent registry of discovered strings, organized by category. diff --git a/src/Nexus.Memory/Infrastructure/PatternScanner.cs b/src/Roboto.Memory/Infrastructure/PatternScanner.cs similarity index 99% rename from src/Nexus.Memory/Infrastructure/PatternScanner.cs rename to src/Roboto.Memory/Infrastructure/PatternScanner.cs index ce3e9c7..379d880 100644 --- a/src/Nexus.Memory/Infrastructure/PatternScanner.cs +++ b/src/Roboto.Memory/Infrastructure/PatternScanner.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; public sealed class PatternScanner { diff --git a/src/Nexus.Memory/Infrastructure/ProcessMemory.cs b/src/Roboto.Memory/Infrastructure/ProcessMemory.cs similarity index 99% rename from src/Nexus.Memory/Infrastructure/ProcessMemory.cs rename to src/Roboto.Memory/Infrastructure/ProcessMemory.cs index 72a9435..1f1d1e4 100644 --- a/src/Nexus.Memory/Infrastructure/ProcessMemory.cs +++ b/src/Roboto.Memory/Infrastructure/ProcessMemory.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; public sealed class ProcessMemory : IDisposable { diff --git a/src/Nexus.Memory/Infrastructure/RttiResolver.cs b/src/Roboto.Memory/Infrastructure/RttiResolver.cs similarity index 98% rename from src/Nexus.Memory/Infrastructure/RttiResolver.cs rename to src/Roboto.Memory/Infrastructure/RttiResolver.cs index a1b02fc..0337b4f 100644 --- a/src/Nexus.Memory/Infrastructure/RttiResolver.cs +++ b/src/Roboto.Memory/Infrastructure/RttiResolver.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Resolves MSVC x64 RTTI type names from vtable addresses and classifies pointers. diff --git a/src/Nexus.Memory/Objects/AreaInstance.cs b/src/Roboto.Memory/Objects/AreaInstance.cs similarity index 99% rename from src/Nexus.Memory/Objects/AreaInstance.cs rename to src/Roboto.Memory/Objects/AreaInstance.cs index 0c7fb53..644e605 100644 --- a/src/Nexus.Memory/Objects/AreaInstance.cs +++ b/src/Roboto.Memory/Objects/AreaInstance.cs @@ -1,6 +1,6 @@ -using Nexus.Memory; +using Roboto.Memory; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads fields from the AreaInstance (IngameData) address. diff --git a/src/Nexus.Memory/Objects/AreaLoading.cs b/src/Roboto.Memory/Objects/AreaLoading.cs similarity index 94% rename from src/Nexus.Memory/Objects/AreaLoading.cs rename to src/Roboto.Memory/Objects/AreaLoading.cs index de10f14..37f93b2 100644 --- a/src/Nexus.Memory/Objects/AreaLoading.cs +++ b/src/Roboto.Memory/Objects/AreaLoading.cs @@ -1,6 +1,6 @@ -using Nexus.Memory; +using Roboto.Memory; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads AreaLoading state (slot 0). Individual field reads — the full struct is 3672B, wasteful to bulk-read. diff --git a/src/Nexus.Memory/Objects/AreaTemplate.cs b/src/Roboto.Memory/Objects/AreaTemplate.cs similarity index 98% rename from src/Nexus.Memory/Objects/AreaTemplate.cs rename to src/Roboto.Memory/Objects/AreaTemplate.cs index 7337936..2ee258d 100644 --- a/src/Nexus.Memory/Objects/AreaTemplate.cs +++ b/src/Roboto.Memory/Objects/AreaTemplate.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads AreaTemplate fields from WorldData → WorldAreaDetailsPtr. diff --git a/src/Nexus.Memory/Objects/EntityList.cs b/src/Roboto.Memory/Objects/EntityList.cs similarity index 99% rename from src/Nexus.Memory/Objects/EntityList.cs rename to src/Roboto.Memory/Objects/EntityList.cs index bfd8f98..c7dc3f4 100644 --- a/src/Nexus.Memory/Objects/EntityList.cs +++ b/src/Roboto.Memory/Objects/EntityList.cs @@ -1,8 +1,8 @@ -using Nexus.Memory; -using Nexus.GameOffsets.Components; -using Nexus.GameOffsets.Entities; +using Roboto.Memory; +using Roboto.GameOffsets.Components; +using Roboto.GameOffsets.Entities; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads entity list from AreaInstance's std::map red-black tree. diff --git a/src/Nexus.Memory/Objects/GameStateType.cs b/src/Roboto.Memory/Objects/GameStateType.cs similarity index 95% rename from src/Nexus.Memory/Objects/GameStateType.cs rename to src/Roboto.Memory/Objects/GameStateType.cs index 254083c..421b04f 100644 --- a/src/Nexus.Memory/Objects/GameStateType.cs +++ b/src/Roboto.Memory/Objects/GameStateType.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Game state types by slot index. Order must match the state array in the controller. diff --git a/src/Nexus.Memory/Objects/GameStates.cs b/src/Roboto.Memory/Objects/GameStates.cs similarity index 99% rename from src/Nexus.Memory/Objects/GameStates.cs rename to src/Roboto.Memory/Objects/GameStates.cs index 1089229..1c35d2c 100644 --- a/src/Nexus.Memory/Objects/GameStates.cs +++ b/src/Roboto.Memory/Objects/GameStates.cs @@ -1,6 +1,6 @@ -using Nexus.Memory; +using Roboto.Memory; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Root state orchestrator. Reads controller from GameStateBase, resolves state slot pointers, diff --git a/src/Nexus.Memory/Objects/InGameState.cs b/src/Roboto.Memory/Objects/InGameState.cs similarity index 95% rename from src/Nexus.Memory/Objects/InGameState.cs rename to src/Roboto.Memory/Objects/InGameState.cs index 080b1b3..87e678c 100644 --- a/src/Nexus.Memory/Objects/InGameState.cs +++ b/src/Roboto.Memory/Objects/InGameState.cs @@ -1,7 +1,7 @@ -using Nexus.Memory; -using IgsStruct = Nexus.GameOffsets.States.InGameState; +using Roboto.Memory; +using IgsStruct = Roboto.GameOffsets.States.InGameState; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads InGameState struct (784B, 1 RPM instead of 4 individual reads). diff --git a/src/Nexus.Memory/Objects/PlayerSkills.cs b/src/Roboto.Memory/Objects/PlayerSkills.cs similarity index 98% rename from src/Nexus.Memory/Objects/PlayerSkills.cs rename to src/Roboto.Memory/Objects/PlayerSkills.cs index 68e11aa..ba1dff8 100644 --- a/src/Nexus.Memory/Objects/PlayerSkills.cs +++ b/src/Roboto.Memory/Objects/PlayerSkills.cs @@ -1,7 +1,7 @@ -using Nexus.Memory; -using Nexus.GameOffsets.Components; +using Roboto.Memory; +using Roboto.GameOffsets.Components; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads active skills from the local player's Actor component. diff --git a/src/Nexus.Memory/Objects/QuestFlags.cs b/src/Roboto.Memory/Objects/QuestFlags.cs similarity index 99% rename from src/Nexus.Memory/Objects/QuestFlags.cs rename to src/Roboto.Memory/Objects/QuestFlags.cs index dc15645..969ee14 100644 --- a/src/Nexus.Memory/Objects/QuestFlags.cs +++ b/src/Roboto.Memory/Objects/QuestFlags.cs @@ -1,7 +1,7 @@ -using Nexus.Memory; +using Roboto.Memory; using Serilog; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads quest flags from ServerData → PlayerServerData → QuestFlags. diff --git a/src/Nexus.Memory/Objects/Terrain.cs b/src/Roboto.Memory/Objects/Terrain.cs similarity index 98% rename from src/Nexus.Memory/Objects/Terrain.cs rename to src/Roboto.Memory/Objects/Terrain.cs index 8c18033..c9fe865 100644 --- a/src/Nexus.Memory/Objects/Terrain.cs +++ b/src/Roboto.Memory/Objects/Terrain.cs @@ -1,8 +1,8 @@ -using Nexus.Memory; +using Roboto.Memory; using Serilog; -using TerrainStruct = Nexus.GameOffsets.States.Terrain; +using TerrainStruct = Roboto.GameOffsets.States.Terrain; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads terrain walkability grid from AreaInstance. diff --git a/src/Nexus.Memory/Objects/UIElements.cs b/src/Roboto.Memory/Objects/UIElements.cs similarity index 99% rename from src/Nexus.Memory/Objects/UIElements.cs rename to src/Roboto.Memory/Objects/UIElements.cs index d0672a7..1bcf01b 100644 --- a/src/Nexus.Memory/Objects/UIElements.cs +++ b/src/Roboto.Memory/Objects/UIElements.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads the UIElement tree from InGameState → UiRootStruct → GameUi. diff --git a/src/Nexus.Memory/Objects/WorldData.cs b/src/Roboto.Memory/Objects/WorldData.cs similarity index 94% rename from src/Nexus.Memory/Objects/WorldData.cs rename to src/Roboto.Memory/Objects/WorldData.cs index e04ca65..3f4ad12 100644 --- a/src/Nexus.Memory/Objects/WorldData.cs +++ b/src/Roboto.Memory/Objects/WorldData.cs @@ -1,8 +1,8 @@ using System.Numerics; -using Nexus.Memory; -using WdStruct = Nexus.GameOffsets.States.WorldData; +using Roboto.Memory; +using WdStruct = Roboto.GameOffsets.States.WorldData; -namespace Nexus.Memory.Objects; +namespace Roboto.Memory.Objects; /// /// Reads WorldData struct (168B, 1 RPM) and resolves the camera matrix. diff --git a/src/Nexus.Memory/QuestStateLookup.cs b/src/Roboto.Memory/QuestStateLookup.cs similarity index 99% rename from src/Nexus.Memory/QuestStateLookup.cs rename to src/Roboto.Memory/QuestStateLookup.cs index 3b8beed..aa67409 100644 --- a/src/Nexus.Memory/QuestStateLookup.cs +++ b/src/Roboto.Memory/QuestStateLookup.cs @@ -1,6 +1,6 @@ using Serilog; -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Builds a (questDatRowPtr, stateId) → text lookup from QuestStates.dat. diff --git a/src/Nexus.Memory/RemoteObject.cs b/src/Roboto.Memory/RemoteObject.cs similarity index 97% rename from src/Nexus.Memory/RemoteObject.cs rename to src/Roboto.Memory/RemoteObject.cs index cdffe06..6afc901 100644 --- a/src/Nexus.Memory/RemoteObject.cs +++ b/src/Roboto.Memory/RemoteObject.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Base class for state objects that read a section of game memory. diff --git a/src/Nexus.Memory/Nexus.Memory.csproj b/src/Roboto.Memory/Roboto.Memory.csproj similarity index 84% rename from src/Nexus.Memory/Nexus.Memory.csproj rename to src/Roboto.Memory/Roboto.Memory.csproj index d43d983..ebdf0c2 100644 --- a/src/Nexus.Memory/Nexus.Memory.csproj +++ b/src/Roboto.Memory/Roboto.Memory.csproj @@ -10,6 +10,6 @@ - + diff --git a/src/Nexus.Memory/Snapshots/ConnectedAreaInfo.cs b/src/Roboto.Memory/Snapshots/ConnectedAreaInfo.cs similarity index 94% rename from src/Nexus.Memory/Snapshots/ConnectedAreaInfo.cs rename to src/Roboto.Memory/Snapshots/ConnectedAreaInfo.cs index 7e752ea..02edeb3 100644 --- a/src/Nexus.Memory/Snapshots/ConnectedAreaInfo.cs +++ b/src/Roboto.Memory/Snapshots/ConnectedAreaInfo.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Lightweight connected area info from WorldAreas.dat for the snapshot. diff --git a/src/Nexus.Memory/Snapshots/Entity.cs b/src/Roboto.Memory/Snapshots/Entity.cs similarity index 99% rename from src/Nexus.Memory/Snapshots/Entity.cs rename to src/Roboto.Memory/Snapshots/Entity.cs index a77a27a..782acd6 100644 --- a/src/Nexus.Memory/Snapshots/Entity.cs +++ b/src/Roboto.Memory/Snapshots/Entity.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; public enum EntityType { diff --git a/src/Nexus.Memory/Snapshots/GameStateSnapshot.cs b/src/Roboto.Memory/Snapshots/GameStateSnapshot.cs similarity index 98% rename from src/Nexus.Memory/Snapshots/GameStateSnapshot.cs rename to src/Roboto.Memory/Snapshots/GameStateSnapshot.cs index 5622202..2a5396c 100644 --- a/src/Nexus.Memory/Snapshots/GameStateSnapshot.cs +++ b/src/Roboto.Memory/Snapshots/GameStateSnapshot.cs @@ -1,7 +1,7 @@ using System.Numerics; -using Nexus.Memory.Objects; +using Roboto.Memory.Objects; -namespace Nexus.Memory; +namespace Roboto.Memory; public class GameStateSnapshot { diff --git a/src/Nexus.Memory/Snapshots/QuestLinkedEntry.cs b/src/Roboto.Memory/Snapshots/QuestLinkedEntry.cs similarity index 98% rename from src/Nexus.Memory/Snapshots/QuestLinkedEntry.cs rename to src/Roboto.Memory/Snapshots/QuestLinkedEntry.cs index 7574e2d..7c6d285 100644 --- a/src/Nexus.Memory/Snapshots/QuestLinkedEntry.cs +++ b/src/Roboto.Memory/Snapshots/QuestLinkedEntry.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// A quest entry from the GameUi linked lists. diff --git a/src/Nexus.Memory/Snapshots/QuestSnapshot.cs b/src/Roboto.Memory/Snapshots/QuestSnapshot.cs similarity index 88% rename from src/Nexus.Memory/Snapshots/QuestSnapshot.cs rename to src/Roboto.Memory/Snapshots/QuestSnapshot.cs index 20de386..3d902f2 100644 --- a/src/Nexus.Memory/Snapshots/QuestSnapshot.cs +++ b/src/Roboto.Memory/Snapshots/QuestSnapshot.cs @@ -1,8 +1,8 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Lightweight quest data from ServerData quest flags. -/// Stored in GameStateSnapshot; mapped to Nexus.Core.QuestProgress in the Data layer. +/// Stored in GameStateSnapshot; mapped to Roboto.Core.QuestProgress in the Data layer. /// public sealed class QuestSnapshot { diff --git a/src/Nexus.Memory/Snapshots/QuestStateEntry.cs b/src/Roboto.Memory/Snapshots/QuestStateEntry.cs similarity index 93% rename from src/Nexus.Memory/Snapshots/QuestStateEntry.cs rename to src/Roboto.Memory/Snapshots/QuestStateEntry.cs index 1caf5a1..8675b0b 100644 --- a/src/Nexus.Memory/Snapshots/QuestStateEntry.cs +++ b/src/Roboto.Memory/Snapshots/QuestStateEntry.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// A quest state entry from the AreaInstance quest state container. diff --git a/src/Nexus.Memory/Snapshots/SkillSnapshot.cs b/src/Roboto.Memory/Snapshots/SkillSnapshot.cs similarity index 92% rename from src/Nexus.Memory/Snapshots/SkillSnapshot.cs rename to src/Roboto.Memory/Snapshots/SkillSnapshot.cs index cee55a1..851e54d 100644 --- a/src/Nexus.Memory/Snapshots/SkillSnapshot.cs +++ b/src/Roboto.Memory/Snapshots/SkillSnapshot.cs @@ -1,8 +1,8 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Lightweight skill data from the Actor component's ActiveSkills vector. -/// Stored in GameStateSnapshot; mapped to Nexus.Core.SkillState in the Data layer. +/// Stored in GameStateSnapshot; mapped to Roboto.Core.SkillState in the Data layer. /// public sealed class SkillSnapshot { diff --git a/src/Nexus.Memory/Snapshots/UIElementNode.cs b/src/Roboto.Memory/Snapshots/UIElementNode.cs similarity index 95% rename from src/Nexus.Memory/Snapshots/UIElementNode.cs rename to src/Roboto.Memory/Snapshots/UIElementNode.cs index 0b93c1f..f7325a4 100644 --- a/src/Nexus.Memory/Snapshots/UIElementNode.cs +++ b/src/Roboto.Memory/Snapshots/UIElementNode.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// Lightweight snapshot of a single UIElement from the game's UI tree. diff --git a/src/Nexus.Memory/Snapshots/UiQuestEntry.cs b/src/Roboto.Memory/Snapshots/UiQuestEntry.cs similarity index 96% rename from src/Nexus.Memory/Snapshots/UiQuestEntry.cs rename to src/Roboto.Memory/Snapshots/UiQuestEntry.cs index 8ce2b6d..5516512 100644 --- a/src/Nexus.Memory/Snapshots/UiQuestEntry.cs +++ b/src/Roboto.Memory/Snapshots/UiQuestEntry.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; /// /// A quest group from the UI element tree (one per quest_display). diff --git a/src/Nexus.Memory/Snapshots/WalkabilityGrid.cs b/src/Roboto.Memory/Snapshots/WalkabilityGrid.cs similarity index 94% rename from src/Nexus.Memory/Snapshots/WalkabilityGrid.cs rename to src/Roboto.Memory/Snapshots/WalkabilityGrid.cs index 22f5453..4b52073 100644 --- a/src/Nexus.Memory/Snapshots/WalkabilityGrid.cs +++ b/src/Roboto.Memory/Snapshots/WalkabilityGrid.cs @@ -1,4 +1,4 @@ -namespace Nexus.Memory; +namespace Roboto.Memory; public sealed class WalkabilityGrid { diff --git a/src/Nexus.Pathfinding/NavigationController.cs b/src/Roboto.Navigation/NavigationController.cs similarity index 57% rename from src/Nexus.Pathfinding/NavigationController.cs rename to src/Roboto.Navigation/NavigationController.cs index 71fd322..4a88f65 100644 --- a/src/Nexus.Pathfinding/NavigationController.cs +++ b/src/Roboto.Navigation/NavigationController.cs @@ -1,8 +1,8 @@ using System.Numerics; -using Nexus.Core; +using Roboto.Core; using Serilog; -namespace Nexus.Pathfinding; +namespace Roboto.Navigation; public enum NavMode { @@ -28,28 +28,16 @@ public sealed class NavigationController // Explored grid — tracks which terrain cells the player has visited private bool[]? _exploredGrid; private int _exploredWidth, _exploredHeight; - private int _exploredOffsetX, _exploredOffsetY; private const int ExploreMarkRadius = 150; // grid cells (~1630 world units) // Stuck detection: rolling window of recent positions private readonly Queue _positionHistory = new(); - private const int StuckWindowSize = 120; // ~2 seconds at 60Hz - private const float StuckThreshold = 50f; // must move at least 50 world units in that window + private const int StuckWindowSize = 10; + private const float StuckThreshold = 5f; // Path failure cooldown — don't retry immediately when pathfinding fails private long _pathFailCooldownMs; - // Grace period after picking a new explore target — don't check stuck immediately - private int _stuckGraceTicks; - - // Repeated stuck detection — force random walk after multiple failures at same spot - private int _repeatedStuckCount; - private Vector2 _lastStuckPos; - private const float RepeatedStuckRadius = 300f; // same-area detection - private const int RepeatedStuckLimit = 3; // failures before random walk - private int _randomWalkTicks; // countdown for forced random direction - private Vector2 _randomWalkDir; - public NavMode Mode { get; private set; } = NavMode.Idle; public Vector2? DesiredDirection { get; private set; } public IReadOnlyList? CurrentPath => _path; @@ -57,8 +45,6 @@ public sealed class NavigationController public bool[]? ExploredGrid => _exploredGrid; public int ExploredWidth => _exploredWidth; public int ExploredHeight => _exploredHeight; - public int ExploredOffsetX => _exploredOffsetX; - public int ExploredOffsetY => _exploredOffsetY; /// /// True when BFS exploration finds no more unexplored walkable cells in the current area. @@ -147,19 +133,42 @@ public sealed class NavigationController _exploreBiasPoint = null; _exploredGrid = null; _pathFailCooldownMs = 0; - _repeatedStuckCount = 0; - _randomWalkTicks = 0; IsExplorationComplete = false; } - // Allocate or resize explored grid to match terrain (preserving old data on expansion) + // Allocate explored grid on first tick with terrain, after area change, + // or when terrain dimensions change (prevents bounds mismatch crash) var terrain = state.Terrain; - if (terrain is not null) - EnsureExploredGrid(terrain); + if (terrain is not null && + (_exploredGrid is null || terrain.Width != _exploredWidth || terrain.Height != _exploredHeight)) + { + _exploredWidth = terrain.Width; + _exploredHeight = terrain.Height; + _exploredGrid = new bool[_exploredWidth * _exploredHeight]; + } // Mark cells near player as explored if (_exploredGrid is not null && terrain is not null) - MarkExplored(playerPos); + { + var pgx = (int)(playerPos.X * _config.WorldToGrid); + var pgy = (int)(playerPos.Y * _config.WorldToGrid); + var r = ExploreMarkRadius; + var r2 = r * r; + var minX = Math.Max(0, pgx - r); + var maxX = Math.Min(_exploredWidth - 1, pgx + r); + var minY = Math.Max(0, pgy - r); + var maxY = Math.Min(_exploredHeight - 1, pgy + r); + for (var y = minY; y <= maxY; y++) + { + var dy = y - pgy; + for (var x = minX; x <= maxX; x++) + { + var dx = x - pgx; + if (dx * dx + dy * dy <= r2) + _exploredGrid[y * _exploredWidth + x] = true; + } + } + } // Resolve goal based on mode var goal = ResolveGoal(state); @@ -202,69 +211,23 @@ public sealed class NavigationController if (_positionHistory.Count > StuckWindowSize) _positionHistory.Dequeue(); - if (_stuckGraceTicks > 0) - _stuckGraceTicks--; - - // Random walk override — forced escape from repeated stuck loops - if (_randomWalkTicks > 0) - { - _randomWalkTicks--; - DesiredDirection = _randomWalkDir; - Status = "Random walk (escape)"; - if (_randomWalkTicks == 0) - { - _path = null; // force repath after random walk - _positionHistory.Clear(); - } - return; - } - var isStuck = false; - if (_stuckGraceTicks <= 0 && _positionHistory.Count >= StuckWindowSize && _path is not null) + if (_positionHistory.Count >= StuckWindowSize && _path is not null) { var oldest = _positionHistory.Peek(); if (Vector2.Distance(oldest, playerPos) < StuckThreshold) { isStuck = true; - - // Track repeated stuck at the same location - if (Vector2.Distance(playerPos, _lastStuckPos) < RepeatedStuckRadius) - _repeatedStuckCount++; - else - _repeatedStuckCount = 1; - _lastStuckPos = playerPos; - - if (_repeatedStuckCount >= RepeatedStuckLimit) - { - // Force random walk to break free - var angle = (float)(_rng.NextDouble() * Math.PI * 2); - _randomWalkDir = new Vector2(MathF.Cos(angle), MathF.Sin(angle)); - _randomWalkTicks = 120; // ~2 seconds of random walk - _repeatedStuckCount = 0; - Log.Information("NavigationController: repeated stuck at ({X:F0},{Y:F0}), forcing random walk", - playerPos.X, playerPos.Y); - DesiredDirection = _randomWalkDir; - return; - } - if (Mode == NavMode.Exploring) { Log.Information("NavigationController: stuck while exploring, picking new target"); - - // Mark cells around the failed goal as explored so BFS won't pick the same target - if (_goalPosition.HasValue && _exploredGrid is not null) - MarkExplored(_goalPosition.Value); - _goalPosition = null; _path = null; _waypointIndex = 0; _positionHistory.Clear(); - _stuckGraceTicks = 120; // 2 seconds grace for next target return; } Log.Debug("NavigationController: stuck detected, repathing"); - _positionHistory.Clear(); - _stuckGraceTicks = 120; // 2 seconds grace before next stuck check } } @@ -301,7 +264,7 @@ public sealed class NavigationController _path = Mode == NavMode.Exploring ? PathFinder.FindPath(terrain, playerPos, goal.Value, _config.WorldToGrid, - _exploredGrid, _exploredWidth, _exploredHeight, _exploredOffsetX, _exploredOffsetY) + _exploredGrid, _exploredWidth, _exploredHeight) : PathFinder.FindPath(terrain, playerPos, goal.Value, _config.WorldToGrid); _waypointIndex = 0; _pathTimestampMs = now; @@ -358,54 +321,18 @@ public sealed class NavigationController // Diagnostic: log every ~60 ticks (once per second at 60Hz) if (state.TickNumber % 60 == 0) { - var gx2 = (int)(playerPos.X * _config.WorldToGrid); - var gy2 = (int)(playerPos.Y * _config.WorldToGrid); - var walkable = state.Terrain?.IsWalkable(gx2, gy2) ?? false; + var gx = (int)(playerPos.X * _config.WorldToGrid); + var gy = (int)(playerPos.Y * _config.WorldToGrid); + var walkable = state.Terrain?.IsWalkable(gx, gy) ?? false; Log.Information( "NAV DIAG: playerWorld=({Px:F0},{Py:F0}) playerGrid=({Gx},{Gy}) walkable={W} " + "waypointWorld=({Tx:F0},{Ty:F0}) dir=({Dx:F2},{Dy:F2})", - playerPos.X, playerPos.Y, gx2, gy2, walkable, + playerPos.X, playerPos.Y, gx, gy, walkable, target.X, target.Y, DesiredDirection.Value.X, DesiredDirection.Value.Y); } } - private void EnsureExploredGrid(WalkabilitySnapshot terrain) - { - var needsResize = _exploredGrid is null - || terrain.Width != _exploredWidth || terrain.Height != _exploredHeight - || terrain.OffsetX != _exploredOffsetX || terrain.OffsetY != _exploredOffsetY; - - if (!needsResize) return; - - var newGrid = new bool[terrain.Width * terrain.Height]; - - // Preserve old explored data in overlapping region - if (_exploredGrid is not null) - { - var overlapMinX = Math.Max(terrain.OffsetX, _exploredOffsetX); - var overlapMinY = Math.Max(terrain.OffsetY, _exploredOffsetY); - var overlapMaxX = Math.Min(terrain.OffsetX + terrain.Width, _exploredOffsetX + _exploredWidth); - var overlapMaxY = Math.Min(terrain.OffsetY + terrain.Height, _exploredOffsetY + _exploredHeight); - - for (var ay = overlapMinY; ay < overlapMaxY; ay++) - for (var ax = overlapMinX; ax < overlapMaxX; ax++) - { - var oldLx = ax - _exploredOffsetX; - var oldLy = ay - _exploredOffsetY; - var newLx = ax - terrain.OffsetX; - var newLy = ay - terrain.OffsetY; - newGrid[newLy * terrain.Width + newLx] = _exploredGrid[oldLy * _exploredWidth + oldLx]; - } - } - - _exploredGrid = newGrid; - _exploredWidth = terrain.Width; - _exploredHeight = terrain.Height; - _exploredOffsetX = terrain.OffsetX; - _exploredOffsetY = terrain.OffsetY; - } - private Vector2? ResolveGoal(GameState state) { switch (Mode) @@ -438,26 +365,22 @@ public sealed class NavigationController if (state.Terrain is null || _exploredGrid is null) return null; var terrain = state.Terrain; + // Bail if terrain dimensions don't match the allocated grid (area transition in progress) if (terrain.Width != _exploredWidth || terrain.Height != _exploredHeight) return null; var gridToWorld = 1f / _config.WorldToGrid; var playerPos = state.Player.Position; - var ox = terrain.OffsetX; - var oy = terrain.OffsetY; var w = terrain.Width; var h = terrain.Height; - // Player in local grid coords - var pgx = (int)(playerPos.X * _config.WorldToGrid); - var pgy = (int)(playerPos.Y * _config.WorldToGrid); - var startLx = Math.Clamp(pgx - ox, 0, w - 1); - var startLy = Math.Clamp(pgy - oy, 0, h - 1); + var startGx = Math.Clamp((int)(playerPos.X * _config.WorldToGrid), 0, w - 1); + var startGy = Math.Clamp((int)(playerPos.Y * _config.WorldToGrid), 0, h - 1); // BFS outward from player to find nearest unexplored walkable cell var visited = new bool[w * h]; - var queue = new Queue<(int lx, int ly)>(); - queue.Enqueue((startLx, startLy)); - visited[startLy * w + startLx] = true; + var queue = new Queue<(int x, int y)>(); + queue.Enqueue((startGx, startGy)); + visited[startGy * w + startGx] = true; var iterations = 0; const int maxIterations = 100_000; @@ -465,15 +388,13 @@ public sealed class NavigationController while (queue.Count > 0 && iterations++ < maxIterations) { var (cx, cy) = queue.Dequeue(); - var ax = cx + ox; - var ay = cy + oy; // Found an unexplored walkable cell - if (terrain.IsWalkable(ax, ay) && !IsExploredAt(ax, ay)) + if (terrain.IsWalkable(cx, cy) && !_exploredGrid[cy * w + cx]) { - var worldPos = new Vector2(ax * gridToWorld, ay * gridToWorld); + var worldPos = new Vector2(cx * gridToWorld, cy * gridToWorld); _goalPosition = worldPos; - Log.Debug("BFS frontier: target ({Gx},{Gy}) after {Iter} iterations", ax, ay, iterations); + Log.Debug("BFS frontier: target ({Gx},{Gy}) after {Iter} iterations", cx, cy, iterations); return worldPos; } @@ -485,101 +406,17 @@ public sealed class NavigationController if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue; var idx = ny * w + nx; if (visited[idx]) continue; - if (!terrain.IsWalkable(nx + ox, ny + oy)) continue; + if (!terrain.IsWalkable(nx, ny)) continue; visited[idx] = true; queue.Enqueue((nx, ny)); } } - // Don't declare exploration complete — with infinite terrain, new cells appear at edges. - // Pick a random distant target to push toward terrain boundaries where expansion triggers. - var randomTarget = PickRandomDistantTarget(playerPos, terrain, gridToWorld); - if (randomTarget is not null) - { - _goalPosition = randomTarget; - Log.Information("BFS frontier: no unexplored cells nearby, roaming to ({X:F0},{Y:F0})", - randomTarget.Value.X, randomTarget.Value.Y); - return randomTarget; - } - Log.Information("BFS frontier: no unexplored cells found — exploration complete"); IsExplorationComplete = true; return null; } - private Vector2? PickRandomDistantTarget(Vector2 playerPos, WalkabilitySnapshot terrain, float gridToWorld) - { - // Try random directions at a moderate distance — aim for terrain edges - for (var attempt = 0; attempt < 20; attempt++) - { - var angle = _rng.NextSingle() * MathF.Tau; - var dist = 1500f + _rng.NextSingle() * 2000f; // 1500-3500 world units away - var target = playerPos + new Vector2(MathF.Cos(angle), MathF.Sin(angle)) * dist; - var gx = (int)(target.X * _config.WorldToGrid); - var gy = (int)(target.Y * _config.WorldToGrid); - if (terrain.IsWalkable(gx, gy)) - return target; - } - - // Fallback: pick a point toward the nearest terrain edge (guaranteed to push toward expansion) - var pgx = (int)(playerPos.X * _config.WorldToGrid); - var pgy = (int)(playerPos.Y * _config.WorldToGrid); - var ox = terrain.OffsetX; - var oy = terrain.OffsetY; - var distToLeft = pgx - ox; - var distToRight = (ox + terrain.Width) - pgx; - var distToTop = pgy - oy; - var distToBottom = (oy + terrain.Height) - pgy; - - // Find the nearest edge and go toward it - var minEdgeDist = Math.Min(Math.Min(distToLeft, distToRight), Math.Min(distToTop, distToBottom)); - int tgx, tgy; - if (minEdgeDist == distToLeft) - { tgx = ox + 10; tgy = pgy; } - else if (minEdgeDist == distToRight) - { tgx = ox + terrain.Width - 10; tgy = pgy; } - else if (minEdgeDist == distToTop) - { tgx = pgx; tgy = oy + 10; } - else - { tgx = pgx; tgy = oy + terrain.Height - 10; } - - if (terrain.IsWalkable(tgx, tgy)) - return new Vector2(tgx * gridToWorld, tgy * gridToWorld); - - return null; - } - - private void MarkExplored(Vector2 worldPos) - { - if (_exploredGrid is null) return; - var gx = (int)(worldPos.X * _config.WorldToGrid); - var gy = (int)(worldPos.Y * _config.WorldToGrid); - var r = ExploreMarkRadius; - var r2 = r * r; - for (var dy = -r; dy <= r; dy++) - for (var dx = -r; dx <= r; dx++) - { - if (dx * dx + dy * dy > r2) continue; - SetExploredAt(gx + dx, gy + dy); - } - } - - private void SetExploredAt(int gx, int gy) - { - var lx = gx - _exploredOffsetX; - var ly = gy - _exploredOffsetY; - if (lx >= 0 && lx < _exploredWidth && ly >= 0 && ly < _exploredHeight) - _exploredGrid![ly * _exploredWidth + lx] = true; - } - - private bool IsExploredAt(int gx, int gy) - { - var lx = gx - _exploredOffsetX; - var ly = gy - _exploredOffsetY; - if (lx < 0 || lx >= _exploredWidth || ly < 0 || ly >= _exploredHeight) return false; - return _exploredGrid![ly * _exploredWidth + lx]; - } - private static readonly int[] _bfsDx = [-1, 0, 1, 0, -1, -1, 1, 1]; private static readonly int[] _bfsDy = [0, -1, 0, 1, -1, 1, -1, 1]; } diff --git a/src/Nexus.Pathfinding/PathFinder.cs b/src/Roboto.Navigation/PathFinder.cs similarity index 81% rename from src/Nexus.Pathfinding/PathFinder.cs rename to src/Roboto.Navigation/PathFinder.cs index 5c3ba60..4b5e4b6 100644 --- a/src/Nexus.Pathfinding/PathFinder.cs +++ b/src/Roboto.Navigation/PathFinder.cs @@ -1,8 +1,8 @@ using System.Numerics; -using Nexus.Core; +using Roboto.Core; using Serilog; -namespace Nexus.Pathfinding; +namespace Roboto.Navigation; public static class PathFinder { @@ -12,27 +12,24 @@ public static class PathFinder /// /// A* pathfinding on WalkabilitySnapshot. Returns world-coord waypoints or null if no path. - /// When exploredGrid is provided, explored cells cost 1.5x more — biasing paths through unexplored territory. + /// When exploredGrid is provided, explored cells cost 3x more — biasing paths through unexplored territory. /// public static List? FindPath( WalkabilitySnapshot terrain, Vector2 start, Vector2 goal, float worldToGrid, - bool[]? exploredGrid = null, int exploredWidth = 0, int exploredHeight = 0, - int exploredOffsetX = 0, int exploredOffsetY = 0) + bool[]? exploredGrid = null, int exploredWidth = 0, int exploredHeight = 0) { var w = terrain.Width; var h = terrain.Height; - var ox = terrain.OffsetX; - var oy = terrain.OffsetY; var gridToWorld = 1f / worldToGrid; - var startGx = Math.Clamp((int)(start.X * worldToGrid), ox, ox + w - 1); - var startGy = Math.Clamp((int)(start.Y * worldToGrid), oy, oy + h - 1); - var goalGx = Math.Clamp((int)(goal.X * worldToGrid), ox, ox + w - 1); - var goalGy = Math.Clamp((int)(goal.Y * worldToGrid), oy, oy + h - 1); + var startGx = Math.Clamp((int)(start.X * worldToGrid), 0, w - 1); + var startGy = Math.Clamp((int)(start.Y * worldToGrid), 0, h - 1); + var goalGx = Math.Clamp((int)(goal.X * worldToGrid), 0, w - 1); + var goalGy = Math.Clamp((int)(goal.Y * worldToGrid), 0, h - 1); // Snap to nearest walkable if start/goal are in walls - (startGx, startGy) = SnapToWalkable(terrain, startGx, startGy); - (goalGx, goalGy) = SnapToWalkable(terrain, goalGx, goalGy); + (startGx, startGy) = SnapToWalkable(terrain, startGx, startGy, w, h); + (goalGx, goalGy) = SnapToWalkable(terrain, goalGx, goalGy, w, h); var startNode = (startGx, startGy); var goalNode = (goalGx, goalGy); @@ -81,7 +78,7 @@ public static class PathFinder var nx = current.x + Dx[i]; var ny = current.y + Dy[i]; - // IsWalkable handles offset and bounds + if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue; if (!terrain.IsWalkable(nx, ny)) continue; var neighbor = (nx, ny); @@ -96,24 +93,8 @@ public static class PathFinder } var stepCost = Cost[i]; - if (exploredGrid is not null) - { - var elx = nx - exploredOffsetX; - var ely = ny - exploredOffsetY; - if (elx >= 0 && elx < exploredWidth && ely >= 0 && ely < exploredHeight - && exploredGrid[ely * exploredWidth + elx]) - stepCost *= 1.5f; - } - - // Wall-proximity penalty — prefer corridor centers - var wallCount = 0; - for (var d = 0; d < 8; d++) - { - if (!terrain.IsWalkable(nx + Dx[d], ny + Dy[d])) - wallCount++; - } - stepCost += wallCount * 0.5f; - + if (exploredGrid is not null && nx < exploredWidth && ny < exploredHeight && exploredGrid[ny * exploredWidth + nx]) + stepCost *= 1.5f; var tentativeG = currentG + stepCost; if (tentativeG < gScore.GetValueOrDefault(neighbor, float.MaxValue)) @@ -148,7 +129,7 @@ public static class PathFinder return Math.Max(dx, dy) + 0.414f * Math.Min(dx, dy); } - private static (int, int) SnapToWalkable(WalkabilitySnapshot terrain, int gx, int gy) + private static (int, int) SnapToWalkable(WalkabilitySnapshot terrain, int gx, int gy, int w, int h) { if (terrain.IsWalkable(gx, gy)) return (gx, gy); @@ -162,7 +143,7 @@ public static class PathFinder if (Math.Abs(dx) != r && Math.Abs(dy) != r) continue; var nx = gx + dx; var ny = gy + dy; - if (terrain.IsWalkable(nx, ny)) + if (nx >= 0 && nx < w && ny >= 0 && ny < h && terrain.IsWalkable(nx, ny)) return (nx, ny); } } diff --git a/src/Nexus.Pathfinding/Nexus.Pathfinding.csproj b/src/Roboto.Navigation/Roboto.Navigation.csproj similarity index 83% rename from src/Nexus.Pathfinding/Nexus.Pathfinding.csproj rename to src/Roboto.Navigation/Roboto.Navigation.csproj index 60b2b65..bcebb88 100644 --- a/src/Nexus.Pathfinding/Nexus.Pathfinding.csproj +++ b/src/Roboto.Navigation/Roboto.Navigation.csproj @@ -8,6 +8,6 @@ - + diff --git a/src/Nexus.Systems/CombatSystem.cs b/src/Roboto.Systems/CombatSystem.cs similarity index 91% rename from src/Nexus.Systems/CombatSystem.cs rename to src/Roboto.Systems/CombatSystem.cs index eb3cfa4..d7423ef 100644 --- a/src/Nexus.Systems/CombatSystem.cs +++ b/src/Roboto.Systems/CombatSystem.cs @@ -1,8 +1,8 @@ using System.Numerics; -using Nexus.Core; +using Roboto.Core; using Serilog; -namespace Nexus.Systems; +namespace Roboto.Systems; public class CombatSystem : ISystem { @@ -76,7 +76,7 @@ public class CombatSystem : ISystem _heldSlots.Clear(); } - public void Update(GameState state, ActionQueue actions, MovementBlender movement) + public void Update(GameState state, ActionQueue actions) { if (state.CameraMatrix is not { } camera) return; @@ -92,19 +92,16 @@ public class CombatSystem : ISystem _lastAreaHash = state.AreaHash; } - // Always submit orbit/herd intent when enemies are nearby — provides continuous - // circular movement that blends with other systems. Override is stronger during GCD - // (no cast competing) and weaker while casting (cast targeting takes priority). - var inGcd = now - _lastCastGlobal < _globalCooldownMs; - if (_kiteEnabled && state.NearestEnemies.Count > 0) - { - var herdOverride = inGcd ? 0.4f : 0.2f; - TryHerd(state, movement, herdOverride); - } - // Global cooldown: don't cast if we recently cast any skill - if (inGcd) + if (now - _lastCastGlobal < _globalCooldownMs) { + // Orbit-herd during cooldown window (after cast animation delay) + if (_kiteEnabled && now - _lastCastGlobal >= _kiteDelayMs + && state.NearestEnemies.Count > 0) + { + TryHerd(state, actions); + } + // Still need to handle MaintainPressed releases UpdateHeldKeys(state, camera, playerZ, actions); return; @@ -208,7 +205,7 @@ public class CombatSystem : ISystem /// Orbit-herding: move perpendicular to enemy centroid so scattered mobs converge /// into a tight cluster for AOE. Maintains ideal distance via radial bias. /// - private void TryHerd(GameState state, MovementBlender movement, float overrideFactor) + private void TryHerd(GameState state, ActionQueue actions) { var playerPos = state.Player.Position; @@ -244,8 +241,26 @@ public class CombatSystem : ISystem var dir = Vector2.Normalize(perp + centroidDir * radialBias); - // Layer 4: orbit/herd with variable override (stronger during GCD, weaker while casting) - movement.Submit(new MovementIntent(4, dir, overrideFactor, "Herd")); + // Validate against terrain — flip orbit direction on wall hit + if (state.Terrain is { } terrain) + { + var validated = TerrainQuery.FindWalkableDirection(terrain, playerPos, dir, _worldToGrid); + + // If terrain forced a significantly different direction, flip orbit + if (Vector2.Dot(validated, dir) < 0.5f) + { + _orbitSign *= -1; + perp = new Vector2(-centroidDir.Y, centroidDir.X) * _orbitSign; + dir = Vector2.Normalize(perp + centroidDir * radialBias); + dir = TerrainQuery.FindWalkableDirection(terrain, playerPos, dir, _worldToGrid); + } + else + { + dir = validated; + } + } + + actions.Submit(new MoveAction(SystemPriority.Combat, dir)); } private void UpdateHeldKeys(GameState state, Matrix4x4 camera, float playerZ, ActionQueue actions) diff --git a/src/Roboto.Systems/LootSystem.cs b/src/Roboto.Systems/LootSystem.cs new file mode 100644 index 0000000..f9c366b --- /dev/null +++ b/src/Roboto.Systems/LootSystem.cs @@ -0,0 +1,15 @@ +using Roboto.Core; + +namespace Roboto.Systems; + +public class LootSystem : ISystem +{ + public int Priority => SystemPriority.Loot; + public string Name => "Loot"; + public bool IsEnabled { get; set; } = false; + + public void Update(GameState state, ActionQueue actions) + { + // STUB: loot detection and pickup logic + } +} diff --git a/src/Roboto.Systems/MovementSystem.cs b/src/Roboto.Systems/MovementSystem.cs new file mode 100644 index 0000000..f19243d --- /dev/null +++ b/src/Roboto.Systems/MovementSystem.cs @@ -0,0 +1,54 @@ +using System.Numerics; +using Roboto.Core; + +namespace Roboto.Systems; + +/// +/// Force-based avoidance: applies inverse-square repulsion from hostile monsters +/// within safe distance. Emits a MoveAction with the escape direction. +/// +public class MovementSystem : ISystem +{ + public int Priority => SystemPriority.Movement; + public string Name => "Movement"; + public bool IsEnabled { get; set; } = true; + + public float SafeDistance { get; set; } = 400f; + public float RepulsionWeight { get; set; } = 1.5f; + + /// World-to-grid conversion factor for terrain queries. + public float WorldToGrid { get; set; } = 23f / 250f; + + public void Update(GameState state, ActionQueue actions) + { + if (!state.Player.HasPosition) return; + if (state.HostileMonsters.Count == 0) return; + + var playerPos = state.Player.Position; + var repulsion = Vector2.Zero; + + foreach (var monster in state.HostileMonsters) + { + if (!monster.IsAlive) continue; + if (monster.DistanceToPlayer > SafeDistance) continue; + + var delta = playerPos - monster.Position; + var distSq = delta.LengthSquared(); + if (distSq < 1f) distSq = 1f; + + // Inverse-square repulsion: stronger when closer + var force = delta / distSq * RepulsionWeight; + repulsion += force; + } + + if (repulsion.LengthSquared() < 0.0001f) return; + + var direction = Vector2.Normalize(repulsion); + + // Validate repulsion direction against terrain — avoid walking into walls + if (state.Terrain is { } terrain) + direction = TerrainQuery.FindWalkableDirection(terrain, state.Player.Position, direction, WorldToGrid); + + actions.Enqueue(new MoveAction(Priority, direction)); + } +} diff --git a/src/Nexus.Systems/NavigationSystem.cs b/src/Roboto.Systems/NavigationSystem.cs similarity index 67% rename from src/Nexus.Systems/NavigationSystem.cs rename to src/Roboto.Systems/NavigationSystem.cs index bded23e..31cc4fd 100644 --- a/src/Nexus.Systems/NavigationSystem.cs +++ b/src/Roboto.Systems/NavigationSystem.cs @@ -1,10 +1,10 @@ using System.Numerics; -using Nexus.Core; +using Roboto.Core; -namespace Nexus.Systems; +namespace Roboto.Systems; /// -/// Simplified navigation system. Pathfinding has moved to Nexus.Pathfinding.PathFinder. +/// Simplified navigation system. Pathfinding has moved to Roboto.Navigation.PathFinder. /// This system just submits a MoveAction if an external direction is set. /// public class NavigationSystem : ISystem @@ -21,9 +21,9 @@ public class NavigationSystem : ISystem /// public Vector2? ExternalDirection { get; set; } - public void Update(GameState state, ActionQueue actions, MovementBlender movement) + public void Update(GameState state, ActionQueue actions) { if (ExternalDirection.HasValue) - movement.Submit(new MovementIntent(3, ExternalDirection.Value, 0f, "Navigation")); + actions.Submit(new MoveAction(Priority, ExternalDirection.Value)); } } diff --git a/src/Nexus.Systems/ResourceSystem.cs b/src/Roboto.Systems/ResourceSystem.cs similarity index 91% rename from src/Nexus.Systems/ResourceSystem.cs rename to src/Roboto.Systems/ResourceSystem.cs index d00c116..44fadda 100644 --- a/src/Nexus.Systems/ResourceSystem.cs +++ b/src/Roboto.Systems/ResourceSystem.cs @@ -1,6 +1,6 @@ -using Nexus.Core; +using Roboto.Core; -namespace Nexus.Systems; +namespace Roboto.Systems; public class ResourceSystem : ISystem { @@ -29,7 +29,7 @@ public class ResourceSystem : ISystem _lastManaFlaskMs = 0; } - public void Update(GameState state, ActionQueue actions, MovementBlender movement) + public void Update(GameState state, ActionQueue actions) { var player = state.Player; if (player.LifeTotal == 0) return; diff --git a/src/Nexus.Data/Nexus.Data.csproj b/src/Roboto.Systems/Roboto.Systems.csproj similarity index 71% rename from src/Nexus.Data/Nexus.Data.csproj rename to src/Roboto.Systems/Roboto.Systems.csproj index 0d85deb..bcebb88 100644 --- a/src/Nexus.Data/Nexus.Data.csproj +++ b/src/Roboto.Systems/Roboto.Systems.csproj @@ -8,7 +8,6 @@ - - + diff --git a/src/Roboto.Systems/ThreatSystem.cs b/src/Roboto.Systems/ThreatSystem.cs new file mode 100644 index 0000000..6dcc52a --- /dev/null +++ b/src/Roboto.Systems/ThreatSystem.cs @@ -0,0 +1,76 @@ +using System.Numerics; +using Roboto.Core; +using Serilog; + +namespace Roboto.Systems; + +/// +/// Emergency threat response. Runs first (priority 50). +/// On Critical danger: urgent flee (blocks casting via priority ≤ 10). +/// On High danger: flee toward safety but allow casting. +/// Medium and below: no action (MovementSystem handles soft avoidance). +/// +public class ThreatSystem : ISystem +{ + public int Priority => SystemPriority.Threat; + public string Name => "Threat"; + public bool IsEnabled { get; set; } = true; + + /// Priority ≤ 10 blocks casting in ActionQueue.Resolve — pure flee. + private const int UrgentFleePriority = 5; + + /// If closest enemy is within this range, escalate to urgent flee. + public float PointBlankRange { get; set; } = 150f; + + /// World-to-grid conversion factor for terrain queries. + public float WorldToGrid { get; set; } = 23f / 250f; + + private DangerLevel _prevDanger = DangerLevel.Safe; + + public void Update(GameState state, ActionQueue actions) + { + if (!state.Player.HasPosition) return; + + var danger = state.Danger; + var threats = state.Threats; + + // Log danger transitions + if (danger != _prevDanger) + { + if (danger >= DangerLevel.High) + Log.Warning("Threat: {Prev} -> {Cur} (hostiles={Total}, close={Close}, closest={Dist:F0})", + _prevDanger, danger, threats.TotalHostiles, threats.CloseRange, threats.ClosestDistance); + else + Log.Debug("Threat: {Prev} -> {Cur}", _prevDanger, danger); + _prevDanger = danger; + } + + if (danger <= DangerLevel.Medium) return; + if (threats.TotalHostiles == 0) return; + + // Compute flee direction: away from threat centroid + var fleeDir = state.Player.Position - threats.ThreatCentroid; + if (fleeDir.LengthSquared() < 0.0001f) + fleeDir = Vector2.UnitY; // fallback if at centroid + + fleeDir = Vector2.Normalize(fleeDir); + + // Validate flee direction against terrain — avoid walking into walls + if (state.Terrain is { } terrain) + fleeDir = TerrainQuery.FindWalkableDirection(terrain, state.Player.Position, fleeDir, WorldToGrid); + + // Point-blank override: if closest enemy is very close, escalate to urgent + var isPointBlank = threats.ClosestDistance < PointBlankRange; + + if (danger == DangerLevel.Critical || isPointBlank) + { + // Urgent flee — blocks casting (priority ≤ 10) + actions.Submit(new MoveAction(UrgentFleePriority, fleeDir)); + } + else // High + { + // Flee but allow casting alongside + actions.Submit(new MoveAction(Priority, fleeDir)); + } + } +}