diff --git a/Squadronista/Solver/Attributes.cs b/Squadronista/Solver/Attributes.cs index 8296e05..2977695 100644 --- a/Squadronista/Solver/Attributes.cs +++ b/Squadronista/Solver/Attributes.cs @@ -1,8 +1,40 @@ +using System; + namespace Squadronista.Solver; -internal class Attributes +internal class Attributes : IEquatable { public required int PhysicalAbility { get; init; } public required int MentalAbility { get; init; } public required int TacticalAbility { get; init; } + + public bool Equals(Attributes? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return PhysicalAbility == other.PhysicalAbility && MentalAbility == other.MentalAbility && TacticalAbility == other.TacticalAbility; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Attributes)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(PhysicalAbility, MentalAbility, TacticalAbility); + } + + public static bool operator ==(Attributes? left, Attributes? right) + { + return Equals(left, right); + } + + public static bool operator !=(Attributes? left, Attributes? right) + { + return !Equals(left, right); + } } diff --git a/Squadronista/Solver/SquadronMemberUiFlags.cs b/Squadronista/Solver/SquadronMemberUiFlags.cs new file mode 100644 index 0000000..c480887 --- /dev/null +++ b/Squadronista/Solver/SquadronMemberUiFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Squadronista.Solver; + +[Flags] +public enum SquadronMemberUiFlags : int +{ + Unknown1 = 1, + IsPartOfMission = 2, + NewChemistryAvailable = 8192, +} diff --git a/Squadronista/Solver/SquadronSolver.cs b/Squadronista/Solver/SquadronSolver.cs index 3d7e376..c5a4152 100644 --- a/Squadronista/Solver/SquadronSolver.cs +++ b/Squadronista/Solver/SquadronSolver.cs @@ -40,9 +40,9 @@ internal sealed class SquadronSolver public IEnumerable SolveFor(SquadronMission mission, int requiredMatchingStats) { - int minPhysical = mission.CurrentAttributes.PhysicalAbility; - int minMental = mission.CurrentAttributes.MentalAbility; - int minTactical = mission.CurrentAttributes.TacticalAbility; + int minPhysical = mission.CurrentAttributes!.PhysicalAbility; + int minMental = mission.CurrentAttributes!.MentalAbility; + int minTactical = mission.CurrentAttributes!.TacticalAbility; bool foundWithoutTraining = false; List intermediates = CalculateForAllMemberCombinations(mission.Level, _state.Bonus); diff --git a/Squadronista/SquadronMission.cs b/Squadronista/SquadronMission.cs index af8b76e..13d4bce 100644 --- a/Squadronista/SquadronMission.cs +++ b/Squadronista/SquadronMission.cs @@ -1,4 +1,5 @@ -using Squadronista.Solver; +using System.Collections.Generic; +using Squadronista.Solver; namespace Squadronista; @@ -9,5 +10,6 @@ internal sealed class SquadronMission public required byte Level { get; init; } public required bool IsFlaggedMission { get; init; } + public required IReadOnlyList PossibleAttributes { get; init; } public Attributes? CurrentAttributes { get; set; } } diff --git a/Squadronista/Squadronista.csproj b/Squadronista/Squadronista.csproj index f7b611a..52f36b3 100644 --- a/Squadronista/Squadronista.csproj +++ b/Squadronista/Squadronista.csproj @@ -1,7 +1,7 @@ net7.0-windows - 0.3 + 0.4 11.0 enable true diff --git a/Squadronista/SquadronistaPlugin.cs b/Squadronista/SquadronistaPlugin.cs index 850e222..4fbafd1 100644 --- a/Squadronista/SquadronistaPlugin.cs +++ b/Squadronista/SquadronistaPlugin.cs @@ -55,6 +55,15 @@ public class SquadronistaPlugin : IDalamudPlugin // 13 and 14 seems to be a duplicate IsFlaggedMission = x.RowId is 7 or 14 or 15 or 34, + PossibleAttributes = Enumerable.Range(0, x.RequiredPhysical.Length) + .Select(i => new Attributes + { + PhysicalAbility = x.RequiredPhysical[i], + MentalAbility = x.RequiredMental[i], + TacticalAbility = x.RequiredTactical[i], + }) + .ToList() + .AsReadOnly(), }) .ToList() .AsReadOnly(); @@ -73,7 +82,7 @@ public class SquadronistaPlugin : IDalamudPlugin .AsReadOnly(); _pluginInterface.UiBuilder.Draw += _windowSystem.Draw; - _clientState.Logout += Logout; + _clientState.Logout += ResetCharacterSpecificData; _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GcArmyMemberList", UpdateSquadronState); _addonLifecycle.RegisterListener(AddonEvent.PostRefresh, "GcArmyExpedition", UpdateExpeditionState); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GcArmyExpedition", UpdateExpeditionState); @@ -94,7 +103,7 @@ public class SquadronistaPlugin : IDalamudPlugin if (addon->AtkValuesCount != 133) { _pluginLog.Error("Unexpected AddonGcArmyMemberList atkvalues count"); - SquadronState = null; + ResetCharacterSpecificData(); return; } @@ -104,7 +113,7 @@ public class SquadronistaPlugin : IDalamudPlugin // can't do any missions like this... if (memberCount < 4) { - SquadronState = null; + ResetCharacterSpecificData(); return; } @@ -157,7 +166,7 @@ public class SquadronistaPlugin : IDalamudPlugin { AvailableMissions.Clear(); if (type == AddonEvent.PostSetup) - SquadronState = null; + ResetCharacterSpecificData(); AddonGcArmyExpedition* addonExpedition = (AddonGcArmyExpedition*)args.Addon; if (addonExpedition->AtkUnitBase.AtkValuesCount != 216) @@ -182,9 +191,11 @@ public class SquadronistaPlugin : IDalamudPlugin return null; } - private void Logout() + private void ResetCharacterSpecificData() { SquadronState = null; + foreach (var mission in _allMissions) + mission.CurrentAttributes = null; } public void Dispose() @@ -193,7 +204,7 @@ public class SquadronistaPlugin : IDalamudPlugin _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GcArmyExpedition", UpdateExpeditionState); _addonLifecycle.UnregisterListener(AddonEvent.PostRefresh, "GcArmyExpedition", UpdateExpeditionState); _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GcArmyMemberList", UpdateSquadronState); - _clientState.Logout -= Logout; + _clientState.Logout -= ResetCharacterSpecificData; _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; } } diff --git a/Squadronista/Windows/MainWindow.cs b/Squadronista/Windows/MainWindow.cs index 6b800c5..8ecd224 100644 --- a/Squadronista/Windows/MainWindow.cs +++ b/Squadronista/Windows/MainWindow.cs @@ -126,7 +126,7 @@ internal sealed class MainWindow : Window, IDisposable { var atkValues = addonMemberList->AtkValues; activeMembers = Enumerable.Range(0, (int)atkValues[4].UInt) - .Where(i => atkValues[5 + i * 15].UInt == 3) + .Where(i => ((SquadronMemberUiFlags)atkValues[5 + i * 15].Int).HasFlag(SquadronMemberUiFlags.IsPartOfMission)) .Select(i => atkValues[6 + i * 15].ReadAtkString()) .Where(x => !string.IsNullOrEmpty(x)) .Cast() @@ -170,21 +170,21 @@ internal sealed class MainWindow : Window, IDisposable ImGui.Text("Final Stats:"); ImGui.SameLine(0); ImGui.TextColored( - result.PhysicalAbility >= selectedMission.CurrentAttributes.PhysicalAbility + result.PhysicalAbility >= selectedMission.CurrentAttributes!.PhysicalAbility ? ImGuiColors.HealerGreen : ImGuiColors.DalamudYellow, $"{result.PhysicalAbility}"); ImGui.SameLine(0); ImGui.Text("/"); ImGui.SameLine(0); ImGui.TextColored( - result.MentalAbility >= selectedMission.CurrentAttributes.MentalAbility + result.MentalAbility >= selectedMission.CurrentAttributes!.MentalAbility ? ImGuiColors.HealerGreen : ImGuiColors.DalamudYellow, $"{result.MentalAbility}"); ImGui.SameLine(0); ImGui.Text("/"); ImGui.SameLine(0); ImGui.TextColored( - result.TacticalAbility >= selectedMission.CurrentAttributes.TacticalAbility + result.TacticalAbility >= selectedMission.CurrentAttributes!.TacticalAbility ? ImGuiColors.HealerGreen : ImGuiColors.DalamudYellow, $"{result.TacticalAbility}"); @@ -193,7 +193,7 @@ internal sealed class MainWindow : Window, IDisposable } else { - ImGui.TextColored(ImGuiColors.DalamudRed, $"No combination of members/trainings can achieve\n{(selectedMission.IsFlaggedMission ? "all" : "2 out of 3")} attributes for {selectedMission.CurrentAttributes.PhysicalAbility} / {selectedMission.CurrentAttributes.MentalAbility} / {selectedMission.CurrentAttributes.TacticalAbility}."); + ImGui.TextColored(ImGuiColors.DalamudRed, $"No combination of members/trainings can achieve\n{(selectedMission.IsFlaggedMission ? "all" : "2 out of 3")} attributes for {selectedMission.CurrentAttributes!.PhysicalAbility} / {selectedMission.CurrentAttributes!.MentalAbility} / {selectedMission.CurrentAttributes!.TacticalAbility}."); ImGui.Text("Level the squadron further and check again."); } } @@ -315,12 +315,16 @@ internal sealed class MainWindow : Window, IDisposable int mental = int.Parse(mentalText->NodeText.ToString()); int tactical = int.Parse(tacticalText->NodeText.ToString()); - selectedMission.CurrentAttributes = new Attributes + var newAttributes = new Attributes { PhysicalAbility = physical, MentalAbility = mental, TacticalAbility = tactical, }; + if (selectedMission.PossibleAttributes.Contains(newAttributes)) + selectedMission.CurrentAttributes = newAttributes; + else + _pluginLog.Warning($"Wrong attributes for {selectedMission.Name}: {physical} / {mental} / {tactical}"); } private static unsafe T* GetNodeById(AtkUldManager uldManager, uint nodeId, NodeType? type = null)