feat: 基建制造站和贸易站新增支持干员编组配置

This commit is contained in:
status102
2023-09-06 23:53:25 +08:00
parent 27dee653dd
commit 905d7da2fd
13 changed files with 238 additions and 19 deletions

View File

@@ -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": [

View File

@@ -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": [

View File

@@ -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": [

View File

@@ -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": [

View File

@@ -126,6 +126,11 @@ namespace asst::infrast
};
bool skip = false;
// 是否使用干员编组
bool use_operator_groups = false;
// 干员编组列表
std::vector<std::vector<std::string>> operator_groups;
// 自定干员
std::vector<std::string> names;
bool autofill = false;
Product product = Product::Unknown;

View File

@@ -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<std::string> partial_result;
// 选择自定义干员,同时输出每一页的干员列表
if (!select_custom_opers(partial_result)) {
return false;
}
// 选完人 / 名单已空
if (static_cast<size_t>(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<std::string>& 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<std::string>& pa
return true;
}
bool asst::InfrastAbstractTask::get_opers(std::vector<std::string>& 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<OcrTaskInfo>("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<std::string>& names)
{
LogTraceFunction;

View File

@@ -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<std::string>& partial_result);
bool select_opers_review(infrast::CustomRoomConfig const& origin_room_config,
size_t num_of_opers_expect = 0); // 复核干员选择是否符合期望
// 扫描满足心情条件的所有干员
bool get_opers(std::vector<std::string>& 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<std::string>& 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 =

View File

@@ -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<std::string> oper_list;
std::vector<std::string> 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;

View File

@@ -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();

View File

@@ -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<std::string, std::vector<std::string>> ori_operator_groups;
if (auto opt = cur_plan.find<json::array>("groups")) {
for (const auto& group_info : opt.value()) {
std::vector<std::string> 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<std::string, infrast::CustomRoomConfig::Product> 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<json::array>("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<std::string> 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<std::string, std::vector<std::string>> pair) {
if (name_set.contains(pair.first)) {
room_config.operator_groups.emplace_back(pair.second);
}
});
}
}
// 备选干员
if (auto candidates_opt = room_info.find<json::array>("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<json::object>("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<json::object>("drones");
if (!drones_opt) {

View File

@@ -80,7 +80,7 @@
<system:String x:Key="SecondResolution">2nd Resolution</system:String>
<system:String x:Key="GeneralWithoutScreencapErr">General Mode (Blocked exception output)</system:String>
<system:String x:Key="DormThreshold">Morale Threshold</system:String>
<system:String x:Key="InfrastThresholdTip">If custom shift is enabled, this field is only valid for autofilled rooms</system:String>
<system:String x:Key="InfrastThresholdTip">If custom shifts are enabled, this field is only valid for rooms with autofill and operator groups</system:String>
<system:String x:Key="RoguelikeStrategyExp">Gains Experience Points, clears as many nodes as possible</system:String>
<system:String x:Key="RoguelikeStrategyGold">Invests coins, exits after arriving at the second floor</system:String>
<!-- system:String x:Key="RoguelikeLastReward">Fight to obtain starting rewards, reach the third floor then exit</system:String -->

View File

@@ -73,7 +73,7 @@
<system:String x:Key="SecondResolution">2番目の決議</system:String>
<system:String x:Key="GeneralWithoutScreencapErr">一般モード(ブロックされた例外出力)</system:String>
<system:String x:Key="DormThreshold">体力が設定した%以下になれば宿舎へ移動する</system:String>
<system:String x:Key="InfrastThresholdTip">カスタムシフトが有効な場合、この項目は自動入力された部屋に対してのみ有効です</system:String>
<system:String x:Key="InfrastThresholdTip">カスタム シフトが有効な場合、このフィールドは自動入力とオペレーター グループのあるルームに対してのみ有効です</system:String>
<system:String x:Key="RoguelikeStrategyExp">経験値を取得し、できるだけクリア</system:String>
<system:String x:Key="RoguelikeStrategyGold">源石錐の収集、2階到着後終了</system:String>
<!-- system:String x:Key="RoguelikeLastReward">序盤の報酬を得るために戦い、3階到着後終了</system:String -->

View File

@@ -80,7 +80,7 @@
<system:String x:Key="SecondResolution">第二分辨率</system:String>
<system:String x:Key="GeneralWithoutScreencapErr">通用模式(屏蔽异常输出)</system:String>
<system:String x:Key="DormThreshold">基建工作心情阈值</system:String>
<system:String x:Key="InfrastThresholdTip">若启用自定义换班,该字段仅针对 autofill 的房间有效</system:String>
<system:String x:Key="InfrastThresholdTip">若启用自定义换班,该字段仅针对 autofill 和使用干员编组的房间有效</system:String>
<system:String x:Key="RoguelikeStrategyExp">刷等级,尽可能稳定地打更多层数</system:String>
<system:String x:Key="RoguelikeStrategyGold">刷源石锭,到达第二层后直接退出</system:String>
<system:String x:Key="RoguelikeLastReward">烧开水,到达第三层后直接退出</system:String>