diff --git a/.editorconfig b/.editorconfig index 253faad..8d46053 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,14 +4,13 @@ root = true [*] charset = utf-8 -end_of_line = lf +end_of_line = crlf insert_final_newline = true # 4 space indentation indent_style = space indent_size = 4 - -# disable redundant style warnings +tab_width = 4 # Microsoft .NET properties csharp_indent_braces = false @@ -21,6 +20,21 @@ csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = false csharp_new_line_before_open_brace = all csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_empty_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_before_open_square_brackets = false +csharp_space_before_comma = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_comma = true +csharp_space_after_cast = false +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = none +csharp_space_between_square_brackets = false csharp_style_var_elsewhere = true:suggestion csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion @@ -64,21 +78,6 @@ dotnet_style_predefined_type_for_member_access = true:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion dotnet_style_parentheses_in_other_operators=always_for_clarity:silent dotnet_style_object_initializer = false:suggestion -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_empty_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_before_open_square_brackets = false -csharp_space_before_comma = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_comma = true -csharp_space_after_cast = false -csharp_space_around_binary_operators = before_and_after -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = none -csharp_space_between_square_brackets = false # ReSharper properties resharper_align_linq_query = true @@ -166,11 +165,6 @@ csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_prefer_not_pattern = true:suggestion csharp_style_prefer_extended_property_pattern = true:suggestion - -[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}] -indent_style = space -indent_size = 4 -tab_width = 4 dotnet_style_parentheses_in_other_operators=always_for_clarity:silent dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion diff --git a/Craftimizer/Service.cs b/Craftimizer/Service.cs index fa4e1c6..54d59fa 100644 --- a/Craftimizer/Service.cs +++ b/Craftimizer/Service.cs @@ -31,5 +31,4 @@ public sealed class Service public static Configuration Configuration { get; internal set; } public static WindowSystem WindowSystem => Plugin.WindowSystem; #pragma warning restore CS8618 - } diff --git a/Solver/Craftimizer.Solver.csproj b/Solver/Craftimizer.Solver.csproj index dd73ca2..011fa03 100644 --- a/Solver/Craftimizer.Solver.csproj +++ b/Solver/Craftimizer.Solver.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/Solver/Heuristics/ExpertFinisher.cs b/Solver/Heuristics/ExpertFinisher.cs new file mode 100644 index 0000000..f247c86 --- /dev/null +++ b/Solver/Heuristics/ExpertFinisher.cs @@ -0,0 +1,117 @@ +using Craftimizer.Simulator; +using Craftimizer.Simulator.Actions; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace Craftimizer.Solver.Heuristics; + +internal sealed class ExpertFinisher : IHeuristic +{ + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ShouldUseAction(Simulator s, ActionType action, BaseAction baseAction) + { + if (!Normal.ShouldUseAction(s, action, baseAction)) + return false; + + // always use Trained Eye if it's available + if (action == ActionType.TrainedEye) + return baseAction.CanUse(s); + + // only allow Focused moves after Observe + if (s.ActionStates.Observed && + action != ActionType.FocusedSynthesis && + action != ActionType.FocusedTouch) + return false; + + // don't allow quality moves under Muscle Memory for difficult crafts + if (s.Input.Recipe.ClassJobLevel == 90 && + s.HasEffect(EffectType.MuscleMemory) && + baseAction.IncreasesQuality) + return false; + + // use First Turn actions if it's available and the craft is difficult + if (s.IsFirstStep && + s.Input.Recipe.ClassJobLevel == 90 && + baseAction.Category != ActionCategory.FirstTurn && + s.CP > 10) + return false; + + // don't allow combo actions if the combo is already in progress + if (s.ActionStates.TouchComboIdx != 0 && + (action == ActionType.StandardTouchCombo || action == ActionType.AdvancedTouchCombo)) + return false; + + // don't allow pure quality moves under Veneration + if (s.HasEffect(EffectType.Veneration) && + !baseAction.IncreasesProgress && + baseAction.IncreasesQuality) + return false; + + // don't allow pure quality moves when it won't be able to finish the craft + if (baseAction.IncreasesQuality && + s.CalculateDurabilityCost(baseAction.DurabilityCost) > s.Durability) + return false; + + if (baseAction.IncreasesProgress) + { + var progressIncrease = s.CalculateProgressGain(baseAction.Efficiency(s)); + var wouldFinish = s.Progress + progressIncrease >= s.Input.Recipe.MaxProgress; + + if (wouldFinish) + { + // don't allow finishing the craft if there is significant quality remaining + if (s.Quality < s.Input.Recipe.MaxQuality / 5) + return false; + } + else + { + // don't allow pure progress moves under Innovation, if it wouldn't finish the craft + if (s.HasEffect(EffectType.Innovation) && + !baseAction.IncreasesQuality && + baseAction.IncreasesProgress) + return false; + } + } + + // Only allow byregot at 2+ stacks + if (action == ActionType.ByregotsBlessing && + s.GetEffectStrength(EffectType.InnerQuiet) <= 1) + return false; + + // Don't execute waste not if a type is already active + if ((action == ActionType.WasteNot || action == ActionType.WasteNot2) && + (s.HasEffect(EffectType.WasteNot) || s.HasEffect(EffectType.WasteNot2))) + return false; + + // Don't observe if you can't combo it into anything + if (action == ActionType.Observe && + s.CP < 12) + return false; + + // Do not Masters Mend if it would restore less than 25 dur + if (action == ActionType.MastersMend && + s.Input.Recipe.MaxDurability - s.Durability < 25) + return false; + + // Don't re-execute manipulation/great strides + if (action == ActionType.Manipulation && + s.HasEffect(EffectType.Manipulation)) + return false; + + if (action == ActionType.GreatStrides && + s.HasEffect(EffectType.GreatStrides)) + return false; + + // Don't overlap/reapply veneration/innovation if there is 2+ steps left + if ((action == ActionType.Veneration || action == ActionType.Innovation) && + (s.GetEffectDuration(EffectType.Veneration) > 1 || s.GetEffectDuration(EffectType.Innovation) > 1)) + return false; + + return true; + } + + [Pure] + public static ActionSet AvailableActions(Simulator s) => + IHeuristic.AvailableActions(s, Normal.AcceptedActions); +} diff --git a/Solver/Heuristics/ExpertHydra.cs b/Solver/Heuristics/ExpertHydra.cs new file mode 100644 index 0000000..c3d532c --- /dev/null +++ b/Solver/Heuristics/ExpertHydra.cs @@ -0,0 +1,22 @@ +using Craftimizer.Simulator; +using System.Diagnostics.Contracts; + +namespace Craftimizer.Solver.Heuristics; + +internal sealed class ExpertHydra // : IHeuristic +{ + [Pure] + public static ActionSet AvailableActions(Simulator s) + { + var qualityTarget = s.Input.Recipe.MaxQuality * 0.8f; // 80% quality at least + if (s.GetEffectStrength(EffectType.InnerQuiet) == 10 || s.Quality > qualityTarget) + return ExpertFinisher.AvailableActions(s); + + qualityTarget = s.Input.Recipe.MaxQuality * 0.2f; // 20% quality at least + var progressTarget = s.Input.Recipe.MaxProgress - s.Input.BaseProgressGain * 2.5f; // 250% efficiency away from completion + if (s.Progress > progressTarget || s.Quality > qualityTarget) + return ExpertQuality.AvailableActions(s); + + return ExpertOpener.AvailableActions(s); + } +} diff --git a/Solver/Heuristics/ExpertOpener.cs b/Solver/Heuristics/ExpertOpener.cs new file mode 100644 index 0000000..30c3f7d --- /dev/null +++ b/Solver/Heuristics/ExpertOpener.cs @@ -0,0 +1,84 @@ +using Craftimizer.Simulator; +using Craftimizer.Simulator.Actions; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace Craftimizer.Solver.Heuristics; + +internal sealed class ExpertOpener : IHeuristic +{ + public static readonly ActionType[] AcceptedActions = new[] + { + ActionType.CarefulObservation, + ActionType.FinalAppraisal, + ActionType.Manipulation, + ActionType.MuscleMemory, + ActionType.PreciseTouch, + ActionType.RapidSynthesis, + ActionType.TricksOfTheTrade, + ActionType.Veneration, + ActionType.HeartAndSoul, + }; + + private static readonly BaseAction RapidSynthesis = ActionType.RapidSynthesis.Base(); + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ShouldUseAction(Simulator s, ActionType action, BaseAction baseAction) + { + // Make sure the first step is muscle memory + if (baseAction.IncreasesStepCount && s.IsFirstStep && action != ActionType.MuscleMemory) + return false; + + // Roll for a malleable or sturdy before taking the first muscle memory step + if (s.Condition is not (Condition.Malleable or Condition.Sturdy) && s.IsFirstStep && action == ActionType.MuscleMemory && s.ActionStates.CarefulObservationCount != 3) + return false; + + if (baseAction.IncreasesProgress) + { + var progressIncrease = s.CalculateProgressGain(baseAction.Efficiency(s)); + var wouldFinish = s.Progress + progressIncrease >= s.Input.Recipe.MaxProgress; + + if (wouldFinish) + return false; + } + + if (action == ActionType.FinalAppraisal) + { + if (s.HasEffect(EffectType.FinalAppraisal)) + return false; + + var rapidProgressIncrease = s.CalculateProgressGain(RapidSynthesis.Efficiency(s)); + var wouldRapidFinish = s.Progress + rapidProgressIncrease >= s.Input.Recipe.MaxProgress; + return wouldRapidFinish; + } + + // Don't reapply manipulation if there is 2+ steps left + if (action == ActionType.Manipulation && + s.GetEffectDuration(EffectType.Manipulation) > 1) + return false; + + // Don't reapply veneration if there is 2+ steps left + if (action == ActionType.Veneration && + s.GetEffectDuration(EffectType.Veneration) > 1) + return false; + + return s.Condition switch + { + Condition.Centered or Condition.Sturdy or Condition.Normal or Condition.GoodOmen => + action is ActionType.RapidSynthesis or ActionType.Veneration || + action is ActionType.Manipulation && !s.HasEffect(EffectType.MuscleMemory), + Condition.Malleable => + action is ActionType.RapidSynthesis, + Condition.Primed or Condition.Pliant => + action is ActionType.RapidSynthesis or ActionType.Manipulation or ActionType.Veneration, + Condition.Good => + action is ActionType.RapidSynthesis or ActionType.TricksOfTheTrade or ActionType.PreciseTouch, + _ => true + }; + } + + [Pure] + public static ActionSet AvailableActions(Simulator s) => + IHeuristic.AvailableActions(s, AcceptedActions); +} diff --git a/Solver/Heuristics/ExpertQuality.cs b/Solver/Heuristics/ExpertQuality.cs new file mode 100644 index 0000000..d716d2f --- /dev/null +++ b/Solver/Heuristics/ExpertQuality.cs @@ -0,0 +1,117 @@ +using Craftimizer.Simulator; +using Craftimizer.Simulator.Actions; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace Craftimizer.Solver.Heuristics; + +internal sealed class ExpertQuality : IHeuristic +{ + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ShouldUseAction(Simulator s, ActionType action, BaseAction baseAction) + { + if (!Normal.ShouldUseAction(s, action, baseAction)) + return false; + + // always use Trained Eye if it's available + if (action == ActionType.TrainedEye) + return baseAction.CanUse(s); + + // only allow Focused moves after Observe + if (s.ActionStates.Observed && + action != ActionType.FocusedSynthesis && + action != ActionType.FocusedTouch) + return false; + + // don't allow quality moves under Muscle Memory for difficult crafts + if (s.Input.Recipe.ClassJobLevel == 90 && + s.HasEffect(EffectType.MuscleMemory) && + baseAction.IncreasesQuality) + return false; + + // use First Turn actions if it's available and the craft is difficult + if (s.IsFirstStep && + s.Input.Recipe.ClassJobLevel == 90 && + baseAction.Category != ActionCategory.FirstTurn && + s.CP > 10) + return false; + + // don't allow combo actions if the combo is already in progress + if (s.ActionStates.TouchComboIdx != 0 && + (action == ActionType.StandardTouchCombo || action == ActionType.AdvancedTouchCombo)) + return false; + + // don't allow pure quality moves under Veneration + if (s.HasEffect(EffectType.Veneration) && + !baseAction.IncreasesProgress && + baseAction.IncreasesQuality) + return false; + + // don't allow pure quality moves when it won't be able to finish the craft + if (baseAction.IncreasesQuality && + s.CalculateDurabilityCost(baseAction.DurabilityCost) > s.Durability) + return false; + + if (baseAction.IncreasesProgress) + { + var progressIncrease = s.CalculateProgressGain(baseAction.Efficiency(s)); + var wouldFinish = s.Progress + progressIncrease >= s.Input.Recipe.MaxProgress; + + if (wouldFinish) + { + // don't allow finishing the craft if there is significant quality remaining + if (s.Quality < s.Input.Recipe.MaxQuality / 5) + return false; + } + else + { + // don't allow pure progress moves under Innovation, if it wouldn't finish the craft + if (s.HasEffect(EffectType.Innovation) && + !baseAction.IncreasesQuality && + baseAction.IncreasesProgress) + return false; + } + } + + // Only allow byregot at 2+ stacks + if (action == ActionType.ByregotsBlessing && + s.GetEffectStrength(EffectType.InnerQuiet) <= 1) + return false; + + // Don't execute waste not if a type is already active + if ((action == ActionType.WasteNot || action == ActionType.WasteNot2) && + (s.HasEffect(EffectType.WasteNot) || s.HasEffect(EffectType.WasteNot2))) + return false; + + // Don't observe if you can't combo it into anything + if (action == ActionType.Observe && + s.CP < 12) + return false; + + // Do not Masters Mend if it would restore less than 25 dur + if (action == ActionType.MastersMend && + s.Input.Recipe.MaxDurability - s.Durability < 25) + return false; + + // Don't re-execute manipulation/great strides + if (action == ActionType.Manipulation && + s.HasEffect(EffectType.Manipulation)) + return false; + + if (action == ActionType.GreatStrides && + s.HasEffect(EffectType.GreatStrides)) + return false; + + // Don't overlap/reapply veneration/innovation if there is 2+ steps left + if ((action == ActionType.Veneration || action == ActionType.Innovation) && + (s.GetEffectDuration(EffectType.Veneration) > 1 || s.GetEffectDuration(EffectType.Innovation) > 1)) + return false; + + return true; + } + + [Pure] + public static ActionSet AvailableActions(Simulator s) => + IHeuristic.AvailableActions(s, Normal.AcceptedActions); +}