feat.修改战斗流程协议,并完成 parser

This commit is contained in:
MistEO
2022-03-19 16:20:25 +08:00
parent 362e7581a3
commit c7a0fb91af
10 changed files with 684 additions and 541 deletions

View File

@@ -1,80 +1,83 @@
# 战斗流程协议
`resource/battle.json` 的使用方法及各字段说明
`resource/battle/*.json` 的使用方法及各字段说明
## 完整字段一览
```jsonc
[
{
"stage_name": "暴君", // 关卡名
"stage_code": "1-7", // 关卡号,与关卡名二选一必选
"opers": [ // 指定干员
{
"name": "棘刺", // 干员名
{
"stage_name": "暴君", // 关卡名,必选
"opers": [ // 指定干员
{
"name": "棘刺", // 干员
"skill": 3, // 技能序号。可选,默认 1取值范围 [1, 3]
"skill": 3, // 技能序号。可选,默认 1取值范围 [1, 3]
"skill_usage": 0 // 技能用法。可选,默认 0
// 0 - 好了就用,有多少次用多少次(例如干员 棘刺 3 技能、桃金娘 1 技能等)
// 1 - 好了就用,仅使用一次(例如干员 山 2 技能)
// 2 - 指定时刻使用(依赖“波数依赖”字段
// 如果是全自动的技能,可以不填写该字段
}
],
"opers_group": [
{
"name": "任意正常群奶", // 群组名,必选
// 自己随便取名字,与下面 deploy 中的 name 对应起来就行
"skill_usage": 0 // 技能用法。可选,默认 0
// 0 - 好了就用,有多少次用多少次(例如干员 棘刺 3 技能、桃金娘 1 技能等)
// 1 - 好了就用,仅使用一次(例如干员 山 2 技能)
// 2 - 自动判断使用时机(画饼.jpg
// 3 - 不自动使用(依赖 "kills_actions" 字段
// 如果是全自动的技能,随便填哪个都一样,也可以不填
},
],
"opers_groups": [
{
"name": "任意正常群奶", // 群组名,必选
// 自己随便取名字,与下面 deploy 中的 name 对应起来就行
"opers": [ // 干员任选其一,无序,会优先选练度高的,用法同上述 opers 字段
{
"name": "夜莺",
"skill": 3,
"skill_usage": 2 // 若是指定时刻,不同的干员技能 cd 可能不同,请留意
},
{
"name": "白面鸮",
"skill": 2,
"skill_usage": 2
}
]
}
],
"auto_deploy": [ // 自动部署干员,有费就下。有序,可
{
"name": "棘刺", // 干员名 或 群组名。必选
"location": [ 5, 5 ], // 放置干员的位置。必选
"opers": [ // 干员任选其一,无序,会优先选练度高的,用法同上述 opers 字段
{
"name": "夜莺",
"skill": 3,
"skill_usage": 2 // 若是指定时刻,不同的干员技能 cd 可能不同,请留意
},
{
"name": "白面鸮",
"skill": 2,
"skill_usage": 2
}
]
}
],
"actions": [ // 战斗中的操作。有序,执行完前一个才会去执行下一个。必
{
"type": 0, // 操作类型,可选,默认 0
// 0 - 部署; 1 - 撤退; 2 - 开技能;
// 100 - 切换二倍速开关101 - 切换 1/5 速度(点击任意干员)
// 若为 0, 当费用不够时,会一直等待到费用够(除非 timeout
// 若为 2, 当技能 cd 没转好时,一直等待到技能 cd 好(除非 timeout
"direction": 1, // 干员朝向。必选
// 0 - 无朝向(适用于召唤物)
// 1 - 右; 2 - 下; 3 - 左; 4 - 上
"kills_condition": 0, // 击杀数条件,如果没达到就一直等待,可选,默认为空。直接执行
// TODO: 其他条件
// TODO: "condition_type": 0, // 执行条件间的关系,可选,默认 0
// // 0 - 且; 1 - 或
"preDelay": 0, // 轮到该干员时,等待多长时间再放。可选,默认 0
"rearDelay": 0 // 放完该干员后,等待多少时间再到下一个干员。可选,默认 0
},
{
"name": "任意正常群奶",
"location": [ 5, 6 ],
"direction": 1,
"preDelay": 0,
"rearDelay": 0
}
],
"kills_deploy": [ // 依赖击杀数的操作。有序,可选
{
"kills": 0, // 击杀数 >= N 的时候,进行该操作,必选
"action": 0, // 操作类型,必选。
// 0 - 开技能1 - 部署2 - 撤退;
// 100 - 切换二倍速开关101 - 切换 1/5 速度(点击任意干员)
"name": "棘刺", // 干员名 或 群组名action < 100 时必选
"name": "史尔特尔", // 干员名action ∈ [0, 100) 时必选
"preDelay": 0 // 击杀数达到后,等待多长时间再进行操作。可选,默认 0
}
]
},
{ // 其他关卡...
"stage_name": "机密情报押运"
}
]
"location": [ 5, 5 ], // 部署干员的位置。action == 0 时必选
"direction": 0, // 部署干员的干员朝向。action == 0 时必选
// 0 - 右; 1 - 下; 2 - 左; 3 - 上
// 4 - 无朝向(适用于召唤物)
"pre_delay": 0, // 前置延时。可选,默认 0, 单位毫秒
"rear_delay": 0, // 后置延时。可选,默认 0, 单位毫秒
"timeout": 999999999 // 超时时间。可选,默认 INT_MAX, 单位毫秒
// 当 type == 0 或 type == 1 且等待超时
// 则放弃当前动作, 转而执行下一个动作
},
// 举例 1
{
"name": "任意正常群奶",
"location": [ 5, 6 ],
"direction": 1
},
// 举例 2
{
"type": 100
}
]
}
```

