diff --git a/RotationSolver.Basic/Configuration/Timeline/DrawingTimeline.cs b/RotationSolver.Basic/Configuration/Timeline/DrawingTimeline.cs index aaa525d7d..2d78def47 100644 --- a/RotationSolver.Basic/Configuration/Timeline/DrawingTimeline.cs +++ b/RotationSolver.Basic/Configuration/Timeline/DrawingTimeline.cs @@ -9,7 +9,10 @@ internal class DrawingTimeline : BaseTimelineItem { public float Duration { get; set; } = 5; - public ITimelineCondition Condition { get; set; } = new TrueTimelineCondition(); + public TimelineConditionSet Condition { get; set; } = new() + { + Conditions = [new TrueTimelineCondition()], + }; public List DrawingGetters { get; set; } = []; private IDisposable[] _drawings = []; @@ -26,7 +29,7 @@ public override bool InPeriod(TimelineItem item) if (time < Time - Duration) return false; if (time > Time) return false; - if (!Condition.IsTrue()) return false; + if (!Condition.IsTrue(item)) return false; return true; } diff --git a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ITimelineCondition.cs b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ITimelineCondition.cs index 87082f6f0..6f7d01b1b 100644 --- a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ITimelineCondition.cs +++ b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ITimelineCondition.cs @@ -1,5 +1,5 @@ namespace RotationSolver.Basic.Configuration.Timeline.TimelineCondition; -public interface ITimelineCondition +internal interface ITimelineCondition { - bool IsTrue(); + bool IsTrue(TimelineItem item); } diff --git a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ITimelineConditionConverter.cs b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ITimelineConditionConverter.cs index 5b5715d9e..34d593dd0 100644 --- a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ITimelineConditionConverter.cs +++ b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ITimelineConditionConverter.cs @@ -1,15 +1,22 @@ using Newtonsoft.Json.Linq; using RotationSolver.Basic.Configuration.Conditions; -using RotationSolver.Basic.Configuration.Timeline.TimelineDrawing; namespace RotationSolver.Basic.Configuration.Timeline.TimelineCondition; internal class ITimelineConditionConverter : JsonCreationConverter { protected override ITimelineCondition? Create(JObject jObject) { - if (FieldExists(nameof(ActionDrawingGetter.ActionID), jObject)) + if (FieldExists(nameof(TimelineConditionSet.Conditions), jObject)) { - //return new ActionDrawingGetter(); + return new TimelineConditionSet(); + } + else if (FieldExists(nameof(TimelineConditionAction.ActionID), jObject)) + { + return new TimelineConditionAction(); + } + else if (FieldExists(nameof(TimelineConditionTargetCount.Getter), jObject)) + { + return new TimelineConditionTargetCount(); } return new TrueTimelineCondition(); diff --git a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ObjectGetter.cs b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ObjectGetter.cs index 20c003a9d..318342101 100644 --- a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ObjectGetter.cs +++ b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/ObjectGetter.cs @@ -1,17 +1,18 @@ using Dalamud.Game.ClientState.Objects.SubKinds; +using System.Text.RegularExpressions; namespace RotationSolver.Basic.Configuration.Timeline.TimelineCondition; internal class ObjectGetter { public bool IsPlayer { get; set; } = false; - public uint DataID { get; set; } = 0; + public string DataID { get; set; } = ""; public JobRole Role { get; set; } = JobRole.None; public bool CanGet(GameObject obj) { if (IsPlayer && obj is not PlayerCharacter) return false; - if (DataID != 0 && obj.DataId != DataID) return false; + if (!string.IsNullOrEmpty(DataID) && new Regex(DataID).IsMatch(obj.DataId.ToString("X"))) return false; if (Role != JobRole.None && !obj.IsJobCategory(Role)) return false; return true; diff --git a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionAction.cs b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionAction.cs new file mode 100644 index 000000000..caf9a0626 --- /dev/null +++ b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionAction.cs @@ -0,0 +1,10 @@ +namespace RotationSolver.Basic.Configuration.Timeline.TimelineCondition; +internal class TimelineConditionAction : ITimelineCondition +{ + public uint ActionID { get; set; } + public bool IsTrue(TimelineItem item) + { + if (ActionID == 0) return true; + return ActionID == item.LastActionID; + } +} diff --git a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionSet.cs b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionSet.cs new file mode 100644 index 000000000..2911f3dfe --- /dev/null +++ b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionSet.cs @@ -0,0 +1,21 @@ +using RotationSolver.Basic.Configuration.Conditions; + +namespace RotationSolver.Basic.Configuration.Timeline.TimelineCondition; + +internal class TimelineConditionSet : ITimelineCondition +{ + public List Conditions { get; set; } = []; + + public LogicalType Type; + public bool IsTrue(TimelineItem item) + { + if (Conditions.Count == 0) return false; + + return Type switch + { + LogicalType.And => Conditions.All(c => c.IsTrue(item)), + LogicalType.Or => Conditions.Any(c => c.IsTrue(item)), + _ => false, + }; + } +} diff --git a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionTargetCount.cs b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionTargetCount.cs new file mode 100644 index 000000000..2648f3506 --- /dev/null +++ b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TimelineConditionTargetCount.cs @@ -0,0 +1,12 @@ +using ECommons.DalamudServices; + +namespace RotationSolver.Basic.Configuration.Timeline.TimelineCondition; +internal class TimelineConditionTargetCount : ITimelineCondition +{ + public int Count { get; set; } + public ObjectGetter Getter { get; set; } = new(); + public bool IsTrue(TimelineItem item) + { + return Svc.Objects.Count(Getter.CanGet) >= Count; + } +} diff --git a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TrueTimelineCondition.cs b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TrueTimelineCondition.cs index d8557bc6c..9c65fab25 100644 --- a/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TrueTimelineCondition.cs +++ b/RotationSolver.Basic/Configuration/Timeline/TimelineCondition/TrueTimelineCondition.cs @@ -1,6 +1,6 @@ namespace RotationSolver.Basic.Configuration.Timeline.TimelineCondition; -public class TrueTimelineCondition : ITimelineCondition +internal class TrueTimelineCondition : ITimelineCondition { - public bool IsTrue() => true; + public bool IsTrue(TimelineItem item) => true; } diff --git a/RotationSolver.Basic/Data/TimelineItem.cs b/RotationSolver.Basic/Data/TimelineItem.cs index 9885fbc33..7ae180d97 100644 --- a/RotationSolver.Basic/Data/TimelineItem.cs +++ b/RotationSolver.Basic/Data/TimelineItem.cs @@ -15,13 +15,13 @@ internal enum TimelineType : byte AddedCombatant, } -internal struct TimelineItem(float time, string name, TimelineType type, JObject? obj, RaidLangs langs, float? jumpTime, float windowMin, float windowMax) +internal class TimelineItem(float time, string name, TimelineType type, JObject? obj, RaidLangs langs, float? jumpTime, float windowMin, float windowMax) { private uint[]? ids = null; public uint LastActionID { get; set; } public uint[] ActionIDs => ids ??= GetActionIds(); - private readonly uint[] GetActionIds() + private uint[] GetActionIds() { if (Type is not TimelineType.Ability and not TimelineType.StartsUsing) return []; var idsRaw = this["id"]; @@ -61,7 +61,7 @@ private readonly uint[] GetActionIds() return [.. reuslt]; } - private readonly RaidLangs.Lang Lang + private RaidLangs.Lang Lang { get { @@ -79,15 +79,15 @@ private readonly RaidLangs.Lang Lang return new RaidLangs.Lang(); } } - public readonly TimelineType Type => type; + public TimelineType Type => type; - public readonly float Time => time; + public float Time => time; - public readonly float WindowMin => windowMin; - public readonly float WindowMax => windowMax; - public readonly JObject? Object => obj; + public float WindowMin => windowMin; + public float WindowMax => windowMax; + public JObject? Object => obj; - public readonly string Name + public string Name { get { @@ -96,14 +96,14 @@ public readonly string Name } } - public readonly bool IsInWindow => DataCenter.RaidTimeRaw >= Time - WindowMin && DataCenter.RaidTimeRaw <= Time + WindowMax; + public bool IsInWindow => DataCenter.RaidTimeRaw >= Time - WindowMin && DataCenter.RaidTimeRaw <= Time + WindowMax; - public readonly bool IsShown => Name is not "--Reset--" and not "--sync--"; + public bool IsShown => Name is not "--Reset--" and not "--sync--"; - public readonly bool this[string propertyName, uint matchValue] + public bool this[string propertyName, uint matchValue] => this[propertyName, matchValue.ToString("X")]; - public readonly bool this[string propertyName, string matchString] + public bool this[string propertyName, string matchString] { get { @@ -124,7 +124,7 @@ public readonly string Name } } - public readonly string[] this[string propertyName] + public string[] this[string propertyName] { get { @@ -203,7 +203,7 @@ private static TimelineType GetTypeFromName(string type) } } - public readonly void UpdateRaidTimeOffset() + public void UpdateRaidTimeOffset() { if (Name == "--Reset--") { @@ -222,7 +222,7 @@ public readonly void UpdateRaidTimeOffset() } } - public readonly override string ToString() + public override string ToString() { return $""" IsShown: {IsShown}, diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 29102c3d8..587e8499f 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -5,7 +5,6 @@ using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Utility; -using ECommons; using ECommons.DalamudServices; using ECommons.ExcelServices; using ECommons.GameFunctions; @@ -16,6 +15,7 @@ using FFXIVClientStructs.FFXIV.Common.Component.BGCollision; using Lumina.Excel.GeneratedSheets; using RotationSolver.Basic.Configuration; +using RotationSolver.Basic.Configuration.Conditions; using RotationSolver.Basic.Configuration.Timeline; using RotationSolver.Basic.Configuration.Timeline.TimelineCondition; using RotationSolver.Basic.Configuration.Timeline.TimelineDrawing; @@ -24,13 +24,9 @@ using RotationSolver.Localization; using RotationSolver.UI.SearchableConfigs; using RotationSolver.Updaters; -using System; using System.Diagnostics; -using System.Xml.Linq; using XIVPainter; using XIVPainter.Vfx; -using static Dalamud.Interface.Utility.Raii.ImRaii; -using static FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentMJIFarmManagement; using GAction = Lumina.Excel.GeneratedSheets.Action; using TargetType = RotationSolver.Basic.Actions.TargetType; @@ -851,7 +847,7 @@ void Down() } else if(timeLineItem is DrawingTimeline drawingItem) { - DrawDrawingTimeline(drawingItem, item.ActionIDs); + DrawDrawingTimeline(drawingItem, item); } } @@ -885,7 +881,7 @@ void AddOneCondition() where T : BaseTimelineItem } } - private static void DrawDrawingTimeline(DrawingTimeline drawingItem, uint[] actionIds) + private static void DrawDrawingTimeline(DrawingTimeline drawingItem, TimelineItem timelineItem) { var duration = drawingItem.Duration; if (ConditionDrawer.DrawDragFloat(ConfigUnitType.Seconds, $"Duration##Duration{drawingItem.GetHashCode()}", ref duration)) @@ -935,10 +931,10 @@ void Down() (Up, [VirtualKey.UP]), (Down, [VirtualKey.DOWN])); - DrawingGetterDraw(item, actionIds); + DrawingGetterDraw(item, timelineItem.ActionIDs); } - TimelineConditionDraw(drawingItem.Condition); + TimelineConditionDraw(drawingItem.Condition, timelineItem); void AddButton() { @@ -953,7 +949,7 @@ void AddButton() AddOneCondition(); AddOneCondition(); - if (actionIds.Length > 0) + if (timelineItem.ActionIDs.Length > 0) { AddOneCondition(); } @@ -970,9 +966,127 @@ void AddOneCondition() where T : IDrawingGetter } } - private static void TimelineConditionDraw(ITimelineCondition condition) + private static void TimelineConditionDraw(ITimelineCondition con, TimelineItem timelineItem) { - //TODO: the condition drawing in the config window. + if (con is TimelineConditionSet set) + { + using var grp = ImRaii.Group(); + AddButton(); + ImGui.SameLine(); + ConditionDrawer.DrawByteEnum($"##Rule{set.GetHashCode()}", ref set.Type); + ImGui.Spacing(); + + for (int i = 0; i < set.Conditions.Count; i++) + { + var condition = set.Conditions[i]; + + void Delete() + { + set.Conditions.RemoveAt(i); + }; + + void Up() + { + set.Conditions.RemoveAt(i); + set.Conditions.Insert(Math.Max(0, i - 1), condition); + }; + + void Down() + { + set.Conditions.RemoveAt(i); + set.Conditions.Insert(Math.Min(set.Conditions.Count, i + 1), condition); + } + + void Copy() + { + var str = JsonConvert.SerializeObject(set.Conditions[i], Formatting.Indented); + ImGui.SetClipboardText(str); + } + + var key = $"Condition Pop Up: {condition.GetHashCode()}"; + + ImGuiHelper.DrawHotKeysPopup(key, string.Empty, + (UiString.ConfigWindow_List_Remove.Local(), Delete, ["Delete"]), + (UiString.ConfigWindow_Actions_MoveUp.Local(), Up, ["↑"]), + (UiString.ConfigWindow_Actions_MoveDown.Local(), Down, ["↓"]), + (UiString.ConfigWindow_Actions_Copy.Local(), Copy, ["Ctrl"])); + + + ConditionDrawer.DrawCondition(condition.IsTrue(timelineItem)); + + ImGuiHelper.ExecuteHotKeysPopup(key, string.Empty, string.Empty, true, + (Delete, [VirtualKey.DELETE]), + (Up, [VirtualKey.UP]), + (Down, [VirtualKey.DOWN]), + (Copy, [VirtualKey.CONTROL])); + + ImGui.SameLine(); + + TimelineConditionDraw(condition, timelineItem); + } + + void AddButton() + { + if (ImGuiEx.IconButton(FontAwesomeIcon.Plus, "AddButton" + set.GetHashCode().ToString())) + { + ImGui.OpenPopup("Popup" + set.GetHashCode().ToString()); + } + + using var popUp = ImRaii.Popup("Popup" + set.GetHashCode().ToString()); + if (popUp) + { + AddOneCondition(); + AddOneCondition(); + AddOneCondition(); + + if (ImGui.Selectable(UiString.ActionSequencer_FromClipboard.Local())) + { + var str = ImGui.GetClipboardText(); + try + { + var s = JsonConvert.DeserializeObject(str, new IConditionConverter())!; + set.Conditions.Add(set); + } + catch (Exception ex) + { + Svc.Log.Warning(ex, "Failed to load the condition."); + } + ImGui.CloseCurrentPopup(); + } + } + + void AddOneCondition() where T : ITimelineCondition + { + if (ImGui.Selectable(typeof(T).Local())) + { + set.Conditions.Add(Activator.CreateInstance()); + ImGui.CloseCurrentPopup(); + } + } + } + + } + else if(con is TimelineConditionTargetCount target) + { + var count = target.Count; + if (ConditionDrawer.DrawDragInt("Target Count: ##" + target.GetHashCode(), ref count)) + { + target.Count = count; + } + + DrawObjectGetter(target.Getter); + } + else if(con is TimelineConditionAction action) + { + var index = Array.IndexOf(timelineItem.ActionIDs, action.ActionID); + var actionNames = timelineItem.ActionIDs.Select(i => Svc.Data.GetExcelSheet()?.GetRow(i)?.Name.RawString ?? "Unnamed Action").ToArray(); + + ImGui.SameLine(); + if (ImGuiHelper.SelectableCombo("Action ##Select Action" + action.GetHashCode(), actionNames, ref index)) + { + action.ActionID = timelineItem.ActionIDs[index]; + } + } } static readonly string[] _omenNames = typeof(GroundOmenHostile).GetRuntimeFields() @@ -1135,16 +1249,16 @@ private static void DrawObjectGetter(ObjectGetter getter) getter.IsPlayer = check; } - var id = (int)getter.DataID; - if(ConditionDrawer.DrawDragInt("Data ID :## " + getter.GetHashCode(), ref id)) + var v = getter.Role; + if (ConditionDrawer.DrawByteEnum("Job Role: ##" + getter.GetHashCode(), ref v)) { - getter.DataID = (uint)id; + getter.Role = v; } - var v = getter.Role; - if(ConditionDrawer.DrawByteEnum("Job Role: ##" + getter.GetHashCode(), ref v)) + var name = getter.DataID; + if(ImGui.InputText("Data ID :## " + getter.GetHashCode(), ref name, 256)) { - getter.Role = v; + getter.DataID = name; } }