feat: 战斗流程协议命令块增强 (#9952)

[#9951](https://github.com/MaaAssistantArknights/MaaAssistantArknights/issues/9951)
This commit is contained in:
萨拉托加
2024-07-30 15:54:10 +08:00
committed by GitHub
8 changed files with 2280 additions and 667 deletions

View File

@@ -57,12 +57,15 @@ icon: ph:sword-bold
]
}
],
"actions": [
// 战斗中的操作。有序,执行完前一个才会去执行下一个。必选
"actions": [ // 战斗中的操作。有序,执行完前一个才会去执行下一个。必选
// 以下介绍一个操作通用携带的属性,包括类型、触发、延时、说明、命令补充信息等
{
"type": "部署", // 操作类型,可选,默认为 "Deploy"
"type": "打印", // 操作类型,可选,默认为 "Deploy"
// "Deploy" | "Skill" | "Retreat" | "SpeedUp" | "BulletTime" | "SkillUsage" | "Output" | "SkillDaemon" | "MoveCamera"
// "部署" | "技能" | "撤退" | "二倍速" | "子弹时间" | "技能用法" | "打印" | "摆完挂机" | "移动镜头"
//
// "Loop" | "Case" | "Check" | "Until" | "SavePoint" | "SyncPoint" | "CheckPoint"
// "循环" | "派发" | "确认" | "轮询" | "保存锚点" | "同步锚点" | "检查锚点"
// 中英文皆可,效果相同
// 若为 "部署", 当费用不够时,会一直等待到费用够(除非 timeout
// 若为 "技能", 当技能 cd 没转好时,一直等待到技能 cd 好(除非 timeout
@@ -74,9 +77,17 @@ icon: ph:sword-bold
// "打印" 界面不显示这条步骤,仅用于输出 doc 里的内容(用来做字幕之类的)
// "摆完挂机" 仅使用 "好了就用" 的技能,其他什么都不做,直到战斗结束
// "移动镜头" 用于 “引航者试炼” 模式,还需要填写 distance 字段
// 目前下面四个条件是且的关系,即 &&
"kills": 0, // 击杀数条件,如果没达到就一直等待。可选,默认为 0直接执行
"costs": 50, // 费用条件,如果没达到就一直等待。可选,默认为 0直接执行
// "循环" 用于构建循环流程,可以重复执行包含的动作
// "派发" 用于根据当前用户在抄作业时在干员组中选定的干员派发出不同的动作
// "检查" 用于根据当前战场中的信息条件做出不同的分支选择,不阻塞
// "直到" 用于创建一个无序的动作集合, 直到其中一个完成,或者是全部完成
// "保存锚点" 手动创建锚点,保存当前的 击杀数,费用数,时间戳等信息快照
// "同步锚点" 表示等待至当前信息快照减去指定锚点的信息快照之后满足指定区间
// "检查锚点" 表示当前信息快照减去指定锚点的信息快照之后是否满足指定区间,不阻塞,根据判断结果来执行分支动作
// 动作的触发前提
"kills": 0, // 可选, 击杀数条件,如果没达到就一直等待。
"costs": 50, // 可选, 费用条件,如果没达到就一直等待。
// 费用受潜能等影响,可能并不完全正确,仅适合对时间轴要求不严格的战斗。
// 否则请使用下面的 cost_changes
// 另外仅在费用是两位数的时候识别的比较准,三位数的费用可能会识别错,不推荐使用
@@ -85,14 +96,35 @@ icon: ph:sword-bold
// 支持负数,即费用变少了(例如“孑”等吸费干员使得费用变少了)
// 另外仅在费用是两位数的时候识别的比较准,三位数的费用可能会识别错,不推荐使用
"cooling": 2, // CD 中干员数量条件,如果没达到就一直等待。可选,默认为 -1不识别
"count" : 0, // 用于循环操作的计数, 0表示一次都不执行100则表示循环执行100次。
// TODO: 其他条件
// TODO: "condition_type": 0, // 执行条件间的关系,可选,默认为 0
// // 0 - 且; 1 - 或
"category" : "all", // 可选succ|not|all|any 表示动作触发条件的组合策略默认是all
// succ表示跳过判断触发动作直接成功
// not 表示所有触发条件都不满足
// all 表示所有触发条件都要满足
// any 表示只要有一个触发条件满足
// 延时操作
"pre_delay": 0, // 前置延时。可选,默认为 0, 单位毫秒,表示在前面的触发条件满足之后,指定延迟才开始执行动作
"post_delay": 0, // 后置延时。可选,默认为 0, 单位毫秒,表示在动作执行完毕之后,指定延迟
// "timeout": 999999999, // 保留字段,暂未实现。
// 超时时间。当 type 为 "部署" | "技能" 时可选。默认 INT_MAX, 单位毫秒
// 等待超时则放弃当前动作, 转而执行下一个动作
"point_code" :"POINT-1", // 可选,表示在执行命令之前注册锚点,锚点功能在后续举例中进行补充
// 说明操作
"doc": "请开始你的表演~", // 描述,可选。会显示在界面上,没有实际作用
"doc_color": "green" // 描述文字的颜色,可选,默认灰色。会显示在界面上,没有实际作用。
},
// 干员操作类动作,一般是 "部署" | "技能" | "撤退" | "子弹时间" | "技能用法"
{
"name": "棘刺", // 干员名 或 群组名, type 为 "部署" 时必选,为 "技能" | "撤退" 时可选
"location": [
5,
5
], // 部署干员的位置。
], // 部署干员的位置,使用技能的位置
// type 为 "部署" 时必选。
// type 为 "技能" | "撤退" 时可选,
// "技能":仅推荐场地上自动的装置等,不填写 name并使用 location 开启技能。正常部署的干员推荐使用 name 开启技能
@@ -105,17 +137,7 @@ icon: ph:sword-bold
// 举例:刚下淘金娘需要她帮忙打几个怪,不能自动开技能,中后期平稳了需要她自动开技能
// 则可以在对应时刻设置为 1
"skill_times": 5, // 技能使用次数。可选,默认为 1
"pre_delay": 0, // 前置延时。可选,默认为 0, 单位毫秒
"post_delay": 0, // 后置延时。可选,默认为 0, 单位毫秒
// "timeout": 999999999, // 保留字段,暂未实现。
// 超时时间。当 type 为 "部署" | "技能" 时可选。默认 INT_MAX, 单位毫秒
// 等待超时则放弃当前动作, 转而执行下一个动作
"distance": [
4.5,
0
], // type 为 "移动镜头" 时必选
// [ x 移动格子数y 移动格子数 ],可为小数
// 注意 "移动镜头" 时是识别不到正在站场中的,需要用 sleep 完整覆盖整个移动动画
"doc": "下棘刺了!", // 描述,可选。会显示在界面上,没有实际作用
"doc_color": "orange" // 描述文字的颜色,可选,默认灰色。会显示在界面上,没有实际作用。
},
@@ -139,10 +161,159 @@ icon: ph:sword-bold
"doc": "你史尔特尔奶奶来啦!",
"doc_color": "red"
},
// 举例 3
// 工具类
{
"type": "二倍速"
}
"type": "二倍速", // 切换速度,如果机器性能不好,建议加一点后置延时,让屏幕点击能流畅衔接
"post_delay" : 200
},
{
"type": "打印", // 单纯用来打印,也可以用来说明一些操作场景的开始与结束
"doc": "你史尔特尔奶奶来啦!",
"doc_color": "red"
},
{
"type": "摆完挂机", // 如果没有摆完挂机maa会直接结束所有操作不再控制在需要自动开启干员技能时都建议在最后加上"摆完挂机"操作
},
// 流程类
{
"type" : "循环",
"kills" : 10,
"costs" : 20,
"category" : "any", // 这里表示 击杀10和费用20 等待到只要其中一个发生就开始触发
"end" : { // 必选,循环结束的触发条件
"kills" : 20,
"costs" : 99,
"count" : 10,
"category" : "all", // 这里表示 击杀20,费用99, 循环10次 全部满足才结束循环
},
"continue" : { // 可选,循环重新执行的条件
"costs" : 20,
"category" : "all", // 表示执行循环操作中的间隙费用超过了20就不继续执行后面的操作重新开头执行
},
"break" : { // 可选,循环中止的条件
"cooling" : 2,
"category" : "all", // 表示执行循环操作中的间隙,有两个干员意外撤退了,终止循环,结束循环命令
},
"loop_actions" :[] // 这里放需要循环执行的操作
},
{
"type" : "派发",
"select" : "法卫", // 必选,需要匹配的干员组,如果不匹配,什么也不做
"dispatch_actions" :{
"史尔特尔" : [
{
"type": "打印",
"doc": "你史尔特尔奶奶来啦!",
"doc_color": "red"
},
],
"拉普兰德" : [
{
"type": "打印",
"doc": "拉狗也有法术输出",
"doc_color": "grey"
}
],
},
"default_action" :[
{
"type": "打印",
"doc": "法卫中的其他杂鱼",
"doc_color": "grey"
}
]
},
{
"type" : "确认",
"condition" : { // 检查的相关条件,不阻塞
"kill" : 50,
"costs" : 60,
"category" : "not" // 表示当前的状态击杀数小于50且 costs也低于60
},
"then_actions" :[
{
"type": "打印",
"doc": "危!练度不足,先锋疲软。"
}
],
"else_actions" :[
{
"type": "打印",
"doc": "不足为惧!"
}
]
},
{
"type" : "轮询", // 轮询执行候选操作中的一个,只要满足就可以执行
"mode" : "all", // 可选默认为allall表示全部执行完才退出any表示其中一个满足就执行并退出其余不识别的字符当作 all策略
"limit" : 100, // 表示当前直到动作满足的过程中限制的循环次数,与 "count" 等效limit指定的优先级更高
"candidate_actions" :[
{
"type": "打印",
"kills" : 2,
"doc": "一鼓作气"
},
{
"type": "打印",
"kills" : 3,
"doc": "再而衰"
},
{
"type": "打印",
"kills" : 1,
"doc": "三而竭"
}
]
},
// 锚点操作类,锚点表示在命令执行前一刻记录下的击杀,费用,时间戳等信息,可以跨操作进行区间比较,减小多个操作连续允许造成的相对误差
{
"type":"保存锚点",
"point_code" : "Point-1" // 必选,设定一个锚点编码。如果多个操作的编码重复,则覆盖之前保存的锚点信息
},
{
"type": "同步锚点", // 等待至条件满足
"point_code" : "Point-2", // 可选,同步锚点在执行时也可以创建锚点
"target_code" : "Point-1", // 需要进行比较的锚点,如果锚点编码不存在,结束操作
"kill_range":[-1, 1], // 当前的击杀数-目标的击杀数 在 [-1,1]之间
"cost_range":[0, 50],
"cooling_range":[0, 50],
"time_range":[5000, 5000], // 相比指定锚点过去了5000ms也就是5s
"mode" : "all", // 表示条件策略, 满足条件策略之后才执行后续的操作。 succ|not|all|any
"then_actions" :[]
},
{
"type": "检查锚点", // 如果条件满足执行then_actions,否则执行else_actions不等待。
"point_code" : "Point-3", // 可选,同步锚点在执行时也可以创建锚点
"target_code" : "Point-2", // 需要进行比较的锚点,如果锚点编码不存在,结束操作
"kill_range":[-1, 1], // 当前的击杀数-目标的击杀数 在 [-1,1]之间
"cost_range":[0, 50],
"cooling_range":[0, 50],
"time_range":[5000, 5000], // 相比指定锚点过去了5000ms也就是5s
"mode" : "all", // 表示条件策略, 满足条件策略之后才执行后续的操作。 succ|not|all|any
"then_actions" :[],
"else_actions" :[],
},
// 特殊场景类
{
"type": "移动镜头",
"distance": [
4.5,
0
], // type 为 "移动镜头" 时必选
// [ x 移动格子数y 移动格子数 ],可为小数, 也可以是负数,用来校准镜头
// 注意 "移动镜头" 时是识别不到正在站场中的,需要用 sleep 完整覆盖整个移动动画
},
],
"minimum_required": "v4.0", // 最低要求 maa 版本号,必选。保留字段,暂未实现
"doc": {

View File

@@ -1,7 +1,11 @@
#pragma once
#include <chrono>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
#include "AsstTypes.h"
@@ -9,330 +13,528 @@
namespace asst::battle
{
// 统一变量名:
// loc, location, 表示格子坐标,例如 [1, 1], [5, 5]
// pos, position, 表示像素坐标,例如 [1280, 720], [500, 300]
// 统一变量名:
// loc, location, 表示格子坐标,例如 [1, 1], [5, 5]
// pos, position, 表示像素坐标,例如 [1280, 720], [500, 300]
enum class SkillUsage // 技能用法
enum class SkillUsage // 技能用法
{
NotUse = 0, // 不自动使用
Possibly = 1, // 有就用,例如干员 棘刺 3 技能
Times = 2, // 用 X 次,例如干员 山 2 技能用 1 次、重岳 3 技能用 5 次,由 "skill_times" 字段控制
InTime = 3, // 自动判断使用时机,画饼.jpg
TimesUsed // 已经使用了 X 次
};
struct OperUsage // 干员用法
{
std::string name;
int skill = 0; // 技能序号,取值范围 [0, 3]0时使用默认技能 或 上次编队时使用的技能
SkillUsage skill_usage = SkillUsage::NotUse;
int skill_times = 1; // 使用技能的次数,默认为 1兼容曾经的作业
};
enum class DeployDirection
{
Right = 0,
Down = 1,
Left = 2,
Up = 3,
None = 4 // 没有方向,通常是无人机之类的
};
enum class Role
{
Unknown,
Caster,
Medic,
Pioneer,
Sniper,
Special,
Support,
Tank,
Warrior,
Drone
};
inline static Role get_role_type(const std::string& role_name)
{
static const std::unordered_map<std::string, Role> NameToRole = {
{ "warrior", Role::Warrior }, { "WARRIOR", Role::Warrior }, { "Warrior", Role::Warrior },
{ "近卫", Role::Warrior }, { "GUARD", Role::Warrior }, { "guard", Role::Warrior },
{ "Guard", Role::Warrior },
{ "pioneer", Role::Pioneer }, { "PIONEER", Role::Pioneer }, { "Pioneer", Role::Pioneer },
{ "先锋", Role::Pioneer }, { "VANGUARD", Role::Pioneer }, { "vanguard", Role::Pioneer },
{ "Vanguard", Role::Pioneer },
{ "medic", Role::Medic }, { "MEDIC", Role::Medic }, { "Medic", Role::Medic },
{ "医疗", Role::Medic },
{ "tank", Role::Tank }, { "TANK", Role::Tank }, { "Tank", Role::Tank },
{ "重装", Role::Tank }, { "DEFENDER", Role::Tank }, { "defender", Role::Tank },
{ "Defender", Role::Tank }, { "坦克", Role::Tank },
{ "sniper", Role::Sniper }, { "SNIPER", Role::Sniper }, { "Sniper", Role::Sniper },
{ "狙击", Role::Sniper },
{ "caster", Role::Caster }, { "CASTER", Role::Caster },
{ "Caster", Role::Caster }, { "术师", Role::Caster }, { "术士", Role::Caster },
{ "法师", Role::Caster },
{ "support", Role::Support }, { "SUPPORT", Role::Support }, { "Support", Role::Support },
{ "supporter", Role::Support }, { "SUPPORTER", Role::Support }, { "Supporter", Role::Support },
{ "辅助", Role::Support }, { "支援", Role::Support },
{ "special", Role::Special }, { "SPECIAL", Role::Special }, { "Special", Role::Special },
{ "特种", Role::Special }, { "SPECIALIST", Role::Special }, { "specialist", Role::Special },
{ "Specialist", Role::Special },
{ "drone", Role::Drone }, { "DRONE", Role::Drone }, { "Drone", Role::Drone },
{ "无人机", Role::Drone }, { "SUMMON", Role::Drone }, { "summon", Role::Drone },
{ "Summon", Role::Drone }, { "召唤物", Role::Drone },
};
if (auto iter = NameToRole.find(role_name); iter != NameToRole.end()) {
return iter->second;
}
return Role::Unknown;
}
enum class OperPosition
{
None,
Blocking, // 阻挡单位
AirDefense, // 对空单位
};
enum class LocationType
{
Invalid = -1,
None = 0,
Melee = 1,
Ranged = 2,
All = 3
};
inline static LocationType get_role_usual_location(const Role& role)
{
switch (role) {
case Role::Warrior:
case Role::Pioneer:
case Role::Tank:
case Role::Special:
case Role::Drone:
return LocationType::Melee;
case Role::Medic:
case Role::Sniper:
case Role::Caster:
case Role::Support:
return LocationType::Ranged;
default:
return LocationType::None;
}
}
struct DeploymentOper
{
size_t index = 0;
Role role = Role::Unknown;
int cost = 0;
bool available = false;
bool cooling = false;
Rect rect;
cv::Mat avatar;
std::string name;
LocationType location_type = LocationType::None;
bool is_unusual_location = false; // 地面辅助,高台先锋等
};
struct OperProps
{
std::string id;
std::string name;
std::string name_en;
std::string name_jp;
std::string name_kr;
std::string name_tw;
Role role = Role::Unknown;
std::array<std::string, 3> ranges;
int rarity = 0;
LocationType location_type = LocationType::None;
std::vector<std::string> tokens; // 召唤物名字
};
using AttackRange = std::vector<Point>;
using RoleCounts = std::unordered_map<Role, int>;
namespace copilot
{
using OperUsageGroups = std::unordered_map<std::string, std::vector<OperUsage>>;
enum class ActionType
{
Deploy, // 部署干员
UseSkill, // 开技能
Retreat, // 撤退干员
SkillUsage, // 技能用法
SwitchSpeed, // 切换二倍速
BulletTime, // 使用 1/5 的速度
Output, // 仅输出,什么都不操作,界面上也不显示
SkillDaemon, // 什么都不做,有技能开技能,直到战斗结束
/* for TRN */
MoveCamera, // 引航者试炼,移动镜头
/* for SSS */
DrawCard, // “调配干员”
CheckIfStartOver, // 检查如果没有某干员则退出重开
/* for Enhance */
Loop, // 创建一个循环流程
Case, // 为干员组进行分化操作
Check, // 根据触发条件执行不同的分支
Until, // 内部动作可无序,知道里边的动作全部执行完毕才能结束
SavePoint, // 保存一个锚点
SyncPoint, // 同步锚点
CheckPoint, // 检查锚点
};
struct Action;
using ActionPtr = std::shared_ptr<Action>;
struct TriggerInfo
{
static constexpr int DEACTIVE_KILLS = -1; // 击杀数未被设置
static constexpr int DEACTIVE_COST = -1; // 费用数未被设置
static constexpr int DEACTIVE_COST_CHANGES = 0; // 费用变更未设置
static constexpr int64_t DEACTIVE_COOLING = -1; // 干员冷却数未设置
static constexpr int DEACTIVE_COUNT = -1; // 循环计数
enum class Category
{
NotUse = 0, // 不自动使用
Possibly = 1, // 有就用,例如干员 棘刺 3 技能
Times = 2, // 用 X 次,例如干员 山 2 技能用 1 次、重岳 3 技能用 5 次,由 "skill_times" 字段控制
InTime = 3, // 自动判断使用时机,画饼.jpg
TimesUsed // 已经使用了 X 次
None, // 未设置, 未指定策略
Succ, // 默认满足, 跳过条件判断
All, // 所有被设置的条件都应该满足
Any, // 所有被设置的条件只要有一个满足
Not // 所有被设置的条件一个都不满足
};
struct OperUsage // 干员用法
Category category = Category::None;
int kills = DEACTIVE_KILLS; // 击杀数条件
int costs = DEACTIVE_COST; // 费用条件
int cost_changes = DEACTIVE_COST_CHANGES; // 费用变化条件
int64_t cooling = DEACTIVE_COOLING; // 冷却中的干员条件
int count = DEACTIVE_COUNT; // 计数条件,不做变化,记录初始值
mutable int counter = 0; // 计数器
// 触发器是否被激活
bool active() const noexcept { return category != Category::None; }
// 重置计数器
void resetCounter() const noexcept { counter = 0; }
// 激活一次计数器
void activeCounter() const noexcept { ++counter; }
static auto loadCategoryFrom(std::string const& _Str) noexcept -> Category
{
std::string name;
int skill = 0; // 技能序号,取值范围 [0, 3]0时使用默认技能 或 上次编队时使用的技能
SkillUsage skill_usage = SkillUsage::NotUse;
int skill_times = 1; // 使用技能的次数,默认为 1兼容曾经的作业
};
enum class DeployDirection
{
Right = 0,
Down = 1,
Left = 2,
Up = 3,
None = 4 // 没有方向,通常是无人机之类的
};
enum class Role
{
Unknown,
Caster,
Medic,
Pioneer,
Sniper,
Special,
Support,
Tank,
Warrior,
Drone
};
inline static Role get_role_type(const std::string& role_name)
{
static const std::unordered_map<std::string, Role> NameToRole = {
{ "warrior", Role::Warrior }, { "WARRIOR", Role::Warrior }, { "Warrior", Role::Warrior },
{ "近卫", Role::Warrior }, { "GUARD", Role::Warrior }, { "guard", Role::Warrior },
{ "Guard", Role::Warrior },
{ "pioneer", Role::Pioneer }, { "PIONEER", Role::Pioneer }, { "Pioneer", Role::Pioneer },
{ "先锋", Role::Pioneer }, { "VANGUARD", Role::Pioneer }, { "vanguard", Role::Pioneer },
{ "Vanguard", Role::Pioneer },
{ "medic", Role::Medic }, { "MEDIC", Role::Medic }, { "Medic", Role::Medic },
{ "医疗", Role::Medic },
{ "tank", Role::Tank }, { "TANK", Role::Tank }, { "Tank", Role::Tank },
{ "重装", Role::Tank }, { "DEFENDER", Role::Tank }, { "defender", Role::Tank },
{ "Defender", Role::Tank }, { "坦克", Role::Tank },
{ "sniper", Role::Sniper }, { "SNIPER", Role::Sniper }, { "Sniper", Role::Sniper },
{ "狙击", Role::Sniper },
{ "caster", Role::Caster }, { "CASTER", Role::Caster },
{ "Caster", Role::Caster }, { "术师", Role::Caster }, { "术士", Role::Caster },
{ "法师", Role::Caster },
{ "support", Role::Support }, { "SUPPORT", Role::Support }, { "Support", Role::Support },
{ "supporter", Role::Support }, { "SUPPORTER", Role::Support }, { "Supporter", Role::Support },
{ "辅助", Role::Support }, { "支援", Role::Support },
{ "special", Role::Special }, { "SPECIAL", Role::Special }, { "Special", Role::Special },
{ "特种", Role::Special }, { "SPECIALIST", Role::Special }, { "specialist", Role::Special },
{ "Specialist", Role::Special },
{ "drone", Role::Drone }, { "DRONE", Role::Drone }, { "Drone", Role::Drone },
{ "无人机", Role::Drone }, { "SUMMON", Role::Drone }, { "summon", Role::Drone },
{ "Summon", Role::Drone }, { "召唤物", Role::Drone },
};
if (auto iter = NameToRole.find(role_name); iter != NameToRole.end()) {
return iter->second;
if (_Str == "succ") {
return Category::Succ;
}
return Role::Unknown;
}
enum class OperPosition
{
None,
Blocking, // 阻挡单位
AirDefense, // 对空单位
};
enum class LocationType
{
Invalid = -1,
None = 0,
Melee = 1,
Ranged = 2,
All = 3
};
inline static LocationType get_role_usual_location(const Role& role)
{
switch (role) {
case Role::Warrior:
case Role::Pioneer:
case Role::Tank:
case Role::Special:
case Role::Drone:
return LocationType::Melee;
case Role::Medic:
case Role::Sniper:
case Role::Caster:
case Role::Support:
return LocationType::Ranged;
default:
return LocationType::None;
if (_Str == "all") {
return Category::All;
}
}
struct DeploymentOper
if (_Str == "any") {
return Category::Any;
}
if (_Str == "not") {
return Category::Not;
}
return Category::None;
}
};
// 用于定义延时信息,每个命令都有
struct DelayInfo
{
int pre_delay = 0; // 执行动作前的延时
int post_delay = 0; // 执行动作之后的延时
int time_out = INT_MAX; // TODO
};
// 定义干员操作信息
struct AvatarInfo
{
std::string name; // 目标名,若 type >= SwitchSpeed, name 为空
Point location; // 目标定位
DeployDirection direction = DeployDirection::Right; // 目标朝向
SkillUsage modify_usage = SkillUsage::NotUse; // 目标的技能使用策略
int modify_times = 1; // 更改使用技能的次数,默认为 1兼容曾经的作业
};
// 定义文本说明信息
struct TextInfo
{
std::string doc; // 文本
std::string doc_color; // 文本颜色
};
// 定义case操作需要的信息
struct CaseInfo
{
std::string group_select; // 选定的干员组
std::map<std::string, std::vector<ActionPtr>> dispatch_actions; // 对应干员的动作
std::vector<ActionPtr> default_action; // 默认的动作
};
// 定义loop操作需要的信息
struct LoopInfo
{
TriggerInfo end_info;
TriggerInfo continue_info;
TriggerInfo break_info;
int counter = 0; // 计数器
std::vector<ActionPtr> loop_actions;
};
// 定义check操作需要的信息
struct CheckInfo
{
TriggerInfo condition_info;
std::vector<ActionPtr> then_actions; // 当条件满足时执行
std::vector<ActionPtr> else_actions; // 当条件不满足时执行
};
// 定义until操作需要的信息
struct UntilInfo
{
TriggerInfo::Category mode = TriggerInfo::Category::All; // all 全部命令执行完毕后才结束, any 只要有一个执行就结束
std::vector<ActionPtr> candidate; // 备用的命令序列
};
// 定义point操作需要的信息
struct PointInfo
{
struct SnapShot
{
size_t index = 0;
Role role = Role::Unknown;
int cost = 0;
bool available = false;
bool cooling = false;
Rect rect;
cv::Mat avatar;
std::string name;
LocationType location_type = LocationType::None;
bool is_unusual_location = false; // 地面辅助,高台先锋等
};
struct OperProps
{
std::string id;
std::string name;
std::string name_en;
std::string name_jp;
std::string name_kr;
std::string name_tw;
Role role = Role::Unknown;
std::array<std::string, 3> ranges;
int rarity = 0;
LocationType location_type = LocationType::None;
std::vector<std::string> tokens; // 召唤物名字
int kills = TriggerInfo::DEACTIVE_KILLS;
int cost = TriggerInfo::DEACTIVE_COST;
int64_t cooling_count = TriggerInfo::DEACTIVE_COOLING;
int interval = 0;
std::chrono::steady_clock::time_point tNow;
SnapShot() {}
};
using AttackRange = std::vector<Point>;
using RoleCounts = std::unordered_map<Role, int>;
std::string target_code;
TriggerInfo::Category mode = TriggerInfo::Category::All;
std::pair<SnapShot, SnapShot> range;
namespace copilot
std::vector<ActionPtr> then_actions; // 当条件满足时执行
std::vector<ActionPtr> else_actions; // 当条件不满足时执行
PointInfo() {}
};
struct CheckIfStartOverInfo
{
std::string name;
RoleCounts role_counts; // 角色统计
CheckIfStartOverInfo() = default;
explicit CheckIfStartOverInfo(RoleCounts&& _Counts) noexcept :
role_counts(std::move(_Counts))
{
using OperUsageGroups = std::unordered_map<std::string, std::vector<OperUsage>>;
}
};
enum class ActionType
{
Deploy, // 部署干员
UseSkill, // 开技能
Retreat, // 撤退干员
SkillUsage, // 技能用法
SwitchSpeed, // 切换二倍速
BulletTime, // 使用 1/5 的速度
Output, // 仅输出,什么都不操作,界面上也不显示
SkillDaemon, // 什么都不做,有技能开技能,直到战斗结束
struct MoveCameraInfo
{
std::pair<double, double> distance;
/* for TRN */
MoveCamera, // 引航者试炼,移动镜头
/* for SSS */
DrawCard, // “调配干员”
CheckIfStartOver, // 检查如果没有某干员则退出重开
};
struct Action
{
int kills = 0;
int costs = 0;
int cost_changes = 0;
int cooling = 0;
ActionType type = ActionType::Deploy;
std::string name; // 目标名,若 type >= SwitchSpeed, name 为空
Point location;
DeployDirection direction = DeployDirection::Right;
SkillUsage modify_usage = SkillUsage::NotUse;
int modify_times = 1; // 更改使用技能的次数,默认为 1兼容曾经的作业
int pre_delay = 0;
int post_delay = 0;
int time_out = INT_MAX; // TODO
std::string doc;
std::string doc_color;
RoleCounts role_counts;
std::pair<double, double> distance;
};
struct BasicInfo
{
std::string stage_name;
std::string minimum_required;
std::string title;
std::string title_color;
std::string details;
std::string details_color;
};
struct CombatData // 作业 JSON 数据
{
BasicInfo info;
OperUsageGroups groups;
std::vector<Action> actions;
};
} // namespace copilot
namespace sss // 保全派驻
explicit MoveCameraInfo(decltype(distance)&& _Dist) noexcept :
distance(std::move(_Dist))
{
struct Strategy
{
std::string core;
RoleCounts tool_men;
Point location;
DeployDirection direction = DeployDirection::None;
};
}
};
struct CombatData : public copilot::CombatData
{
std::vector<Strategy> strategies;
bool draw_as_possible = false;
int retry_times = 0;
std::vector<std::string> order_of_drops;
};
struct Action
{
ActionType type = ActionType::Deploy;
std::string point_code; // 锚点编码
TriggerInfo trigger; // 必须拥有,表示动作触发时的条件信息
DelayInfo delay; // 延时信息
TextInfo text; // 表示文本输出,用于命令提示亦或是调试输出
enum class EquipmentType
{
NotChoose,
A,
B,
};
// 为了便于内存管理使用variant管理额外的action信息通过type来进行还原
// AvatarInfo 表示干员或辅助装置的部署、撤退、技能策略更改等
// CaseInfo 表示对干员组内选择对不同干员进行分化动作
// LoopInfo 表示循环命令中对动作信息的相关设置
// CheckInfo 表示根据条件的成立于否来确认执行的命令序列
// UntilInfo 表示根据条件的成立于否来无序执行命令序列
std::variant<
std::monostate,
CheckIfStartOverInfo,
MoveCameraInfo,
AvatarInfo,
CaseInfo,
LoopInfo,
CheckInfo,
UntilInfo,
PointInfo>
payload; // 信息载荷
struct CompleteData
{
copilot::BasicInfo info;
// 是否携带干员信息
bool hasAvatarInfo() const noexcept { return std::holds_alternative<AvatarInfo>(payload); }
std::string buff;
std::vector<EquipmentType> equipment;
std::string strategy;
copilot::OperUsageGroups groups;
RoleCounts tool_men;
std::vector<std::string> order_of_drops;
std::unordered_set<std::string> blacklist;
std::unordered_map<std::string, CombatData> stages_data;
};
// 读取载荷
template <typename PayLoad_T>
auto getPayload() const noexcept -> PayLoad_T const&
{
return std::get<PayLoad_T>(payload);
}
namespace roguelike
{
struct ReplacementHome
{
Point location;
DeployDirection direction = DeployDirection::Right;
};
// 创建堆对象,由智能指针管理内存
static auto create() -> ActionPtr { return std::make_shared<Action>(); }
};
struct DeployInfoWithRank
{
Point location;
DeployDirection direction = DeployDirection::None;
int rank = 0;
int kill_lower_bound = 0;
int kill_upper_bound = 9999;
};
struct BasicInfo
{
std::string stage_name;
std::string minimum_required;
std::string title;
std::string title_color;
std::string details;
std::string details_color;
};
struct ForceDeployDirection
{
DeployDirection direction = DeployDirection::Right;
std::unordered_set<Role> role = {};
};
struct CombatData // 作业 JSON 数据
{
BasicInfo info;
OperUsageGroups groups;
std::vector<Action> actions;
};
} // namespace copilot
struct CombatData
{
std::string stage_name;
std::vector<ReplacementHome> replacement_home;
std::unordered_set<Point> blacklist_location;
std::unordered_map<Point, ForceDeployDirection> force_deploy_direction;
std::array<Role, 9> role_order = {};
bool use_dice_stage = true;
int stop_deploy_blocking_num = INT_MAX;
int force_deploy_air_defense_num = 0;
bool force_ban_medic = false;
std::unordered_map<std::string, std::vector<DeployInfoWithRank>> deploy_plan;
std::vector<DeployInfoWithRank> retreat_plan;
};
namespace sss // 保全派驻
{
struct Strategy
{
std::string core;
RoleCounts tool_men;
Point location;
DeployDirection direction = DeployDirection::None;
};
struct Recruitment
{
std::string name;
Rect rect;
int elite = 0;
int level = 0;
};
struct CombatData : public copilot::CombatData
{
std::vector<Strategy> strategies;
bool draw_as_possible = false;
int retry_times = 0;
std::vector<std::string> order_of_drops;
};
enum class SupportAnalyzeMode
{
ChooseSupportBtn,
AnalyzeChars,
RefreshSupportBtn
};
enum class EquipmentType
{
NotChoose,
A,
B,
};
struct RecruitSupportCharInfo
{
Recruitment oper_info;
bool is_friend = false; // 是否为好友助战
int max_elite = 0; // 两次招募后的实际精英化与等级
int max_level = 0;
};
struct CompleteData
{
copilot::BasicInfo info;
struct RefreshSupportInfo
{
Rect rect;
bool in_cooldown = false;
int remain_secs = 0; // 刷新冷却时间
};
} // namespace roguelike
std::string buff;
std::vector<EquipmentType> equipment;
std::string strategy;
copilot::OperUsageGroups groups;
RoleCounts tool_men;
std::vector<std::string> order_of_drops;
std::unordered_set<std::string> blacklist;
std::unordered_map<std::string, CombatData> stages_data;
};
}
namespace roguelike
{
struct ReplacementHome
{
Point location;
DeployDirection direction = DeployDirection::Right;
};
struct DeployInfoWithRank
{
Point location;
DeployDirection direction = DeployDirection::None;
int rank = 0;
int kill_lower_bound = 0;
int kill_upper_bound = 9999;
};
struct ForceDeployDirection
{
DeployDirection direction = DeployDirection::Right;
std::unordered_set<Role> role = {};
};
struct CombatData
{
std::string stage_name;
std::vector<ReplacementHome> replacement_home;
std::unordered_set<Point> blacklist_location;
std::unordered_map<Point, ForceDeployDirection> force_deploy_direction;
std::array<Role, 9> role_order = {};
bool use_dice_stage = true;
int stop_deploy_blocking_num = INT_MAX;
int force_deploy_air_defense_num = 0;
bool force_ban_medic = false;
std::unordered_map<std::string, std::vector<DeployInfoWithRank>> deploy_plan;
std::vector<DeployInfoWithRank> retreat_plan;
};
struct Recruitment
{
std::string name;
Rect rect;
int elite = 0;
int level = 0;
};
enum class SupportAnalyzeMode
{
ChooseSupportBtn,
AnalyzeChars,
RefreshSupportBtn
};
struct RecruitSupportCharInfo
{
Recruitment oper_info;
bool is_friend = false; // 是否为好友助战
int max_elite = 0; // 两次招募后的实际精英化与等级
int max_level = 0;
};
struct RefreshSupportInfo
{
Rect rect;
bool in_cooldown = false;
int remain_secs = 0; // 刷新冷却时间
};
} // namespace roguelike
} // namespace asst::battle

View File

@@ -113,6 +113,363 @@ asst::battle::copilot::OperUsageGroups asst::CopilotConfig::parse_groups(const j
return groups;
}
TriggerInfo asst::CopilotConfig::parse_trigger(const json::value& json)
{
TriggerInfo trigger;
trigger.kills = json.get("kills", TriggerInfo::DEACTIVE_KILLS);
trigger.costs = json.get("costs", TriggerInfo::DEACTIVE_COST);
trigger.cost_changes = json.get("cost_changes", TriggerInfo::DEACTIVE_COST_CHANGES);
trigger.cooling = json.get("cooling", TriggerInfo::DEACTIVE_COOLING);
trigger.count = json.get("count", TriggerInfo::DEACTIVE_COUNT);
if (auto category = json.find("category")) {
trigger.category = TriggerInfo::loadCategoryFrom(category.value().as_string());
}
return trigger;
}
bool asst::CopilotConfig::parse_action(const json::value& action_info, asst::battle::copilot::Action* _Out)
{
LogTraceFunction;
static const std::unordered_map<std::string, ActionType> ActionTypeMapping = {
{ "Deploy", ActionType::Deploy },
{ "DEPLOY", ActionType::Deploy },
{ "deploy", ActionType::Deploy },
{ "部署", ActionType::Deploy },
{ "Skill", ActionType::UseSkill },
{ "SKILL", ActionType::UseSkill },
{ "skill", ActionType::UseSkill },
{ "技能", ActionType::UseSkill },
{ "Retreat", ActionType::Retreat },
{ "RETREAT", ActionType::Retreat },
{ "retreat", ActionType::Retreat },
{ "撤退", ActionType::Retreat },
{ "SkillUsage", ActionType::SkillUsage },
{ "SKILLUSAGE", ActionType::SkillUsage },
{ "Skillusage", ActionType::SkillUsage },
{ "skillusage", ActionType::SkillUsage },
{ "技能用法", ActionType::SkillUsage },
{ "SpeedUp", ActionType::SwitchSpeed },
{ "SPEEDUP", ActionType::SwitchSpeed },
{ "Speedup", ActionType::SwitchSpeed },
{ "speedup", ActionType::SwitchSpeed },
{ "二倍速", ActionType::SwitchSpeed },
{ "BulletTime", ActionType::BulletTime },
{ "BULLETTIME", ActionType::BulletTime },
{ "Bullettime", ActionType::BulletTime },
{ "bullettime", ActionType::BulletTime },
{ "子弹时间", ActionType::BulletTime },
{ "Output", ActionType::Output },
{ "OUTPUT", ActionType::Output },
{ "output", ActionType::Output },
{ "输出", ActionType::Output },
{ "打印", ActionType::Output },
{ "SkillDaemon", ActionType::SkillDaemon },
{ "skilldaemon", ActionType::SkillDaemon },
{ "SKILLDAEMON", ActionType::SkillDaemon },
{ "Skilldaemon", ActionType::SkillDaemon },
{ "DoNothing", ActionType::SkillDaemon },
{ "摆完挂机", ActionType::SkillDaemon },
{ "开摆", ActionType::SkillDaemon },
{ "MoveCamera", ActionType::MoveCamera },
{ "movecamera", ActionType::MoveCamera },
{ "MOVECAMERA", ActionType::MoveCamera },
{ "Movecamera", ActionType::MoveCamera },
{ "移动镜头", ActionType::MoveCamera },
{ "DrawCard", ActionType::DrawCard },
{ "drawcard", ActionType::DrawCard },
{ "DRAWCARD", ActionType::DrawCard },
{ "Drawcard", ActionType::DrawCard },
{ "抽卡", ActionType::DrawCard },
{ "抽牌", ActionType::DrawCard },
{ "调配", ActionType::DrawCard },
{ "调配干员", ActionType::DrawCard },
{ "CheckIfStartOver", ActionType::CheckIfStartOver },
{ "Checkifstartover", ActionType::CheckIfStartOver },
{ "CHECKIFSTARTOVER", ActionType::CheckIfStartOver },
{ "checkifstartover", ActionType::CheckIfStartOver },
{ "检查重开", ActionType::CheckIfStartOver },
{ "Loop", ActionType::Loop },
{ "loop", ActionType::Loop },
{ "LOOP", ActionType::Loop },
{ "循环", ActionType::Loop },
{ "Case", ActionType::Case },
{ "case", ActionType::Case },
{ "CASE", ActionType::Case },
{ "派发", ActionType::Case },
{ "干员派发", ActionType::Case },
{ "Check", ActionType::Check },
{ "check", ActionType::Check },
{ "CHECK", ActionType::Check },
{ "确认", ActionType::Check },
{ "分支", ActionType::Check },
{ "Until", ActionType::Until },
{ "until", ActionType::Until },
{ "UNTIL", ActionType::Until },
{ "轮询", ActionType::Until },
{ "SavePoint", ActionType::SavePoint },
{ "savepoint", ActionType::SavePoint },
{ "SAVEPOINT", ActionType::SavePoint },
{ "锚点", ActionType::SavePoint },
{ "保存锚点", ActionType::SavePoint },
{ "SyncPoint", ActionType::SyncPoint },
{ "syncpoint", ActionType::SyncPoint },
{ "SYNCPOINT", ActionType::SyncPoint },
{ "同步锚点", ActionType::SyncPoint },
{ "CheckPoint", ActionType::CheckPoint },
{ "checkpoint", ActionType::CheckPoint },
{ "CHECKPOINT", ActionType::CheckPoint },
{ "检查锚点", ActionType::CheckPoint },
};
auto& action = (*_Out);
std::string type_str = action_info.get("type", "Deploy");
if (auto iter = ActionTypeMapping.find(type_str); iter != ActionTypeMapping.end()) {
action.type = iter->second;
}
else {
Log.warn("Unknown action type:", type_str);
return false;
}
// 解析锚点编码,可选
action.point_code = action_info.get("point_code", std::string());
// 解析动作触发信息
action.trigger = parse_trigger(action_info);
// 解析前置条件满足之后的前后时延
action.delay.pre_delay = action_info.get("pre_delay", 0);
auto post_delay_opt = action_info.find<int>("post_delay");
action.delay.post_delay = post_delay_opt ? *post_delay_opt : action_info.get("rear_delay", 0);
// 历史遗留字段,兼容一下
action.delay.time_out = action_info.get("timeout", INT_MAX);
// 解析行动的相关附加文本
action.text.doc = action_info.get("doc", std::string());
action.text.doc_color = action_info.get("doc_color", std::string());
// 根据动作的类型解析载荷
switch (action.type) {
case ActionType::Deploy:
case ActionType::UseSkill:
case ActionType::Retreat:
case ActionType::BulletTime:
case ActionType::SkillUsage: {
auto& avatar = action.payload.emplace<AvatarInfo>();
avatar.name = action_info.get("name", std::string());
avatar.location.x = action_info.get("location", 0, 0);
avatar.location.y = action_info.get("location", 1, 0);
avatar.direction = string_to_direction(action_info.get("direction", "Right"));
avatar.modify_usage = static_cast<battle::SkillUsage>(action_info.get("skill_usage", 0));
avatar.modify_times = action_info.get("skill_times", 1);
} break;
case ActionType::CheckIfStartOver: {
auto& info = action.payload.emplace<CheckIfStartOverInfo>();
info.name = action_info.get("name", std::string());
if (auto tool_men = action_info.find("tool_men")) {
info.role_counts = parse_role_counts(*tool_men);
}
} break;
case ActionType::MoveCamera: {
auto dist_arr = action_info.at("distance").as_array();
action.payload.emplace<MoveCameraInfo>(
std::pair<double, double>(dist_arr[0].as_double(), dist_arr[1].as_double()));
} break;
case ActionType::Loop: {
auto& loop = action.payload.emplace<LoopInfo>();
// 必选字段
loop.end_info = parse_trigger(action_info.at("end"));
if (loop.end_info.category == TriggerInfo::Category::None) {
// 默认使用all策略表示只有全部满足才算生效
loop.end_info.category = TriggerInfo::Category::All;
}
// 可选字段
if (auto t = action_info.find("continue")) {
loop.continue_info = parse_trigger(t.value());
if (loop.continue_info.category == TriggerInfo::Category::None) {
// 默认使用all策略表示只有全部满足才算生效
loop.continue_info.category = TriggerInfo::Category::All;
}
}
// 可选字段
if (auto t = action_info.find("break")) {
loop.break_info = parse_trigger(t.value());
if (loop.break_info.category == TriggerInfo::Category::None) {
// 默认使用all策略表示只有全部满足才算生效
loop.break_info.category = TriggerInfo::Category::All;
}
}
// 可选字段
if (auto t = action_info.find("loop_actions")) {
loop.loop_actions = parse_actions_ptr(t.value());
}
} break;
case ActionType::Case: {
auto& case_info = action.payload.emplace<CaseInfo>();
// 必选字段
case_info.group_select = action_info.at("select").as_string();
// 可选字段
if (auto t = action_info.find("dispatch_actions")) {
for (auto const& [name, batch] : t.value().as_object()) {
case_info.dispatch_actions.emplace(name, parse_actions_ptr(batch));
}
}
// 可选字段
if (auto t = action_info.find("default_action")) {
case_info.default_action = parse_actions_ptr(t.value());
}
} break;
case ActionType::Check: {
auto& check = action.payload.emplace<CheckInfo>();
// 必选字段
check.condition_info = parse_trigger(action_info.at("condition"));
if (check.condition_info.category == TriggerInfo::Category::None) {
// 默认使用all策略表示只有全部满足才算生效
check.condition_info.category = TriggerInfo::Category::All;
}
// 可选字段
if (auto t = action_info.find("then_actions")) {
check.then_actions = parse_actions_ptr(t.value());
}
// 可选字段
if (auto t = action_info.find("else_actions")) {
check.else_actions = parse_actions_ptr(t.value());
}
} break;
case ActionType::Until: {
auto& until = action.payload.emplace<UntilInfo>();
// 可选字段
if (auto t = action_info.find("mode")) {
until.mode = TriggerInfo::loadCategoryFrom(t.value().as_string());
}
switch (until.mode) {
case TriggerInfo::Category::Any:
case TriggerInfo::Category::All:
break;
default:
until.mode = TriggerInfo::Category::All;
break;
}
// 可选字段缺省值为0
if (auto t = action_info.find("limit")) {
action.trigger.count = t.value().as_integer();
}
else if (action.trigger.count == TriggerInfo::DEACTIVE_COUNT) {
action.trigger.count = 0;
}
// 可选字段
if (auto t = action_info.find("candidate_actions")) {
until.candidate = parse_actions_ptr(t.value());
}
} break;
case ActionType::SyncPoint:
case ActionType::CheckPoint: {
auto& point = action.payload.emplace<PointInfo>();
point.target_code = action_info.at("target_code").as_string();
point.mode = TriggerInfo::loadCategoryFrom(action_info.at("mode").as_string());
switch (point.mode) {
case TriggerInfo::Category::Any:
case TriggerInfo::Category::All:
case TriggerInfo::Category::Not:
case TriggerInfo::Category::Succ:
break;
default:
point.mode = TriggerInfo::Category::All;
break;
}
if (auto t = action_info.find("kill_range")) {
auto range = t.value().as_array();
point.range.first.kills = range[0].as_integer();
point.range.second.kills = range[1].as_integer();
}
if (auto t = action_info.find("cost_range")) {
auto range = t.value().as_array();
point.range.first.cost = range[0].as_integer();
point.range.second.cost = range[1].as_integer();
}
if (auto t = action_info.find("cooling_range")) {
auto range = t.value().as_array();
point.range.first.cooling_count = range[0].as_integer();
point.range.second.cooling_count = range[1].as_integer();
}
if (auto t = action_info.find("time_range")) {
auto range = t.value().as_array();
point.range.first.interval = range[0].as_integer();
point.range.second.interval = range[1].as_integer();
}
if (auto t = action_info.find("then_actions")) {
point.then_actions = parse_actions_ptr(t.value());
}
if (action.type == ActionType::CheckPoint) {
if (auto t = action_info.find("else_actions")) {
point.else_actions = parse_actions_ptr(t.value());
}
}
} break;
case ActionType::SwitchSpeed:
case ActionType::Output:
case ActionType::SkillDaemon:
case ActionType::DrawCard:
case ActionType::SavePoint:
[[fallthrough]];
default:
break;
}
return true;
}
std::vector<asst::battle::copilot::Action> asst::CopilotConfig::parse_actions(const json::value& json)
{
LogTraceFunction;
@@ -120,115 +477,10 @@ std::vector<asst::battle::copilot::Action> asst::CopilotConfig::parse_actions(co
std::vector<battle::copilot::Action> actions_list;
for (const auto& action_info : json.at("actions").as_array()) {
Action action;
static const std::unordered_map<std::string, ActionType> ActionTypeMapping = {
{ "Deploy", ActionType::Deploy },
{ "DEPLOY", ActionType::Deploy },
{ "deploy", ActionType::Deploy },
{ "部署", ActionType::Deploy },
{ "Skill", ActionType::UseSkill },
{ "SKILL", ActionType::UseSkill },
{ "skill", ActionType::UseSkill },
{ "技能", ActionType::UseSkill },
{ "Retreat", ActionType::Retreat },
{ "RETREAT", ActionType::Retreat },
{ "retreat", ActionType::Retreat },
{ "撤退", ActionType::Retreat },
{ "SpeedUp", ActionType::SwitchSpeed },
{ "SPEEDUP", ActionType::SwitchSpeed },
{ "Speedup", ActionType::SwitchSpeed },
{ "speedup", ActionType::SwitchSpeed },
{ "二倍速", ActionType::SwitchSpeed },
{ "BulletTime", ActionType::BulletTime },
{ "BULLETTIME", ActionType::BulletTime },
{ "Bullettime", ActionType::BulletTime },
{ "bullettime", ActionType::BulletTime },
{ "子弹时间", ActionType::BulletTime },
{ "SkillUsage", ActionType::SkillUsage },
{ "SKILLUSAGE", ActionType::SkillUsage },
{ "Skillusage", ActionType::SkillUsage },
{ "skillusage", ActionType::SkillUsage },
{ "技能用法", ActionType::SkillUsage },
{ "Output", ActionType::Output },
{ "OUTPUT", ActionType::Output },
{ "output", ActionType::Output },
{ "输出", ActionType::Output },
{ "打印", ActionType::Output },
{ "SkillDaemon", ActionType::SkillDaemon },
{ "skilldaemon", ActionType::SkillDaemon },
{ "SKILLDAEMON", ActionType::SkillDaemon },
{ "Skilldaemon", ActionType::SkillDaemon },
{ "DoNothing", ActionType::SkillDaemon },
{ "摆完挂机", ActionType::SkillDaemon },
{ "开摆", ActionType::SkillDaemon },
{ "MoveCamera", ActionType::MoveCamera },
{ "movecamera", ActionType::MoveCamera },
{ "MOVECAMERA", ActionType::MoveCamera },
{ "Movecamera", ActionType::MoveCamera },
{ "移动镜头", ActionType::MoveCamera },
{ "DrawCard", ActionType::DrawCard },
{ "drawcard", ActionType::DrawCard },
{ "DRAWCARD", ActionType::DrawCard },
{ "Drawcard", ActionType::DrawCard },
{ "抽卡", ActionType::DrawCard },
{ "抽牌", ActionType::DrawCard },
{ "调配", ActionType::DrawCard },
{ "调配干员", ActionType::DrawCard },
{ "CheckIfStartOver", ActionType::CheckIfStartOver },
{ "Checkifstartover", ActionType::CheckIfStartOver },
{ "CHECKIFSTARTOVER", ActionType::CheckIfStartOver },
{ "checkifstartover", ActionType::CheckIfStartOver },
{ "检查重开", ActionType::CheckIfStartOver },
};
std::string type_str = action_info.get("type", "Deploy");
if (auto iter = ActionTypeMapping.find(type_str); iter != ActionTypeMapping.end()) {
action.type = iter->second;
}
else {
Log.warn("Unknown action type:", type_str);
battle::copilot::Action action;
if (!parse_action(action_info, &action)) {
continue;
}
action.kills = action_info.get("kills", 0);
action.cost_changes = action_info.get("cost_changes", 0);
action.costs = action_info.get("costs", 0);
action.cooling = action_info.get("cooling", -1);
action.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 = string_to_direction(action_info.get("direction", "Right"));
action.modify_usage = static_cast<battle::SkillUsage>(action_info.get("skill_usage", 0));
action.modify_times = action_info.get("skill_times", 1);
action.pre_delay = action_info.get("pre_delay", 0);
auto post_delay_opt = action_info.find<int>("post_delay");
// 历史遗留字段,兼容一下
action.post_delay = post_delay_opt ? *post_delay_opt : action_info.get("rear_delay", 0);
action.time_out = action_info.get("timeout", INT_MAX);
action.doc = action_info.get("doc", std::string());
action.doc_color = action_info.get("doc_color", std::string());
if (action.type == ActionType::CheckIfStartOver) {
if (auto tool_men = action_info.find("tool_men")) {
action.role_counts = parse_role_counts(*tool_men);
}
}
else if (action.type == ActionType::MoveCamera) {
auto dist_arr = action_info.at("distance").as_array();
action.distance = std::make_pair(dist_arr[0].as_double(), dist_arr[1].as_double());
}
actions_list.emplace_back(std::move(action));
}
@@ -236,6 +488,24 @@ std::vector<asst::battle::copilot::Action> asst::CopilotConfig::parse_actions(co
return actions_list;
}
std::vector<asst::battle::copilot::ActionPtr> asst::CopilotConfig::parse_actions_ptr(const json::value& json)
{
LogTraceFunction;
std::vector<battle::copilot::ActionPtr> actions_list;
for (const auto& action_info : json.as_array()) {
battle::copilot::ActionPtr action = std::make_shared<battle::copilot::Action>();
if (!parse_action(action_info, action.get())) {
continue;
}
actions_list.emplace_back(action);
}
return actions_list;
}
asst::battle::RoleCounts asst::CopilotConfig::parse_role_counts(const json::value& json)
{
battle::RoleCounts counts;

View File

@@ -4,28 +4,33 @@
namespace asst
{
class CopilotConfig : public SingletonHolder<CopilotConfig>, public AbstractConfig
{
public:
static battle::copilot::BasicInfo parse_basic_info(const json::value& json);
static battle::copilot::OperUsageGroups parse_groups(const json::value& json);
static std::vector<battle::copilot::Action> parse_actions(const json::value& json);
static battle::RoleCounts parse_role_counts(const json::value& json);
static battle::DeployDirection string_to_direction(const std::string& str);
class CopilotConfig : public SingletonHolder<CopilotConfig>, public AbstractConfig
{
public:
static battle::copilot::BasicInfo parse_basic_info(const json::value& json);
static battle::copilot::OperUsageGroups parse_groups(const json::value& json);
static battle::copilot::TriggerInfo parse_trigger(const json::value& json);
static bool parse_action(const json::value& json, asst::battle::copilot::Action*);
static std::vector<battle::copilot::ActionPtr> parse_actions_ptr(const json::value& json);
static std::vector<battle::copilot::Action> parse_actions(const json::value& json);
static battle::RoleCounts parse_role_counts(const json::value& json);
static battle::DeployDirection string_to_direction(const std::string& str);
public:
virtual ~CopilotConfig() override = default;
public:
virtual ~CopilotConfig() override = default;
const battle::copilot::CombatData& get_data() const noexcept { return m_data; }
const std::string& get_stage_name() const noexcept { return m_data.info.stage_name; }
bool parse_magic_code(const std::string& copilot_magic_code);
void clear();
const battle::copilot::CombatData& get_data() const noexcept { return m_data; }
protected:
virtual bool parse(const json::value& json) override;
const std::string& get_stage_name() const noexcept { return m_data.info.stage_name; }
battle::copilot::CombatData m_data;
};
bool parse_magic_code(const std::string& copilot_magic_code);
void clear();
inline static auto& Copilot = CopilotConfig::get_instance();
protected:
virtual bool parse(const json::value& json) override;
battle::copilot::CombatData m_data;
};
inline static auto& Copilot = CopilotConfig::get_instance();
}

View File

@@ -14,88 +14,96 @@
namespace asst
{
class BattleHelper
{
public:
~BattleHelper() = default;
class BattleHelper
{
public:
~BattleHelper() = default;
protected:
BattleHelper(Assistant* inst);
protected:
BattleHelper(Assistant* inst);
virtual AbstractTask& this_task() = 0;
virtual AbstractTask& this_task() = 0;
virtual bool set_stage_name(const std::string& name);
virtual void clear();
virtual const std::string oper_name_ocr_task_name() const noexcept { return "BattleOperName"; }
virtual bool do_strategic_action(const cv::Mat& reusable = cv::Mat());
virtual bool set_stage_name(const std::string& name);
virtual void clear();
bool calc_tiles_info(const std::string& stage_name, double shift_x = 0, double shift_y = 0);
virtual const std::string oper_name_ocr_task_name() const noexcept { return "BattleOperName"; }
bool pause();
bool speed_up();
bool abandon();
virtual bool do_strategic_action(const cv::Mat& reusable = cv::Mat());
bool update_deployment(bool init = false, const cv::Mat& reusable = cv::Mat(), bool need_oper_cost = false);
bool update_kills(const cv::Mat& reusable = cv::Mat());
bool update_cost(const cv::Mat& reusable = cv::Mat());
bool calc_tiles_info(const std::string& stage_name, double shift_x = 0, double shift_y = 0);
bool deploy_oper(const std::string& name, const Point& loc, battle::DeployDirection direction);
bool retreat_oper(const std::string& name);
bool retreat_oper(const Point& loc, bool manually = true);
bool use_skill(const std::string& name, bool keep_waiting = true);
bool use_skill(const Point& loc, bool keep_waiting = true);
bool check_pause_button(const cv::Mat& reusable = cv::Mat());
bool check_skip_plot_button(const cv::Mat& reusable = cv::Mat());
bool check_in_speed_up(const cv::Mat& reusable = cv::Mat());
virtual bool check_in_battle(const cv::Mat& reusable = cv::Mat(), bool weak = true);
virtual bool wait_until_start(bool weak = true);
bool wait_until_end(bool weak = true);
bool use_all_ready_skill(const cv::Mat& reusable = cv::Mat());
bool check_and_use_skill(const std::string& name, bool& has_error, const cv::Mat& reusable = cv::Mat());
bool check_and_use_skill(const Point& loc, bool& has_error, const cv::Mat& reusable = cv::Mat());
void save_map(const cv::Mat& image);
bool pause();
bool speed_up();
bool abandon();
bool click_oper_on_deployment(const std::string& name);
bool click_oper_on_deployment(const Rect& rect);
bool click_oper_on_battlefield(const std::string& name);
bool click_oper_on_battlefield(const Point& loc);
bool click_retreat(); // 这个是不带识别的,直接点
bool click_skill(bool keep_waiting = true); // 这个是带识别的,转好了才点
bool cancel_oper_selection();
// 修正终点超出范围的滑动,纠正时是否需要顺时针旋转
void fix_swipe_out_of_limit(Point& p1, Point& p2, int width, int height, int max_distance = INT_MAX,
double radian = 0);
bool move_camera(const std::pair<double, double>& delta);
bool update_deployment(bool init = false, const cv::Mat& reusable = cv::Mat(), bool need_oper_cost = false);
bool update_kills(const cv::Mat& reusable = cv::Mat());
bool update_cost(const cv::Mat& reusable = cv::Mat());
std::string analyze_detail_page_oper_name(const cv::Mat& image);
bool deploy_oper(const std::string& name, const Point& loc, battle::DeployDirection direction);
bool retreat_oper(const std::string& name);
bool retreat_oper(const Point& loc, bool manually = true);
bool use_skill(const std::string& name, bool keep_waiting = true);
bool use_skill(const Point& loc, bool keep_waiting = true);
bool check_pause_button(const cv::Mat& reusable = cv::Mat());
bool check_skip_plot_button(const cv::Mat& reusable = cv::Mat());
bool check_in_speed_up(const cv::Mat& reusable = cv::Mat());
virtual bool check_in_battle(const cv::Mat& reusable = cv::Mat(), bool weak = true);
virtual bool wait_until_start(bool weak = true);
bool wait_until_end(bool weak = true);
bool use_all_ready_skill(const cv::Mat& reusable = cv::Mat());
bool check_and_use_skill(const std::string& name, bool& has_error, const cv::Mat& reusable = cv::Mat());
bool check_and_use_skill(const Point& loc, bool& has_error, const cv::Mat& reusable = cv::Mat());
void save_map(const cv::Mat& image);
std::optional<Rect> get_oper_rect_on_deployment(const std::string& name) const;
bool click_oper_on_deployment(const std::string& name);
bool click_oper_on_deployment(const Rect& rect);
bool click_oper_on_battlefield(const std::string& name);
bool click_oper_on_battlefield(const Point& loc);
bool click_retreat(); // 这个是不带识别的,直接点
bool click_skill(bool keep_waiting = true); // 这个是带识别的,转好了才点
bool cancel_oper_selection();
// 修正终点超出范围的滑动,纠正时是否需要顺时针旋转
void fix_swipe_out_of_limit(
Point& p1,
Point& p2,
int width,
int height,
int max_distance = INT_MAX,
double radian = 0);
bool move_camera(const std::pair<double, double>& delta);
std::string m_stage_name;
Map::Level m_map_data;
std::unordered_map<Point, TilePack::TileInfo> m_side_tile_info; // 子弹时间的坐标映射
std::unordered_map<Point, TilePack::TileInfo> m_normal_tile_info; // 正常的坐标映射
Point m_skill_button_pos;
Point m_retreat_button_pos;
std::unordered_map<std::string, battle::SkillUsage> m_skill_usage;
std::unordered_map<std::string, int> m_skill_times;
std::unordered_map<std::string, int> m_skill_error_count;
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_last_use_skill_time;
int m_camera_count = 0;
std::pair<double, double> m_camera_shift = { 0., 0. };
std::string analyze_detail_page_oper_name(const cv::Mat& image);
/* 实时更新的数据 */
bool m_in_battle = false;
int m_kills = 0;
int m_total_kills = 0;
int m_cost = 0;
std::optional<Rect> get_oper_rect_on_deployment(const std::string& name) const;
std::vector<battle::DeploymentOper> m_cur_deployment_opers;
std::string m_stage_name;
Map::Level m_map_data;
std::unordered_map<Point, TilePack::TileInfo> m_side_tile_info; // 子弹时间的坐标映射
std::unordered_map<Point, TilePack::TileInfo> m_normal_tile_info; // 正常的坐标映射
Point m_skill_button_pos;
Point m_retreat_button_pos;
std::unordered_map<std::string, battle::SkillUsage> m_skill_usage;
std::unordered_map<std::string, int> m_skill_times;
std::unordered_map<std::string, int> m_skill_error_count;
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_last_use_skill_time;
int m_camera_count = 0;
std::pair<double, double> m_camera_shift = { 0., 0. };
std::map<std::string, Point> m_battlefield_opers;
std::map<Point, std::string> m_used_tiles;
/* 实时更新的数据 */
bool m_in_battle = false;
int m_total_kills = 0;
int m_kills = 0;
int m_cost = 0;
int m_cooling_count = 0;
private:
InstHelper m_inst_helper;
};
std::vector<battle::DeploymentOper> m_cur_deployment_opers;
std::map<std::string, Point> m_battlefield_opers;
std::map<Point, std::string> m_used_tiles;
private:
InstHelper m_inst_helper;
};
} // namespace asst

File diff suppressed because it is too large Load Diff

View File

@@ -8,45 +8,78 @@
namespace asst
{
class BattleProcessTask : public AbstractTask, public BattleHelper
class BattleProcessTask : public AbstractTask, public BattleHelper
{
public:
BattleProcessTask(const AsstCallback& callback, Assistant* inst, std::string_view task_chain);
virtual ~BattleProcessTask() override = default;
virtual bool set_stage_name(const std::string& stage_name) override;
void set_wait_until_end(bool wait_until_end);
void set_formation_task_ptr(std::shared_ptr<std::unordered_map<std::string, std::string>> value);
protected:
virtual bool _run() override;
virtual AbstractTask& this_task() override { return *this; }
virtual void clear() override;
virtual bool
do_derived_action([[maybe_unused]] const battle::copilot::Action& action, [[maybe_unused]] size_t index)
{
public:
BattleProcessTask(const AsstCallback& callback, Assistant* inst, std::string_view task_chain);
virtual ~BattleProcessTask() override = default;
return false;
}
virtual bool set_stage_name(const std::string& stage_name) override;
void set_wait_until_end(bool wait_until_end);
void set_formation_task_ptr(std::shared_ptr<std::unordered_map<std::string, std::string>> value);
virtual battle::copilot::CombatData& get_combat_data() { return m_combat_data; }
protected:
virtual bool _run() override;
virtual AbstractTask& this_task() override { return *this; }
virtual void clear() override;
virtual bool need_to_wait_until_end() const { return m_need_to_wait_until_end; }
virtual bool do_derived_action([[maybe_unused]] const battle::copilot::Action& action,
[[maybe_unused]] size_t index)
{
return false;
}
virtual battle::copilot::CombatData& get_combat_data() { return m_combat_data; }
virtual bool need_to_wait_until_end() const { return m_need_to_wait_until_end; }
bool to_group();
bool to_group();
bool do_action(const battle::copilot::Action& action, size_t index);
// 考虑到程序共用部分把actions执行部分抽取出来
bool do_action(const battle::copilot::Action& action, [[maybe_unused]] size_t index);
const std::string& get_name_from_group(const std::string& action_name);
void notify_action(const battle::copilot::Action& action);
bool wait_condition(const battle::copilot::Action& action);
bool enter_bullet_time(const std::string& name, const Point& location);
void sleep_and_do_strategy(unsigned millisecond);
// 阻塞式判断action命令中的前置条件是否满足阻塞至满足才执行后续的操作
bool do_action_sync(const battle::copilot::Action& action, [[maybe_unused]] size_t index);
virtual bool check_in_battle(const cv::Mat& reusable = cv::Mat(), bool weak = true) override;
// 非阻塞式判断action命令中的前置条件是否满足如果条件满足执行后续的操作否则返回false
bool do_action_async(const battle::copilot::Action& action, [[maybe_unused]] size_t index);
battle::copilot::CombatData m_combat_data;
std::unordered_map</*group*/ std::string, /*oper*/ std::string> m_oper_in_group;
const std::string& get_name_from_group(const std::string& action_name);
void notify_action(const battle::copilot::Action& action);
bool wait_operator_ready(const battle::copilot::Action& action);
void update_image_if_empty(cv::Mat* _Image);
void do_strategy_and_update_image(cv::Mat* _Image);
bool wait_condition_not(const battle::copilot::Action& action); // 等待所有条件都不满足
bool wait_condition_all(const battle::copilot::Action& action); // 等待所有条件都满足
bool wait_condition_any(const battle::copilot::Action& action); // 等待某个条件都满足
bool check_condition_not(const battle::copilot::TriggerInfo& action); // 所有条件都不满足
bool check_condition_all(const battle::copilot::TriggerInfo& action); // 所有条件都满足
bool check_condition_any(const battle::copilot::TriggerInfo& action); // 某个条件都满足
bool check_condition(const battle::copilot::TriggerInfo& action);
bool m_in_bullet_time = false;
bool m_need_to_wait_until_end = false;
std::shared_ptr<std::unordered_map<std::string, std::string>> m_formation_ptr = nullptr;
};
bool check_point(const battle::copilot::PointInfo& _Current);
bool check_point_not(const battle::copilot::PointInfo& _Current);
bool check_point_all(const battle::copilot::PointInfo& _Current);
bool check_point_any(const battle::copilot::PointInfo& _Current);
bool enter_bullet_time(const std::string& name, const Point& location);
void sleep_and_do_strategy(unsigned millisecond);
auto gen_snap_shot() -> battle::copilot::PointInfo::SnapShot;
bool find_snap_shot(const std::string& code) const noexcept;
void save_snap_shot(const std::string& code);
auto get_snap_shot(const std::string& code) const noexcept -> battle::copilot::PointInfo::SnapShot const&;
virtual bool check_in_battle(const cv::Mat& reusable = cv::Mat(), bool weak = true) override;
battle::copilot::CombatData m_combat_data;
std::unordered_map</*group*/ std::string, /*oper*/ std::string> m_oper_in_group;
std::map<std::string, battle::copilot::PointInfo::SnapShot> m_snap_shots; // 在运行过程中存储的快照信息
bool m_in_bullet_time = false;
bool m_need_to_wait_until_end = false;
std::shared_ptr<std::unordered_map<std::string, std::string>> m_formation_ptr = nullptr;
};
}

View File

@@ -19,11 +19,14 @@ bool asst::SSSBattleProcessTask::set_stage_name(const std::string& stage_name)
return false;
}
m_sss_combat_data = SSSCopilot.get_data(stage_name);
ranges::transform(m_sss_combat_data.strategies, std::inserter(m_all_cores, m_all_cores.begin()),
[](const auto& strategy) { return strategy.core; });
ranges::transform(
m_sss_combat_data.strategies,
std::inserter(m_all_cores, m_all_cores.begin()),
[](const auto& strategy) { return strategy.core; });
for (const auto& action : m_sss_combat_data.actions) {
if (action.type == battle::copilot::ActionType::Deploy) {
m_all_action_opers.emplace(action.name);
auto& info = action.getPayload<battle::copilot::AvatarInfo>();
m_all_action_opers.emplace(info.name);
}
}
@@ -58,7 +61,8 @@ bool asst::SSSBattleProcessTask::update_deployment_with_skip(const cv::Mat& reus
}
if (ranges::equal(
m_cur_deployment_opers, old_deployment_opers,
m_cur_deployment_opers,
old_deployment_opers,
[](const DeploymentOper& oper1, const DeploymentOper& oper2) { return oper1.name == oper2.name; })) {
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_same_time).count() > 30000) {
// 30s 能回 60 费,基本上已经到了挂机的时候,放缓检查的速度
@@ -100,8 +104,7 @@ bool asst::SSSBattleProcessTask::do_strategic_action(const cv::Mat& reusable)
static const auto min_frame_interval = std::chrono::milliseconds(Config.get_options().sss_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));
}
@@ -145,8 +148,9 @@ bool asst::SSSBattleProcessTask::wait_until_start(bool weak)
}
else {
replace_count = 4;
if (ranges::count_if(m_cur_deployment_opers,
[](const auto& oper) { return oper.role == Role::Pioneer; }) /* 先锋数量 */
if (ranges::count_if(
m_cur_deployment_opers,
[](const auto& oper) { return oper.role == Role::Pioneer; }) /* 先锋数量 */
< 2) {
cost_limit = 25; // 先锋低于2个时降低费用阈值以试图换出先锋
}
@@ -219,8 +223,9 @@ bool asst::SSSBattleProcessTask::check_and_do_strategy(const cv::Mat& reusable)
Role role_for_lambda = role;
// 如果有可用的干员,直接使用
auto available_iter = ranges::find_if(
tool_men, [&](const DeploymentOper& oper) { return oper.available && oper.role == role_for_lambda; });
auto available_iter = ranges::find_if(tool_men, [&](const DeploymentOper& oper) {
return oper.available && oper.role == role_for_lambda;
});
if (available_iter != tool_men.cend()) {
--quantity;
// 部署完,画面会发生变化,所以直接返回,后续逻辑交给下次循环处理
@@ -252,18 +257,19 @@ bool asst::SSSBattleProcessTask::check_if_start_over(const battle::copilot::Acti
update_deployment();
bool to_abandon = false;
auto& info = action.getPayload<battle::copilot::CheckIfStartOverInfo>();
if (!action.name.empty() &&
!ranges::any_of(m_cur_deployment_opers, [&](const auto& oper) { return oper.name == action.name; }) &&
!m_battlefield_opers.contains(action.name)) {
if (!info.name.empty() &&
!ranges::any_of(m_cur_deployment_opers, [&](const auto& oper) { return oper.name == info.name; }) &&
!m_battlefield_opers.contains(info.name)) {
to_abandon = true;
}
else if (!action.role_counts.empty()) {
else if (!info.role_counts.empty()) {
std::unordered_map<Role, size_t> cur_counts;
for (const auto& oper : m_cur_deployment_opers) {
cur_counts[oper.role] += 1;
}
for (const auto& [role, number] : action.role_counts) {
for (const auto& [role, number] : info.role_counts) {
if (cur_counts[role] < static_cast<size_t>(number)) {
to_abandon = true;
break;