0
resource/battle.json Normal file
View File

View File

@@ -0,0 +1,61 @@
#pragma once
#include <vector>
#include <string>
#include <unordered_map>
#include "AsstTypes.h"
namespace asst
{
enum class BattleSkillUsage // 干员技能使用方法
{
Possibly = 0, // 有就用,例如干员 棘刺 3 技能
Once = 1, // 只用一次,例如干员 银灰 2技能
InTime = 2, // 关键时刻使用,自动判断使用时机,例如干员 假日威龙陈 3 技能
NotUse = 3, // 不自动使用
OnceUsed
};
struct BattleOper // 干员
{
std::string name;
int skill = 1; // 技能序号,取值范围 [1, 3]
BattleSkillUsage skill_usage = BattleSkillUsage::Possibly;
};
enum class DeployDirection
{
Right = 0,
Down = 1,
Left = 2,
Up = 3,
No = 4 // 没有方向,通常是无人机之类的
};
enum class BattleActionType // 操作类型
{
Deploy = 0, // 部署干员
Retreat = 1, // 撤退干员
UseSkill = 2, // 开技能
SwitchSpeed = 100, // 切换二倍速
SlowMode = 101 // 使用 1/5 的速度(点击任意干员),会在下一个任意操作后恢复原速度
};
struct BattleAction // 操作
{
int kills_condition = 0;
BattleActionType type = BattleActionType::Deploy;
std::string group_name; // 目标名,若 type >= SwitchSpeed, group_name 为空
Point location;
DeployDirection direction = DeployDirection::Right;
int pre_delay = 0;
int rear_delay = 0;
int time_out = INT_MAX;
};
struct BattleActions
{
std::unordered_map<std::string, std::vector<BattleOper>> opers_groups;
std::vector<BattleAction> actions;
};
}

View File

