Merge pull request #2167 from MaaAssistantArknights/perf/roguefight

perf: 优化肉鸽自动战斗逻辑
This commit is contained in:
zzyyyl
2022-10-09 01:07:57 +08:00
committed by GitHub
18 changed files with 2574 additions and 617 deletions

View File

@@ -30,7 +30,21 @@
"key_kills": [ // 关键击杀数,可选,默认为空
15, // 若不为空,干员技能会攒着,不是好了就放,而是等到击杀数达到时再释放
... // 当跑完数组里所有的元素之后,再把干员技能变成好了就放
]
],
"not_use_dice": false, // 若为true则核心干员撤退后不使用骰子
"role_order": [ // 部署干员的顺序
"warrior",
"pioneer",
"medic",
"tank",
"sniper",
"caster",
"support",
"special",
"drone"
],
"stop_melee_deploy_num": 3 // 在部署了3人后立即部署2名远程干员
"deploy_ranged_num": 2 // 填写上一项后此项必填
},
...
]

File diff suppressed because it is too large Load Diff

View File

@@ -15,67 +15,80 @@
"name": "Sharp",
"skill": 1,
"recruit_priority": 990,
"promote_priority": 650
"recruit_priority_when_team_full": 890,
"recruit_priority_when_team_full_Doc": "不填默认 recruit_priority - 100",
"promote_priority": 650,
"promote_priority_when_team_full": 750,
"promote_priority_when_team_full_Doc": "不填默认 promote_priority + 100",
"Doc": "编队满了且编队内所有干员 promote_priority 均大于200才会走两个 when_team_full 的逻辑"
},
{
"name": "海沫",
"skill": 1,
"recruit_priority": 960,
"promote_priority": 650
"promote_priority": 650,
"promote_priority_when_team_full": 750
},
{
"name": "羽毛笔",
"skill": 1,
"recruit_priority": 950,
"promote_priority": 650
"promote_priority": 650,
"promote_priority_when_team_full": 750
},
{
"name": "百炼嘉维尔",
"skill": 3,
"alternate_skill": 2,
"skill": 2,
"recruit_priority": 900,
"promote_priority": 750
"promote_priority": 750,
"promote_priority_when_team_full": 950
},
{
"name": "棘刺",
"skill": 3,
"alternate_skill": 1,
"recruit_priority": 780,
"promote_priority": 1000
"promote_priority": 1000,
"promote_priority_when_team_full": 1200
},
{
"name": "山",
"skill": 2,
"skill_usage": 2,
"recruit_priority": 750,
"promote_priority": 550
"promote_priority": 550,
"promote_priority_when_team_full":750
},
{
"name": "玛恩纳",
"skill": 3,
"alternate_skill": 2,
"recruit_priority": 750,
"promote_priority": 700
"promote_priority": 700,
"promote_priority_when_team_full":750
},
{
"name": "煌",
"skill": 2,
"alternate_skill": 1,
"recruit_priority": 750,
"promote_priority": 640
"promote_priority": 640,
"promote_priority_when_team_full":750
},
{
"name": "拉普兰德",
"skill": 2,
"recruit_priority": 750,
"promote_priority": 750
"promote_priority": 750,
"promote_priority_when_team_full":800
},
{
"name": "银灰",
"skill": 3,
"alternate_skill": 1,
"recruit_priority": 750,
"promote_priority": 800
"promote_priority": 800,
"promote_priority_when_team_full":800
},
{
"name": "艾丽妮",
@@ -149,9 +162,11 @@
},
{
"name": "风笛",
"skill": 2,
"skill": 3,
"alternate_skill": 2,
"recruit_priority": 600,
"promote_priority": 610
"promote_priority": 610,
"promote_priority_when_team_full": 850
},
{
"name": "推进之王",
@@ -209,19 +224,21 @@
"name": "塞雷娅",
"skill": 2,
"recruit_priority": 800,
"promote_priority": 760
"promote_priority": 760,
"promote_priority_when_team_full": 850
},
{
"name": "瑕光",
"skill": 1,
"recruit_priority": 200,
"promote_priority": 100
"promote_priority": 200
},
{
"name": "临光",
"skill": 1,
"recruit_priority": 400,
"promote_priority": 1
"promote_priority": 200,
"promote_priority_when_team_full": 750
},
{
"name": "古米",
@@ -247,27 +264,31 @@
"name": "Stormeye",
"skill": 1,
"recruit_priority": 900,
"promote_priority": 650
"promote_priority": 650,
"promote_priority_when_team_full": 100
},
{
"name": "能天使",
"skill": 3,
"alternate_skill": 1,
"recruit_priority": 900,
"promote_priority": 1000
},
{
"name": "空弦",
"skill": 1,
"recruit_priority": 800,
"promote_priority": 800
"promote_priority": 1000,
"promote_priority_when_team_full": 1200
},
{
"name": "假日威龙陈",
"skill": 3,
"alternate_skill": 1,
"recruit_priority": 790,
"promote_priority": 1000
"recruit_priority": 810,
"promote_priority": 1000,
"promote_priority_when_team_full": 1000
},
{
"name": "空弦",
"skill": 1,
"recruit_priority": 800,
"promote_priority": 800,
"promote_priority_when_team_full": 800
},
{
"name": "鸿雪",
@@ -299,13 +320,15 @@
"name": "白金",
"skill": 2,
"recruit_priority": 550,
"promote_priority": 550
"promote_priority": 550,
"promote_priority_when_team_full": 750
},
{
"name": "蓝毒",
"skill": 1,
"recruit_priority": 550,
"promote_priority": 550
"promote_priority": 550,
"promote_priority_when_team_full": 750
},
{
"name": "梅",
@@ -337,20 +360,23 @@
"name": "Pith",
"skill": 1,
"recruit_priority": 900,
"promote_priority": 600
"promote_priority": 600,
"promote_priority_when_team_full": 100
},
{
"name": "澄闪",
"skill": 3,
"alternate_skill": 2,
"recruit_priority": 700,
"promote_priority": 1000
"promote_priority": 1000,
"promote_priority_when_team_full": 1200
},
{
"name": "艾雅法拉",
"skill": 2,
"recruit_priority": 600,
"promote_priority": 700
"promote_priority": 700,
"promote_priority_when_team_full":1000
},
{
"name": "异客",
@@ -421,7 +447,8 @@
"name": "归溟幽灵鲨",
"skill": 2,
"recruit_priority": 700,
"promote_priority": 700
"promote_priority": 700,
"promote_priority_when_team_full": 100
},
{
"name": "砾",
@@ -492,25 +519,29 @@
"name": "蜜莓",
"skill": 1,
"recruit_priority": 700,
"promote_priority": 610
"promote_priority": 610,
"promote_priority_when_team_full": 850
},
{
"name": "桑葚",
"skill": 1,
"recruit_priority": 600,
"promote_priority": 500
"promote_priority": 500,
"promote_priority_when_team_full": 850
},
{
"name": "白面鸮",
"skill": 2,
"recruit_priority": 550,
"promote_priority": 550
"promote_priority": 550,
"promote_priority_when_team_full": 600
},
{
"name": "华法琳",
"skill": 2,
"recruit_priority": 540,
"promote_priority": 550
"promote_priority": 550,
"promote_priority_when_team_full": 600
},
{
"name": "赫默",
@@ -568,7 +599,8 @@
"skill": 3,
"alternate_skill": 1,
"recruit_priority": 20,
"promote_priority": 500
"promote_priority": 500,
"promote_priority_when_team_full": 900
},
{
"name": "令",

View File

@@ -1927,6 +1927,10 @@
[
"限星",
"陨星"
],
[
"般子",
"骰子"
]
]
},
@@ -6074,6 +6078,10 @@
"template": "empty.png",
"templThreshold": 0.6
},
"DiceAvatarMatch": {
"template": "empty.png",
"templThreshold": 0.8
},
"BattleKillsFlag": {
"roi": [
150,
@@ -8148,6 +8156,19 @@
30
]
},
"Roguelike1SkillSelectionTeamFull": {
"cache": false,
"roi": [
135,
22,
1032,
668
],
"maskRange": [
200,
255
]
},
"Roguelike1StartAction": {
"template": "Roguelike1StartAction.png",
"Doc": "本任务注册了插件 RoguelikeSkillSelectionTaskPlugin、RoguelikeBattleTaskPlugin",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -2,6 +2,7 @@
#include "Utils/NoWarningCV.h"
#include "General/MatchImageAnalyzer.h"
#include "General/MultiMatchImageAnalyzer.h"
#include "General/OcrWithPreprocessImageAnalyzer.h"
#include "Resource/RoguelikeRecruitConfiger.h"
@@ -40,6 +41,8 @@ bool asst::RoguelikeSkillSelectionImageAnalyzer::analyze()
m_result.emplace(std::move(name), std::move(skills));
}
team_full_analyze();
return !m_result.empty();
}
@@ -74,3 +77,10 @@ std::vector<asst::Rect> asst::RoguelikeSkillSelectionImageAnalyzer::skill_analyz
}
return result;
}
void asst::RoguelikeSkillSelectionImageAnalyzer::team_full_analyze()
{
MatchImageAnalyzer analyzer(m_image);
analyzer.set_task_info("Roguelike1SkillSelectionTeamFull");
m_team_full = !analyzer.analyze();
}

