diff --git a/BossMod/AI/AIConfig.cs b/BossMod/AI/AIConfig.cs index eae3bc69e0..722a0a2417 100644 --- a/BossMod/AI/AIConfig.cs +++ b/BossMod/AI/AIConfig.cs @@ -19,6 +19,7 @@ public enum Slot { One, Two, Three, Four } public bool ShowDTR = true; // ai settings + // TODO: this is really bad, it should not be here! it's a transient thing, doesn't make sense to preserve in config [PropertyDisplay($"Follow slot")] public Slot FollowSlot = 0; diff --git a/BossMod/AI/AIManager.cs b/BossMod/AI/AIManager.cs index a6c4d72f32..c2c387a4eb 100644 --- a/BossMod/AI/AIManager.cs +++ b/BossMod/AI/AIManager.cs @@ -3,9 +3,7 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; -using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Client.Game.Group; -using ImGuiNET; namespace BossMod.AI; @@ -13,23 +11,24 @@ sealed class AIManager : IDisposable { private readonly RotationModuleManager _autorot; private readonly AIController _controller; - private readonly AIConfig _config; - private int MasterSlot => (int)_config.FollowSlot; // non-zero means corresponding player is master - private readonly UISimpleWindow _ui; - private WorldState WorldState => _autorot.Bossmods.WorldState; - private string _aiStatus = ""; - private string _naviStatus = ""; - public float ForceMovementIn => Behaviour?.ForceMovementIn ?? float.MaxValue; + public readonly AIConfig Config = Service.Config.Get(); public AIBehaviour? Behaviour { get; private set; } + public string AIStatus { get; private set; } = ""; + public string NaviStatus { get; private set; } = ""; + + public int MasterSlot => (int)Config.FollowSlot; // non-zero means corresponding player is master + public WorldState WorldState => _autorot.Bossmods.WorldState; + public float ForceMovementIn => Behaviour?.ForceMovementIn ?? float.MaxValue; - private bool Enabled + // TODO: this is not good, callers should use SwitchToXXX directly + public bool Enabled { - get => _config.Enabled; + get => Config.Enabled; set { - if (_config.Enabled != value) - _config.Enabled = value; + if (Config.Enabled != value) + Config.Enabled = value; if (!value && Behaviour != null) SwitchToIdle(); @@ -42,34 +41,23 @@ public AIManager(RotationModuleManager autorot, ActionManagerEx amex, MovementOv { _autorot = autorot; _controller = new(amex, movement); - _config = Service.Config.Get(); - _ui = new("###AI", DrawOverlay, false, new(100, 100), ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoFocusOnAppearing) - { - RespectCloseHotkey = false, - ShowCloseButton = false, - WindowName = $"AI: off###AI", - TitleBarButtons = [new() { Icon = FontAwesomeIcon.WindowClose, IconOffset = new(1, 1), Click = _ => _config.DrawUI = false }] - }; Service.ChatGui.ChatMessage += OnChatMessage; - Service.CommandManager.AddHandler("/vbmai", new Dalamud.Game.Command.CommandInfo(OnCommand) { HelpMessage = "Toggle AI mode" }); } public void Dispose() { SwitchToIdle(); - _ui.Dispose(); Service.ChatGui.ChatMessage -= OnChatMessage; - Service.CommandManager.RemoveHandler("/vbmai"); } public void Update() { - Enabled = _config.Enabled; + Enabled = Config.Enabled; if (!WorldState.Party.Members[MasterSlot].IsValid()) SwitchToIdle(); - if (!_config.Enabled && Behaviour != null) + if (!Config.Enabled && Behaviour != null) SwitchToIdle(); var player = WorldState.Party.Player(); @@ -85,41 +73,9 @@ public void Update() } _controller.Update(player, _autorot.Hints, WorldState.CurrentTime); - _aiStatus = $"AI: {(Behaviour != null ? $"on, {$"master={master?.Name}[{(int)_config.FollowSlot + 1}]"}" : "off")}"; + AIStatus = $"AI: {(Behaviour != null ? $"on, {$"master={master?.Name}[{(int)Config.FollowSlot + 1}]"}" : "off")}"; var dist = _controller.NaviTargetPos != null && player != null ? (_controller.NaviTargetPos.Value - player.Position).Length() : 0; - _naviStatus = $"Navi={_controller.NaviTargetPos?.ToString() ?? ""} (d={dist:f3}, max-cast={MathF.Min(Behaviour?.ForceMovementIn ?? float.MaxValue, 1000):f3})"; - _ui.IsOpen = player != null && _config.DrawUI; - _ui.WindowName = _config.ShowStatusOnTitlebar ? $"{_aiStatus}, {_naviStatus}###AI" : $"AI###AI"; - } - - private void DrawOverlay() - { - if (!_config.ShowStatusOnTitlebar) - { - ImGui.TextUnformatted(_aiStatus); - ImGui.TextUnformatted(_naviStatus); - } - Behaviour?.DrawDebug(); - - using (var leaderCombo = ImRaii.Combo("Follow", Behaviour == null ? "" : WorldState.Party[MasterSlot]?.Name ?? "")) - { - if (leaderCombo) - { - if (ImGui.Selectable("", Behaviour == null)) - { - Enabled = false; - } - foreach (var (i, p) in WorldState.Party.WithSlot(true)) - { - if (ImGui.Selectable(p.Name, MasterSlot == i)) - { - _config.FollowSlot = (AIConfig.Slot)i; - _config.Modified.Fire(); - Enabled = true; - } - } - } - } + NaviStatus = $"Navi={_controller.NaviTargetPos?.ToString() ?? ""} (d={dist:f3}, max-cast={MathF.Min(Behaviour?.ForceMovementIn ?? float.MaxValue, 1000):f3})"; } public void SwitchToIdle() @@ -127,16 +83,16 @@ public void SwitchToIdle() Behaviour?.Dispose(); Behaviour = null; - _config.FollowSlot = PartyState.PlayerSlot; - _config.Modified.Fire(); + Config.FollowSlot = PartyState.PlayerSlot; + Config.Modified.Fire(); _controller.Clear(); } public void SwitchToFollow(int masterSlot) { SwitchToIdle(); - _config.FollowSlot = (AIConfig.Slot)masterSlot; - _config.Modified.Fire(); + Config.FollowSlot = (AIConfig.Slot)masterSlot; + Config.Modified.Fire(); Behaviour = new AIBehaviour(_controller, _autorot); } @@ -159,7 +115,7 @@ private unsafe int FindPartyMemberSlotFromSender(SeString sender) private void OnChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) { - if (!_config.Enabled || type != XivChatType.Party) + if (!Config.Enabled || type != XivChatType.Party) return; var messagePrefix = message.Payloads.FirstOrDefault() as TextPayload; @@ -185,78 +141,4 @@ private void OnChatMessage(XivChatType type, int timestamp, ref SeString sender, break; } } - - private void OnCommand(string cmd, string message) - { - var messageData = message.Split(' '); - switch (messageData[0]) - { - case "on": - Enabled = true; - break; - case "off": - Enabled = false; - break; - case "toggle": - Enabled ^= true; - break; - case "follow": - if (messageData.Length < 2) - { - Service.Log($"[AI] [Follow] Usage: /vbmai follow name"); - return; - } - - var masterString = messageData.Length > 2 ? $"{messageData[1]} {messageData[2]}" : messageData[1]; - var masterStringIsSlot = masterString[..4].Equals("slot", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt32(masterString.Substring(4, 1)) : 0; - - var master = masterStringIsSlot > 0 ? (masterStringIsSlot - 1, WorldState.Party[masterStringIsSlot - 1]) : WorldState.Party.WithSlot().FirstOrDefault(x => x.Item2.Name.Equals(masterString, StringComparison.OrdinalIgnoreCase)); - - if (master.Item2 is null) - { - Service.Log($"[AI] [Follow] Error: can't find {masterString} in our party"); - return; - } - - _config.FollowSlot = (AIConfig.Slot)master.Item1; - _config.Modified.Fire(); - Enabled = true; - - break; - case "ui": - _config.DrawUI ^= true; - _config.Modified.Fire(); - break; - default: - List list = []; - list.Add("AIConfig"); - list.AddRange(messageData); - - if (list.Count == 2) - { - //toggle - var result = Service.Config.ConsoleCommand(list); - if (bool.TryParse(result[0], out var resultBool)) - { - list.Add((!resultBool).ToString()); - Service.Config.ConsoleCommand(list); - } - else - Service.Log($"[AI] Unknown command: {messageData[0]}"); - } - else if (list.Count == 3) - { - //set - var onOffReplace = list[2].Replace("on", "true", StringComparison.InvariantCultureIgnoreCase).Replace("off", "false", StringComparison.InvariantCultureIgnoreCase); - list[2] = onOffReplace; - - if (Service.Config.ConsoleCommand(list).Count > 0) - Service.Log($"[AI] Unknown command: {messageData[0]}"); - } - else - Service.Log($"[AI] Unknown command: {messageData[0]}"); - - break; - } - } } diff --git a/BossMod/AI/AIWindow.cs b/BossMod/AI/AIWindow.cs new file mode 100644 index 0000000000..861779ab78 --- /dev/null +++ b/BossMod/AI/AIWindow.cs @@ -0,0 +1,76 @@ +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; + +namespace BossMod.AI; + +internal sealed class AIWindow : UIWindow +{ + private const string _windowID = "###AI"; + + private readonly AIManager _manager; + private readonly EventSubscriptions _subscriptions; + + public AIWindow(AIManager mgr) : base(_windowID, false, new(100, 100), ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoFocusOnAppearing) + { + _manager = mgr; + _subscriptions = new + ( + _manager.Config.Modified.ExecuteAndSubscribe(() => IsOpen = _manager.Config.DrawUI) + ); + RespectCloseHotkey = false; + } + + protected override void Dispose(bool disposing) + { + _subscriptions.Dispose(); + base.Dispose(disposing); + } + + public void SetVisible(bool vis) + { + if (_manager.Config.DrawUI != vis) + { + _manager.Config.DrawUI = vis; + _manager.Config.Modified.Fire(); + } + } + + public override bool DrawConditions() => _manager.WorldState.Party.Player() != null; + + public override void PreDraw() + { + var windowName = _manager.Config.ShowStatusOnTitlebar ? _manager.AIStatus : "AI"; + WindowName = windowName + _windowID; + base.PreDraw(); + } + + public override void Draw() + { + if (!_manager.Config.ShowStatusOnTitlebar) + ImGui.TextUnformatted(_manager.AIStatus); + ImGui.TextUnformatted(_manager.NaviStatus); + _manager.Behaviour?.DrawDebug(); + + using (var leaderCombo = ImRaii.Combo("Follow", _manager.Behaviour == null ? "" : _manager.WorldState.Party[_manager.MasterSlot]?.Name ?? "")) + { + if (leaderCombo) + { + if (ImGui.Selectable("", _manager.Behaviour == null)) + { + _manager.Enabled = false; + } + foreach (var (i, p) in _manager.WorldState.Party.WithSlot(true)) + { + if (ImGui.Selectable(p.Name, _manager.MasterSlot == i)) + { + _manager.Config.FollowSlot = (AIConfig.Slot)i; + _manager.Config.Modified.Fire(); + _manager.Enabled = true; + } + } + } + } + } + + public override void OnClose() => SetVisible(false); +} diff --git a/BossMod/Autorotation/UIRotationWindow.cs b/BossMod/Autorotation/UIRotationWindow.cs index 85c598b025..7c0c6b4d3c 100644 --- a/BossMod/Autorotation/UIRotationWindow.cs +++ b/BossMod/Autorotation/UIRotationWindow.cs @@ -9,22 +9,42 @@ public sealed class UIRotationWindow : UIWindow private readonly RotationModuleManager _mgr; private readonly ActionManagerEx _amex; private readonly AutorotationConfig _config = Service.Config.Get(); + private readonly EventSubscriptions _subscriptions; public UIRotationWindow(RotationModuleManager mgr, ActionManagerEx amex, Action openConfig) : base("Autorotation", false, new(400, 400), ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoFocusOnAppearing) { _mgr = mgr; _amex = amex; - ShowCloseButton = false; + _subscriptions = new + ( + _config.Modified.ExecuteAndSubscribe(() => IsOpen = _config.ShowUI) + ); RespectCloseHotkey = false; TitleBarButtons.Add(new() { Icon = FontAwesomeIcon.Cog, IconOffset = new(1), Click = _ => openConfig() }); } + protected override void Dispose(bool disposing) + { + _subscriptions.Dispose(); + base.Dispose(disposing); + } + + public void SetVisible(bool vis) + { + if (_config.ShowUI != vis) + { + _config.ShowUI = vis; + _config.Modified.Fire(); + } + } + public override void PreOpenCheck() { - IsOpen = _config.ShowUI && _mgr.WorldState.Party.Player() != null; DrawPositional(); } + public override bool DrawConditions() => _mgr.WorldState.Party.Player() != null; + public override void Draw() { var player = _mgr.Player; @@ -79,6 +99,8 @@ public override void Draw() } } + public override void OnClose() => SetVisible(false); + public static bool DrawRotationSelector(RotationModuleManager mgr) { var modified = false; diff --git a/BossMod/BossModule/BossModuleMainWindow.cs b/BossMod/BossModule/BossModuleMainWindow.cs index 5aac1843fc..6cf866e891 100644 --- a/BossMod/BossModule/BossModuleMainWindow.cs +++ b/BossMod/BossModule/BossModuleMainWindow.cs @@ -30,6 +30,9 @@ public override void PreOpenCheck() if (_mgr.Config.Lock) Flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoInputs; ForceMainWindow = _mgr.Config.TrishaMode; // NoBackground flag without ForceMainWindow works incorrectly for whatever reason + + if (_mgr.Config.ShowWorldArrows && _mgr.ActiveModule != null && _mgr.WorldState.Party[PartyState.PlayerSlot] is var pc && pc != null) + DrawMovementHints(_mgr.ActiveModule.CalculateMovementHintsForRaidMember(PartyState.PlayerSlot, pc), pc.PosRot.Y); } public override void OnOpen() @@ -65,8 +68,6 @@ public override void Draw() try { _mgr.ActiveModule.Draw(_mgr.Config.RotateArena ? _mgr.WorldState.Client.CameraAzimuth : default, PartyState.PlayerSlot, !_mgr.Config.HintsInSeparateWindow, true); - if (_mgr.Config.ShowWorldArrows && _mgr.WorldState.Party[PartyState.PlayerSlot] is var pc && pc != null) - DrawMovementHints(_mgr.ActiveModule.CalculateMovementHintsForRaidMember(PartyState.PlayerSlot, pc), pc.PosRot.Y); } catch (Exception ex) { diff --git a/BossMod/Config/ConfigRoot.cs b/BossMod/Config/ConfigRoot.cs index a53756fbdc..ae0d231a01 100644 --- a/BossMod/Config/ConfigRoot.cs +++ b/BossMod/Config/ConfigRoot.cs @@ -77,10 +77,10 @@ public void SaveToFile(FileInfo file) } } - public List ConsoleCommand(IReadOnlyList args, bool save = true) + public List ConsoleCommand(ReadOnlySpan args, bool save = true) { List result = []; - if (args.Count == 0) + if (args.Length == 0) { result.Add("Usage: /vbm cfg "); result.Add("Both config-type and field can be shortened. Valid config-types:"); @@ -105,7 +105,7 @@ public List ConsoleCommand(IReadOnlyList args, bool save = true) foreach (var n in matchingNodes) result.Add($"- {n.GetType().Name}"); } - else if (args.Count == 1) + else if (args.Length == 1) { result.Add("Usage: /vbm cfg "); result.Add($"Valid fields for {matchingNodes[0].GetType().Name}:"); @@ -139,7 +139,7 @@ public List ConsoleCommand(IReadOnlyList args, bool save = true) { try { - if (args.Count == 2) + if (args.Length == 2) result.Add(matchingFields[0].GetValue(matchingNodes[0])?.ToString() ?? $"Failed to get value of '{args[2]}'"); else { @@ -158,7 +158,7 @@ public List ConsoleCommand(IReadOnlyList args, bool save = true) } catch (Exception e) { - if (args.Count == 2) + if (args.Length == 2) result.Add($"Failed to get value of {matchingNodes[0].GetType().Name}.{matchingFields[0].Name} : {e}"); else result.Add($"Failed to set {matchingNodes[0].GetType().Name}.{matchingFields[0].Name} to {args[2]}: {e}"); diff --git a/BossMod/Framework/DTRProvider.cs b/BossMod/Framework/DTRProvider.cs index 017ec64268..e61acffa4b 100644 --- a/BossMod/Framework/DTRProvider.cs +++ b/BossMod/Framework/DTRProvider.cs @@ -4,8 +4,6 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface.Utility.Raii; -using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.UI; using ImGuiNET; diff --git a/BossMod/Framework/IPCProvider.cs b/BossMod/Framework/IPCProvider.cs index ad6d5adcd8..c08dc37954 100644 --- a/BossMod/Framework/IPCProvider.cs +++ b/BossMod/Framework/IPCProvider.cs @@ -26,7 +26,7 @@ public IPCProvider(RotationModuleManager autorotation, ActionManagerEx amex, Mov Register("HasModuleByDataId", (uint dataId) => BossModuleRegistry.FindByOID(dataId) != null); Register("IsMoving", movement.IsMoving); Register("ForbiddenZonesCount", () => autorotation.Hints.ForbiddenZones.Count); - Register("Configuration", (IReadOnlyList args, bool save) => Service.Config.ConsoleCommand(args, save)); + Register("Configuration", (List args, bool save) => Service.Config.ConsoleCommand(args.AsSpan(), save)); //Register("InitiateCombat", () => autorotation.ClassActions?.UpdateAutoAction(CommonActions.AutoActionAIFight, float.MaxValue, true)); //Register("SetAutorotationState", (bool state) => Service.Config.Get().Enabled = state); diff --git a/BossMod/Framework/Plugin.cs b/BossMod/Framework/Plugin.cs index 19a8555189..4a852d3dca 100644 --- a/BossMod/Framework/Plugin.cs +++ b/BossMod/Framework/Plugin.cs @@ -38,6 +38,7 @@ public sealed class Plugin : IDalamudPlugin private readonly BossModuleHintsWindow _wndBossmodHints; private readonly ReplayManagementWindow _wndReplay; private readonly UIRotationWindow _wndRotation; + private readonly AI.AIWindow _wndAI; private readonly MainDebugWindow _wndDebug; public unsafe Plugin(IDalamudPluginInterface dalamud, ICommandManager commandManager, ISigScanner sigScanner, IDataManager dataManager) @@ -69,6 +70,7 @@ public unsafe Plugin(IDalamudPluginInterface dalamud, ICommandManager commandMan CommandManager = commandManager; CommandManager.AddHandler("/vbm", new CommandInfo(OnCommand) { HelpMessage = "Show boss mod settings UI" }); + CommandManager.AddHandler("/vbmai", new CommandInfo((cmd, args) => ParseAICommands(args.Split(' ', StringSplitOptions.RemoveEmptyEntries)))); //TODO: deprecated ActionDefinitions.Instance.UnlockCheck = QuestUnlocked; // ensure action definitions are initialized and set unlock check functor (we don't really store the quest progress in clientstate, for now at least) @@ -94,6 +96,7 @@ public unsafe Plugin(IDalamudPluginInterface dalamud, ICommandManager commandMan _wndBossmodHints = new(_bossmod, _zonemod); _wndReplay = new(_ws, _rotationDB, replayDir); _wndRotation = new(_rotation, _amex, () => OpenConfigUI("Autorotation Presets")); + _wndAI = new(_ai); _wndDebug = new(_ws, _rotation, _amex, _hintsBuilder, dalamud); dalamud.UiBuilder.DisableAutomaticUiHide = true; @@ -107,6 +110,7 @@ public void Dispose() { Service.Condition.ConditionChange -= OnConditionChanged; _wndDebug.Dispose(); + _wndAI.Dispose(); _wndRotation.Dispose(); _wndReplay.Dispose(); _wndBossmodHints.Dispose(); @@ -124,6 +128,7 @@ public void Dispose() _dtr.Dispose(); ActionDefinitions.Instance.Dispose(); CommandManager.RemoveHandler("/vbm"); + CommandManager.RemoveHandler("/vbmai"); // TODO: deprecated } private void OnCommand(string cmd, string args) @@ -143,7 +148,7 @@ private void OnCommand(string cmd, string args) _wndDebug.BringToFront(); break; case "cfg": - var output = Service.Config.ConsoleCommand(new ArraySegment(split, 1, split.Length - 1)); + var output = Service.Config.ConsoleCommand(split.AsSpan(1)); foreach (var msg in output) Service.ChatGui.Print(msg); break; @@ -158,6 +163,9 @@ private void OnCommand(string cmd, string args) case "ar": ParseAutorotationCommands(split); break; + case "ai": + ParseAICommands(split.AsSpan(1)); + break; } } @@ -245,6 +253,11 @@ private void ParseAutorotationCommands(string[] cmd) case "toggle": ParseAutorotationSetCommand(cmd.Length > 2 ? cmd[2] : "", true); break; + case "ui": + _wndRotation.SetVisible(!_wndRotation.IsOpen); + break; + case "help": + case "?": default: PrintAutorotationHelp(); break; @@ -274,6 +287,98 @@ private void PrintAutorotationHelp() Service.ChatGui.Print("* /vbm ar set Preset - start executing specified preset"); Service.ChatGui.Print("* /vbm ar toggle - force disable autorotation if not already; otherwise clear overrides"); Service.ChatGui.Print("* /vbm ar toggle Preset - start executing specified preset unless it's already active; clear otherwise"); + Service.ChatGui.Print("* /vbm ar ui - toggle autorotation ui"); + } + + private void ParseAICommands(ReadOnlySpan cmd) + { + switch (cmd.Length > 0 ? cmd[0] : "") + { + case "on": + _ai.Enabled = true; + break; + case "off": + _ai.Enabled = false; + break; + case "toggle": + _ai.Enabled ^= true; + break; + case "follow": + if (cmd.Length < 1) + { + PrintAIHelp(); + return; + } + + var masterString = cmd.Length > 1 ? $"{cmd[0]} {cmd[1]}" : cmd[0]; + var masterStringIsSlot = masterString.StartsWith("slot", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt32(masterString.Substring(4, 1)) : 0; + var master = masterStringIsSlot > 0 ? (masterStringIsSlot - 1, _ws.Party[masterStringIsSlot - 1]) : _ws.Party.WithSlot().FirstOrDefault(x => x.Item2.Name.Equals(masterString, StringComparison.OrdinalIgnoreCase)); + if (master.Item2 is null) + { + Service.ChatGui.PrintError($"[AI] [Follow] Error: can't find {masterString} in our party"); + return; + } + _ai.SwitchToFollow(master.Item1); + _ai.Enabled = true; + break; + case "ui": + _wndAI.SetVisible(!_wndAI.IsOpen); + break; + case "help": + case "?": + case "": + PrintAIHelp(); + break; + default: + // TODO: this should really be removed, it's synonym for /vbm cfg AIConfig ... + List list = []; + list.Add("AIConfig"); + list.AddRange(cmd); + + if (list.Count == 2) + { + //toggle + var result = Service.Config.ConsoleCommand(list.AsSpan()); + if (bool.TryParse(result[0], out var resultBool)) + { + list.Add((!resultBool).ToString()); + Service.Config.ConsoleCommand(list.AsSpan()); + } + else + { + Service.Log($"[AI] Unknown command: {cmd[0]}"); + } + } + else if (list.Count == 3) + { + //set + var onOffReplace = list[2].Replace("on", "true", StringComparison.InvariantCultureIgnoreCase).Replace("off", "false", StringComparison.InvariantCultureIgnoreCase); + if (list[2] == "on") + list[2] = "true"; + else if (list[2] == "off") + list[2] = "false"; + + if (Service.Config.ConsoleCommand(list.AsSpan()).Count > 0) + Service.Log($"[AI] Unknown command: {cmd[0]}"); + } + else + { + Service.Log($"[AI] Unknown command: {cmd[0]}"); + } + + break; + } + } + + private void PrintAIHelp() + { + Service.ChatGui.Print("AI commands:"); + Service.ChatGui.Print("* /vbm ai on - enable AI mode"); + Service.ChatGui.Print("* /vbm ai off - disable AI mode"); + Service.ChatGui.Print("* /vbm ai toggle - toggle AI mode"); + Service.ChatGui.Print("* /vbm ai follow - enable AI mode and follow party member with specified name"); + Service.ChatGui.Print("* /vbm ai follow slot - enable AI mode and follow party member at slot #N (1-8)"); + Service.ChatGui.Print("* /vbm ai ui - toggle AI ui"); } private void OnConditionChanged(ConditionFlag flag, bool value) diff --git a/TODO b/TODO index 2858930fc4..ed8cd231f4 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,11 @@ immediate plans - nechuciho - seen incorrect rotations... -- merge prs! +- questbattles - collisions for pathfinding -- embedded mode -- brd rotation general: +- refactor ipc/dtr/slashcommands - horizontal timeline / cooldown planner - cdplanner should use real cds - assignments per ui order rather than per player + class-specific assignments @@ -58,14 +58,12 @@ autorotation: -- target selection should be part of a module/preset -- ai manager should use selected preset for combat, out of combat use forcedtarget - action history/queue visualization -- proper gcd time calculations? - simulation in replay analysis - spell out shared cooldowns in actiondefinitions comments instead of group? - delete/rework commonstate/legacy - delete autorotationlegacy dir - dot/regen server tick tracking - brd --- aoe rotation (2/3/4+ targets, barrage usage) -- take traits into account (ij proccing rs, ea proccing repertoire) - drg -- priorities...