@@ -0,0 +1,62 @@
#include "BattleConfiger.h"
#include <meojson/json.hpp>
#include "Logger.hpp"
bool asst::BattleConfiger::parse(const json::value& json)
{
std::string stage_name = json.at("stage_name").as_string();
BattleActions battle_actions;
if (json.contains("opers_groups")) {
for (const auto& group_info : json.at("opers_groups").as_array()) {
std::string group_name = group_info.at("name").as_string();
std::vector<BattleOper> oper_vec;
for (const auto& oper_info : group_info.at("opers").as_array()) {
BattleOper oper;
oper.name = oper_info.at("name").as_string();
oper.skill = oper_info.get("skill", 1);
oper.skill_usage = static_cast<BattleSkillUsage>(oper_info.get("skill_usage", 0));
oper_vec.emplace_back(std::move(oper));
}
battle_actions.opers_groups.emplace(std::move(group_name), std::move(oper_vec));
}
}
if (json.contains("opers")) {
for (const auto& oper_info : json.at("opers").as_array()) {
BattleOper oper;
oper.name = oper_info.at("name").as_string();
oper.skill = oper_info.get("skill", 1);
oper.skill_usage = static_cast<BattleSkillUsage>(oper_info.get("skill_usage", 0));
// 单个干员的,干员名直接作为组名
std::string group_name = oper.name;
battle_actions.opers_groups.emplace(std::move(group_name), std::vector{ std::move(oper) });
}
}
for (const auto& action_info : json.at("actions").as_array()) {
BattleAction action;
action.type = static_cast<BattleActionType>(action_info.get("type", 0));
action.kills_condition = action_info.get("kills_condition", 0);
action.group_name = action_info.get("name", std::string());
action.location.x = action_info.get("location", 0, 0);
action.location.y = action_info.get("location", 1, 0);
action.direction = static_cast<DeployDirection>(action_info.get("direction", 0));
action.pre_delay = action_info.get("pre_delay", 0);
action.rear_delay = action_info.get("rear_delay", 0);
action.time_out = action_info.get("timeout", INT_MAX);
battle_actions.actions.emplace_back(std::move(action));
}
m_battle_actions[std::move(stage_name)] = std::move(battle_actions);
return true;
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include "AbstractConfiger.h"
#include "AsstBattleDef.h"
namespace asst
{
class BattleConfiger : public AbstractConfiger
{
public:
virtual ~BattleConfiger() = default;
protected:
virtual bool parse(const json::value& json) override;
std::unordered_map<std::string, BattleActions> m_battle_actions;
};
}

View File

@@ -25,6 +25,7 @@
<ClInclude Include="AsstMsg.h" />
<ClInclude Include="AutoRecruitTask.h" />
<ClInclude Include="AwardTask.h" />
<ClInclude Include="BattleConfiger.h" />
<ClInclude Include="BattleImageAnalyzer.h" />
<ClInclude Include="BattlePerspectiveImageAnalyzer.h" />
<ClInclude Include="DebugTask.h" />
@@ -94,6 +95,7 @@
<ClCompile Include="AbstractTaskPlugin.cpp" />
<ClCompile Include="AutoRecruitTask.cpp" />
<ClCompile Include="AwardTask.cpp" />
<ClCompile Include="BattleConfiger.cpp" />
<ClCompile Include="BattleImageAnalyzer.cpp" />
<ClCompile Include="BattlePerspectiveImageAnalyzer.cpp" />
<ClCompile Include="DebugTask.cpp" />

View File

@@ -306,6 +306,9 @@
<ClInclude Include="ReplenishOriginiumShardTaskPlugin.h">
<Filter>头文件\Task\Plugin</Filter>
</ClInclude>
<ClInclude Include="BattleConfiger.h">
<Filter>头文件\Resource</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Controller.cpp">
@@ -509,6 +512,9 @@
<ClCompile Include="ReplenishOriginiumShardTaskPlugin.cpp">
<Filter>源文件\Task\Plugin</Filter>
</ClCompile>
<ClCompile Include="BattleConfiger.cpp">
<Filter>源文件\Resource</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="..\..\resource\config.json">

View File

@@ -11,336 +11,336 @@
bool asst::RoguelikeBattleTaskPlugin::verify(AsstMsg msg, const json::value& details) const
{
if (msg != AsstMsg::SubTaskCompleted
|| details.get("subtask", std::string()) != "ProcessTask") {
return false;
}
if (msg != AsstMsg::SubTaskCompleted
|| details.get("subtask", std::string()) != "ProcessTask") {
return false;
}
if (details.at("details").at("task").as_string() == "Roguelike1StartAction") {
return true;
}
else {
return false;
}
if (details.at("details").at("task").as_string() == "Roguelike1StartAction") {
return true;
}
else {
return false;
}
}
void asst::RoguelikeBattleTaskPlugin::set_skill_usage(SkillUsageMap usage_map)
{
m_skill_usage = std::move(usage_map);
m_skill_usage = std::move(usage_map);
}
void asst::RoguelikeBattleTaskPlugin::set_stage_name(std::string stage)
{
m_stage_name = std::move(stage);
m_stage_name = std::move(stage);
}
bool asst::RoguelikeBattleTaskPlugin::_run()
{
bool getted_info = get_stage_info();
bool getted_info = get_stage_info();
speed_up();
speed_up();
if (!getted_info) {
return true;
}
if (!getted_info) {
return true;
}
while (!need_exit()) {
// 不在战斗场景,且已使用过了干员,说明已经打完了,就结束循环
if (!auto_battle() && m_opers_used) {
break;
}
}
while (!need_exit()) {
// 不在战斗场景,且已使用过了干员,说明已经打完了,就结束循环
if (!auto_battle() && m_opers_used) {
break;
}
}
clear();
clear();
return true;
return true;
}
bool asst::RoguelikeBattleTaskPlugin::get_stage_info()
{
LogTraceFunction;
LogTraceFunction;
const auto& tile = Resrc.tile();
bool calced = false;
const auto& tile = Resrc.tile();
bool calced = false;
if (m_stage_name.empty()) {
const auto stage_name_task_ptr = Task.get("BattleStageName");
sleep(stage_name_task_ptr->pre_delay);
if (m_stage_name.empty()) {
const auto stage_name_task_ptr = Task.get("BattleStageName");
sleep(stage_name_task_ptr->pre_delay);
constexpr int StageNameRetryTimes = 50;
for (int i = 0; i != StageNameRetryTimes; ++i) {
cv::Mat image = m_ctrler->get_image();
OcrImageAnalyzer name_analyzer(image);
constexpr int StageNameRetryTimes = 50;
for (int i = 0; i != StageNameRetryTimes; ++i) {
cv::Mat image = m_ctrler->get_image();
OcrImageAnalyzer name_analyzer(image);
name_analyzer.set_task_info(stage_name_task_ptr);
if (!name_analyzer.analyze()) {
continue;
}
name_analyzer.set_task_info(stage_name_task_ptr);
if (!name_analyzer.analyze()) {
continue;
}
for (const auto& tr : name_analyzer.get_result()) {
auto side_info = tile.calc(tr.text, true);
if (side_info.empty()) {
continue;
}
m_side_tile_info = std::move(side_info);
m_normal_tile_info = tile.calc(tr.text, false);
m_stage_name = tr.text;
calced = true;
break;
}
if (calced) {
break;
}
}
}
else {
m_side_tile_info = tile.calc(m_stage_name, true);
calced = true;
}
for (const auto& tr : name_analyzer.get_result()) {
auto side_info = tile.calc(tr.text, true);
if (side_info.empty()) {
continue;
}
m_side_tile_info = std::move(side_info);
m_normal_tile_info = tile.calc(tr.text, false);
m_stage_name = tr.text;
calced = true;
break;
}
if (calced) {
break;
}
}
}
else {
m_side_tile_info = tile.calc(m_stage_name, true);
calced = true;
}
if (calced) {
if (calced) {
#ifdef ASST_DEBUG
auto normal_tiles = tile.calc(m_stage_name, false);
cv::Mat draw = m_ctrler->get_image();
for (const auto& [point, info] : normal_tiles) {
using TileKey = TilePack::TileKey;
static const std::unordered_map<TileKey, std::string> TileKeyMapping = {
{ TileKey::Invalid, "invalid" },
{ TileKey::Forbidden, "forbidden" },
{ TileKey::Wall, "wall" },
{ TileKey::Road, "road" },
{ TileKey::Home, "end" },
{ TileKey::EnemyHome, "start" },
{ TileKey::Floor, "floor" },
{ TileKey::Hole, "hole" },
{ TileKey::Telin, "telin" },
{ TileKey::Telout, "telout" }
};
auto normal_tiles = tile.calc(m_stage_name, false);
cv::Mat draw = m_ctrler->get_image();
for (const auto& [point, info] : normal_tiles) {
using TileKey = TilePack::TileKey;
static const std::unordered_map<TileKey, std::string> TileKeyMapping = {
{ TileKey::Invalid, "invalid" },
{ TileKey::Forbidden, "forbidden" },
{ TileKey::Wall, "wall" },
{ TileKey::Road, "road" },
{ TileKey::Home, "end" },
{ TileKey::EnemyHome, "start" },
{ TileKey::Floor, "floor" },
{ TileKey::Hole, "hole" },
{ TileKey::Telin, "telin" },
{ TileKey::Telout, "telout" }
};
cv::putText(draw, TileKeyMapping.at(info.key), cv::Point(info.pos.x, info.pos.y), 1, 1, cv::Scalar(0, 0, 255));
}
cv::putText(draw, TileKeyMapping.at(info.key), cv::Point(info.pos.x, info.pos.y), 1, 1, cv::Scalar(0, 0, 255));
}
#endif
auto cb_info = basic_info_with_what("StageInfo");
auto& details = cb_info["details"];
details["name"] = m_stage_name;
details["size"] = m_side_tile_info.size();
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
else {
callback(AsstMsg::SubTaskExtraInfo, basic_info_with_what("StageInfoError"));
}
auto cb_info = basic_info_with_what("StageInfo");
auto& details = cb_info["details"];
details["name"] = m_stage_name;
details["size"] = m_side_tile_info.size();
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
else {
callback(AsstMsg::SubTaskExtraInfo, basic_info_with_what("StageInfoError"));
}
return calced;
return calced;
}
bool asst::RoguelikeBattleTaskPlugin::auto_battle()
{
LogTraceFunction;
LogTraceFunction;
using Oper = asst::BattleImageAnalyzer::Oper;
using Oper = asst::BattleImageAnalyzer::Oper;
BattleImageAnalyzer battle_analyzer(m_ctrler->get_image());
BattleImageAnalyzer battle_analyzer(m_ctrler->get_image());
if (!battle_analyzer.analyze()) {
return false;
}
if (!battle_analyzer.analyze()) {
return false;
}
//if (int hp = battle_analyzer.get_hp();
// hp != 0) {
// bool used_skills = false;
// if (hp < m_pre_hp) { // 说明漏怪了,漏怪就开技能(
// for (const Rect& rect : battle_analyzer.get_ready_skills()) {
// used_skills = true;
// if (!use_skill(rect)) {
// break;
// }
// }
// }
// m_pre_hp = hp;
// if (used_skills) {
// return true;
// }
//}
//if (int hp = battle_analyzer.get_hp();
// hp != 0) {
// bool used_skills = false;
// if (hp < m_pre_hp) { // 说明漏怪了,漏怪就开技能(
// for (const Rect& rect : battle_analyzer.get_ready_skills()) {
// used_skills = true;
// if (!use_skill(rect)) {
// break;
// }
// }
// }
// m_pre_hp = hp;
// if (used_skills) {
// return true;
// }
//}
for (const Rect& rect : battle_analyzer.get_ready_skills()) {
// 找出这个可以使用的技能是哪个干员的(根据之前放干员的位置)
std::string name = "NotFound";
for (const auto& [loc, oper_name] : m_used_tiles) {
auto point = m_normal_tile_info[loc].pos;
if (rect.include(point)) {
name = oper_name;
break;
}
}
for (const Rect& rect : battle_analyzer.get_ready_skills()) {
// 找出这个可以使用的技能是哪个干员的(根据之前放干员的位置)
std::string name = "NotFound";
for (const auto& [loc, oper_name] : m_used_tiles) {
auto point = m_normal_tile_info[loc].pos;
if (rect.include(point)) {
name = oper_name;
break;
}
}
auto& usage = m_skill_usage[name];
Log.info("Oper", name, ", skill usage", static_cast<int>(usage));
switch (usage) {
case SkillUsage::Once:
use_skill(rect);
usage = SkillUsage::OnceUsed;
return true;
break;
case SkillUsage::Possibly:
use_skill(rect);
return true;
break;
}
}
auto& usage = m_skill_usage[name];
Log.info("Oper", name, ", skill usage", static_cast<int>(usage));
switch (usage) {
case BattleSkillUsage::Once:
use_skill(rect);
usage = BattleSkillUsage::OnceUsed;
return true;
break;
case BattleSkillUsage::Possibly:
use_skill(rect);
return true;
break;
}
}
const auto& opers = battle_analyzer.get_opers();
if (opers.empty()) {
return true;
}
const auto& opers = battle_analyzer.get_opers();
if (opers.empty()) {
return true;
}
static const std::array<Role, 9> RoleOrder = {
Role::Pioneer,
Role::Sniper,
Role::Warrior,
Role::Support,
Role::Medic,
Role::Caster,
Role::Special,
Role::Tank,
Role::Drone
};
const auto use_oper_task_ptr = Task.get("BattleUseOper");
const auto swipe_oper_task_ptr = Task.get("BattleSwipeOper");
static const std::array<Role, 9> RoleOrder = {
Role::Pioneer,
Role::Sniper,
Role::Warrior,
Role::Support,
Role::Medic,
Role::Caster,
Role::Special,
Role::Tank,
Role::Drone
};
const auto use_oper_task_ptr = Task.get("BattleUseOper");
const auto swipe_oper_task_ptr = Task.get("BattleSwipeOper");
// 点击当前最合适的干员
Oper opt_oper;
bool oper_found = false;
for (auto role : RoleOrder) {
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;
}
m_ctrler->click(opt_oper.rect);
sleep(use_oper_task_ptr->pre_delay);
// 点击当前最合适的干员
Oper opt_oper;
bool oper_found = false;
for (auto role : RoleOrder) {
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;
}
m_ctrler->click(opt_oper.rect);
sleep(use_oper_task_ptr->pre_delay);
OcrImageAnalyzer oper_name_analyzer(m_ctrler->get_image());
oper_name_analyzer.set_task_info(Task.get("BattleOperName"));
oper_name_analyzer.set_replace(
std::dynamic_pointer_cast<OcrTaskInfo>(
Task.get("Roguelike1RecruitData"))
->replace_map);
OcrImageAnalyzer oper_name_analyzer(m_ctrler->get_image());
oper_name_analyzer.set_task_info(Task.get("BattleOperName"));
oper_name_analyzer.set_replace(
std::dynamic_pointer_cast<OcrTaskInfo>(
Task.get("Roguelike1RecruitData"))
->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;
}
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;
}
// 将干员拖动到场上
Loc loc = Loc::All;
switch (opt_oper.role) {
case Role::Medic:
case Role::Support:
case Role::Sniper:
case Role::Caster:
loc = Loc::Ranged;
break;
case Role::Pioneer:
case Role::Warrior:
case Role::Tank:
loc = Loc::Melee;
break;
case Role::Special:
case Role::Drone:
default:
// 特种和无人机,有的只能放地面,有的又只能放高台,不好判断
// 笨办法,都试试,总有一次能成的
//{
// static Loc static_loc = Loc::Melee;
// loc = static_loc;
// if (static_loc == Loc::Melee) {
// static_loc = Loc::Ranged;
// }
// else {
// static_loc = Loc::Melee;
// }
//}
loc = Loc::Melee;
break;
}
// 将干员拖动到场上
Loc loc = Loc::All;
switch (opt_oper.role) {
case Role::Medic:
case Role::Support:
case Role::Sniper:
case Role::Caster:
loc = Loc::Ranged;
break;
case Role::Pioneer:
case Role::Warrior:
case Role::Tank:
loc = Loc::Melee;
break;
case Role::Special:
case Role::Drone:
default:
// 特种和无人机,有的只能放地面,有的又只能放高台,不好判断
// 笨办法,都试试,总有一次能成的
//{
// static Loc static_loc = Loc::Melee;
// loc = static_loc;
// if (static_loc == Loc::Melee) {
// static_loc = Loc::Ranged;
// }
// else {
// static_loc = Loc::Melee;
// }
//}
loc = Loc::Melee;
break;
}
Point placed_loc = get_placed(loc);
Point placed_point = m_side_tile_info.at(placed_loc).pos;
Point placed_loc = get_placed(loc);
Point placed_point = m_side_tile_info.at(placed_loc).pos;
#ifdef ASST_DEBUG
auto image = m_ctrler->get_image();
cv::circle(image, cv::Point(placed_point.x, placed_point.y), 10, cv::Scalar(0, 0, 255), -1);
auto image = m_ctrler->get_image();
cv::circle(image, cv::Point(placed_point.x, placed_point.y), 10, cv::Scalar(0, 0, 255), -1);
#endif
Rect placed_rect(placed_point.x, placed_point.y, 1, 1);
m_ctrler->swipe(opt_oper.rect, placed_rect, swipe_oper_task_ptr->pre_delay);
sleep(use_oper_task_ptr->rear_delay);
Rect placed_rect(placed_point.x, placed_point.y, 1, 1);
m_ctrler->swipe(opt_oper.rect, placed_rect, swipe_oper_task_ptr->pre_delay);
sleep(use_oper_task_ptr->rear_delay);
// 计算往哪边拖动(干员朝向)
Point direction = calc_direction(placed_loc, opt_oper.role);
// 计算往哪边拖动(干员朝向)
Point direction = calc_direction(placed_loc, opt_oper.role);
// 将方向转换为实际的 swipe end 坐标点
Point end_point = placed_point;
constexpr int coeff = 500;
end_point.x += direction.x * coeff;
end_point.y += direction.y * coeff;
// 将方向转换为实际的 swipe end 坐标点
Point end_point = placed_point;
constexpr int coeff = 500;
end_point.x += direction.x * coeff;
end_point.y += direction.y * coeff;
end_point.x = std::max(0, end_point.x);
end_point.x = std::min(end_point.x, WindowWidthDefault);
end_point.y = std::max(0, end_point.y);
end_point.y = std::min(end_point.y, WindowHeightDefault);
end_point.x = std::max(0, end_point.x);
end_point.x = std::min(end_point.x, WindowWidthDefault);
end_point.y = std::max(0, end_point.y);
end_point.y = std::min(end_point.y, WindowHeightDefault);
m_ctrler->swipe(placed_point, end_point, swipe_oper_task_ptr->rear_delay);
m_ctrler->swipe(placed_point, end_point, swipe_oper_task_ptr->rear_delay);
m_used_tiles.emplace(placed_loc, oper_name);
m_opers_used = true;
++m_cur_home_index;
m_used_tiles.emplace(placed_loc, oper_name);
m_opers_used = true;
++m_cur_home_index;
return true;
return true;
}
bool asst::RoguelikeBattleTaskPlugin::speed_up()
{
ProcessTask task(*this, { "BattleSpeedUp" });
return task.run();
ProcessTask task(*this, { "BattleSpeedUp" });
return task.run();
}
bool asst::RoguelikeBattleTaskPlugin::use_skill(const asst::Rect& rect)
{
m_ctrler->click(rect);
m_ctrler->click(rect);
ProcessTask task(*this, { "BattleUseSkillBegin" });
task.set_retry_times(0);
return task.run();
ProcessTask task(*this, { "BattleUseSkillBegin" });
task.set_retry_times(0);
return task.run();
}
void asst::RoguelikeBattleTaskPlugin::clear()
{
m_opers_used = false;
m_pre_hp = 0;
m_homes.clear();
m_cur_home_index = 0;
m_stage_name.clear();
m_side_tile_info.clear();
m_used_tiles.clear();
m_opers_used = false;
m_pre_hp = 0;
m_homes.clear();
m_cur_home_index = 0;
m_stage_name.clear();
m_side_tile_info.clear();
m_used_tiles.clear();
for (auto& [_, usage] : m_skill_usage) {
if (usage == SkillUsage::OnceUsed) {
usage = SkillUsage::Once;
}
}
for (auto& [_, usage] : m_skill_usage) {
if (usage == BattleSkillUsage::OnceUsed) {
usage = BattleSkillUsage::Once;
}
}
}
//asst::Rect asst::RoguelikeBattleTaskPlugin::get_placed_by_cv()
@@ -358,177 +358,177 @@ void asst::RoguelikeBattleTaskPlugin::clear()
asst::Point asst::RoguelikeBattleTaskPlugin::get_placed(Loc buildable_type)
{
LogTraceFunction;
LogTraceFunction;
if (m_homes.empty()) {
for (const auto& [loc, side] : m_side_tile_info) {
if (side.key == TilePack::TileKey::Home) {
m_homes.emplace_back(loc);
}
}
if (m_homes.empty()) {
Log.error("Unknown home pos");
}
}
if (m_cur_home_index >= m_homes.size()) {
m_cur_home_index = 0;
}
if (m_homes.empty()) {
for (const auto& [loc, side] : m_side_tile_info) {
if (side.key == TilePack::TileKey::Home) {
m_homes.emplace_back(loc);
}
}
if (m_homes.empty()) {
Log.error("Unknown home pos");
}
}
if (m_cur_home_index >= m_homes.size()) {
m_cur_home_index = 0;
}
Point nearest;
int min_dist = INT_MAX;
Point nearest;
int min_dist = INT_MAX;
Point home(5, 5); // 默认值,一般是地图的中间
if (m_cur_home_index < m_homes.size()) {
home = m_homes.at(m_cur_home_index);
}
Point home(5, 5); // 默认值,一般是地图的中间
if (m_cur_home_index < m_homes.size()) {
home = m_homes.at(m_cur_home_index);
}
for (const auto& [loc, tile] : m_side_tile_info) {
if (tile.buildable == buildable_type
|| tile.buildable == Loc::All) {
if (m_used_tiles.find(loc) != m_used_tiles.cend()) {
continue;
}
int dx = std::abs(home.x - loc.x);
int dy = std::abs(home.y - loc.y);
int dist = dx * dx + dy * dy;
if (dist <= min_dist) {
min_dist = dist;
nearest = loc;
}
}
}
Log.info(__FUNCTION__, nearest.to_string());
for (const auto& [loc, tile] : m_side_tile_info) {
if (tile.buildable == buildable_type
|| tile.buildable == Loc::All) {
if (m_used_tiles.find(loc) != m_used_tiles.cend()) {
continue;
}
int dx = std::abs(home.x - loc.x);
int dy = std::abs(home.y - loc.y);
int dist = dx * dx + dy * dy;
if (dist <= min_dist) {
min_dist = dist;
nearest = loc;
}
}
}
Log.info(__FUNCTION__, nearest.to_string());
return nearest;
return nearest;
}
asst::Point asst::RoguelikeBattleTaskPlugin::calc_direction(Point loc, Role role)
{
LogTraceFunction;
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);
}
Point home_point = m_side_tile_info.at(home_loc).pos;
Rect home_rect(home_point.x, home_point.y, 1, 1);
// 根据家门的方向计算一下大概的朝向
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);
}
Point home_point = m_side_tile_info.at(home_loc).pos;
Rect home_rect(home_point.x, home_point.y, 1, 1);
int dx = 0;
if (loc.x > home_loc.x) dx = 1;
else if (loc.x < home_loc.x) dx = -1;
else dx = 0;
int dx = 0;
if (loc.x > home_loc.x) dx = 1;
else if (loc.x < home_loc.x) dx = -1;
else dx = 0;
int dy = 0;
if (loc.y > home_loc.y) dy = 1;
else if (loc.y < home_loc.y) dy = -1;
else dy = 0;
int dy = 0;
if (loc.y > home_loc.y) dy = 1;
else if (loc.y < home_loc.y) dy = -1;
else dy = 0;
Point base_direction(0, 0);
switch (role) {
case Role::Medic:
{
if (std::abs(dx) < std::abs(dy)) {
base_direction.y = -dy;
}
else {
base_direction.x = -dx;
}
}
break;
case Role::Support:
case Role::Pioneer:
case Role::Warrior:
case Role::Sniper:
case Role::Special:
case Role::Tank:
case Role::Caster:
case Role::Drone:
default:
{
if (std::abs(dx) < std::abs(dy)) {
base_direction.y = dy;
}
else {
base_direction.x = dx;
}
}
break;
}
Point base_direction(0, 0);
switch (role) {
case Role::Medic:
{
if (std::abs(dx) < std::abs(dy)) {
base_direction.y = -dy;
}
else {
base_direction.x = -dx;
}
}
break;
case Role::Support:
case Role::Pioneer:
case Role::Warrior:
case Role::Sniper:
case Role::Special:
case Role::Tank:
case Role::Caster:
case Role::Drone:
default:
{
if (std::abs(dx) < std::abs(dy)) {
base_direction.y = dy;
}
else {
base_direction.x = dx;
}
}
break;
}
using TileKey = TilePack::TileKey;
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, 1000 },
{ TileKey::EnemyHome, 1000 },
{ TileKey::Floor, 1000 },
{ TileKey::Hole, 0 },
{ TileKey::Telin, 0 },
{ TileKey::Telout, 0 }
};
// 战斗干员朝向的权重
static const std::unordered_map<TileKey, int> TileKeyFightWeights = {
{ TileKey::Invalid, 0 },
{ TileKey::Forbidden, 0 },
{ TileKey::Wall, 500 },
{ TileKey::Road, 1000 },
{ TileKey::Home, 1000 },
{ TileKey::EnemyHome, 1000 },
{ TileKey::Floor, 1000 },
{ TileKey::Hole, 0 },
{ TileKey::Telin, 0 },
{ TileKey::Telout, 0 }
};
static const std::unordered_map<Point, Point> DirectionStartingPoint = {
{ Point(1, 0), Point(0, -1) }, // 朝右
{ Point(0, 1), Point(-1, 0) }, // 朝下
{ Point(-1, 0), Point(-2, -1) }, // 朝左
{ Point(0, -1), Point(-1, -2) }, // 朝上
};
static const std::unordered_map<Point, Point> DirectionStartingPoint = {
{ Point(1, 0), Point(0, -1) }, // 朝右
{ Point(0, 1), Point(-1, 0) }, // 朝下
{ Point(-1, 0), Point(-2, -1) }, // 朝左
{ Point(0, -1), Point(-1, -2) }, // 朝上
};
int max_score = 0;
Point opt_direction;
int max_score = 0;
Point opt_direction;
// 计算每个方向上的得分
for (const auto& [direction, point_move] : DirectionStartingPoint) {
Point start_point = loc;
start_point.x += point_move.x;
start_point.y += point_move.y;
int score = 0;
// 计算每个方向上的得分
for (const auto& [direction, point_move] : DirectionStartingPoint) {
Point start_point = loc;
start_point.x += point_move.x;
start_point.y += point_move.y;
int score = 0;
constexpr int AttackRangeSize = 3;
// 这个方向上 3x3 的格子,计算总的得分
for (int i = 0; i != AttackRangeSize; ++i) {
for (int j = 0; j != AttackRangeSize; ++j) {
Point cur_point = start_point;
cur_point.x += i;
cur_point.y += j;
constexpr int AttackRangeSize = 3;
// 这个方向上 3x3 的格子,计算总的得分
for (int i = 0; i != AttackRangeSize; ++i) {
for (int j = 0; j != AttackRangeSize; ++j) {
Point cur_point = start_point;
cur_point.x += i;
cur_point.y += j;
switch (role) {
// 医疗干员根据哪个方向上人多决定朝向哪
case Role::Medic:
if (m_used_tiles.find(cur_point) != m_used_tiles.cend()) {
score += 1000;
}
break;
// 其他干员(战斗干员)根据哪个方向上权重高决定朝向哪
default:
if (auto iter = m_side_tile_info.find(cur_point);
iter == m_side_tile_info.cend()) {
continue;
}
else {
score += TileKeyFightWeights.at(iter->second.key);
}
}
}
}
switch (role) {
// 医疗干员根据哪个方向上人多决定朝向哪
case Role::Medic:
if (m_used_tiles.find(cur_point) != m_used_tiles.cend()) {
score += 1000;
}
break;
// 其他干员(战斗干员)根据哪个方向上权重高决定朝向哪
default:
if (auto iter = m_side_tile_info.find(cur_point);
iter == m_side_tile_info.cend()) {
continue;
}
else {
score += TileKeyFightWeights.at(iter->second.key);
}
}
}
}
if (direction == base_direction) {
score += 50;
}
if (direction == base_direction) {
score += 50;
}
if (score > max_score) {
max_score = score;
opt_direction = direction;
}
}
if (score > max_score) {
max_score = score;
opt_direction = direction;
}
}
return opt_direction;
}
return opt_direction;
}

