diff --git a/docs/3.6-基建排班协议.md b/docs/3.6-基建排班协议.md index 4cec46fd78..b6e2889082 100644 --- a/docs/3.6-基建排班协议.md +++ b/docs/3.6-基建排班协议.md @@ -47,6 +47,24 @@ icon: material-symbols:view-quilt-rounded "rule": "all", // 使用规则,保留字段,目前无作用。以后可能拿来支持插拔等操作 "order": "pre" // 在换干员前使用还是在换完才用,可选,取值范围 "pre" / "post",默认 "pre" }, + "groups":[ // 对于制造站和贸易站,可以设置干员编组 + { + "name":"古+银", + "operators":[ + "古米", + "银灰", + "梅" + ] + }, + { + "name":"清流", + "operators":[ + "清流", + "森蚺", + "温蒂" + ] + } + ], "rooms": { // 房间信息,必选 // 取值范围 "control" / "manufacture" / "trading" / "power" / "meeting" / "hire" / "dormitory" // 缺少某个则该设施使用默认算法进行换班。 @@ -99,6 +117,13 @@ icon: material-symbols:view-quilt-rounded "白面鸮", "赫默" ] + }, + { + "use_operator_groups":true, // 设置为 true 以使用 groups 中的干员编组,默认为 false + "operators":[ // 启用后, operators 中的名字将被解释为编组名 + "古+银", // 将按照心情阈值以及设置顺序选择编组 + "清流" // 如 古+银 组中有干员心情低于阈值,将使用 清流 组 + ] } ], "meeting": [ diff --git a/docs/en-us/3.6-INFRASTRUCTURE_SCHEDULING_SCHEMA.md b/docs/en-us/3.6-INFRASTRUCTURE_SCHEDULING_SCHEMA.md index 7c7d62d199..e6134a6d77 100644 --- a/docs/en-us/3.6-INFRASTRUCTURE_SCHEDULING_SCHEMA.md +++ b/docs/en-us/3.6-INFRASTRUCTURE_SCHEDULING_SCHEMA.md @@ -44,6 +44,24 @@ Usage and Field Description for `resource/custom_infrast/*.json` files "rule": "all", // Usage rules. Reserved field. Currently unused, but may be used in the future to support plug-ins and other operations. "order": "pre" // Whether to use drones before or after changing operators. Optional. Possible values: "pre" / "post". Default is "pre". }, + "groups":[ // For manufacturing stations and trading stations, operator groups can be set + { + "name":"A", + "operators":[ + "古米", + "银灰", + "梅" + ] + }, + { + "name":"B", + "operators":[ + "清流", + "森蚺", + "温蒂" + ] + } + ], "rooms": { // Room information. Required. // Possible values: "control" / "manufacture" / "trading" / "power" / "meeting" / "hire" / "dormitory". // If a room is not specified, the default algorithm will be used to change operators. @@ -96,6 +114,13 @@ Usage and Field Description for `resource/custom_infrast/*.json` files "白面鸮", "赫默" ] + }, + { + "use_operator_groups":true, // Set to true to use the grouping of operators in groups, default is false + "operators":[ // When enabled, names in operators will be interpreted as group names + "A", // Grouping will be selected according to mood threshold and setting order + "B" // If any operator in group A whose mood is below the threshold, group B will be used + ] } ], "meeting": [ diff --git a/docs/ja-jp/3.6-インフラスケジュール設定.md b/docs/ja-jp/3.6-インフラスケジュール設定.md index 3350ac010f..ec3783ae74 100644 --- a/docs/ja-jp/3.6-インフラスケジュール設定.md +++ b/docs/ja-jp/3.6-インフラスケジュール設定.md @@ -37,6 +37,24 @@ "rule": "all", // ルール、予約フィールド、未実装. 後で使用する可能性がある。 "order": "pre" // オペレーターの交換前後の使用設定、オプション、引数 "pre" / "post", デフォルト "pre" (テキーラなど) }, + "groups":[ // 製造ステーションと取引ステーションでは、オペレータグループを設定できます + { + "name":"A", + "operators":[ + "古米", + "银灰", + "梅" + ] + }, + { + "name":"B", + "operators":[ + "清流", + "森蚺", + "温蒂" + ] + } + ], "rooms": { // 部屋情報,必須 // 引数 "control" / "manufacture" / "trading" / "power" / "meeting" / "hire" / "dormitory" // 1つもないということは、その施設ではシフト変更にデフォルトのアルゴリズムが使用されていることを意味します。 @@ -87,6 +105,13 @@ "フィリオプシス", "サイレンス" ] + }, + { + "use_operator_groups":true, // グループ内の演算子のグループ化を使用するには true に設定します。デフォルトは false です。 + "operators":[ // 有効にすると、演算子の名前がグループ名として解釈されます。 + "A", // グループ化は気分の閾値と設定順序に従って選択されます + "B" // グループ A に気分がしきい値を下回るオペレーターがいる場合、グループ B が使用されます + ] } ], "meeting": [ diff --git a/docs/zh-tw/3.6-基建排班協議.md b/docs/zh-tw/3.6-基建排班協議.md index 01eec5f8e2..1548188632 100644 --- a/docs/zh-tw/3.6-基建排班協議.md +++ b/docs/zh-tw/3.6-基建排班協議.md @@ -47,6 +47,24 @@ icon: material-symbols:view-quilt-rounded "rule": "all", // 使用規則,保留欄位,目前無作用。以後可能拿來支援插拔等操作 "order": "pre" // 在換幹員前使用還是在換完才用,可選,取值範圍 "pre" / "post",預設 "pre" }, + "groups":[ // 對於製造站和貿易站,可以設置幹員編組 + { + "name":"古+银", + "operators":[ + "古米", + "银灰", + "梅" + ] + }, + { + "name":"清流", + "operators":[ + "清流", + "森蚺", + "温蒂" + ] + } + ], "rooms": { // 房間資訊,必選 // 取值範圍 "control" / "manufacture" / "trading" / "power" / "meeting" / "hire" / "dormitory" // 缺少某個則該設施使用預設算法進行換班。 @@ -99,6 +117,13 @@ icon: material-symbols:view-quilt-rounded "白面鴞", "赫默" ] + }, + { + "use_operator_groups":true, // 設置為 true 以使用 groups 中的干員編組,默認為 false + "operators":[ // 啟用後, operators 中的名字將被解釋為編組名 + "古+银", // 將按照心情閾值以及設置順序選擇編組 + "清流" // 如 古+银 組中有乾員心情低於閾值,將使用 清流 組 + ] } ], "meeting": [ diff --git a/src/MaaCore/Common/AsstInfrastDef.h b/src/MaaCore/Common/AsstInfrastDef.h index 6e32340167..be0e4477fc 100644 --- a/src/MaaCore/Common/AsstInfrastDef.h +++ b/src/MaaCore/Common/AsstInfrastDef.h @@ -126,6 +126,11 @@ namespace asst::infrast }; bool skip = false; + // 是否使用干员编组 + bool use_operator_groups = false; + // 干员编组列表 + std::vector> operator_groups; + // 自定干员 std::vector names; bool autofill = false; Product product = Product::Unknown; diff --git a/src/MaaCore/Task/Infrast/InfrastAbstractTask.cpp b/src/MaaCore/Task/Infrast/InfrastAbstractTask.cpp index cd7dc69f24..451bae5f26 100644 --- a/src/MaaCore/Task/Infrast/InfrastAbstractTask.cpp +++ b/src/MaaCore/Task/Infrast/InfrastAbstractTask.cpp @@ -136,6 +136,7 @@ bool asst::InfrastAbstractTask::is_use_custom_opers() } /// @brief 按技能排序->清空干员->选择定制干员->按指定顺序排序 +/// @param is_dorm_order 当前房间是不是宿舍 bool asst::InfrastAbstractTask::swipe_and_select_custom_opers(bool is_dorm_order) { LogTraceFunction; @@ -168,14 +169,17 @@ bool asst::InfrastAbstractTask::swipe_and_select_custom_opers(bool is_dorm_order bool retried = false; bool pre_result_no_changes = false; int swipe_times = 0; + while (true) { if (need_exit()) { return false; } std::vector partial_result; + // 选择自定义干员,同时输出每一页的干员列表 if (!select_custom_opers(partial_result)) { return false; } + // 选完人 / 名单已空 if (static_cast(room_config.selected) >= max_num_of_opers() || (room_config.names.empty() && room_config.candidates.empty())) { break; @@ -347,8 +351,8 @@ bool asst::InfrastAbstractTask::select_custom_opers(std::vector& pa if (auto iter = ranges::find(room_config.names, name); iter != room_config.names.end()) { room_config.names.erase(iter); } - else if (max_num_of_opers() - room_config.selected > - room_config.names.size()) { // names中的数量,比剩余的空位多,就可以选备选的 + else if (max_num_of_opers() - room_config.selected > room_config.names.size()) { + // names中的数量,比剩余的空位多,就可以选备选的 if (auto candd_iter = ranges::find(room_config.candidates, name); candd_iter != room_config.candidates.end()) { room_config.candidates.erase(candd_iter); @@ -370,6 +374,36 @@ bool asst::InfrastAbstractTask::select_custom_opers(std::vector& pa return true; } +bool asst::InfrastAbstractTask::get_opers(std::vector& result, double mood) +{ + LogTraceFunction; + + const auto image = ctrler()->get_image(); + InfrastOperImageAnalyzer oper_analyzer(image); + oper_analyzer.set_to_be_calced(InfrastOperImageAnalyzer::ToBeCalced::Mood); + if (!oper_analyzer.analyze()) { + Log.warn(__FUNCTION__, "No oper"); + return false; + } + oper_analyzer.sort_by_loc(); + + const auto& ocr_replace = Task.get("CharsNameOcrReplace"); + for (const auto& oper : oper_analyzer.get_result()) { + RegionOCRer name_analyzer; + name_analyzer.set_replace(ocr_replace->replace_map, ocr_replace->replace_full); + name_analyzer.set_image(oper.name_img); + name_analyzer.set_bin_expansion(0); + if (!name_analyzer.analyze()) { + continue; + } + std::string name = name_analyzer.get_result().text; + if (oper.mood_ratio >= mood) { + result.emplace_back(std::move(name)); + } + } + return true; +} + void asst::InfrastAbstractTask::order_opers_selection(const std::vector& names) { LogTraceFunction; diff --git a/src/MaaCore/Task/Infrast/InfrastAbstractTask.h b/src/MaaCore/Task/Infrast/InfrastAbstractTask.h index e188b30b17..826da1ce11 100644 --- a/src/MaaCore/Task/Infrast/InfrastAbstractTask.h +++ b/src/MaaCore/Task/Infrast/InfrastAbstractTask.h @@ -28,29 +28,40 @@ namespace asst virtual bool on_run_fails() override; bool enter_facility(int index = 0); - bool enter_oper_list_page(); // 从刚点进设施的界面,到干员列表页 + // 从刚点进设施的界面,到干员列表页 + bool enter_oper_list_page(); virtual int operlist_swipe_times() const noexcept { return 2; } - void swipe_to_the_left_of_operlist(int loop_times = -1); // 滑动到干员列表的最左侧 - void swipe_to_the_left_of_main_ui(); // 滑动基建的主界面到最左侧 - void swipe_to_the_right_of_main_ui(); // 滑动基建的主界面到最右侧 + // 滑动到干员列表的最左侧 + void swipe_to_the_left_of_operlist(int loop_times = -1); + // 滑动基建的主界面到最左侧 + void swipe_to_the_left_of_main_ui(); + // 滑动基建的主界面到最右侧 + void swipe_to_the_right_of_main_ui(); void swipe_of_operlist(); bool is_use_custom_opers(); infrast::CustomRoomConfig& current_room_config(); bool swipe_and_select_custom_opers(bool is_dorm_order = false); bool select_custom_opers(std::vector& partial_result); - bool select_opers_review(infrast::CustomRoomConfig const& origin_room_config, - size_t num_of_opers_expect = 0); // 复核干员选择是否符合期望 + // 扫描满足心情条件的所有干员 + bool get_opers(std::vector& result, double mood = 1); + // 复核干员选择是否符合期望 + bool select_opers_review(infrast::CustomRoomConfig const& origin_room_config, size_t num_of_opers_expect = 0); void order_opers_selection(const std::vector& names); virtual void click_return_button() override; - virtual bool click_bottom_left_tab(); // 点击进入设施后,左下角的tab(我也不知道这玩意该叫啥) - virtual bool click_clear_button(); // 点击干员选择页面的“清空选择”按钮 - virtual bool click_sort_by_trust_button(); // 点击干员选择页面的“按信赖值排序”按钮 - virtual bool click_filter_menu_not_stationed_button(); // 点击干员选择页面的筛选菜单按钮的“未进驻”按钮 - virtual bool - click_filter_menu_cancel_not_stationed_button(); // 取消点击干员选择页面的筛选菜单按钮的“未进驻”按钮 - virtual bool click_confirm_button(); // 点击干员选择页面的“确认”按钮 + // 点击进入设施后,左下角的tab(我也不知道这玩意该叫啥) + virtual bool click_bottom_left_tab(); + // 点击干员选择页面的“清空选择”按钮 + virtual bool click_clear_button(); + // 点击干员选择页面的“按信赖值排序”按钮 + virtual bool click_sort_by_trust_button(); + // 点击干员选择页面的筛选菜单按钮的“未进驻”按钮 + virtual bool click_filter_menu_not_stationed_button(); + // 取消点击干员选择页面的筛选菜单按钮的“未进驻”按钮 + virtual bool click_filter_menu_cancel_not_stationed_button(); + // 点击干员选择页面的“确认”按钮 + virtual bool click_confirm_button(); int m_last_swipe_id = 0; const std::string m_work_mode_name = diff --git a/src/MaaCore/Task/Infrast/InfrastProductionTask.cpp b/src/MaaCore/Task/Infrast/InfrastProductionTask.cpp index f1f17fb77c..b0fe536ff8 100644 --- a/src/MaaCore/Task/Infrast/InfrastProductionTask.cpp +++ b/src/MaaCore/Task/Infrast/InfrastProductionTask.cpp @@ -213,6 +213,42 @@ bool asst::InfrastProductionTask::shift_facility_list() ctrler()->click(add_button); sleep(add_task_ptr->post_delay); + // 如果是使用了编队组来排班 + if (current_room_config().use_operator_groups) { + current_room_config().names.clear(); + int swipe_times = 0; + + std::set oper_list; + std::vector temp, pre_temp; + while (true) { + if (need_exit()) { + return false; + } + temp.clear(); + if (!get_opers(temp, m_mood_threshold)) { + return false; + } + if (pre_temp == temp) { + break; + } + oper_list.insert(temp.begin(), temp.end()); + pre_temp = temp; + swipe_of_operlist(); + swipe_times++; + } + swipe_to_the_left_of_operlist(swipe_times + 1); + swipe_times = 0; + // 筛选第一个满足要求的干员组 + for (auto it = current_room_config().operator_groups.begin(); + it != current_room_config().operator_groups.end(); it++) { + + if (ranges::all_of(*it, [oper_list](std::string& oper) { return oper_list.contains(oper); })) { + current_room_config().names.insert(current_room_config().names.end(), it->begin(), it->end()); + break; + } + } + } + for (int i = 0; i <= OperSelectRetryTimes; ++i) { if (need_exit()) { return false; diff --git a/src/MaaCore/Task/Infrast/InfrastProductionTask.h b/src/MaaCore/Task/Infrast/InfrastProductionTask.h index c28932b993..f64bb5fdd9 100644 --- a/src/MaaCore/Task/Infrast/InfrastProductionTask.h +++ b/src/MaaCore/Task/Infrast/InfrastProductionTask.h @@ -22,7 +22,8 @@ namespace asst bool shift_facility_list(); bool facility_list_detect(); bool opers_detect_with_swipe(); - size_t opers_detect(); // 返回当前页面的干员数 + // 返回当前页面的干员数 (可用? + size_t opers_detect(); bool optimal_calc(); bool opers_choose(); bool use_drone(); diff --git a/src/MaaCore/Task/Interface/InfrastTask.cpp b/src/MaaCore/Task/Interface/InfrastTask.cpp index 0aa0c0a658..d3c976abe0 100644 --- a/src/MaaCore/Task/Interface/InfrastTask.cpp +++ b/src/MaaCore/Task/Interface/InfrastTask.cpp @@ -170,6 +170,18 @@ bool asst::InfrastTask::parse_and_set_custom_config(const std::filesystem::path& } auto& cur_plan = all_plans.at(index); + // 录入干员编组 + std::unordered_map> ori_operator_groups; + if (auto opt = cur_plan.find("groups")) { + for (const auto& group_info : opt.value()) { + std::vector oper_group; + for (const auto& oper_info : group_info.at("operators").as_array()) { + oper_group.emplace_back(oper_info.as_string()); + } + ori_operator_groups.emplace(group_info.at("name").as_string(), std::move(oper_group)); + } + } + for (const auto& [facility, facility_info] : cur_plan.at("rooms").as_object()) { infrast::CustomFacilityConfig facility_config; @@ -178,6 +190,8 @@ bool asst::InfrastTask::parse_and_set_custom_config(const std::filesystem::path& room_config.skip = room_info.get("skip", false); room_config.autofill = room_info.get("autofill", false); room_config.sort = room_info.get("sort", false); + room_config.use_operator_groups = room_info.get("use_operator_groups", false); + static std::unordered_map ProductNames = { { "Battle Record", infrast::CustomRoomConfig::Product::BattleRecord }, { "Pure Gold", infrast::CustomRoomConfig::Product::PureGold }, @@ -197,6 +211,7 @@ bool asst::InfrastTask::parse_and_set_custom_config(const std::filesystem::path& } } + // 设置干员 if (auto opers_opt = room_info.find("operators")) { for (const auto& oper_name : opers_opt.value()) { std::string name = oper_name.as_string(); @@ -206,7 +221,21 @@ bool asst::InfrastTask::parse_and_set_custom_config(const std::filesystem::path& } room_config.names.emplace_back(std::move(name)); } + if (room_config.use_operator_groups) { + // 将引用了的干员编组,装载到对应房间配置 + // name数组此后可以作废 + std::set name_set; + name_set.insert(room_config.names.begin(), room_config.names.end()); + ranges::for_each(ori_operator_groups, + [name_set, &room_config](std::pair> pair) { + if (name_set.contains(pair.first)) { + room_config.operator_groups.emplace_back(pair.second); + } + }); + } } + + // 备选干员 if (auto candidates_opt = room_info.find("candidates")) { for (const auto& candidate_name : candidates_opt.value()) { std::string name = candidate_name.as_string(); @@ -220,6 +249,7 @@ bool asst::InfrastTask::parse_and_set_custom_config(const std::filesystem::path& facility_config.emplace_back(std::move(room_config)); } + // 不同类型建筑配置 if (facility == "control") { m_control_task_ptr->set_custom_config(facility_config); } @@ -247,6 +277,7 @@ bool asst::InfrastTask::parse_and_set_custom_config(const std::filesystem::path& } } + // 菲亚梅塔 bool Fia_is_pre = false; do { auto Fia_opt = cur_plan.find("Fiammetta"); @@ -292,6 +323,7 @@ bool asst::InfrastTask::parse_and_set_custom_config(const std::filesystem::path& } } while (false); + // 无人机 do { auto drones_opt = cur_plan.find("drones"); if (!drones_opt) { diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml index 28cd5dffc0..e2960f5d30 100644 --- a/src/MaaWpfGui/Res/Localizations/en-us.xaml +++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml @@ -80,7 +80,7 @@ 2nd Resolution General Mode (Blocked exception output) Morale Threshold - If custom shift is enabled, this field is only valid for autofilled rooms + If custom shifts are enabled, this field is only valid for rooms with autofill and operator groups Gains Experience Points, clears as many nodes as possible Invests coins, exits after arriving at the second floor diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml index 48cc148b67..ed74812f28 100644 --- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml +++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml @@ -73,7 +73,7 @@ 2番目の決議 一般モード(ブロックされた例外出力) 体力が設定した%以下になれば宿舎へ移動する - カスタムシフトが有効な場合、この項目は自動入力された部屋に対してのみ有効です + カスタム シフトが有効な場合、このフィールドは自動入力とオペレーター グループのあるルームに対してのみ有効です 経験値を取得し、できるだけクリア 源石錐の収集、2階到着後終了 diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml index efed7f68a5..8b933e3075 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml @@ -80,7 +80,7 @@ 第二分辨率 通用模式(屏蔽异常输出) 基建工作心情阈值 - 若启用自定义换班,该字段仅针对 autofill 的房间有效 + 若启用自定义换班,该字段仅针对 autofill 和使用干员编组的房间有效 刷等级,尽可能稳定地打更多层数 刷源石锭,到达第二层后直接退出 烧开水,到达第三层后直接退出