fix: 修复肉鸽阵容完备度中单个干员重复出现于多个干员组的问题 (#10806)

* fix: 修复肉鸽阵容完备度中单个干员重复出现于多个干员组的问题

* style: clang
This commit is contained in:
晓丶梦丶仁
2024-10-21 02:18:34 +08:00
committed by GitHub
parent 5e5b18edea
commit e24b5e18be
6 changed files with 439 additions and 337 deletions

View File

@@ -6,50 +6,94 @@
#include "Utils/Logger.hpp"
const asst::RoguelikeOperInfo& asst::RoguelikeRecruitConfig::get_oper_info(const std::string& theme,
const std::string& name) noexcept
const asst::RoguelikeOperInfo&
asst::RoguelikeRecruitConfig::get_oper_info(const std::string& theme, const std::string& oper_name) noexcept
{
auto& opers = m_all_opers.at(theme);
if (opers.contains(name)) {
return opers.at(name);
if (opers.contains(oper_name)) {
return opers.at(oper_name);
}
else {
RoguelikeOperInfo info;
info.name = name;
info.group_id = get_group_id(theme, name);
opers.emplace(name, std::move(info));
return opers.at(name);
info.name = oper_name;
info.group_id = get_group_ids_of_oper(theme, oper_name);
opers.emplace(oper_name, std::move(info));
return opers.at(oper_name);
}
}
const std::vector<std::string> asst::RoguelikeRecruitConfig::get_group_info(const std::string& theme) const noexcept
const asst::RoguelikeGroupInfo&
asst::RoguelikeRecruitConfig::get_group_info(const std::string& theme, const std::string& group_name) noexcept
{
auto& groups = m_all_groups.at(theme);
if (groups.contains(group_name)) {
return groups.at(group_name);
}
else {
RoguelikeGroupInfo info;
info.name = group_name;
groups.emplace(group_name, std::move(info));
return groups.at(group_name);
}
}
const std::vector<std::string> asst::RoguelikeRecruitConfig::get_group_names(const std::string& theme) const noexcept
{
return m_oper_groups.at(theme);
}
const std::vector<asst::RecruitPriorityOffset> asst::RoguelikeRecruitConfig::get_team_complete_info(
const std::string& theme) const noexcept
const std::vector<asst::RecruitPriorityOffset>
asst::RoguelikeRecruitConfig::get_team_complete_info(const std::string& theme) const noexcept
{
return m_team_complete_comdition.at(theme);
return m_team_complete_condition.at(theme);
}
std::vector<int> asst::RoguelikeRecruitConfig::get_group_id(const std::string& theme,
const std::string& name) const noexcept
std::vector<int> asst::RoguelikeRecruitConfig::get_group_ids_of_oper(
const std::string& theme,
const std::string& oper_name) const noexcept
{
auto& opers = m_all_opers.at(theme);
if (auto find_iter = opers.find(name); find_iter != opers.cend()) {
if (auto find_iter = opers.find(oper_name); find_iter != opers.cend()) {
return find_iter->second.group_id;
}
else {
const auto& role = BattleData.get_role(name);
const auto& role = BattleData.get_role(oper_name);
if (role != battle::Role::Pioneer && role != battle::Role::Tank && role != battle::Role::Warrior &&
role != battle::Role::Special)
role != battle::Role::Special) {
return { static_cast<int>(m_oper_groups.at(theme).size()) - 2 };
else
}
else {
return { static_cast<int>(m_oper_groups.at(theme).size()) - 1 };
}
}
}
int asst::RoguelikeRecruitConfig::get_group_id_from_name(
const std::string& theme,
const std::string& group_name) noexcept
{
auto& groups = m_all_groups.at(theme);
if (groups.contains(group_name)) {
return groups.at(group_name).id;
}
else {
RoguelikeGroupInfo info;
info.name = group_name;
groups.emplace(group_name, std::move(info));
return groups.at(group_name).id;
}
}
const std::string
asst::RoguelikeRecruitConfig::get_group_name_from_id(const std::string& theme, const int group_id) const noexcept
{
auto it = ranges::find_if(m_all_groups.at(theme), [&](const auto& group) { return group.second.id == group_id; });
if (it != m_all_groups.at(theme).end()) {
return it->first;
}
return "";
}
bool asst::RoguelikeRecruitConfig::parse(const json::value& json)
{
LogTraceFunction;
@@ -61,52 +105,60 @@ bool asst::RoguelikeRecruitConfig::parse(const json::value& json)
int group_id = 0;
//{"name":干员组名, "opers":组内干员组成的array}
for (const auto& group_json : json.at("priority").as_array()) {
m_oper_groups[theme].emplace_back(group_json.at("name").as_string());
const std::string group_name = group_json.at("name").as_string();
m_oper_groups[theme].emplace_back(group_name);
// 干员组信息
RoguelikeGroupInfo group_info;
group_info.name = group_name;
group_info.id = group_id;
// 干员在组内的顺序,int
int order_in_group = 0;
// 遍历"opers"数组
for (const auto& oper_info : group_json.at("opers").as_array()) {
std::string name = oper_info.at("name").as_string();
for (const auto& oper_json : group_json.at("opers").as_array()) {
std::string name = oper_json.at("name").as_string();
group_info.opers.emplace(name);
// 肉鸽干员招募信息
RoguelikeOperInfo info;
RoguelikeOperInfo oper_info;
auto iter = m_all_opers[theme].find(name);
if (iter != m_all_opers[theme].end()) {
// 干员已存在时仅做更新
info = iter->second;
oper_info = iter->second;
}
info.name = name;
info.group_id.push_back(group_id);
info.order_in_group[group_id] = order_in_group;
info.recruit_priority = oper_info.get("recruit_priority", info.recruit_priority);
info.promote_priority = oper_info.get("promote_priority", info.promote_priority);
info.is_alternate = oper_info.get("is_alternate", info.is_alternate);
info.skill = oper_info.get("skill", info.skill);
info.alternate_skill = oper_info.get("alternate_skill", info.alternate_skill);
info.skill_usage =
static_cast<battle::SkillUsage>(oper_info.get("skill_usage", static_cast<int>(info.skill_usage)));
info.skill_times = oper_info.get("skill_times", info.skill_times);
info.alternate_skill_usage = static_cast<battle::SkillUsage>(
oper_info.get("alternate_skill_usage", static_cast<int>(info.alternate_skill_usage)));
info.alternate_skill_times = oper_info.get("alternate_skill_times", info.alternate_skill_times);
info.is_key = oper_info.get("is_key", info.is_key);
info.is_start = oper_info.get("is_start", info.is_start);
info.auto_retreat = oper_info.get("auto_retreat", info.auto_retreat);
oper_info.name = name;
oper_info.group_id.push_back(group_id);
oper_info.order_in_group[group_id] = order_in_group;
oper_info.recruit_priority = oper_json.get("recruit_priority", oper_info.recruit_priority);
oper_info.promote_priority = oper_json.get("promote_priority", oper_info.promote_priority);
oper_info.is_alternate = oper_json.get("is_alternate", oper_info.is_alternate);
oper_info.skill = oper_json.get("skill", oper_info.skill);
oper_info.alternate_skill = oper_json.get("alternate_skill", oper_info.alternate_skill);
oper_info.skill_usage =
static_cast<battle::SkillUsage>(oper_json.get("skill_usage", static_cast<int>(oper_info.skill_usage)));
oper_info.skill_times = oper_json.get("skill_times", oper_info.skill_times);
oper_info.alternate_skill_usage = static_cast<battle::SkillUsage>(
oper_json.get("alternate_skill_usage", static_cast<int>(oper_info.alternate_skill_usage)));
oper_info.alternate_skill_times = oper_json.get("alternate_skill_times", oper_info.alternate_skill_times);
oper_info.is_key = oper_json.get("is_key", oper_info.is_key);
oper_info.is_start = oper_json.get("is_start", oper_info.is_start);
oper_info.auto_retreat = oper_json.get("auto_retreat", oper_info.auto_retreat);
// __________________will-be-removed-begin__________________
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 + 300);
if (auto opt = oper_info.find<json::array>("recruit_priority_offset")) {
oper_info.recruit_priority_when_team_full =
oper_json.get("recruit_priority_when_team_full", oper_info.recruit_priority - 100);
oper_info.promote_priority_when_team_full =
oper_json.get("promote_priority_when_team_full", oper_info.promote_priority + 300);
if (auto opt = oper_json.find<json::array>("recruit_priority_offset")) {
for (const auto& offset : opt.value()) {
std::pair<int, int> offset_pair = std::make_pair(offset[0].as_integer(), offset[1].as_integer());
info.recruit_priority_offset.emplace_back(offset_pair);
oper_info.recruit_priority_offset.emplace_back(offset_pair);
}
}
info.offset_melee = oper_info.get("offset_melee", false);
oper_info.offset_melee = oper_json.get("offset_melee", false);
// __________________will-be-removed-end__________________
if (auto opt = oper_info.find<json::array>("recruit_priority_offsets")) {
if (auto opt = oper_json.find<json::array>("recruit_priority_offsets")) {
for (const auto& offset_json : opt.value()) {
RecruitPriorityOffset offset;
for (const auto& group : offset_json.at("groups").as_array()) {
@@ -115,40 +167,63 @@ bool asst::RoguelikeRecruitConfig::parse(const json::value& json)
offset.threshold = offset_json.get("threshold", 0);
offset.is_less = offset_json.get("is_less", false);
offset.offset = offset_json.get("offset", 0);
info.recruit_priority_offsets.emplace_back(std::move(offset));
oper_info.recruit_priority_offsets.emplace_back(std::move(offset));
}
}
if (auto opt = oper_info.find<json::array>("collection_priority_offsets")) {
if (auto opt = oper_json.find<json::array>("collection_priority_offsets")) {
for (const auto& offset_json : opt.value()) {
CollectionPriorityOffset offset;
offset.collection = offset_json.get("collection", "");
offset.offset = offset_json.get("offset", 0);
info.collection_priority_offsets.emplace_back(std::move(offset));
oper_info.collection_priority_offsets.emplace_back(std::move(offset));
}
}
m_all_opers[theme][name] = std::move(info);
m_all_opers[theme][name] = std::move(oper_info);
order_in_group++;
}
m_all_groups[theme][group_name] = std::move(group_info);
group_id++;
}
// 对所有存在 offset 组的干员进行初始化
for (auto& [oper_name, oper_info] : m_all_opers[theme]) { // 所有干员
if (oper_info.recruit_priority_offsets.empty()) {
continue;
}
for (auto& offset : oper_info.recruit_priority_offsets) { // 干员的所有 offset 策略组
for (auto& group : offset.groups) { // 策略组内的每个干员组
offset.opers.insert( // 向当前策略组计入这个干员组的无重复干员
get_group_info(theme, group).opers.begin(),
get_group_info(theme, group).opers.end());
}
}
}
for (const auto& condition_json : json.at("team_complete_condition").as_array()) {
RecruitPriorityOffset condition;
for (const auto& group : condition_json.at("groups").as_array()) {
condition.groups.emplace_back(group.as_string());
std::string group_name = group.as_string();
condition.groups.emplace_back(group_name);
condition.opers.insert( // 向当前策略组计入这个干员组的无重复干员
get_group_info(theme, group_name).opers.begin(),
get_group_info(theme, group_name).opers.end());
}
condition.threshold = condition_json.at("threshold").as_integer();
m_team_complete_comdition[theme].emplace_back(std::move(condition));
m_team_complete_require[theme] += condition.threshold;
m_team_complete_condition[theme].emplace_back(std::move(condition));
}
return true;
}
void asst::RoguelikeRecruitConfig::clear(const std::string& key)
void asst::RoguelikeRecruitConfig::clear(const std::string& theme)
{
m_all_opers.erase(key);
m_oper_groups.erase(key);
m_team_complete_comdition.erase(key);
m_all_opers.erase(theme);
m_all_groups.erase(theme);
m_oper_groups.erase(theme);
m_team_complete_condition.erase(theme);
m_team_complete_require.erase(theme);
}

View File

@@ -11,67 +11,97 @@
namespace asst
{
// 招募priority影响因子
struct RecruitPriorityOffset
// 招募 priority 影响因子
struct RecruitPriorityOffset
{
std::vector<std::string> groups; // 造成优先级改变的所有干员组
std::unordered_set<std::string> opers; // 所有干员组的所有无重复干员
int threshold = 0; // 已拥有组内干员数量阈值,达到此阈值时应用优先级改变
bool is_less = false; // 高于阈值时改变还是低于阈值时改变
int offset = 0; // 优先级改变的大小
};
// 收藏品 priority 影响因子
struct CollectionPriorityOffset
{
std::string collection; // 造成优先级改变的藏品名称
int offset = 0; // 优先级改变的大小
};
// 干员信息,战斗相关
struct RoguelikeOperInfo
{
std::string name; // 干员名
std::vector<int> group_id = {}; // 干员组 id,允许一个干员存在于多个干员组
std::unordered_map<int, int> order_in_group; // 干员在干员组内的顺序 干员组 id:干员组内顺序
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)
std::vector<std::pair<int, int>> recruit_priority_offset; // [deprecated]
bool offset_melee = false; // [deprecated]
bool is_key = false; // 是否为核心干员
bool is_start = false; // 是否为开局干员
std::vector<RecruitPriorityOffset> recruit_priority_offsets; // 招募 priority 影响因子
std::vector<CollectionPriorityOffset> collection_priority_offsets; // 收藏品 priority 影响因子
bool is_alternate = false; // 是否后备干员 (允许重复招募、划到后备干员时不再往右划动)
int skill = 0; // 使用几技能
int alternate_skill = 0; // 当没有指定技能时使用的备选技能一般是6星干员未精二且精二后使用3技能时才需要指定
battle::SkillUsage skill_usage = battle::SkillUsage::Possibly; // 技能使用模式
int skill_times = 1; // 技能使用次数
battle::SkillUsage alternate_skill_usage = battle::SkillUsage::Possibly; // 备选技能使用模式
int alternate_skill_times = 1; // 备选技能使用次数
int auto_retreat = 0; // 部署几秒后自动撤退
};
// 干员组信息,招募相关
struct RoguelikeGroupInfo
{
std::string name; // 干员组名
std::unordered_set<std::string> opers; // 干员组的所有干员名(用于查重)
int id = -1; // 干员组 id
// 干员组的所有干员,后续可以基于此重构干员信息存储、作战部署
std::vector<std::shared_ptr<RoguelikeOperInfo>> opers_in_order; // 未初始化,暂不可用
};
class RoguelikeRecruitConfig final : public SingletonHolder<RoguelikeRecruitConfig>, public AbstractConfig
{
public:
virtual ~RoguelikeRecruitConfig() override = default;
const RoguelikeOperInfo& get_oper_info(const std::string& theme, const std::string& oper_name) noexcept;
const RoguelikeGroupInfo& get_group_info(const std::string& theme, const std::string& group_name) noexcept;
const std::vector<RecruitPriorityOffset> get_team_complete_info(const std::string& theme) const noexcept;
std::vector<int> get_group_ids_of_oper(const std::string& theme, const std::string& oper_name)
const noexcept; // renamed from "get_group_id"
const std::vector<std::string> get_group_names(const std::string& theme)
const noexcept; // 获取该肉鸽内用到的干员组[干员组1,干员组2, ...], renamed from "get_group_info"
int get_group_id_from_name(
const std::string& theme,
const std::string& group_name) noexcept; // 用干员组名获取干员组 id
const std::string
get_group_name_from_id(const std::string& theme, const int group_id) const noexcept; // 用干员组 id 获取干员组名
int get_team_complete_require(const std::string& theme) noexcept
{
std::vector<std::string> groups; // 造成优先级改变的干员组
int threshold = 0; // 已拥有组内干员数量阈值,达到此阈值时应用优先级改变
bool is_less = false; // 高于阈值时改变还是低于阈值时改变
int offset = 0; // 优先级改变的大小
};
return m_team_complete_require[theme];
} // 所有完备度要求的数值总和
// 收藏品priority影响因子
struct CollectionPriorityOffset
{
std::string collection; // 造成优先级改变的藏品名称
int offset = 0; // 优先级改变的大小
};
// 干员信息,战斗相关
struct RoguelikeOperInfo
{
std::string name;
std::vector<int> group_id = {}; // 干员组id,允许一个干员存在于多个干员组
std::unordered_map<int, int> order_in_group; // 干员在干员组内的顺序 干员组id:干员组内顺序
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)
std::vector<std::pair<int, int>> recruit_priority_offset; // [deprecated]
bool offset_melee = false; // [deprecated]
bool is_key = false; // 是否为核心干员
bool is_start = false; // 是否为开局干员
std::vector<RecruitPriorityOffset> recruit_priority_offsets; // 招募priority影响因子
std::vector<CollectionPriorityOffset> collection_priority_offsets; // 收藏品priority影响因子
bool is_alternate = false; // 是否后备干员 (允许重复招募、划到后备干员时不再往右划动)
int skill = 0; // 使用几技能
int alternate_skill = 0; // 当没有指定技能时使用的备选技能一般是6星干员未精二且精二后使用3技能时才需要指定
battle::SkillUsage skill_usage = battle::SkillUsage::Possibly; // 技能使用模式
int skill_times = 1; // 技能使用次数
battle::SkillUsage alternate_skill_usage = battle::SkillUsage::Possibly; // 备选技能使用模式
int alternate_skill_times = 1; // 备选技能使用次数
int auto_retreat = 0; // 部署几秒后自动撤退
};
protected:
virtual bool parse(const json::value& json) override;
class RoguelikeRecruitConfig final : public SingletonHolder<RoguelikeRecruitConfig>, public AbstractConfig
{
public:
virtual ~RoguelikeRecruitConfig() override = default;
void clear(const std::string& theme);
const RoguelikeOperInfo& get_oper_info(const std::string& theme, const std::string& name) noexcept;
// 获取该肉鸽内用到的干员组[干员组1,干员组2,...]
const std::vector<std::string> get_group_info(const std::string& theme) const noexcept;
const std::vector<RecruitPriorityOffset> get_team_complete_info(const std::string& theme) const noexcept;
std::vector<int> get_group_id(const std::string& theme, const std::string& name) const noexcept;
std::unordered_map<std::string, std::unordered_map<std::string, RoguelikeOperInfo>>
m_all_opers; // <theme, <oper_name, oper_info>>
std::unordered_map<std::string, std::unordered_map<std::string, RoguelikeGroupInfo>>
m_all_groups; // <theme, <group_name, group_info>>
std::unordered_map<std::string, std::vector<std::string>>
m_oper_groups; // <theme, group_names> 保留此变量以保证可能用到的有序性
std::unordered_map<std::string, std::vector<RecruitPriorityOffset>> m_team_complete_condition; // <theme, offsets>
std::unordered_map<std::string, int> m_team_complete_require; // <theme, require> 阵容完备所需干员总数
};
protected:
virtual bool parse(const json::value& json) override;
void clear(const std::string& theme);
std::unordered_map<std::string, std::unordered_map<std::string, RoguelikeOperInfo>> m_all_opers;
std::unordered_map<std::string, std::vector<std::string>> m_oper_groups;
std::unordered_map<std::string, std::vector<RecruitPriorityOffset>> m_team_complete_comdition;
};
inline static auto& RoguelikeRecruit = RoguelikeRecruitConfig::get_instance();
inline static auto& RoguelikeRecruit = RoguelikeRecruitConfig::get_instance();
}

View File

@@ -37,8 +37,7 @@ asst::RoguelikeBattleTaskPlugin::RoguelikeBattleTaskPlugin(
bool asst::RoguelikeBattleTaskPlugin::verify(AsstMsg msg, const json::value& details) const
{
if (msg != AsstMsg::SubTaskCompleted
|| details.get("subtask", std::string()) != "ProcessTask") {
if (msg != AsstMsg::SubTaskCompleted || details.get("subtask", std::string()) != "ProcessTask") {
return false;
}
@@ -108,8 +107,7 @@ void asst::RoguelikeBattleTaskPlugin::wait_until_start_button_clicked()
.run();
}
std::string
asst::RoguelikeBattleTaskPlugin::oper_name_in_config(const battle::DeploymentOper& oper) const
std::string asst::RoguelikeBattleTaskPlugin::oper_name_in_config(const battle::DeploymentOper& oper) const
{
if (oper.role == Role::Warrior && oper.name == "阿米娅") {
return "阿米娅-WARRIOR"; // 在 BattleData.json 中有
@@ -192,7 +190,8 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info()
if (opt == std::nullopt) {
opt = RoguelikeCopilot.get_stage_data(m_stage_name);
}
} else {
}
else {
opt = RoguelikeCopilot.get_stage_data(m_stage_name);
}
@@ -202,10 +201,9 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info()
m_blacklist_location = opt->blacklist_location;
m_role_order = opt->role_order;
m_force_deploy_direction = opt->force_deploy_direction;
m_force_air_defense =
AirDefenseData { .stop_blocking_deploy_num = opt->stop_deploy_blocking_num,
.deploy_air_defense_num = opt->force_deploy_air_defense_num,
.ban_medic = opt->force_ban_medic };
m_force_air_defense = AirDefenseData { .stop_blocking_deploy_num = opt->stop_deploy_blocking_num,
.deploy_air_defense_num = opt->force_deploy_air_defense_num,
.ban_medic = opt->force_ban_medic };
m_deploy_plan = opt->deploy_plan;
m_retreat_plan = opt->retreat_plan;
}
@@ -224,10 +222,9 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info()
}
else {
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);
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);
}
@@ -249,14 +246,13 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info()
return true;
}
asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_oper_location_type(
const battle::DeploymentOper& oper) const
asst::battle::LocationType
asst::RoguelikeBattleTaskPlugin::get_oper_location_type(const battle::DeploymentOper& oper) const
{
return BattleData.get_location_type(oper_name_in_config(oper));
}
asst::battle::OperPosition
asst::RoguelikeBattleTaskPlugin::get_role_position(const battle::Role& role) const
asst::battle::OperPosition asst::RoguelikeBattleTaskPlugin::get_role_position(const battle::Role& role) const
{
switch (role) {
case battle::Role::Support:
@@ -270,8 +266,7 @@ asst::battle::OperPosition
return battle::OperPosition::Blocking;
break;
case battle::Role::Medic:
return m_force_air_defense.ban_medic ? battle::OperPosition::None
: battle::OperPosition::AirDefense;
return m_force_air_defense.ban_medic ? battle::OperPosition::None : battle::OperPosition::AirDefense;
break;
case battle::Role::Special:
case battle::Role::Drone:
@@ -285,9 +280,7 @@ void asst::RoguelikeBattleTaskPlugin::cache_oper_elite_status()
{
const auto& oper_list = m_config->get_oper();
for (const auto& oper : m_cur_deployment_opers) {
m_oper_elite.emplace(
oper.name,
oper_list.contains(oper.name) ? oper_list.at(oper.name).elite : 0);
m_oper_elite.emplace(oper.name, oper_list.contains(oper.name) ? oper_list.at(oper.name).elite : 0);
}
}
@@ -318,9 +311,7 @@ void asst::RoguelikeBattleTaskPlugin::set_position_full(const Point& loc, bool f
}
}
void asst::RoguelikeBattleTaskPlugin::set_position_full(
const battle::DeploymentOper& oper,
bool full)
void asst::RoguelikeBattleTaskPlugin::set_position_full(const battle::DeploymentOper& oper, bool full)
{
set_position_full(get_oper_location_type(oper), full);
}
@@ -369,7 +360,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy()
// 构造当前地图的部署指令列表
std::vector<DeployPlanInfo> deploy_plan_list;
// 获取当前肉鸽的分组信息[干员组1名称,干员组2名称,...]
const auto& groups = RoguelikeRecruit.get_group_info(m_config->get_theme());
const auto& groups = RoguelikeRecruit.get_group_names(m_config->get_theme());
// 获取当前肉鸽的分组内排名信息
// const auto& group_rank = RoguelikeRecruit.get_group_rank(rogue_theme);
for (const auto& oper : m_cur_deployment_opers) {
@@ -383,8 +374,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy()
// 获取招募信息
const auto& recruit_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), oper_name);
// 获取会用到该干员的干员组[干员组1序号,干员组2序号,...]
std::vector<int> group_ids =
RoguelikeRecruit.get_group_id(m_config->get_theme(), oper_name);
std::vector<int> group_ids = RoguelikeRecruit.get_group_ids_of_oper(m_config->get_theme(), oper_name);
for (const auto& group_id : group_ids) {
// 当前干员组名,string类型
@@ -393,12 +383,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy()
if (m_deploy_plan.contains(group_name)) {
for (const auto& info : m_deploy_plan[group_name]) {
if (m_kills < info.kill_lower_bound || m_kills > info.kill_upper_bound) {
Log.debug(
" deploy info",
oper.name,
"in group",
group_name,
"is waiting.");
Log.debug(" deploy info", oper.name, "in group", group_name, "is waiting.");
is_success = true; // 如果发现了待命干员此函数最终返回true
continue;
}
@@ -409,9 +394,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy()
// deploy_plan.oper_order_in_group = recruit_info.order_in_group.at(group_id);
deploy_plan.oper_order_in_group =
recruit_info.order_in_group.empty()
? INT_MAX
: recruit_info.order_in_group.at(group_id);
recruit_info.order_in_group.empty() ? INT_MAX : recruit_info.order_in_group.at(group_id);
deploy_plan.rank = info.rank;
deploy_plan.placed = info.location;
deploy_plan.direction = info.direction;
@@ -434,8 +417,8 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy()
}
std::sort(deploy_plan_list.begin(), deploy_plan_list.end());
for (const auto& deploy_plan : deploy_plan_list) {
if (!m_used_tiles.contains(deploy_plan.placed)
&& !m_blacklist_location.contains(deploy_plan.placed) /* 判断该位置是否已被占据 */) {
if (!m_used_tiles.contains(deploy_plan.placed) &&
!m_blacklist_location.contains(deploy_plan.placed) /* 判断该位置是否已被占据 */) {
if (auto oper_it = ranges::find_if(
m_cur_deployment_opers,
[&](const auto& oper) { return oper.name == deploy_plan.oper_name; });
@@ -455,8 +438,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy()
auto deployed_time = std::chrono::steady_clock::now();
m_deployed_time.insert_or_assign(deploy_plan.oper_name, deployed_time);
// 获取技能用法和使用次数
const auto& oper_info =
RoguelikeRecruit.get_oper_info(m_config->get_theme(), deploy_plan.oper_name);
const auto& oper_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), deploy_plan.oper_name);
m_skill_usage[deploy_plan.oper_name] = oper_info.skill_usage;
m_skill_times[deploy_plan.oper_name] = oper_info.skill_times;
Log.trace(" best deploy is", deploy_plan.oper_name, "with rank", deploy_plan.rank);
@@ -476,8 +458,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_once()
std::chrono::milliseconds(Config.get_options().roguelike_fight_screencap_interval);
// prevent our program from consuming too much CPU
if (const auto now = std::chrono::steady_clock::now();
prev_frame_time > now - min_frame_interval) [[unlikely]] {
if (const auto now = std::chrono::steady_clock::now(); prev_frame_time > now - min_frame_interval) [[unlikely]] {
Log.debug("Sleeping for framerate limit");
std::this_thread::sleep_for(min_frame_interval - (now - prev_frame_time));
}
@@ -502,8 +483,8 @@ bool asst::RoguelikeBattleTaskPlugin::do_once()
const auto& oper_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), name);
auto iter = m_deployed_time.find(name);
if (iter != m_deployed_time.end() && oper_info.auto_retreat > 0) {
if (std::chrono::steady_clock::now() - m_deployed_time.at(name)
>= oper_info.auto_retreat * std::chrono::seconds(1)) {
if (std::chrono::steady_clock::now() - m_deployed_time.at(name) >=
oper_info.auto_retreat * std::chrono::seconds(1)) {
// 时间到了就撤退
asst::BattleHelper::retreat_oper(name);
m_deployed_time.erase(name);
@@ -605,8 +586,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_once()
auto deployed_time = std::chrono::steady_clock::now();
m_deployed_time.insert_or_assign(best_oper.name, deployed_time);
// 获取技能用法和使用次数
const auto& oper_info =
RoguelikeRecruit.get_oper_info(m_config->get_theme(), best_oper.name);
const auto& oper_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), best_oper.name);
m_skill_usage[best_oper.name] = oper_info.skill_usage;
m_skill_times[best_oper.name] = oper_info.skill_times;
@@ -633,8 +613,7 @@ void asst::RoguelikeBattleTaskPlugin::postproc_of_deployment_conditions(
std::vector<size_t> contain_index;
for (const Point& relative_pos : get_attack_range(oper, direction)) {
Point absolute_pos = placed_loc + relative_pos;
if (auto iter = m_blocking_for_home_index.find(absolute_pos);
iter != m_blocking_for_home_index.end()) {
if (auto iter = m_blocking_for_home_index.find(absolute_pos); iter != m_blocking_for_home_index.end()) {
m_homes_status[iter->second].wait_medic = false;
contain_index.emplace_back(iter->second);
}
@@ -659,8 +638,7 @@ void asst::RoguelikeBattleTaskPlugin::postproc_of_deployment_conditions(
case OperPosition::AirDefense:
if (!m_force_air_defense.has_finished_deploy_air_defense) {
m_force_air_defense.has_deployed_air_defense_num++;
if (m_force_air_defense.has_deployed_air_defense_num
>= m_force_air_defense.deploy_air_defense_num) {
if (m_force_air_defense.has_deployed_air_defense_num >= m_force_air_defense.deploy_air_defense_num) {
m_force_air_defense.has_finished_deploy_air_defense = true;
Log.info("FORCE RANGED OPER DEPLOY END");
}
@@ -670,8 +648,7 @@ void asst::RoguelikeBattleTaskPlugin::postproc_of_deployment_conditions(
break;
}
if (m_force_air_defense.has_finished_deploy_air_defense
&& position != OperPosition::AirDefense) {
if (m_force_air_defense.has_finished_deploy_air_defense && position != OperPosition::AirDefense) {
Log.info("FORCE RANGED OPER DEPLOY END");
m_force_air_defense.has_finished_deploy_air_defense = true;
}
@@ -684,12 +661,7 @@ void asst::RoguelikeBattleTaskPlugin::check_drone_tiles()
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");
Log.info("Drone at location (", placed_loc.x, ",", placed_loc.y, ") is recognized as retreated");
set_position_full(placed_loc, false);
m_battlefield_opers.erase(iter->second);
m_used_tiles.erase(iter);
@@ -783,20 +755,12 @@ std::optional<asst::battle::DeploymentOper> asst::RoguelikeBattleTaskPlugin::cal
}
}
bool use_air_defense = has_air_defense && !m_force_air_defense.has_finished_deploy_air_defense
&& m_force_air_defense.has_deployed_blocking_num
>= m_force_air_defense.stop_blocking_deploy_num
&& !m_ranged_full;
bool use_blocking =
has_blocking && m_homes_status[m_cur_home_index].wait_blocking && !m_melee_full;
bool use_air_defense =
has_air_defense && !m_force_air_defense.has_finished_deploy_air_defense &&
m_force_air_defense.has_deployed_blocking_num >= m_force_air_defense.stop_blocking_deploy_num && !m_ranged_full;
bool use_blocking = has_blocking && m_homes_status[m_cur_home_index].wait_blocking && !m_melee_full;
bool use_medic = has_medic && m_homes_status[m_cur_home_index].wait_medic && !m_ranged_full;
Log.trace(
"use_air_defense",
use_air_defense,
", use_blocking",
use_blocking,
", use_medic",
use_medic);
Log.trace("use_air_defense", use_air_defense, ", use_blocking", use_blocking, ", use_medic", use_medic);
std::vector<DeploymentOper> cur_available;
for (const auto& oper : m_cur_deployment_opers) {
@@ -805,9 +769,7 @@ std::optional<asst::battle::DeploymentOper> asst::RoguelikeBattleTaskPlugin::cal
}
}
// 费用高的优先用,放前面
ranges::sort(cur_available, [](const auto& lhs, const auto& rhs) {
return lhs.cost > rhs.cost;
});
ranges::sort(cur_available, [](const auto& lhs, const auto& rhs) { return lhs.cost > rhs.cost; });
DeploymentOper best_oper;
@@ -889,8 +851,7 @@ void asst::RoguelikeBattleTaskPlugin::clear()
m_oper_elite.clear();
}
std::vector<asst::Point>
asst::RoguelikeBattleTaskPlugin::available_locations(const DeploymentOper& oper) const
std::vector<asst::Point> asst::RoguelikeBattleTaskPlugin::available_locations(const DeploymentOper& oper) const
{
auto type = get_oper_location_type(oper);
if (type == LocationType::Invalid || type == LocationType::None) {
@@ -899,18 +860,15 @@ std::vector<asst::Point>
return available_locations(type);
}
std::vector<asst::Point>
asst::RoguelikeBattleTaskPlugin::available_locations(battle::LocationType type) const
std::vector<asst::Point> asst::RoguelikeBattleTaskPlugin::available_locations(battle::LocationType type) const
{
std::vector<Point> result;
for (const auto& [loc, tile] : m_normal_tile_info) {
bool position_matched =
tile.buildable == type || tile.buildable == battle::LocationType::All;
position_matched |= (type == battle::LocationType::All)
&& (tile.buildable == battle::LocationType::Melee
|| tile.buildable == battle::LocationType::Ranged);
if (position_matched && tile.key != TilePack::TileKey::DeepSea
&& // 水上要先放板子才能放人,肉鸽里也没板子,那就当作不可放置
bool position_matched = tile.buildable == type || tile.buildable == battle::LocationType::All;
position_matched |= (type == battle::LocationType::All) && (tile.buildable == battle::LocationType::Melee ||
tile.buildable == battle::LocationType::Ranged);
if (position_matched &&
tile.key != TilePack::TileKey::DeepSea && // 水上要先放板子才能放人,肉鸽里也没板子,那就当作不可放置
!m_used_tiles.contains(loc) && !m_blacklist_location.contains(loc)) {
result.emplace_back(loc);
}
@@ -966,10 +924,8 @@ asst::battle::AttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range(
}
}
for (auto cur_direction : { DeployDirection::Right,
DeployDirection::Up,
DeployDirection::Left,
DeployDirection::Down }) {
for (auto cur_direction :
{ DeployDirection::Right, DeployDirection::Up, DeployDirection::Left, DeployDirection::Down }) {
if (cur_direction == direction) {
break;
}
@@ -1023,12 +979,10 @@ std::optional<asst::RoguelikeBattleTaskPlugin::DeployInfo>
// 取距离最近的N个点计算分数。然后使用得分最高的点
constexpr int CalcPointCount = 4;
for (const auto& loc : available_loc | views::take(CalcPointCount)) {
const auto& [cur_direction, cur_socre] =
calc_best_direction_and_score(loc, oper, home.direction);
const auto& [cur_direction, cur_socre] = calc_best_direction_and_score(loc, oper, home.direction);
// 离得远的要扣分
constexpr int DistWeights = -1050;
int extra_dist =
std::abs(loc.x - home.location.x) + std::abs(loc.y - home.location.y) - min_dist;
int extra_dist = std::abs(loc.x - home.location.x) + std::abs(loc.y - home.location.y) - min_dist;
int extra_dist_score = DistWeights * extra_dist;
if (oper.role == battle::Role::Medic) { // 医疗干员离得远无所谓
extra_dist_score = 0;
@@ -1042,8 +996,7 @@ std::optional<asst::RoguelikeBattleTaskPlugin::DeployInfo>
}
// 强制变化为确定的攻击方向
if (auto iter = m_force_deploy_direction.find(best_location);
iter != m_force_deploy_direction.end()) {
if (auto iter = m_force_deploy_direction.find(best_location); iter != m_force_deploy_direction.end()) {
if (iter->second.role.contains(oper.role)) {
best_direction = iter->second.direction;
}
@@ -1052,11 +1005,10 @@ std::optional<asst::RoguelikeBattleTaskPlugin::DeployInfo>
return DeployInfo { best_location, best_direction };
}
asst::RoguelikeBattleTaskPlugin::DirectionAndScore
asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score(
Point loc,
const battle::DeploymentOper& oper,
DeployDirection recommended_direction) const
asst::RoguelikeBattleTaskPlugin::DirectionAndScore asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score(
Point loc,
const battle::DeploymentOper& oper,
DeployDirection recommended_direction) const
{
LogTraceFunction;
@@ -1112,10 +1064,8 @@ asst::RoguelikeBattleTaskPlugin::DirectionAndScore
int max_score = 0;
DeployDirection best_direction = DeployDirection::None;
for (auto direction : { DeployDirection::Right,
DeployDirection::Up,
DeployDirection::Left,
DeployDirection::Down }) {
for (auto direction :
{ DeployDirection::Right, DeployDirection::Up, DeployDirection::Left, DeployDirection::Down }) {
int score = 0;
// 按朝右算,后面根据方向做转换
for (const Point& relative_pos : get_attack_range(oper, direction)) {
@@ -1124,14 +1074,12 @@ asst::RoguelikeBattleTaskPlugin::DirectionAndScore
using TileKey = TilePack::TileKey;
// 战斗干员朝向的权重
static const std::unordered_map<TileKey, int> TileKeyFightWeights = {
{ TileKey::Invalid, 0 }, { TileKey::Forbidden, 0 },
{ TileKey::Wall, 500 }, { TileKey::Road, 1000 },
{ TileKey::Home, 500 }, { TileKey::EnemyHome, 1000 },
{ TileKey::Airport, 1000 }, { TileKey::Floor, 1000 },
{ TileKey::Hole, 0 }, { TileKey::Telin, 700 },
{ TileKey::Telout, 800 }, { TileKey::Grass, 500 },
{ TileKey::DeepSea, 1000 }, { TileKey::Volcano, 1000 },
{ TileKey::Healing, 1000 }, { TileKey::Fence, 800 },
{ TileKey::Invalid, 0 }, { TileKey::Forbidden, 0 }, { TileKey::Wall, 500 },
{ TileKey::Road, 1000 }, { TileKey::Home, 500 }, { TileKey::EnemyHome, 1000 },
{ TileKey::Airport, 1000 }, { TileKey::Floor, 1000 }, { TileKey::Hole, 0 },
{ TileKey::Telin, 700 }, { TileKey::Telout, 800 }, { TileKey::Grass, 500 },
{ TileKey::DeepSea, 1000 }, { TileKey::Volcano, 1000 }, { TileKey::Healing, 1000 },
{ TileKey::Fence, 800 },
};
// 治疗干员朝向的权重
static const std::unordered_map<TileKey, int> TileKeyMedicWeights = {
@@ -1146,19 +1094,16 @@ asst::RoguelikeBattleTaskPlugin::DirectionAndScore
switch (oper.role) {
case battle::Role::Medic:
if (auto iter = m_used_tiles.find(absolute_pos);
iter != m_used_tiles.cend()
&& BattleData.get_role(iter->second)
!= battle::Role::Drone) { // 根据哪个方向上人多决定朝向哪
iter != m_used_tiles.cend() &&
BattleData.get_role(iter->second) != battle::Role::Drone) { // 根据哪个方向上人多决定朝向哪
score += 10000;
}
if (auto iter = m_side_tile_info.find(absolute_pos);
iter != m_side_tile_info.end()) {
if (auto iter = m_side_tile_info.find(absolute_pos); iter != m_side_tile_info.end()) {
score += TileKeyMedicWeights.at(iter->second.key);
}
break;
default:
if (auto iter = m_side_tile_info.find(absolute_pos);
iter != m_side_tile_info.end()) {
if (auto iter = m_side_tile_info.find(absolute_pos); iter != m_side_tile_info.end()) {
score += TileKeyFightWeights.at(iter->second.key);
}
break;

View File

@@ -11,8 +11,7 @@
bool asst::RoguelikeFormationTaskPlugin::verify(AsstMsg msg, const json::value& details) const
{
if (msg != AsstMsg::SubTaskCompleted
|| details.get("subtask", std::string()) != "ProcessTask") {
if (msg != AsstMsg::SubTaskCompleted || details.get("subtask", std::string()) != "ProcessTask") {
return false;
}
@@ -73,8 +72,7 @@ bool asst::RoguelikeFormationTaskPlugin::_run()
}
auto& new_result = formation_analyzer.get_result();
size_t new_selected_count =
ranges::count_if(new_result, [](const auto& oper) { return oper.selected; });
size_t new_selected_count = ranges::count_if(new_result, [](const auto& oper) { return oper.selected; });
// 说明 select_count 计数没生效,即都没点上
if (new_selected_count == pre_selected) {
reselect = true;
@@ -114,20 +112,16 @@ void asst::RoguelikeFormationTaskPlugin::clear_and_reselect()
std::vector<asst::RoguelikeFormationImageAnalyzer::FormationOper> sorted_oper_list;
std::unordered_set<std::string> oper_to_select; // 和上面的 vector 一致,用于快速查重
const auto& team_complete_condition =
RoguelikeRecruit.get_team_complete_info(m_config->get_theme());
const auto& group_list = RoguelikeRecruit.get_group_info(m_config->get_theme());
const auto& team_complete_condition = RoguelikeRecruit.get_team_complete_info(m_config->get_theme());
const auto& group_list = RoguelikeRecruit.get_group_names(m_config->get_theme());
for (const auto& condition : team_complete_condition) { // 优先选择阵容核心干员
int count = 0;
const int require = condition.threshold;
for (const std::string& group_name : condition.groups) {
auto group_filter = views::filter([&](const auto& oper) {
const auto& group_ids =
RoguelikeRecruit.get_group_id(m_config->get_theme(), oper.name);
return ranges::any_of(group_ids, [&](int id) {
return group_list[id] == group_name;
});
const auto& group_ids = RoguelikeRecruit.get_group_ids_of_oper(m_config->get_theme(), oper.name);
return ranges::any_of(group_ids, [&](int id) { return group_list[id] == group_name; });
});
for (const auto& oper : oper_list | group_filter | views::take(require - count)) {
if (!oper_to_select.contains(oper.name)) {
@@ -143,12 +137,8 @@ void asst::RoguelikeFormationTaskPlugin::clear_and_reselect()
}
std::erase_if(oper_list, [&](const auto& oper) { return oper_to_select.contains(oper.name); });
ranges::stable_partition(oper_list, [](const auto& oper) {
return !oper.name.starts_with("预备干员");
});
ranges::move(
oper_list | views::take(13 - sorted_oper_list.size()),
std::back_inserter(sorted_oper_list));
ranges::stable_partition(oper_list, [](const auto& oper) { return !oper.name.starts_with("预备干员"); });
ranges::move(oper_list | views::take(13 - sorted_oper_list.size()), std::back_inserter(sorted_oper_list));
for (const auto& oper : sorted_oper_list) {
select(oper);
@@ -174,9 +164,7 @@ bool asst::RoguelikeFormationTaskPlugin::analyze()
auto unique_filter = views::filter([&](const auto& oper) {
// TODO: 这里没考虑多个相同预备干员的情况,不过影响应该不大
return !ranges::any_of(oper_list, [&](const auto& existing_oper) {
return oper.name == existing_oper.name;
});
return !ranges::any_of(oper_list, [&](const auto& existing_oper) { return oper.name == existing_oper.name; });
});
auto append_page_proj = views::transform([&](auto oper) {
Log.info(__FUNCTION__, "oper: ", oper.name, " page: ", cur_page);

View File

@@ -1,7 +1,6 @@
#include "RoguelikeRecruitTaskPlugin.h"
#include "Config/Miscellaneous/BattleDataConfig.h"
#include "Config/Roguelike/RoguelikeRecruitConfig.h"
#include "Config/TaskData.h"
#include "Controller/Controller.h"
#include "Status.h"
@@ -47,11 +46,26 @@ asst::battle::Role asst::RoguelikeRecruitTaskPlugin::get_oper_role(const std::st
bool asst::RoguelikeRecruitTaskPlugin::is_oper_melee(const std::string& name)
{
const auto role = get_oper_role(name);
if (role != battle::Role::Pioneer && role != battle::Role::Tank && role != battle::Role::Warrior) return false;
if (role != battle::Role::Pioneer && role != battle::Role::Tank && role != battle::Role::Warrior) {
return false;
}
const auto loc = BattleData.get_location_type(name);
return loc == battle::LocationType::Melee;
}
std::unordered_set<std::string> asst::RoguelikeRecruitTaskPlugin::calculate_condition_oper(
const RecruitPriorityOffset& condition,
const std::unordered_map<std::string, RoguelikeOper>& chars_map)
{
std::unordered_set<std::string> opers; // 符合这个策略组的干员
for (const auto& [oper_name, oper_level] : chars_map) {
if (condition.opers.contains(oper_name)) {
opers.insert(oper_name);
}
}
return opers;
}
bool asst::RoguelikeRecruitTaskPlugin::_run()
{
LogTraceFunction;
@@ -88,50 +102,48 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
std::unordered_map<battle::Role, int> team_roles;
int offset_melee_num = 0;
for (const auto& [name, oper] : chars_map) {
if (name.starts_with("预备干员")) continue;
if (name.starts_with("预备干员")) {
continue;
}
team_roles[battle::get_role_type(name)]++;
if (is_oper_melee(name)) offset_melee_num++;
}
// __________________will-be-removed-end__________________
std::unordered_map<std::string, int> group_count;
const auto& group_list = RoguelikeRecruit.get_group_info(m_config->get_theme());
for (const auto& [name, oper] : chars_map) {
std::vector<int> group_ids = RoguelikeRecruit.get_group_id(m_config->get_theme(), name);
for (const auto& group_id : group_ids) {
const std::string& group_name = group_list[group_id];
group_count[group_name]++;
if (is_oper_melee(name)) {
offset_melee_num++;
}
}
// __________________will-be-removed-end__________________
if (!m_starts_complete) {
for (const auto& oper : chars_map) {
auto& recruit_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), oper.first);
if (recruit_info.is_start) m_starts_complete = true;
if (recruit_info.is_start) {
m_starts_complete = true;
}
}
}
if (!m_team_complete) {
bool complete = true;
int complete_count = 0;
int complete_require = 0;
const auto& team_complete_condition = RoguelikeRecruit.get_team_complete_info(m_config->get_theme());
for (const auto& condition : team_complete_condition) {
int count = 0;
complete_require += condition.threshold;
for (const std::string& group_name : condition.groups) {
count += group_count[group_name];
complete_count += group_count[group_name];
}
if (count < condition.threshold) {
for (const auto& condition : team_complete_condition) { // 每个完备度的策略组
std::unordered_set<std::string> opers =
calculate_condition_oper(condition, chars_map); // 符合这个策略组的干员
complete_count += int(opers.size());
if (int(opers.size()) < condition.threshold) {
complete = false;
}
Log.trace("__FUNCTION__", "groups:", condition.groups);
Log.trace("__FUNCTION__", "opers:", opers);
}
m_team_complete = complete;
if (complete_count <= complete_require / 2 && m_recruit_count >= 10) {
if (complete_count <= RoguelikeRecruit.get_team_complete_require(m_config->get_theme()) / 2 &&
m_recruit_count >= 10) {
// 如果第10次招募还没拿到半队key干员说明账号阵容不齐放开招募限制有啥用啥吧
m_team_complete = complete;
m_team_complete = true;
}
Log.trace("__FUNCTION__", "complete_count:", complete_count, "m_team_complete:", m_team_complete);
}
if (m_recruit_count >= 3 && !m_starts_complete) {
@@ -180,7 +192,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
std::unordered_set<std::string> oper_names;
const auto& oper_list = analyzer.get_result();
bool stop_swipe = false;
std::vector<std::string> owned_collection=m_config->get_collection();
std::vector<std::string> owned_collection = m_config->get_collection();
int max_oper_x = 0;
for (const auto& oper_info : oper_list) {
@@ -199,9 +211,17 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
// 偏移低于阈值,代表划动没有效果,已经到达屏幕最右侧
if (x_offset < 20 && y_offset < 5) {
stop_swipe = true;
Log.trace(__FUNCTION__, "| Page", i, "oper", oper_info.name,
"last rect:", rect_it->second.to_string(), "current rect:", oper_info.rect.to_string(),
" - stop swiping");
Log.trace(
__FUNCTION__,
"| Page",
i,
"oper",
oper_info.name,
"last rect:",
rect_it->second.to_string(),
"current rect:",
oper_info.rect.to_string(),
" - stop swiping");
break;
}
@@ -248,8 +268,12 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
}
else {
// 精一55级以下默认不招募
Log.trace(__FUNCTION__, "| Ignored low level oper:", oper_info.name, oper_info.elite,
oper_info.level);
Log.trace(
__FUNCTION__,
"| Ignored low level oper:",
oper_info.name,
oper_info.elite,
oper_info.level);
}
if (temp_recruit_exist) { // 临时招募干员具有极高抓取优先级,但是在编队时会占用前面的位置
@@ -277,29 +301,35 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
// }
// __________________will-be-removed-end__________________
for (const auto& priority_offset : recruit_info.recruit_priority_offsets) {
int count = 0;
for (const std::string& group_name : priority_offset.groups) {
count += group_count[group_name];
}
std::unordered_set<std::string> opers = // 符合这个策略组的干员
calculate_condition_oper(priority_offset, chars_map);
if (priority_offset.is_less) {
if (count <= priority_offset.threshold) priority += priority_offset.offset;
if (int(opers.size()) <= priority_offset.threshold) {
priority += priority_offset.offset;
}
}
else {
if (count >= priority_offset.threshold) priority += priority_offset.offset;
if (int(opers.size()) >= priority_offset.threshold) {
priority += priority_offset.offset;
}
}
}
for (const auto& priority_offset : recruit_info.collection_priority_offsets) {
for (const auto& priority_offset : recruit_info.collection_priority_offsets) {
auto iter =
std::find(owned_collection.begin(), owned_collection.end(), priority_offset.collection);
if (iter != owned_collection.end()) {
priority += priority_offset.offset;
}
if (iter != owned_collection.end()) {
priority += priority_offset.offset;
}
}
if (!m_starts_complete) {
if (!recruit_info.is_start) priority -= 1000;
if (!recruit_info.is_start) {
priority -= 1000;
}
}
else if (!m_team_complete) {
if (!recruit_info.is_key) priority -= 1000;
if (!recruit_info.is_key) {
priority -= 1000;
}
}
}
}
@@ -310,8 +340,9 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
}
// 添加到候选名单
auto existing_it = ranges::find_if(
recruit_list, [&](const RoguelikeRecruitInfo& pri) -> bool { return pri.name == recruit_info.name; });
auto existing_it = ranges::find_if(recruit_list, [&](const RoguelikeRecruitInfo& pri) -> bool {
return pri.name == recruit_info.name;
});
if (existing_it == recruit_list.cend()) {
RoguelikeRecruitInfo info;
info.name = recruit_info.name;
@@ -376,7 +407,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
if (info.elite != 2) {
continue;
}
Log.trace(__FUNCTION__, "| Choose random elite 2:", info.name, info.elite, info.level);
Log.trace(__FUNCTION__, "| Choose temporary recruitment elite 2:", info.name, info.elite, info.level);
recruit_oper(info);
return true;
}
@@ -403,8 +434,15 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
}
std::string char_name = selected_oper->name;
Log.trace(__FUNCTION__, "| Top priority oper:", char_name, selected_oper->priority, "page",
selected_oper->page_index, "/", i);
Log.trace(
__FUNCTION__,
"| Top priority oper:",
char_name,
selected_oper->priority,
"page",
selected_oper->page_index,
"/",
i);
// 滑动方向
// 页码大于一半: 从右往左划动
@@ -426,8 +464,8 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
void asst::RoguelikeRecruitTaskPlugin::reset_in_run_variables()
{
m_recruit_count = 0;
m_starts_complete = false;
m_team_complete = false;
m_starts_complete = false;
m_team_complete = false;
}
bool asst::RoguelikeRecruitTaskPlugin::lazy_recruit()
@@ -438,7 +476,7 @@ bool asst::RoguelikeRecruitTaskPlugin::lazy_recruit()
return true;
}
bool asst::RoguelikeRecruitTaskPlugin::recruit_appointed_char(const std::string& char_name, bool is_rtl)
bool asst::RoguelikeRecruitTaskPlugin::recruit_appointed_char(const std::string& char_name, bool is_rtl)
{
LogTraceFunction;
// 最大滑动次数
@@ -464,12 +502,15 @@ bool asst::RoguelikeRecruitTaskPlugin::lazy_recruit()
if (analyzer.analyze()) {
const auto& chars = analyzer.get_result();
max_oper_x = ranges::max(chars | views::transform([&](const auto& x) { return x.rect.x; }));
auto it = ranges::find_if(
chars, [&](const battle::roguelike::Recruitment& oper) -> bool { return oper.name == char_name; });
auto it = ranges::find_if(chars, [&](const battle::roguelike::Recruitment& oper) -> bool {
return oper.name == char_name;
});
std::unordered_set<std::string> oper_names;
ranges::transform(chars, std::inserter(oper_names, oper_names.end()),
std::mem_fn(&battle::roguelike::Recruitment::name));
ranges::transform(
chars,
std::inserter(oper_names, oper_names.end()),
std::mem_fn(&battle::roguelike::Recruitment::name));
Log.info(__FUNCTION__, "| Oper list:", oper_names);
if (it != chars.cend()) {
@@ -528,7 +569,9 @@ bool asst::RoguelikeRecruitTaskPlugin::recruit_support_char()
auto core_opt = m_config->get_core_char();
m_config->set_core_char("");
if (!core_opt.empty()) {
if (recruit_support_char(core_opt, MaxRefreshTimes)) return true;
if (recruit_support_char(core_opt, MaxRefreshTimes)) {
return true;
}
}
return false;
}
@@ -566,17 +609,28 @@ bool asst::RoguelikeRecruitTaskPlugin::recruit_support_char(const std::string& n
auto check_satisfy = [&use_nonfriend_support](const RecruitSupportCharInfo& chara) {
return chara.is_friend || use_nonfriend_support;
};
std::copy_if(chars_page.begin(), chars_page.end(),
std::inserter(satisfied_chars, std::begin(satisfied_chars)), check_satisfy);
std::copy_if(
chars_page.begin(),
chars_page.end(),
std::inserter(satisfied_chars, std::begin(satisfied_chars)),
check_satisfy);
if (satisfied_chars.size()) break;
if (satisfied_chars.size()) {
break;
}
}
if (page != MaxPageCnt - 1) {
ProcessTask(*this, { "RoguelikeSupportSwipeRight" }).run();
}
if (page != MaxPageCnt - 1) ProcessTask(*this, { "RoguelikeSupportSwipeRight" }).run();
}
if (satisfied_chars.size()) break;
if (satisfied_chars.size()) {
break;
}
// 刷新助战
if (retry >= max_refresh) break;
if (retry >= max_refresh) {
break;
}
auto screen_refresh = ctrler()->get_image();
RoguelikeRecruitSupportAnalyzer analyzer_refresh(screen_refresh);
analyzer_refresh.set_mode(SupportAnalyzeMode::RefreshSupportBtn);
@@ -585,7 +639,9 @@ bool asst::RoguelikeRecruitTaskPlugin::recruit_support_char(const std::string& n
return false;
}
auto& refresh_info = analyzer_refresh.get_result_refresh();
if (refresh_info.in_cooldown) sleep(refresh_info.remain_secs * 1000);
if (refresh_info.in_cooldown) {
sleep(refresh_info.remain_secs * 1000);
}
ctrler()->click(refresh_info.rect);
sleep(Task.get("RoguelikeRefreshSupportBtnOcr")->post_delay);
ProcessTask(*this, { "RoguelikeSupportSwipeLeft" }).run();
@@ -641,20 +697,24 @@ void asst::RoguelikeRecruitTaskPlugin::slowly_swipe(bool to_left, int swipe_dist
{
std::string swipe_task_name =
to_left ? "RoguelikeRecruitOperListSlowlySwipeToTheLeft" : "RoguelikeRecruitOperListSlowlySwipeToTheRight";
if (!ControlFeat::support(ctrler()->support_features(),
ControlFeat::PRECISE_SWIPE)) { // 不能精准滑动时不使用 swipe_dist 参数
if (!ControlFeat::support(
ctrler()->support_features(),
ControlFeat::PRECISE_SWIPE)) { // 不能精准滑动时不使用 swipe_dist 参数
ProcessTask(*this, { swipe_task_name }).run();
return;
}
if (!to_left) swipe_dist = -swipe_dist;
if (!to_left) {
swipe_dist = -swipe_dist;
}
auto swipe_task = Task.get(swipe_task_name);
const Rect& StartPoint = swipe_task->specific_rect;
ctrler()->swipe(StartPoint,
{ StartPoint.x + swipe_dist - StartPoint.width, StartPoint.y, StartPoint.width, StartPoint.height },
swipe_task->special_params.empty() ? 0 : swipe_task->special_params.at(0),
(swipe_task->special_params.size() < 2) ? false : swipe_task->special_params.at(1),
(swipe_task->special_params.size() < 3) ? 1 : swipe_task->special_params.at(2),
(swipe_task->special_params.size() < 4) ? 1 : swipe_task->special_params.at(3));
ctrler()->swipe(
StartPoint,
{ StartPoint.x + swipe_dist - StartPoint.width, StartPoint.y, StartPoint.width, StartPoint.height },
swipe_task->special_params.empty() ? 0 : swipe_task->special_params.at(0),
(swipe_task->special_params.size() < 2) ? false : swipe_task->special_params.at(1),
(swipe_task->special_params.size() < 3) ? 1 : swipe_task->special_params.at(2),
(swipe_task->special_params.size() < 4) ? 1 : swipe_task->special_params.at(3));
sleep(swipe_task->post_delay);
}

View File

@@ -1,15 +1,16 @@
#pragma once
#include "AbstractRoguelikeTaskPlugin.h"
#include "Common/AsstBattleDef.h"
#include "Config/Roguelike/RoguelikeRecruitConfig.h"
namespace asst
{
// 干员招募信息
struct RoguelikeRecruitInfo
{
std::string name; // 干员名字
int priority = 0; // 招募优先级 (0-1000)
int page_index = 0; // 所在页码 (用于判断翻页方向)
std::string name; // 干员名字
int priority = 0; // 招募优先级 (0-1000)
int page_index = 0; // 所在页码 (用于判断翻页方向)
bool is_alternate = false; // 是否后备干员 (允许重复招募、划到后备干员时不再往右划动)
};
@@ -49,10 +50,13 @@ private:
bool recruit_appointed_char(const std::string& char_name, bool is_rtl = false);
// 选择干员
void select_oper(const battle::roguelike::Recruitment& oper);
// 找出给定的 Offset 组所有满足条件的干员
std::unordered_set<std::string> calculate_condition_oper(
const RecruitPriorityOffset& condition,
const std::unordered_map<std::string, RoguelikeOper>& chars_map);
int m_recruit_count = 0; // 第几次招募
bool m_starts_complete =
false; // 开局干员是否已经招募阵容中必须有开局干员没有前仅招募start干员或预备干员
bool m_team_complete = false; // 阵容是否完备阵容完备前仅招募key干员或预备干员
int m_recruit_count = 0; // 第几次招募
bool m_starts_complete = false; // 开局干员是否已经招募阵容中必须有开局干员没有前仅招募start干员或预备干员
bool m_team_complete = false; // 阵容是否完备阵容完备前仅招募key干员或预备干员
};
}