View File

@@ -4,22 +4,16 @@
#include "AsstTypes.h"
#include "TilePack.h"
#include "BattleImageAnalyzer.h"
#include "AsstBattleDef.h"
namespace asst
{
enum class SkillUsage
{
Possibly = 0, // 有就用,例如干员 棘刺 3 技能
Once = 1, // 只用一次,例如干员 银灰 2技能
InTime = 2, // 关键时刻使用,例如干员 假日威龙陈 3 技能
OnceUsed
};
class RoguelikeBattleTaskPlugin : public AbstractTaskPlugin
{
using Loc = asst::TilePack::BuildableType;
using Role = asst::BattleImageAnalyzer::Role;
public:
using SkillUsageMap = std::unordered_map<std::string, SkillUsage>;
using SkillUsageMap = std::unordered_map<std::string, BattleSkillUsage>;
public:
using AbstractTaskPlugin::AbstractTaskPlugin;
virtual ~RoguelikeBattleTaskPlugin() = default;

View File

@@ -10,78 +10,78 @@
#include "Logger.hpp"
asst::RoguelikeTask::RoguelikeTask(AsstCallback callback, void* callback_arg)
: PackageTask(callback, callback_arg, TaskType),
m_roguelike_task_ptr(std::make_shared<ProcessTask>(callback, callback_arg, TaskType))
: PackageTask(callback, callback_arg, TaskType),
m_roguelike_task_ptr(std::make_shared<ProcessTask>(callback, callback_arg, TaskType))
{
m_roguelike_task_ptr->set_tasks({ "Roguelike1Begin" })
.set_retry_times(50);
m_roguelike_task_ptr->set_tasks({ "Roguelike1Begin" })
.set_retry_times(50);
m_roguelike_task_ptr->regiseter_plugin<RoguelikeFormationTaskPlugin>();
m_roguelike_task_ptr->regiseter_plugin<RoguelikeFormationTaskPlugin>();
m_battle_task_ptr = m_roguelike_task_ptr->regiseter_plugin<RoguelikeBattleTaskPlugin>();
m_battle_task_ptr = m_roguelike_task_ptr->regiseter_plugin<RoguelikeBattleTaskPlugin>();
m_recruit_task_ptr = m_roguelike_task_ptr->regiseter_plugin<RoguelikeRecruitTaskPlugin>();
m_recruit_task_ptr->set_retry_times(2);
m_recruit_task_ptr = m_roguelike_task_ptr->regiseter_plugin<RoguelikeRecruitTaskPlugin>();
m_recruit_task_ptr->set_retry_times(2);
m_skill_task_ptr = m_roguelike_task_ptr->regiseter_plugin<RoguelikeSkillSelectionTaskPlugin>();
m_skill_task_ptr->set_retry_times(0);
m_skill_task_ptr = m_roguelike_task_ptr->regiseter_plugin<RoguelikeSkillSelectionTaskPlugin>();
m_skill_task_ptr->set_retry_times(0);
// 这个任务如果卡住会放弃当前的肉鸽并重新开始,所以多添加一点。先这样凑合用
for (int i = 0; i != 10000; ++i) {
m_subtasks.emplace_back(m_roguelike_task_ptr);
}
// 这个任务如果卡住会放弃当前的肉鸽并重新开始,所以多添加一点。先这样凑合用
for (int i = 0; i != 10000; ++i) {
m_subtasks.emplace_back(m_roguelike_task_ptr);
}
}
bool asst::RoguelikeTask::set_params(const json::value& params)
{
// 0 - 尽可能一直往后打
// 1 - 第一层投资完源石锭就退出
// 2 - 投资过后再退出,没有投资就继续往后打
// 0 - 尽可能一直往后打
// 1 - 第一层投资完源石锭就退出
// 2 - 投资过后再退出,没有投资就继续往后打
if (!params.contains("opers") || !params.at("opers").is_array()) {
return false;
}
if (!params.contains("opers") || !params.at("opers").is_array()) {
return false;
}
std::vector<std::string> opers_vec;
RoguelikeSkillSelectionTaskPlugin::SkillMap skill_map;
RoguelikeBattleTaskPlugin::SkillUsageMap usage_map;
std::vector<std::string> opers_vec;
RoguelikeSkillSelectionTaskPlugin::SkillMap skill_map;
RoguelikeBattleTaskPlugin::SkillUsageMap usage_map;
for (auto& oper : params.at("opers").as_array()) {
if (!oper.contains("name") || !oper.at("name").is_string()) {
return false;
}
int skill = oper.get("skill", 1);
if (skill < 1 || 3 < skill) {
return false;
}
std::string name = oper.at("name").as_string();
opers_vec.emplace_back(name);
skill_map.emplace(name, skill);
for (auto& oper : params.at("opers").as_array()) {
if (!oper.contains("name") || !oper.at("name").is_string()) {
return false;
}
int skill = oper.get("skill", 1);
if (skill < 1 || 3 < skill) {
return false;
}
std::string name = oper.at("name").as_string();
opers_vec.emplace_back(name);
skill_map.emplace(name, skill);
auto usage = static_cast<SkillUsage>(oper.get("skill_usage", 0));
usage_map.emplace(name, usage);
}
skill_map.emplace("Unknown", 3);
auto usage = static_cast<BattleSkillUsage>(oper.get("skill_usage", 0));
usage_map.emplace(name, usage);
}
skill_map.emplace("Unknown", 3);
int mode = params.get("mode", 0);
switch (mode) {
case 0:
break;
case 1:
m_roguelike_task_ptr->set_times_limit("Roguelike1StageTraderLeave", 0);
break;
case 2:
m_roguelike_task_ptr->set_times_limit("Roguelike1StageTraderInvestCancel", 0);
break;
default:
Log.error(__FUNCTION__, "| Unknown mode", mode);
return false;
}
int mode = params.get("mode", 0);
switch (mode) {
case 0:
break;
case 1:
m_roguelike_task_ptr->set_times_limit("Roguelike1StageTraderLeave", 0);
break;
case 2:
m_roguelike_task_ptr->set_times_limit("Roguelike1StageTraderInvestCancel", 0);
break;
default:
Log.error(__FUNCTION__, "| Unknown mode", mode);
return false;
}
m_recruit_task_ptr->set_opers(std::move(opers_vec));
m_skill_task_ptr->set_skill_map(std::move(skill_map));
m_recruit_task_ptr->set_opers(std::move(opers_vec));
m_skill_task_ptr->set_skill_map(std::move(skill_map));
m_battle_task_ptr->set_skill_usage(std::move(usage_map));
m_battle_task_ptr->set_skill_usage(std::move(usage_map));
return true;
}
return true;
}