View File

@@ -15,6 +15,7 @@ namespace asst
virtual bool analyze() override;
const auto& get_result() const noexcept { return m_result; }
bool get_team_full() const noexcept { return m_team_full; }
private:
// 该分析器不支持外部设置ROI
@@ -22,7 +23,9 @@ namespace asst
std::string name_analyze(const Rect& roi);
std::vector<Rect> skill_analyze(const Rect& roi);
void team_full_analyze();
std::unordered_map<std::string, std::vector<Rect>> m_result; // 干员名 : 技能的位置(多个技能)
bool m_team_full = false;
};
}

View File

@@ -2,6 +2,7 @@
#include <meojson/json.hpp>
#include "Utils/AsstUtils.hpp"
#include "Utils/Logger.hpp"
std::optional<asst::RoguelikeBattleData> asst::RoguelikeCopilotConfiger::get_stage_data(
@@ -60,6 +61,94 @@ bool asst::RoguelikeCopilotConfiger::parse(const json::value& json)
data.key_kills.emplace_back(static_cast<int>(key_kill));
}
}
data.use_dice_stage = !stage_info.get("not_use_dice", false);
data.stop_melee_deploy_num = stage_info.get("stop_melee_deploy_num", INT_MAX);
data.deploy_ranged_num = stage_info.get("deploy_ranged_num", 0);
if (data.deploy_ranged_num == 0) {
data.stop_melee_deploy_num = INT_MAX;
}
constexpr int RoleNumber = 9;
static constexpr std::array<BattleRole, RoleNumber> RoleOrder = {
BattleRole::Warrior, BattleRole::Pioneer, BattleRole::Medic, BattleRole::Tank, BattleRole::Sniper,
BattleRole::Caster, BattleRole::Support, BattleRole::Special, BattleRole::Drone,
};
static const std::unordered_map<std::string, BattleRole> NameToRole = {
{ "warrior", BattleRole::Warrior }, { "WARRIOR", BattleRole::Warrior },
{ "Warrior", BattleRole::Warrior }, { "近卫", BattleRole::Warrior },
{ "pioneer", BattleRole::Pioneer }, { "PIONEER", BattleRole::Pioneer },
{ "Pioneer", BattleRole::Pioneer }, { "先锋", BattleRole::Pioneer },
{ "medic", BattleRole::Medic }, { "MEDIC", BattleRole::Medic },
{ "Medic", BattleRole::Medic }, { "医疗", BattleRole::Medic },
{ "tank", BattleRole::Tank }, { "TANK", BattleRole::Tank },
{ "Tank", BattleRole::Tank }, { "重装", BattleRole::Tank },
{ "sniper", BattleRole::Sniper }, { "SNIPER", BattleRole::Sniper },
{ "Sniper", BattleRole::Sniper }, { "狙击", BattleRole::Sniper },
{ "caster", BattleRole::Caster }, { "CASTER", BattleRole::Caster },
{ "Caster", BattleRole::Caster }, { "术师", BattleRole::Caster },
{ "support", BattleRole::Support }, { "SUPPORT", BattleRole::Support },
{ "Support", BattleRole::Support }, { "辅助", BattleRole::Support },
{ "special", BattleRole::Special }, { "SPECIAL", BattleRole::Special },
{ "Special", BattleRole::Special }, { "特种", BattleRole::Special },
{ "drone", BattleRole::Drone }, { "DRONE", BattleRole::Drone },
{ "Drone", BattleRole::Drone }, { "无人机", BattleRole::Drone },
};
auto to_lower = [](char c) -> char { return static_cast<char>(std::tolower(c)); };
if (auto opt = stage_info.find<json::array>("role_order")) {
const auto& raw_roles = opt.value();
using views::filter, views::transform;
std::unordered_set<BattleRole> specified_role;
std::vector<BattleRole> role_order;
bool is_legal = true;
if (ranges::find_if_not(raw_roles | views::all, std::mem_fn(&json::value::is_string)) != raw_roles.end()) {
Log.error("BattleRole should be string");
return false;
}
auto roles = raw_roles | filter(&json::value::is_string) | transform(&json::value::as_string) |
transform([&](std::string name) {
ranges::for_each(name, to_lower);
return std::move(name);
});
for (const std::string& role_name : roles) {
auto iter = NameToRole.find(role_name);
if (iter == NameToRole.end()) [[unlikely]] {
Log.error("Unknown BattleRole:", role_name);
is_legal = false;
break;
}
if (specified_role.contains(iter->second)) [[unlikely]] {
Log.error("Duplicated BattleRole:", role_name);
is_legal = false;
break;
}
specified_role.emplace(iter->second);
role_order.emplace_back(iter->second);
}
if (is_legal) [[likely]] {
ranges::copy(RoleOrder | filter([&](BattleRole role) { return !specified_role.contains(role); }),
std::back_inserter(role_order));
if (role_order.size() != RoleNumber) [[unlikely]] {
Log.error("Unexpected role_order size:", role_order.size());
return false;
}
ranges::move(role_order, data.role_order.begin());
}
else {
data.role_order = RoleOrder;
}
}
else {
data.role_order = RoleOrder;
}
m_stage_data.emplace(std::move(stage_name), std::move(data));
}
return true;

