diff --git a/README.md b/README.md index cfc58148..10d0aa29 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,23 @@ - 自动排序,将更好的宏排在靠前位置,无需手动记录最优解 - 离线使用,秒开秒用,体验不受网络环境影响 - 程序极小,电脑硬盘空间不足的玩家也可以放心安装 -- 跨平台,(支持Linux与MacOS系统)Windows用户请在[发行版页面](https://gitee.com/Tnze/ffxiv-best-craft/releases)下载`.msi`安装包 +- 跨平台,(支持Linux与MacOS系统) 缺点: - 开启“掌握”支持时求解器占用内存较大 - 用的人太少了(?) +## Download 下载 + +Windows用户请在[发行版页面](https://gitee.com/Tnze/ffxiv-best-craft/releases)下载包含`.msi`字样的安装包 + +Linux 与 MacOS 用户请移步[Github Releases](https://github.com/Tnze/ffxiv-best-craft/releases)下载对应的安装包 + +- Mac 用户请下载`dmg`或`app`文件 +- Debian 及 Ubuntu 用户请下载`AppImage`或`deb`文件 +- 其他 Linux 用户请下载`AppImage`文件 + ## Dev (开发人员帮助) ### 安装依赖项 diff --git a/src-tauri/src/dynamic_programing_solver.rs b/src-tauri/src/dynamic_programing_solver.rs index 44957ae6..88af123d 100644 --- a/src-tauri/src/dynamic_programing_solver.rs +++ b/src-tauri/src/dynamic_programing_solver.rs @@ -1,4 +1,4 @@ -use ffxiv_crafting::{Actions, Status, Buffs}; +use ffxiv_crafting::{Actions, Buffs, Status}; use micro_ndarray::Array; use std::cell::Cell; @@ -9,7 +9,7 @@ struct SolverSlot { action: Option, } -const SYNTH_SKILLS: [Actions; 9] = [ +const SYNTH_SKILLS: [Actions; 11] = [ Actions::BasicSynthesis, Actions::WasteNot, Actions::Veneration, @@ -19,9 +19,11 @@ const SYNTH_SKILLS: [Actions; 9] = [ Actions::DelicateSynthesis, Actions::IntensiveSynthesis, Actions::PrudentSynthesis, + Actions::Observe, + Actions::FocusedSynthesis, ]; -const TOUCH_SKILLS: [Actions; 13] = [ +const TOUCH_SKILLS: [Actions; 15] = [ Actions::BasicTouch, Actions::MastersMend, Actions::WasteNot, @@ -35,31 +37,46 @@ const TOUCH_SKILLS: [Actions; 13] = [ Actions::AdvancedTouch, Actions::TrainedFinesse, Actions::Manipulation, + Actions::Observe, + Actions::FocusedTouch, ]; pub struct QualitySolver { progress_solver: ProgressSolver, - mn: usize, + mn: bool, wn: usize, - // results [iq][iv][gs][mn][wn][touch][d][cp] - results: Array>>, 8>, + obz: bool, + // results [obz][iq][iv][gs][mn][wn][touch][d][cp] + results: Array>>, 9>, } impl QualitySolver { - pub fn new(init_status: Status, mn: usize, wn: usize) -> Self { + pub fn new(init_status: Status, mn: bool, wn: usize, obz: bool) -> Self { let cp = init_status.attributes.craft_points as usize; let du = init_status.recipe.durability as usize; - let progress_solver = ProgressSolver::new(init_status, mn, wn); + let progress_solver = ProgressSolver::new(init_status, mn, wn, obz); Self { progress_solver, wn, mn, - results: Array::new([11, 5, 4, mn + 1, wn + 1, 3, du / 5 + 1, cp + 1]), + obz, + results: Array::new([ + obz as usize + 1, + 11, + 5, + 4, + mn as usize * 8 + 1, + wn + 1, + 3, + du / 5 + 1, + cp + 1, + ]), } } fn get(&self, s: &Status) -> &Cell>> { let i = [ + s.buffs.observed as usize, s.buffs.inner_quiet as usize, s.buffs.innovation as usize, s.buffs.great_strides as usize, @@ -96,28 +113,22 @@ impl QualitySolver { step: 0, action: None, }; - for sk in &TOUCH_SKILLS { - match sk { - &Actions::Manipulation if self.mn == 0 => continue, - &Actions::WasteNot | &Actions::WasteNotII if self.wn == 0 => continue, - _ => {} + for sk in TOUCH_SKILLS { + if (matches!(sk, Actions::Manipulation) && !self.mn) + || (matches!(sk, Actions::WasteNotII) && self.wn < 8) + || (matches!(sk, Actions::WasteNot) && self.wn < 4) + || (matches!(sk, Actions::Observe) && !self.obz) + || (matches!(sk, Actions::FocusedTouch) && s.buffs.observed == 0) + { + continue; } - if s.is_action_allowed(*sk).is_err() { + if s.is_action_allowed(sk).is_err() { continue; } let mut new_s = s.clone(); - new_s.buffs = Buffs { - great_strides: s.buffs.great_strides, - innovation: s.buffs.innovation, - inner_quiet: s.buffs.inner_quiet, - manipulation: s.buffs.manipulation, - wast_not: s.buffs.wast_not, - touch_combo_stage: s.buffs.touch_combo_stage, - ..Buffs::default() - }; new_s.quality = 0; - new_s.cast_action(*sk); + new_s.cast_action(sk); let progress = self.progress_solver.inner_read(&new_s).value; if new_s.progress + progress >= new_s.recipe.difficulty { @@ -132,7 +143,7 @@ impl QualitySolver { best = SolverSlot { value: quality, step, - action: Some(*sk), + action: Some(sk), } } } @@ -147,10 +158,20 @@ impl crate::solver::Solver for QualitySolver { fn read(&self, s: &Status) -> Option { if s.is_finished() { - return None + return None; } let max_quality = s.recipe.quality; let mut new_s = s.clone(); + new_s.buffs = Buffs { + great_strides: s.buffs.great_strides, + innovation: s.buffs.innovation, + inner_quiet: s.buffs.inner_quiet, + manipulation: s.buffs.manipulation, + wast_not: s.buffs.wast_not, + touch_combo_stage: s.buffs.touch_combo_stage, + observed: s.buffs.observed, + ..Buffs::default() + }; let max_addon = max_quality - s.quality; let mut best = { let SolverSlot { @@ -183,25 +204,36 @@ impl crate::solver::Solver for QualitySolver { /// ProgressSolver 是一种专注于推动进展的求解器,给定玩家属性和配方并经过初始化后, /// 对于任意的当前状态,可以以O(1)时间复杂度算出剩余资源最多可推多少进展。 pub struct ProgressSolver { - mn: usize, + mn: bool, wn: usize, - // results[ve][mm][mn][wn][d][cp] - results: Array>>, 6>, + obz: bool, + // [obz][ve][mm][mn][wn][d][cp] + results: Array>>, 7>, } impl ProgressSolver { - pub fn new(init_status: Status, mn: usize, wn: usize) -> Self { + pub fn new(init_status: Status, mn: bool, wn: usize, obz: bool) -> Self { let cp = init_status.attributes.craft_points as usize; let du = init_status.recipe.durability as usize; Self { mn, wn, - results: Array::new([5, 6, mn + 1, mn + 1, du / 5 + 1, cp + 1]), + obz, + results: Array::new([ + obz as usize + 1, + 5, + 6, + mn as usize * 8 + 1, + wn + 1, + du / 5 + 1, + cp + 1, + ]), } } fn get(&self, s: &Status) -> &Cell>> { let i = [ + s.buffs.observed as usize, s.buffs.veneration as usize, s.buffs.muscle_memory as usize, s.buffs.manipulation as usize, @@ -236,26 +268,21 @@ impl ProgressSolver { step: 0, action: None, }; - for sk in &SYNTH_SKILLS { - match *sk { - Actions::Manipulation if self.mn < 9 => continue, - Actions::WasteNot if self.wn < 5 => continue, - Actions::WasteNotII if self.wn < 9 => continue, - _ => {} + for sk in SYNTH_SKILLS { + if (matches!(sk, Actions::Manipulation) && !self.mn) + || (matches!(sk, Actions::WasteNotII) && self.wn < 8) + || (matches!(sk, Actions::WasteNot) && self.wn < 4) + || (matches!(sk, Actions::Observe) && !self.obz) + || (matches!(sk, Actions::FocusedSynthesis) && s.buffs.observed == 0) + { + continue; } - if s.is_action_allowed(*sk).is_err() { + if s.is_action_allowed(sk).is_err() { continue; } let mut new_s = s.clone(); - new_s.buffs = Buffs{ - muscle_memory: s.buffs.muscle_memory, - veneration: s.buffs.veneration, - manipulation: s.buffs.manipulation, - wast_not: s.buffs.wast_not, - ..Buffs::default() - }; new_s.progress = 0; - new_s.cast_action(*sk); + new_s.cast_action(sk); let mut progress = new_s.progress; let mut step = 1; if new_s.durability > 0 { @@ -267,7 +294,7 @@ impl ProgressSolver { best = SolverSlot { value: progress, step, - action: Some(*sk), + action: Some(sk), } } } @@ -281,7 +308,7 @@ impl crate::solver::Solver for ProgressSolver { fn read(&self, s: &Status) -> Option { if s.is_finished() { - return None + return None; } let difficulty = s.recipe.difficulty; let max_addon = difficulty - s.progress; @@ -295,6 +322,14 @@ impl crate::solver::Solver for ProgressSolver { (progress, step, action) }; let mut new_s2 = s.clone(); + new_s2.buffs = Buffs { + muscle_memory: s.buffs.muscle_memory, + veneration: s.buffs.veneration, + manipulation: s.buffs.manipulation, + wast_not: s.buffs.wast_not, + observed: s.buffs.observed, + ..Buffs::default() + }; for cp in 0..=s.craft_points { new_s2.craft_points = cp; for du in 1..=s.durability { @@ -343,7 +378,7 @@ mod test { #[test] fn test() { let init_status = init(); - let solver = ProgressSolver::new(init_status.clone(), 8, 8); + let solver = ProgressSolver::new(init_status.clone(), true, 8, true); let actions = solver.read_all(&init_status); println!("{actions:?}"); } @@ -352,7 +387,7 @@ mod test { fn test2() { let mut init_status = init(); init_status.cast_action(ffxiv_crafting::Actions::Reflect); - let solver = QualitySolver::new(init_status.clone(), 8, 8); + let solver = QualitySolver::new(init_status.clone(), true, 8, true); let actions = solver.read_all(&init_status); println!("{actions:?}"); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 762b6abc..f289771a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -316,6 +316,7 @@ async fn create_solver( status: Status, use_muscle_memory: bool, use_manipulation: bool, + use_obzerve: bool, app_state: tauri::State<'_, AppState>, ) -> Result<(), String> { let key = solver::SolverHash { @@ -340,14 +341,15 @@ async fn create_solver( Box::new(preprogress_solver::PreprogressSolver::new( status, progress_list, - use_manipulation as usize * 8, + use_manipulation, 8, )) } else { Box::new(dynamic_programing_solver::QualitySolver::new( status, - use_manipulation as usize * 8, - 8, + use_manipulation, + 8 + 1, + use_obzerve, )) }; *solver_slot.lock().await = Some(solver); diff --git a/src-tauri/src/preprogress_solver.rs b/src-tauri/src/preprogress_solver.rs index 0e03d9d5..19e25e06 100644 --- a/src-tauri/src/preprogress_solver.rs +++ b/src-tauri/src/preprogress_solver.rs @@ -9,7 +9,7 @@ pub struct PreprogressSolver { } impl PreprogressSolver { - pub fn new(init_status: Status, progress_list: Vec, mn: usize, wn: usize) -> Self { + pub fn new(init_status: Status, progress_list: Vec, mn: bool, wn: usize) -> Self { let progress_index = progress_list .iter() .scan(0, |prev, &x| { @@ -30,7 +30,7 @@ impl PreprogressSolver { .map(|v| { let mut s = init_status.clone(); s.progress = s.recipe.difficulty - *v; - QualitySolver::new(s, mn, wn) + QualitySolver::new(s, mn, wn, false) }) .collect(); Self { @@ -107,7 +107,7 @@ mod test { #[test] fn test() { let mut status1 = init(); - let solver = PreprogressSolver::new(status1.clone(), preprogress_list(&status1), 8, 8); + let solver = PreprogressSolver::new(status1.clone(), preprogress_list(&status1), true, 8); let init_actions = [ Actions::MuscleMemory, Actions::Manipulation, diff --git a/src/Solver.ts b/src/Solver.ts index d41e4d6f..1f2ebf66 100644 --- a/src/Solver.ts +++ b/src/Solver.ts @@ -5,12 +5,14 @@ import { invoke } from "@tauri-apps/api/tauri"; export const create_solver = ( status: Status, useMuscleMemory: boolean, - useManipulation: boolean + useManipulation: boolean, + useObzerve: boolean, ) => { return invoke("create_solver", { status, useMuscleMemory, useManipulation, + useObzerve, }); }; diff --git a/src/components/designer/SolverList.vue b/src/components/designer/SolverList.vue index 2a8c0a4f..567e2d96 100644 --- a/src/components/designer/SolverList.vue +++ b/src/components/designer/SolverList.vue @@ -24,6 +24,7 @@ interface Solver { const solvers = ref([]) const useManipulation = ref(false) const useMuscleMemory = ref(false) +const useObserve = ref(true) const activeNames = ref([]) const rikaIsSolving = ref(false) @@ -48,7 +49,8 @@ const createSolver = async () => { await create_solver( solver.initStatus, useMuscleMemory.value, - useManipulation.value + useManipulation.value, + useObserve.value, ) const stop_time = new Date().getTime(); ElMessage({ @@ -125,6 +127,8 @@ async function runRikaSolver() {
+ +
@@ -181,6 +185,7 @@ dp-solver = 动态规划求解 bfs-solver = 广度优先搜索 manipulation-select-info = { manipulation }(时间&内存×9) +observe-select-info = { observe }(内存×2) muscle-memory-select-info = { muscle-memory }(内存×2) start-solver = 启动求解器 release-solver = 释放 @@ -206,6 +211,7 @@ dp-solver = Dynamic Programing bfs-solver = Breadth First Search manipulation-select-info = { manipulation }(Time & Memory × 9) +observe-select-info = { observe }(Memory × 2) muscle-memory-select-info = { muscle-memory }(Memory × 2) start-solver = Create solver release-solver = Release