View File

@@ -28,6 +28,10 @@ bool asst::RoguelikeRecruitConfiger::parse(const json::value& json)
info.name = name;
info.recruit_priority = oper_info.get("recruit_priority", 0);
info.promote_priority = oper_info.get("promote_priority", 0);
info.recruit_priority_when_team_full =
oper_info.get("recruit_priority_when_team_full", info.recruit_priority - 100);
info.promote_priority_when_team_full =
oper_info.get("promote_priority_when_team_full", info.promote_priority + 100);
info.is_alternate = oper_info.get("is_alternate", false);
info.skill = oper_info.at("skill").as_integer();
info.alternate_skill = oper_info.get("alternate_skill", 0);

View File

@@ -15,8 +15,10 @@ namespace asst
struct RoguelikeOperInfo
{
std::string name;
int recruit_priority = 0; // 招募优先级 (0-1000)
int promote_priority = 0; // 晋升优先级 (0-1000)
int recruit_priority = 0; // 招募优先级 (0-1000)
int promote_priority = 0; // 晋升优先级 (0-1000)
int recruit_priority_when_team_full = 0; // 队伍满时的招募优先级 (0-1000)
int promote_priority_when_team_full = 0; // 队伍满时的晋升优先级 (0-1000)
bool is_alternate = false; // 是否后备干员 (允许重复招募、划到后备干员时不再往右划动)
int skill = 0;
int alternate_skill = 0;

View File

@@ -35,7 +35,7 @@ std::unordered_map<asst::Point, asst::TilePack::TileInfo> asst::TilePack::calc(c
bool ret = m_tile_calculator->run(stage_code, side, pos, tiles);
Log.trace("After tiles cacl run");
Log.trace("After tiles calc run");
if (!ret) {
Log.info("Tiles calc error!");
return {};

View File

@@ -37,7 +37,10 @@ namespace asst
static inline const std::string RoguelikeCharLevelPrefix = "RoguelikeLevel-";
static inline const std::string RoguelikeCharRarityPrefix = "RoguelikeRarity-";
static inline const std::string RoguelikeCharOverview = "RoguelikeOverview";
static inline const std::string RoguelikeCoreChar = "RoguelikeCoreChar";
static inline const std::string RoguelikeTraderNoLongerBuy = "RoguelikeNoLongerBuy";
static inline const std::string RoguelikeSkillUsagePrefix = "RoguelikeSkillUsage-";
static inline const std::string RoguelikeTeamFullWithoutRookie = "RoguelikeTeamFullWithoutRookie";
private:
std::unordered_map<std::string, int64_t> m_number;

View File

@@ -140,16 +140,19 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info()
auto opt = RoguelikeCopilot.get_stage_data(m_stage_name);
if (opt && !opt->replacement_home.empty()) {
m_homes = opt->replacement_home;
std::string log_str = "[ ";
for (auto& home : m_homes) {
if (!m_normal_tile_info.contains(home.location)) {
Log.error("No replacement home point", home.location.x, home.location.y);
}
log_str += "( " + std::to_string(home.location.x) + ", " + std::to_string(home.location.y) + " ), ";
auto homes_pos = m_homes | views::transform(&ReplacementHome::location);
auto invalid_homes_pos =
homes_pos | views::filter([&](const auto& home_pos) { return !m_normal_tile_info.contains(home_pos); }) |
views::transform(&Point::to_string);
if (!invalid_homes_pos.empty()) {
Log.error("No replacement homes point:", invalid_homes_pos);
}
log_str += "]";
Log.info("replacement home:", log_str);
Log.info("replacement home:", homes_pos | views::transform(&Point::to_string));
m_blacklist_location = opt->blacklist_location;
m_stage_use_dice = opt->use_dice_stage;
m_role_order = opt->role_order;
m_stop_melee_deploy_num = opt->stop_melee_deploy_num;
m_deploy_ranged_num = opt->deploy_ranged_num;
}
else {
for (const auto& [loc, side] : m_normal_tile_info) {
@@ -157,6 +160,21 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info()
m_homes.emplace_back(ReplacementHome { loc, BattleDeployDirection::None });
}
}
m_stage_use_dice = true;
m_role_order = {
BattleRole::Warrior, BattleRole::Pioneer, BattleRole::Medic, BattleRole::Tank, BattleRole::Sniper,
BattleRole::Caster, BattleRole::Support, BattleRole::Special, BattleRole::Drone,
};
}
m_wait_melee.assign(m_homes.size(), true);
m_wait_medic.assign(m_homes.size(), true);
m_indeed_no_medic.assign(m_homes.size(), false);
// 认为开局时未在所有路线放干员时为紧急状态,并把路线压入栈
// 由于开局路线设为0故0不需要被压入栈
m_cur_home_index = 0;
m_is_cur_urgent = true;
for (size_t index = m_homes.size() - 1; index > 0; index--) {
m_next_urgent_index.push(index);
}
if (opt && !opt->key_kills.empty()) {
std::string log_str = "[ ";
@@ -186,6 +204,33 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info()
return calced;
}
bool asst::RoguelikeBattleTaskPlugin::battle_pause()
{
return ProcessTask(*this, { "BattlePause" }).run();
}
asst::RoguelikeBattleTaskPlugin::BattleRoleType asst::RoguelikeBattleTaskPlugin::get_role_type(const BattleRole& role)
{
switch (role) {
case BattleRole::Medic:
case BattleRole::Support:
case BattleRole::Sniper:
case BattleRole::Caster:
return BattleRoleType::Ranged;
break;
case BattleRole::Pioneer:
case BattleRole::Warrior:
case BattleRole::Tank:
return BattleRoleType::Melee;
break;
case BattleRole::Special:
case BattleRole::Drone:
default:
return BattleRoleType::Other;
break;
}
}
bool asst::RoguelikeBattleTaskPlugin::auto_battle()
{
LogTraceFunction;
@@ -207,6 +252,18 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle()
// }
// }
// 将存在于场上超过限时的召唤物所在的地块重新设为可用
Time_Point now_time = std::chrono::system_clock::now();
while ((!m_need_clear_tiles.empty()) && m_need_clear_tiles.top().placed_time < now_time) {
const auto& placed_loc = m_need_clear_tiles.top().placed_loc;
if (auto iter = m_used_tiles.find(placed_loc); iter != m_used_tiles.end()) {
Log.info("Drone at location (", placed_loc.x, ",", placed_loc.y, ") is recognized as retreated");
m_opers_in_field.erase((iter->second).first);
m_used_tiles.erase(iter);
}
m_need_clear_tiles.pop();
}
const cv::Mat& image = m_ctrler->get_image();
if (try_possible_skill(image)) {
return true;
@@ -220,23 +277,144 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle()
}
battle_analyzer.sort_opers_by_cost();
const auto& opers = battle_analyzer.get_opers();
auto opers = battle_analyzer.get_opers();
if (opers.empty()) {
return true;
}
// 如果已经放了一些人了,就不要再有费就下了
size_t available_count = 0;
size_t cooling_count = 0;
battle_analyzer.set_target(BattleImageAnalyzer::Target::Oper);
if (m_cur_home_index >= m_homes.size()) {
m_cur_home_index = 0;
}
int available_count = 0;
int cooling_count = 0;
std::vector<size_t> new_urgent;
std::vector<std::string> cooling_opers;
const auto use_oper_task_ptr = Task.get("BattleUseOper");
bool has_dice = false;
BattleRealTimeOper dice;
for (const auto& oper : opers) {
if (oper.available) {
available_count += 1;
}
if (oper.cooling) {
cooling_count += 1;
if (oper.cooling) cooling_count++;
if (oper.available) available_count++;
}
for (auto& oper : opers) {
if (oper.role == BattleRole::Drone) {
if (!m_dice_image.empty()) {
MatchImageAnalyzer dice_analyzer(oper.avatar);
dice_analyzer.set_task_info("DiceAvatarMatch");
dice_analyzer.set_templ(m_dice_image);
if (dice_analyzer.analyze()) {
has_dice = true;
oper.name = "骰子";
dice = oper;
break;
}
}
}
}
if (m_used_tiles.size() >= std::max(m_homes.size(), static_cast<size_t>(2))) {
// 超过一半的人费用都没好,那就不下人
bool can_use_dice = m_stage_use_dice && has_dice;
if (!can_use_dice) {
m_use_dice = false;
}
// 如果发现有新撤退就更新m_retreated_opers
// 如果发现有新转好的只更新m_last_cooling_count在部署时从set中删去
if (cooling_count > m_last_cooling_count) {
battle_pause();
int remain_add = cooling_count - m_last_cooling_count;
Rect cur_rect;
for (size_t i = 0; i < opers.size(); ++i) {
const auto& cur_opers = battle_analyzer.get_opers();
if (cur_opers.empty()) {
// 只会在战斗结束时发生战斗结束后cur_opers为空导致访问越界
return true;
}
size_t offset = opers.size() > cur_opers.size() ? opers.size() - cur_opers.size() : 0;
const auto& oper = cur_opers.at(i - offset);
if ((!oper.cooling) || oper.role == BattleRole::Drone) {
continue;
}
cur_rect = oper.rect;
m_ctrler->click(cur_rect);
sleep(use_oper_task_ptr->pre_delay);
const cv::Mat& new_image = m_ctrler->get_image();
OcrWithPreprocessImageAnalyzer oper_name_analyzer(new_image);
oper_name_analyzer.set_task_info("BattleOperName");
oper_name_analyzer.set_replace(Task.get<OcrTaskInfo>("CharsNameOcrReplace")->replace_map);
std::string oper_name = "Unknown";
if (oper_name_analyzer.analyze()) {
oper_name_analyzer.sort_result_by_score();
oper_name = oper_name_analyzer.get_result().front().text;
}
battle_analyzer.set_image(new_image);
battle_analyzer.analyze();
battle_analyzer.sort_opers_by_cost();
if (oper_name == "Unknown") {
continue;
}
if (m_retreated_opers.contains(oper_name)) {
continue;
}
auto iter = m_opers_in_field.find(oper_name);
if (iter == m_opers_in_field.end()) {
continue;
}
Log.info(oper_name, "retreated");
if (auto del_pos_melee = m_melee_for_home_index.find(iter->second);
del_pos_melee != m_melee_for_home_index.end()) {
m_wait_melee[del_pos_melee->second] = true;
new_urgent.emplace_back(del_pos_melee->second);
m_melee_for_home_index.erase(del_pos_melee);
}
else if (auto del_pos_medic = m_medic_for_home_index.find(iter->second);
del_pos_medic != m_medic_for_home_index.end()) {
for (const size_t& home_index : del_pos_medic->second) {
m_wait_medic[home_index] = true;
}
m_medic_for_home_index.erase(del_pos_medic);
}
if (auto del_pos_tiles = m_used_tiles.find(iter->second); del_pos_tiles != m_used_tiles.end()) {
if (get_role_type(del_pos_tiles->second.second) == BattleRoleType::Melee) {
m_has_deployed_melee_num--;
}
m_used_tiles.erase(del_pos_tiles);
}
m_opers_in_field.erase(iter);
m_retreated_opers.emplace(oper_name);
remain_add--;
if (!remain_add) break;
}
m_ctrler->click(cur_rect);
battle_pause();
}
m_last_cooling_count = cooling_count;
if (!new_urgent.empty()) {
// 出现新的紧急情况,立即切到这条线路,并把其他紧急情况压入栈
Log.info("New urgent situation detected");
if (m_is_cur_urgent) {
m_next_urgent_index.push(m_cur_home_index);
}
else {
m_last_not_urgent = static_cast<int>(m_cur_home_index);
}
m_cur_home_index = new_urgent.at(0);
for (size_t i = 1; i < new_urgent.size(); ++i) {
m_next_urgent_index.push(new_urgent.at(i));
}
if (can_use_dice) {
// 先放骰子,再放近战
m_use_dice = true;
m_next_urgent_index.push(new_urgent.at(0));
}
}
else if ((!m_is_cur_urgent) && m_used_tiles.size() >= 2) {
// 超过一半的人费用都没好,且没有紧急情况,那就不下人
size_t not_cooling_count = opers.size() - cooling_count;
if (available_count <= not_cooling_count / 2) {
Log.trace("already used", m_used_tiles.size(), ", now_total", opers.size(), ", available", available_count,
@@ -245,90 +423,153 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle()
}
}
static const std::array<BattleRole, 9> RoleOrder = {
BattleRole::Warrior, BattleRole::Pioneer, BattleRole::Medic, BattleRole::Tank, BattleRole::Sniper,
BattleRole::Caster, BattleRole::Support, BattleRole::Special, BattleRole::Drone,
};
const auto use_oper_task_ptr = Task.get("BattleUseOper");
const auto swipe_oper_task_ptr = Task.get("BattleSwipeOper");
// 点击当前最合适的干员
BattleRealTimeOper opt_oper;
bool oper_found = false;
// 一个人都没有的时候,先下个地面单位(如果有的话)
// 第二个人下奶(如果有的话)
bool wait_melee = false;
bool wait_medic = false;
if (m_used_tiles.empty() || m_used_tiles.size() == 1) {
for (const auto& op : opers) {
if (m_used_tiles.empty()) {
if (op.role == BattleRole::Warrior || op.role == BattleRole::Pioneer || op.role == BattleRole::Tank) {
wait_melee = true;
}
}
else if (m_used_tiles.size() == 1 && m_homes.size() == 1) {
if (op.role == BattleRole::Medic) {
wait_medic = true;
}
}
}
}
for (auto role : RoleOrder) {
if (wait_melee) {
if (role != BattleRole::Warrior && role != BattleRole::Pioneer && role != BattleRole::Tank) {
continue;
}
}
else if (wait_medic) {
if (role != BattleRole::Medic) {
continue;
}
}
for (const auto& oper : opers) {
if (!oper.available) {
continue;
}
if (oper.role == role) {
opt_oper = oper;
oper_found = true;
break;
}
}
if (oper_found) {
break;
}
}
if (!oper_found) {
return true;
}
// 预计算干员是否有点地方放
if (available_locations(opt_oper.role).empty()) {
Log.info("Tiles full");
if (cooling_count) {
Log.info("has cooling, clear and re-calc");
m_used_tiles.clear();
}
else {
bool has_melee = false;
bool has_medic = false;
bool wait_melee = m_wait_melee[m_cur_home_index];
bool wait_medic = m_wait_medic[m_cur_home_index];
bool force_need_ranged =
(!m_has_finished_deploy_ranged) && (m_has_deployed_melee_num >= m_stop_melee_deploy_num) && (!m_use_dice);
if (m_use_dice) {
opt_oper = std::move(dice);
oper_found = true;
if (available_locations(opt_oper.role).empty()) {
Log.info("Tiles full");
return true;
}
}
else {
// 对于每个蓝门,先下个地面单位(如果有的话)
// 第二个人下奶(如果有的话)
bool has_ranged = false;
for (const auto& op : opers) {
const auto role_type = get_role_type(op.role);
if (role_type == BattleRoleType::Melee && (!op.cooling)) {
has_melee = true;
}
else if (role_type == BattleRoleType::Ranged && (!op.cooling)) {
has_ranged = true;
if (op.role == BattleRole::Medic && (!op.cooling)) {
has_medic = true;
}
}
}
m_ctrler->click(opt_oper.rect);
sleep(use_oper_task_ptr->pre_delay);
wait_melee &= has_melee;
wait_medic &= has_medic;
if (force_need_ranged) {
Log.info("RANGED ROLE IS NEEDED UNDER FORCE");
if (!has_ranged) {
m_has_finished_deploy_ranged = true;
Log.info("FORCE RANGED OPER DEPLOY END");
return true;
}
}
for (auto role : m_role_order) {
const auto role_type = get_role_type(role);
if (force_need_ranged) {
if (role_type != BattleRoleType::Ranged) continue;
}
else {
if (wait_melee) {
if (role_type != BattleRoleType::Melee) continue;
}
else if (wait_medic) {
if (role != BattleRole::Medic) {
continue;
}
}
}
OcrWithPreprocessImageAnalyzer oper_name_analyzer(m_ctrler->get_image());
oper_name_analyzer.set_task_info("BattleOperName");
oper_name_analyzer.set_replace(Task.get<OcrTaskInfo>("CharsNameOcrReplace")->replace_map);
for (const auto& oper : opers) {
if (!oper.available) {
continue;
}
if (oper.name == "骰子") {
continue;
}
if (oper.role == role) {
opt_oper = oper;
oper_found = true;
break;
}
}
if (oper_found) {
break;
}
}
if (!oper_found) {
return true;
}
std::string oper_name = "Unknown";
if (oper_name_analyzer.analyze()) {
oper_name_analyzer.sort_result_by_score();
oper_name = oper_name_analyzer.get_result().front().text;
opt_oper.name = oper_name;
// 预计算干员是否有点地方放
if (available_locations(opt_oper.role).empty()) {
Log.info("Tiles full");
if (force_need_ranged) {
m_has_finished_deploy_ranged = true;
Log.info("FORCE RANGED OPER DEPLOY END");
}
return true;
}
if (m_first_deploy) {
m_first_deploy = false;
battle_pause();
Rect cur_rect;
for (size_t i = 0; i < opers.size(); ++i) {
const auto& cur_opers = battle_analyzer.get_opers();
if (cur_opers.empty()) {
// 只会在战斗结束时发生战斗结束后cur_opers为空导致访问越界
return true;
}
size_t offset = opers.size() > cur_opers.size() ? opers.size() - cur_opers.size() : 0;
const auto& oper = cur_opers.at(i - offset);
if (oper.role == BattleRole::Drone) {
cur_rect = oper.rect;
m_ctrler->click(cur_rect);
sleep(use_oper_task_ptr->pre_delay);
const cv::Mat& new_image = m_ctrler->get_image();
OcrWithPreprocessImageAnalyzer oper_name_analyzer(new_image);
oper_name_analyzer.set_task_info("BattleOperName");
oper_name_analyzer.set_replace(Task.get<OcrTaskInfo>("CharsNameOcrReplace")->replace_map);
battle_analyzer.set_image(new_image);
battle_analyzer.analyze();
battle_analyzer.sort_opers_by_cost();
if (!oper_name_analyzer.analyze()) continue;
oper_name_analyzer.sort_result_by_score();
if (oper_name_analyzer.get_result().front().text == "骰子") {
m_dice_image = opers.at(i).avatar;
Log.info("Dice detected");
break;
}
}
}
if (!cur_rect.empty()) {
m_ctrler->click(cur_rect);
}
battle_pause();
}
m_ctrler->click(opt_oper.rect);
sleep(use_oper_task_ptr->pre_delay);
OcrWithPreprocessImageAnalyzer oper_name_analyzer(m_ctrler->get_image());
oper_name_analyzer.set_task_info("BattleOperName");
oper_name_analyzer.set_replace(Task.get<OcrTaskInfo>("CharsNameOcrReplace")->replace_map);
opt_oper.name = "Unknown";
if (oper_name_analyzer.analyze()) {
oper_name_analyzer.sort_result_by_score();
opt_oper.name = oper_name_analyzer.get_result().front().text;
}
}
// 计算最优部署位置及方向
@@ -345,18 +586,82 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle()
sleep(use_oper_task_ptr->rear_delay);
// 将方向转换为实际的 swipe end 坐标点
constexpr int coeff = 500;
Point end_point = placed_point + (direction * coeff);
m_ctrler->swipe(placed_point, end_point, swipe_oper_task_ptr->rear_delay, true, 100);
if (direction != Point::zero()) {
constexpr int coeff = 500;
Point end_point = placed_point + (direction * coeff);
m_ctrler->swipe(placed_point, end_point, swipe_oper_task_ptr->rear_delay, true, 100);
}
if (opt_oper.role == BattleRole::Drone) {
cancel_oper_selection();
now_time = std::chrono::system_clock::now();
if (opt_oper.name == "骰子") {
m_need_clear_tiles.emplace(now_time + std::chrono::seconds(20), placed_loc);
}
else {
m_need_clear_tiles.emplace(now_time + std::chrono::seconds(35), placed_loc);
}
}
m_used_tiles.emplace(placed_loc, oper_name);
Log.info("Try to deploy oper", opt_oper.name);
m_opers_used = true;
++m_cur_home_index;
m_used_tiles.emplace(placed_loc, std::make_pair(opt_oper.name, opt_oper.role));
m_opers_in_field.emplace(opt_oper.name, placed_loc);
m_retreated_opers.erase(opt_oper.name);
if (get_role_type(opt_oper.role) == BattleRoleType::Melee) {
m_has_deployed_melee_num++;
}
if (force_need_ranged) {
m_has_deployed_ranged_num++;
if (m_has_deployed_ranged_num >= m_deploy_ranged_num) {
m_has_finished_deploy_ranged = true;
Log.info("FORCE RANGED OPER DEPLOY END");
}
// 不改变当前index直接进入下一步
return true;
}
if (wait_melee) {
m_wait_melee[m_cur_home_index] = false;
m_melee_for_home_index.emplace(placed_loc, m_cur_home_index);
}
else if (wait_medic) {
// 两次轮到wait_medic的轮次时仍没有医疗干员奶当前位置即认为奶不到
if (m_wait_medic[m_cur_home_index]) {
m_indeed_no_medic[m_cur_home_index] = false;
}
else {
if (m_indeed_no_medic[m_cur_home_index]) {
m_wait_medic[m_cur_home_index] = false;
}
else {
m_indeed_no_medic[m_cur_home_index] = true;
}
}
}
// 如果有紧急需要部署的路线,那么下一条路线设为它
if (m_next_urgent_index.empty()) {
if (m_last_not_urgent != -1) {
// 从上一次不是紧急的下一条路线开始
m_cur_home_index = static_cast<size_t>(m_last_not_urgent) + 1;
m_last_not_urgent = -1;
}
else {
++m_cur_home_index;
}
m_is_cur_urgent = false;
}
else {
if (m_last_not_urgent == -1) {
m_last_not_urgent = static_cast<int>(m_cur_home_index);
}
m_is_cur_urgent = true;
m_cur_home_index = m_next_urgent_index.top();
Log.info("Enter urgent situtation");
m_next_urgent_index.pop();
}
Log.info("To path", m_cur_home_index);
return true;
}
@@ -405,15 +710,38 @@ void asst::RoguelikeBattleTaskPlugin::clear()
m_opers_used = false;
m_pre_hp = 0;
m_homes.clear();
m_wait_melee.clear();
m_wait_medic.clear();
m_indeed_no_medic.clear();
m_melee_for_home_index.clear();
m_medic_for_home_index.clear();
decltype(m_next_urgent_index) empty_stack;
m_next_urgent_index.swap(empty_stack);
m_is_cur_urgent = false;
m_last_not_urgent = -1;
m_blacklist_location.clear();
decltype(m_key_kills) empty;
m_key_kills.swap(empty);
m_retreated_opers.clear();
decltype(m_need_clear_tiles) empty_heap;
m_need_clear_tiles.swap(empty_heap);
decltype(m_key_kills) empty_queue;
m_key_kills.swap(empty_queue);
m_cur_home_index = 0;
m_stage_name.clear();
m_side_tile_info.clear();
m_used_tiles.clear();
m_opers_in_field.clear();
m_kills = 0;
m_total_kills = 0;
m_use_dice = false;
m_stage_use_dice = true;
m_dice_image = cv::Mat();
m_first_deploy = true;
m_stop_melee_deploy_num = INT_MAX;
m_deploy_ranged_num = 0;
m_has_deployed_melee_num = 0;
m_has_deployed_ranged_num = 0;
m_has_finished_deploy_ranged = false;
m_last_cooling_count = 0;
for (auto& [key, status] : m_restore_status) {
m_status->set_number(key, status);
@@ -433,8 +761,8 @@ bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image)
MatchImageAnalyzer analyzer(image);
analyzer.set_task_info(task_ptr);
bool used = false;
for (auto& [loc, name] : m_used_tiles) {
std::string status_key = "RoguelikeSkillUsage-" + name;
for (auto& [loc, oper_pair] : m_used_tiles) {
std::string status_key = RuntimeStatus::RoguelikeSkillUsagePrefix + oper_pair.first;
auto usage = BattleSkillUsage::Possibly;
auto usage_opt = m_status->get_number(status_key);
if (usage_opt) {
@@ -548,34 +876,16 @@ std::vector<asst::Point> asst::RoguelikeBattleTaskPlugin::available_locations(Ba
LogTraceFunction;
auto buildable_type = Loc::All;
switch (role) {
case BattleRole::Medic:
case BattleRole::Support:
case BattleRole::Sniper:
case BattleRole::Caster:
const auto role_type = get_role_type(role);
switch (role_type) {
case BattleRoleType::Ranged:
buildable_type = Loc::Ranged;
break;
case BattleRole::Pioneer:
case BattleRole::Warrior:
case BattleRole::Tank:
case BattleRoleType::Melee:
buildable_type = Loc::Melee;
break;
case BattleRole::Special:
case BattleRole::Drone:
case BattleRoleType::Other:
default:
//// 特种和无人机,有的只能放地面,有的又只能放高台,不好判断
//// 笨办法,都试试,总有一次能成的
//{
// static Loc static_loc = Loc::Melee;
// loc = static_loc;
// if (static_loc == Loc::Melee) {
// static_loc = Loc::Ranged;
// }
// else {
// static_loc = Loc::Melee;
// }
//}
// 大部分无人机都是可以摆在地上的,摆烂了
buildable_type = Loc::Melee;
break;
}
@@ -591,107 +901,9 @@ std::vector<asst::Point> asst::RoguelikeBattleTaskPlugin::available_locations(Ba
return result;
}
asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::calc_best_plan(
const BattleRealTimeOper& oper)
asst::BattleAttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range(const BattleRealTimeOper& oper)
{
if (m_cur_home_index >= m_homes.size()) {
m_cur_home_index = 0;
}
Point home(5, 5); // 实在找不到家门了,随便取个点当家门用算了,一般是地图的中间
Point recommended_direction;
if (m_cur_home_index < m_homes.size()) {
const auto& rp_home = m_homes.at(m_cur_home_index);
home = rp_home.location;
static const std::unordered_map<BattleDeployDirection, Point> direction_map = {
{ BattleDeployDirection::Up, Point::up() }, { BattleDeployDirection::Down, Point::down() },
{ BattleDeployDirection::Left, Point::left() }, { BattleDeployDirection::Right, Point::right() },
{ BattleDeployDirection::None, Point() },
};
recommended_direction = direction_map.at(rp_home.direction);
}
auto comp_dist = [&](const Point& lhs, const Point& rhs) -> bool {
int lhs_y_dist = std::abs(lhs.y - home.y);
int lhs_dist = std::abs(lhs.x - home.x) + lhs_y_dist;
int rhs_y_dist = std::abs(rhs.y - home.y);
int rhs_dist = std::abs(rhs.x - home.x) + rhs_y_dist;
// 距离一样选择 x 轴上的,因为一般的地图都是横向的长方向
return lhs_dist == rhs_dist ? lhs_y_dist < rhs_y_dist : lhs_dist < rhs_dist;
};
std::vector<Point> available_loc = available_locations(oper.role);
// 把所有可用的点按距离排个序
ranges::sort(available_loc, comp_dist);
// 取距离最近的N个点计算分数。然后使用得分最高的点
constexpr int CalcPointCount = 4;
if (available_loc.size() > CalcPointCount) {
available_loc.erase(available_loc.begin() + CalcPointCount, available_loc.end());
}
Point best_location;
Point best_direction;
int max_score = INT_MIN;
const auto& near_loc = available_loc.at(0);
int min_dist = std::abs(near_loc.x - home.x) + std::abs(near_loc.y - home.y);
for (const auto& loc : available_loc) {
auto cur_result = calc_best_direction_and_score(loc, oper, recommended_direction);
// 离得远的要扣分
constexpr int DistWeights = -1050;
int extra_dist = std::abs(loc.x - home.x) + std::abs(loc.y - home.y) - min_dist;
int extra_dist_score = DistWeights * extra_dist;
if (oper.role == BattleRole::Medic) { // 医疗干员离得远无所谓
extra_dist_score = 0;
}
if (cur_result.second + extra_dist_score > max_score) {
max_score = cur_result.second + extra_dist_score;
best_location = loc;
best_direction = cur_result.first;
}
}
return { best_location, best_direction };
}
std::pair<asst::Point, int> asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score(
Point loc, const BattleRealTimeOper& oper, Point recommended_direction)
{
LogTraceFunction;
// 根据家门的方向计算一下大概的朝向
if (m_cur_home_index >= m_homes.size()) {
m_cur_home_index = 0;
}
Point home_loc(5, 5);
if (m_cur_home_index < m_homes.size()) {
home_loc = m_homes.at(m_cur_home_index).location;
}
auto sgn = [](const int& x) -> int {
if (x > 0) return 1;
if (x < 0) return -1;
return 0;
};
Point base_direction(0, 0);
if (loc.x == home_loc.x) {
base_direction.y = sgn(loc.y - home_loc.y);
}
else {
base_direction.x = sgn(loc.x - home_loc.x);
}
Point home_direction(-base_direction.x, -base_direction.y);
// 医疗反着算
if (oper.role == BattleRole::Medic) {
base_direction = -base_direction;
}
int64_t elite = m_status->get_number(RuntimeStatus::RoguelikeCharElitePrefix + oper.name).value_or(0);
// 按朝右算,后面根据方向做转换
BattleAttackRange right_attack_range = BattleData.get_range(oper.name, elite);
if (right_attack_range == BattleDataConfiger::EmptyRange) {
@@ -732,6 +944,137 @@ std::pair<asst::Point, int> asst::RoguelikeBattleTaskPlugin::calc_best_direction
}
}
return right_attack_range;
}
asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::calc_best_plan(
const BattleRealTimeOper& oper)
{
if (m_cur_home_index >= m_homes.size()) {
m_cur_home_index = 0;
}
Point home(5, 5); // 实在找不到家门了,随便取个点当家门用算了,一般是地图的中间
Point recommended_direction;
if (m_cur_home_index < m_homes.size()) {
const auto& rp_home = m_homes.at(m_cur_home_index);
home = rp_home.location;
static const std::unordered_map<BattleDeployDirection, Point> direction_map = {
{ BattleDeployDirection::Up, Point::up() }, { BattleDeployDirection::Down, Point::down() },
{ BattleDeployDirection::Left, Point::left() }, { BattleDeployDirection::Right, Point::right() },
{ BattleDeployDirection::None, Point() },
};
recommended_direction = direction_map.at(rp_home.direction);
}
auto comp_dist = [&](const Point& lhs, const Point& rhs) -> bool {
int lhs_y_dist = std::abs(lhs.y - home.y);
int lhs_dist = std::abs(lhs.x - home.x) + lhs_y_dist;
int rhs_y_dist = std::abs(rhs.y - home.y);
int rhs_dist = std::abs(rhs.x - home.x) + rhs_y_dist;
// 距离一样选择 x 轴上的,因为一般的地图都是横向的长方向
return lhs_dist == rhs_dist ? lhs_y_dist < rhs_y_dist : lhs_dist < rhs_dist;
};
std::vector<Point> available_loc = available_locations(oper.role);
// 把所有可用的点按距离排个序
ranges::sort(available_loc, comp_dist);
if (oper.name == "骰子") {
return { available_loc.back(), Point::zero() };
}
// 取距离最近的N个点计算分数。然后使用得分最高的点
constexpr int CalcPointCount = 4;
if (available_loc.size() > CalcPointCount) {
available_loc.erase(available_loc.begin() + CalcPointCount, available_loc.end());
}
Point best_location;
Point best_direction;
int max_score = INT_MIN;
const auto& near_loc = available_loc.at(0);
int min_dist = std::abs(near_loc.x - home.x) + std::abs(near_loc.y - home.y);
for (const auto& loc : available_loc) {
auto cur_result = calc_best_direction_and_score(loc, oper, recommended_direction);
// 离得远的要扣分
constexpr int DistWeights = -1050;
int extra_dist = std::abs(loc.x - home.x) + std::abs(loc.y - home.y) - min_dist;
int extra_dist_score = DistWeights * extra_dist;
if (oper.role == BattleRole::Medic) { // 医疗干员离得远无所谓
extra_dist_score = 0;
}
if (cur_result.second + extra_dist_score > max_score) {
max_score = cur_result.second + extra_dist_score;
best_location = loc;
best_direction = cur_result.first;
}
}
// 如果是医疗干员,判断覆盖范围内有无第一次放置的干员
if (oper.role == BattleRole::Medic) {
BattleAttackRange right_attack_range = get_attack_range(oper);
for (const Point& direction : { Point::right(), Point::up(), Point::left(), Point::down() }) {
if (direction == best_direction) break;
for (Point& point : right_attack_range)
point = { point.y, -point.x };
}
std::vector<size_t> contain_index;
for (const Point& relative_pos : right_attack_range) {
Point absolute_pos = best_location + relative_pos;
if (auto iter = m_melee_for_home_index.find(absolute_pos); iter != m_melee_for_home_index.end()) {
m_wait_medic[iter->second] = false;
contain_index.emplace_back(iter->second);
}
}
if (!contain_index.empty()) {
m_medic_for_home_index.emplace(best_location, std::move(contain_index));
}
}
return { best_location, best_direction };
}
std::pair<asst::Point, int> asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score(
Point loc, const BattleRealTimeOper& oper, Point recommended_direction)
{
LogTraceFunction;
// 根据家门的方向计算一下大概的朝向
if (m_cur_home_index >= m_homes.size()) {
m_cur_home_index = 0;
}
Point home_loc(5, 5);
if (m_cur_home_index < m_homes.size()) {
home_loc = m_homes.at(m_cur_home_index).location;
}
auto sgn = [](const int& x) -> int {
if (x > 0) return 1;
if (x < 0) return -1;
return 0;
};
Point base_direction(0, 0);
if (loc.x == home_loc.x) {
base_direction.y = sgn(loc.y - home_loc.y);
}
else {
base_direction.x = sgn(loc.x - home_loc.x);
}
Point home_direction(-base_direction.x, -base_direction.y);
// 医疗反着算
if (oper.role == BattleRole::Medic) {
base_direction = -base_direction;
}
// 按朝右算,后面根据方向做转换
BattleAttackRange right_attack_range = get_attack_range(oper);
int max_score = 0;
Point opt_direction;
@@ -762,7 +1105,8 @@ std::pair<asst::Point, int> asst::RoguelikeBattleTaskPlugin::calc_best_direction
switch (oper.role) {
case BattleRole::Medic:
if (m_used_tiles.contains(absolute_pos)) // 根据哪个方向上人多决定朝向哪
if (m_used_tiles.contains(absolute_pos) &&
m_used_tiles[absolute_pos].second != BattleRole::Drone) // 根据哪个方向上人多决定朝向哪
score += 10000;
if (auto iter = m_side_tile_info.find(absolute_pos); iter != m_side_tile_info.end())
score += TileKeyMedicWeights.at(iter->second.key);

View File

@@ -1,6 +1,7 @@
#pragma once
#include <queue>
#include <stack>
#include "AbstractTaskPlugin.h"
#include "ImageAnalyzer/BattleImageAnalyzer.h"
@@ -13,6 +14,7 @@ namespace asst
class RoguelikeBattleTaskPlugin : public AbstractTaskPlugin
{
using Loc = asst::TilePack::BuildableType;
using Time_Point = std::chrono::time_point<std::chrono::system_clock>;
public:
using AbstractTaskPlugin::AbstractTaskPlugin;
@@ -25,8 +27,12 @@ namespace asst
protected:
virtual bool _run() override;
enum class BattleRoleType {Melee, Ranged, Other};
BattleRoleType get_role_type(const BattleRole& role);
void wait_for_start();
bool get_stage_info();
bool battle_pause();
bool auto_battle();
void all_melee_retreat();
bool speed_up();
@@ -46,6 +52,7 @@ namespace asst
Point placed;
Point direction;
};
BattleAttackRange get_attack_range(const BattleRealTimeOper& oper);
DeployInfo calc_best_plan(const BattleRealTimeOper& oper);
// 计算摆放干员的朝向
@@ -53,20 +60,54 @@ namespace asst
std::pair<Point, int> calc_best_direction_and_score(Point loc, const BattleRealTimeOper& oper,
Point recommended_direction);
struct DroneTile
{
Time_Point placed_time;
Point placed_loc;
DroneTile(Time_Point placed_time, Point placed_loc)
{
this->placed_time = placed_time;
this->placed_loc = placed_loc;
}
bool operator<(const DroneTile& x) const { return x.placed_time < placed_time; }
};
bool m_opers_used = false;
bool m_is_cur_urgent = false;
bool m_stage_use_dice = true;
bool m_use_dice = false;
bool m_first_deploy = true;
int m_last_not_urgent = -1;
int m_pre_hp = 0;
int m_kills = 0;
int m_total_kills = 0;
int m_stop_melee_deploy_num = INT_MAX;
int m_has_deployed_melee_num = 0;
int m_deploy_ranged_num = 0;
int m_has_deployed_ranged_num = 0;
int m_last_cooling_count = 0;
bool m_has_finished_deploy_ranged = false;
size_t m_cur_home_index = 0;
cv::Mat m_dice_image;
std::array<BattleRole, 9> m_role_order;
std::unordered_map<Point, TilePack::TileInfo> m_side_tile_info;
std::unordered_map<Point, TilePack::TileInfo> m_normal_tile_info;
std::vector<ReplacementHome> m_homes;
std::vector<bool> m_wait_melee;
std::vector<bool> m_wait_medic;
std::vector<bool> m_indeed_no_medic;
std::unordered_map<Point, size_t> m_melee_for_home_index;
std::unordered_map<Point, std::vector<size_t>> m_medic_for_home_index;
std::stack<size_t> m_next_urgent_index;
std::unordered_set<Point> m_blacklist_location;
std::set<std::string> m_retreated_opers;
std::queue<int> m_key_kills;
size_t m_cur_home_index = 0;
std::unordered_map<Point, std::string> m_used_tiles;
std::unordered_map<Point, std::pair<std::string, BattleRole>> m_used_tiles;
std::unordered_map<std::string, Point> m_opers_in_field;
std::unordered_map<std::string, int64_t> m_restore_status;
std::priority_queue<DroneTile> m_need_clear_tiles;
std::string m_stage_name;
};
}
} // namespace asst

View File

@@ -131,6 +131,6 @@ bool asst::RoguelikeCustomStartTaskPlugin::hijack_core_char()
sleep(Task.get("Roguelike1Custom-HijackCoChar")->pre_delay);
m_status->set_str("RoguelikeCoreChar", char_name);
m_status->set_str(RuntimeStatus::RoguelikeCoreChar, char_name);
return true;
}

View File

@@ -37,6 +37,9 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
recruited = true;
};
bool team_full_without_rookie = m_status->get_number(RuntimeStatus::RoguelikeTeamFullWithoutRookie).value_or(0);
Log.info("team_full_without_rookie", team_full_without_rookie);
// 编队信息 (已有角色)
std::string str_chars_info =
m_status->get_str(RuntimeStatus::RoguelikeCharOverview).value_or(json::value().to_string());
@@ -112,22 +115,26 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
// 干员已在编队中,又出现在招募列表,只有待晋升和预备干员两种情况
if (recruit_info.is_alternate) {
// 预备干员可以重复招募
priority = recruit_info.recruit_priority;
priority = team_full_without_rookie ? recruit_info.recruit_priority_when_team_full
: recruit_info.recruit_priority;
}
else {
// 干员待晋升
priority = recruit_info.promote_priority;
priority = team_full_without_rookie ? recruit_info.promote_priority_when_team_full
: recruit_info.promote_priority;
}
}
else {
// 干员待招募,检查练度
if (oper_info.elite == 2) {
// TODO: 招募时已精二 (随机提升或对应分队) => promoted_priority
priority = recruit_info.recruit_priority;
priority = team_full_without_rookie ? recruit_info.recruit_priority_when_team_full
: recruit_info.recruit_priority;
}
else if (oper_info.elite == 1 && oper_info.level >= 50) {
// 精一50级以上
priority = recruit_info.recruit_priority;
priority = team_full_without_rookie ? recruit_info.recruit_priority_when_team_full
: recruit_info.recruit_priority;
}
else {
// 精一50级以下默认不招募
@@ -137,7 +144,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
}
// 优先级为0可能练度不够被忽略
if (priority == 0) {
if (priority <= 0) {
continue;
}
@@ -311,11 +318,11 @@ bool asst::RoguelikeRecruitTaskPlugin::check_core_char()
{
LogTraceFunction;
auto core_opt = m_status->get_str("RoguelikeCoreChar");
auto core_opt = m_status->get_str(RuntimeStatus::RoguelikeCoreChar);
if (!core_opt || core_opt->empty()) {
return false;
}
m_status->set_str("RoguelikeCoreChar", "");
m_status->set_str(RuntimeStatus::RoguelikeCoreChar, "");
return check_char(core_opt.value());
}

View File

@@ -33,6 +33,7 @@ bool asst::RoguelikeSkillSelectionTaskPlugin::_run()
return false;
}
bool has_rookie = false;
for (const auto& [name, skill_vec] : analyzer.get_result()) {
if (name.empty()) {
continue;
@@ -47,8 +48,20 @@ bool asst::RoguelikeSkillSelectionTaskPlugin::_run()
Log.info(__FUNCTION__, name, " select main skill:", oper_info.skill);
m_ctrler->click(skill_vec.at(oper_info.skill - 1));
}
m_status->set_number("RoguelikeSkillUsage-" + name, static_cast<int>(oper_info.skill_usage));
constexpr int RookieStd = 200;
if (oper_info.promote_priority < RookieStd) {
has_rookie = true;
}
m_status->set_number(RuntimeStatus::RoguelikeSkillUsagePrefix + name, static_cast<int>(oper_info.skill_usage));
}
if (analyzer.get_team_full() && !has_rookie) {
Log.info("Team full and no rookie");
m_status->set_number(RuntimeStatus::RoguelikeTeamFullWithoutRookie, 1);
}
else {
Log.info("Team not full or has rookie");
m_status->set_number(RuntimeStatus::RoguelikeTeamFullWithoutRookie, 0);
}
return true;
}

View File

@@ -119,6 +119,10 @@ namespace asst
std::vector<ReplacementHome> replacement_home;
std::unordered_set<Point> blacklist_location;
std::vector<int> key_kills;
std::array<BattleRole, 9> role_order;
bool use_dice_stage = true;
int stop_melee_deploy_num = INT_MAX;
int deploy_ranged_num = 0;
};
struct BattleCharData