Merge branch 'dev' into feat/Helloworld/tools-copilot-parse-groups

# Conflicts:
#	tools/CopilotDesinger/自动战斗作业生成器.html
This commit is contained in:
Helloworld
2022-05-29 22:34:52 +08:00
14 changed files with 521 additions and 384 deletions

View File

@@ -59,7 +59,11 @@
// "二倍速" 是可切换的,即使用一次变成二倍速,再次使用又变回一倍速
// "子弹时间" 即点击任意干员后的 1/5 速度,再进行任意 action 会恢复正常速度
"kills": 0, // 击杀数条件,如果没达到就一直等待。可选,默认为 0直接执行
"kills": 0, // 击杀数条件,如果没达到就一直等待。可选,默认为 0直接执行
"cost_changes": 5, // 费用变化量,如果没达到就一直等待。可选,默认为 0直接执行
// 注意是从开始执行本 actions 开始计算的(即前一个 action 结束时的费用作为基准)
// 另外仅在费用是两位数的时候识别的比较准,三位数的费用可能会识别错,不推荐使用
// TODO: 其他条件
// TODO: "condition_type": 0, // 执行条件间的关系,可选,默认 0
// // 0 - 且; 1 - 或
@@ -67,8 +71,10 @@
"name": "棘刺", // 干员名 或 群组名, type 为 "部署" | "技能" | "撤退" 之一时必选
"location": [ 5, 5 ], // 部署干员的位置。
// type 为 "部署" 时必选
// type 为 `撤退` 时可选(仅推荐部署多个同名召唤物时使用 location 进行撤退,正常部署的干员请使用 name 进行撤退)
// type 为 "部署" 时必选
// type 为 `撤退` 时可选
// 仅推荐有多个同名召唤物时,不填写 name, 并使用 location 进行撤退。
// 正常部署的干员请使用 name 进行撤退
"direction": "左", // 部署干员的干员朝向。 type 为 "部署" 时必选
// "Left" | "Right" | "Up" | "Down" | "None"
@@ -82,7 +88,8 @@
"pre_delay": 0, // 前置延时。可选,默认 0, 单位毫秒
"rear_delay": 0, // 后置延时。可选,默认 0, 单位毫秒
"timeout": 999999999, // 超时时间。当 type 为 "部署" | "技能" 时可选。默认 INT_MAX, 单位毫秒
// "timeout": 999999999, // 保留字段,暂未实现。
// 超时时间。当 type 为 "部署" | "技能" 时可选。默认 INT_MAX, 单位毫秒
// 等待超时则放弃当前动作, 转而执行下一个动作
"doc": "下棘刺了!", // 描述,可选。会显示在界面上,没有实际作用
@@ -94,6 +101,7 @@
"location": [ 5, 6 ],
"direction": "右"
},
// 举例 2
{
"name": "史尔特尔",
"location": [ 4, 5 ],
@@ -101,14 +109,13 @@
"doc": "你史尔特尔奶奶来啦!",
"doc_color": "red"
},
// 举例 2
// 举例 3
{
"type": "二倍速"
}
],
"minimum_required": "v4.0", // 最低要求 maa 版本号,必选。保留字段
"doc": { // 描述,可选,保留字段
// 后期完成了作业分享平台后,可能直接展示该字段给其他用户看
"minimum_required": "v4.0", // 最低要求 maa 版本号,必选。保留字段,暂未实现
"doc": { // 描述,可选。
"title": "低练度高成功率作业",
"title_color": "dark",
"details": "对练度要求很低balabala……", // 建议在这里写上你的名字!(作者名)、参考的视频攻略链接等

View File

@@ -6,8 +6,8 @@
- 若选项中没有你需要的关卡,请手动进入游戏 **蓝色开始按钮** 界面,并选择 `当前关卡`
- 主界面上的 `吃理智``吃石头``指定次数` 三个选项为短路开关,即三个选项中的任一条件达到,均会视为任务完成,停止刷理智。
- 举例1设置 `吃理智药` : `999``吃石头` : `10``指定次数` : `1`。则在刷完 **一次** 后,由于满足了 `指定次数`:`1` 的条件,视为任务完成,停止刷理智。
- 举例2不勾选 `吃理智药`、不勾选 `吃石头`,设置 `指定次数` : `100`。则在当前可用理智全部刷完后(可能只刷了几次),由于满足了 `不吃理智药``不吃石头` 的条件,视为任务完成,停止刷理智。
- 举例1设置 `吃理智药` : `999``吃石头` : `10``指定次数` : `1`。则在刷完 **一次** 后,由于满足了 `指定次数`:`1` 的条件,视为任务完成,停止刷理智。
- 举例2不勾选 `吃理智药`、不勾选 `吃石头`,设置 `指定次数` : `100`。则在当前可用理智全部刷完后(可能只刷了几次),由于满足了 `不吃理智药``不吃石头` 的条件,视为任务完成,停止刷理智。
- 刷完自动上传 [企鹅物流数据统计](https://penguin-stats.cn/)
- 可自定义企鹅物流 ID
- 识别并显示材料掉落
@@ -44,10 +44,12 @@
### 自动肉鸽功能
- 设置中的三个选项
- 尽可能往后打:用于刷蜡烛
- 刷源石锭投资,第一层商店后直接退出:刷源石锭最高效的模式,建议手动选择 `古堡观光` (简单)模式
- 刷源石锭投资,投资后退出:兼顾刷蜡烛和刷源石锭投资的模式
- 设置中的三个选项
- 尽可能往后打:用于刷蜡烛
- 刷源石锭投资,第一层商店后直接退出:刷源石锭最高效的模式,建议手动选择 `古堡观光` (简单)模式
- 刷源石锭投资,投资后退出:兼顾刷蜡烛和刷源石锭投资的模式
- 肉鸽已支持自动识别干员及练度,并自动选择较优干员及技能
- 支持掉线重连、支持凌晨 4 点更新后继续回去刷
- 如果软件出现 bug 卡住了,会自动放弃当次探索并重试。但如果经常在某个地方卡住然后放弃、严重影响效率的,欢迎提个 issue 反馈一下~
@@ -55,6 +57,7 @@
### 自动抄作业
欢迎加作业分享 Q 群: 912322156
#### 抄作业
- 本功能需要在有 `开始行动` 的关卡入口界面开始运行
@@ -64,6 +67,7 @@
- exe 同目录下有简易的制作器,可以结合 [文档](战斗流程协议.md) 使用
- `stage_name` 需要是关卡中文名(但危机合约的关卡又需要是关卡 id, 目前有点乱,之后版本会想办法统一下)
- 地图坐标获取:填写 `stage_name` 后直接开一局,会在目录下生成 `map.png`, 可以用于参考。后面会想办法直接在制作器上显示坐标(画饼)
- 若某个干员名字一直识别不对,可以打开日志 `asst.log` 看一下到底被识别成了什么,并修改 `resource/tasks.json` - `CharsNameOcrReplace` 进行正则替换
- 推荐在作业描述里填上你自己的名字(作者名)、参考的攻略视频 url, 其他想说的等等(

View File

@@ -4647,10 +4647,10 @@
"algorithm": "OcrDetect",
"text": [],
"roi": [
1197,
492,
1196,
494,
83,
48
44
]
},
"BattleAutoSkillFlag": {

View File

@@ -54,6 +54,7 @@ namespace asst
struct BattleAction // 操作
{
int kills = 0;
int cost_changes = 0;
BattleActionType type = BattleActionType::Deploy;
std::string group_name; // 目标名,若 type >= SwitchSpeed, group_name 为空
Point location;

View File

@@ -5,6 +5,7 @@
#include "MultiMatchImageAnalyzer.h"
#include "MatchImageAnalyzer.h"
#include "OcrImageAnalyzer.h"
#include "OcrWithPreprocessImageAnalyzer.h"
#include "HashImageAnalyzer.h"
#include "Logger.hpp"
#include "TaskData.h"
@@ -86,6 +87,11 @@ int asst::BattleImageAnalyzer::get_kills() const noexcept
return m_kills;
}
int asst::BattleImageAnalyzer::get_cost() const noexcept
{
return m_cost;
}
void asst::BattleImageAnalyzer::clear() noexcept
{
m_opers.clear();
@@ -468,14 +474,15 @@ bool asst::BattleImageAnalyzer::kills_analyze()
bool asst::BattleImageAnalyzer::cost_analyze()
{
OcrImageAnalyzer cost_analyzer(m_image);
cost_analyzer.set_task_info("BattleKillsFlag");
OcrWithPreprocessImageAnalyzer cost_analyzer(m_image);
cost_analyzer.set_task_info("BattleCostData");
cost_analyzer.set_replace(
std::dynamic_pointer_cast<OcrTaskInfo>(Task.get("NumberOcrReplace"))->replace_map);
if (!cost_analyzer.analyze()) {
return false;
}
cost_analyzer.sort_result_by_score();
std::string cost_str = cost_analyzer.get_result().front().text;
if (cost_str.empty() ||

View File

@@ -34,6 +34,7 @@ namespace asst
const std::vector<Rect>& get_ready_skills() const noexcept;
int get_hp() const noexcept;
int get_kills() const noexcept;
int get_cost() const noexcept;
void clear() noexcept;

View File

@@ -333,13 +333,37 @@ bool asst::BattleProcessTask::do_action(const BattleAction& action)
bool asst::BattleProcessTask::wait_condition(const BattleAction& action)
{
cv::Mat image;
// 因为要算基准cost所以这个要放在kills前面
if (action.cost_changes != 0) {
int cost_base = -1;
while (true) {
image = m_ctrler->get_image();
BattleImageAnalyzer analyzer(image);
analyzer.set_target(BattleImageAnalyzer::Target::Cost);
if (analyzer.analyze()) {
int cost = analyzer.get_cost();
if (cost_base == -1) {
cost_base = cost;
continue;
}
if (cost >= cost_base + action.cost_changes) {
break;
}
}
try_possible_skill(image);
std::this_thread::yield();
}
}
while (m_kills < action.kills) {
if (need_exit()) {
return false;
}
image = m_ctrler->get_image();
BattleImageAnalyzer analyzer(image);
analyzer.set_target(BattleImageAnalyzer::Target::Kills);
if (analyzer.analyze()) {
m_kills = analyzer.get_kills();

View File

@@ -90,6 +90,7 @@ bool asst::CopilotConfiger::parse(const json::value& json)
action.type = BattleActionType::Deploy;
}
action.kills = action_info.get("kills", 0);
action.cost_changes = action_info.get("cost_changes", 0);
action.group_name = action_info.get("name", std::string());
action.location.x = action_info.get("location", 0, 0);

View File

@@ -100,6 +100,25 @@ bool asst::MatchImageAnalyzer::match_templ(const cv::Mat templ)
{
cv::Mat matched;
Log.trace(__FUNCTION__, "raw_roi", m_roi.to_string());
if (m_roi.x < 0) {
Log.info(__FUNCTION__, "roi is out of range");
m_roi.x = 0;
}
if (m_roi.y < 0) {
Log.info(__FUNCTION__, "roi is out of range");
m_roi.y = 0;
}
if (m_roi.x + m_roi.width >= m_image.cols) {
Log.info(__FUNCTION__, "roi is out of range");
m_roi.width = m_image.cols - m_roi.x;
}
if (m_roi.y + m_roi.height >= m_image.rows) {
Log.info(__FUNCTION__, "roi is out of range");
m_roi.height = m_image.rows - m_roi.y;
}
cv::Mat image_roi = m_image(utils::make_rect<cv::Rect>(m_roi));
if (templ.cols > image_roi.cols || templ.rows > image_roi.rows) {
Log.error("templ size is too large", m_templ_name,

View File

@@ -60,6 +60,8 @@ bool asst::StageDropsImageAnalyzer::analyze_stage_code()
}
m_stage_code = analyzer.get_result().front().text;
Log.info(__FUNCTION__, "stage_code", m_stage_code);
#ifdef ASST_DEBUG
const Rect& text_rect = analyzer.get_result().front().rect;
cv::rectangle(m_image_draw, utils::make_rect<cv::Rect>(text_rect), cv::Scalar(0, 0, 255), 2);
@@ -104,6 +106,8 @@ bool asst::StageDropsImageAnalyzer::analyze_stars()
}
m_stars = matched_stars;
Log.info(__FUNCTION__, "stars", m_stars);
#ifdef ASST_DEBUG
cv::rectangle(m_image_draw, utils::make_rect<cv::Rect>(matched_rect), cv::Scalar(0, 0, 255), 2);
cv::putText(m_image_draw, std::to_string(m_stars) + " stars", cv::Point(matched_rect.x, matched_rect.y + matched_rect.height + 20),
@@ -150,6 +154,7 @@ bool asst::StageDropsImageAnalyzer::analyze_difficulty()
}
}
m_difficulty = matched;
Log.info(__FUNCTION__, "difficulty", static_cast<int>(m_difficulty));
#ifdef ASST_DEBUG
cv::rectangle(m_image_draw, utils::make_rect<cv::Rect>(matched_rect), cv::Scalar(0, 0, 255), 2);
@@ -285,6 +290,10 @@ bool asst::StageDropsImageAnalyzer::analyze_baseline()
m_baseline.emplace_back(baseline, match_droptype(baseline));
}
}
Log.trace(__FUNCTION__, "baseline size", m_baseline.size());
for (const auto& baseline : m_baseline) {
Log.trace(__FUNCTION__, "baseline", baseline.first.to_string());
}
return !m_baseline.empty();
}

View File

@@ -1,354 +1,404 @@
from __future__ import annotations
import json
# ======================= constants begin =========================
class Direction:
UP = ""
DOWN = ""
RIGHT = ""
LEFT = ""
class ActionType:
Deploy = "部署"
Skill = "技能"
Retreat = "撤退"
SpeedUp = "二倍速"
BulletTime = "子弹时间"
SkillUsage = "技能用法"
class SkillUsageType:
NotAutoUse = 0 # 0 - 不自动使用 默认
UseWhenOk = 1 # 1 - 好了就用,有多少次用多少次(例如干员 棘刺 3 技能、桃金娘 1 技能等)
UseWhenOkOnce = 2 # 2 - 好了就用,仅使用一次(例如干员 山 2 技能)
AutoUse = 3 # 3 - 自动判断使用时机(画饼.jpg
# ========================= constants end =============================
class Document:
def __init__(self, title: str, details: str, title_color: str = "dark", details_color: str = "dark"):
self._details_color = details_color
self._details = details
self._title_color = title_color
self._title = title
def to_dict(self) -> dict:
return {
"title": self._title,
"title_color": self._title_color,
"details": self._details,
"details_color": self._details_color
}
class Requirements:
def __init__(self, elite: int, level: int, skill_level: int, module: int, potentiality: int):
self._potentiality = potentiality
self._module = module
self._skill_level = skill_level
self._level = level
self._elite = elite
def to_dict(self):
return {
"elite": self._elite,
"level": self._level,
"skill_level": self._skill_level,
"module": self._module,
"potentiality": self._potentiality
}
class Action:
def __init__(self, action_type: str, name: str = None, location: tuple = None, direction: str = "", kills: int = 0,
skill_usage: int = None, pre_delay: int = 0, rear_delay: int = 0, timeout: int = 999999999,
doc: str = None, doc_color: str = None):
self._doc_color = doc_color
self._doc = doc
self._timeout = timeout
self._rear_delay = rear_delay
self._pre_delay = pre_delay
self._skill_usage = skill_usage
self._kills = kills
self._direction = direction
self._name = name
self._location = location
self._action_type = action_type
def to_dict(self) -> dict:
res = {
"type": self._action_type,
}
if self._kills != 0:
res["kills"] = self._kills
if self._pre_delay != 0:
res["pre_delay"] = self._pre_delay
if self._rear_delay != 0:
res["rear_delay"] = self._rear_delay
if self._action_type == ActionType.Deploy or self._action_type == ActionType.Skill \
or self._action_type == ActionType.Retreat:
res["name"] = self._name
if self._action_type == ActionType.Deploy:
res["location"] = self._location
res["direction"] = self._direction
return res
class OperatorOrGroup:
def __init__(self, name: str, battle: Battle):
self._battle = battle
self._name = name
self._pre_delay = 0
self._rear_delay = 0
self._wait_kills = 0
def pre_delay(self, times: int) -> OperatorOrGroup:
self._pre_delay = times
return self
def rear_delay(self, times: int) -> OperatorOrGroup:
self._rear_delay = times
return self
def wait_kills(self, kills: int) -> OperatorOrGroup:
self._wait_kills = kills
return self
def deploy(self, location: tuple, direction: str):
self._battle.add_action(self._add_conditions(Action(ActionType.Deploy, self._name, location, direction)))
def use_skill(self):
self._battle.add_action(self._add_conditions(Action(ActionType.Skill, self._name)))
def retreat(self):
self._battle.add_action(self._add_conditions(Action(ActionType.Retreat, self._name)))
def _add_conditions(self, action: Action) -> Action:
if self._pre_delay != 0:
action._pre_delay = self._pre_delay
if self._rear_delay != 0:
action._rear_delay = self._rear_delay
if self._wait_kills != 0:
action._kills = self._wait_kills
self._clear_conditions()
return action
def _clear_conditions(self):
self._pre_delay = 0
self._rear_delay = 0
self._wait_kills = 0
def to_dict(self) -> dict:
# 此处不应该被执行到
raise Exception("请使用子类方法")
class Group(OperatorOrGroup):
def __init__(self, name: str, battle: Battle, operators: tuple):
"""
请使用 Battle.create_group()
:param name:
:param battle:
:param operators:
"""
super().__init__(name, battle)
self._operators = operators
def to_dict(self) -> dict:
return {
"name": self._name,
"opers": [operator.to_dict() for operator in self._operators]
}
class Operator(OperatorOrGroup):
def __init__(self, name: str, battle: Battle, skill: int, skill_usage: int = SkillUsageType.NotAutoUse,
requirements: Requirements = None):
"""
仅当要创建群组时才应该被调用
正常使用请用 Battle.create_operator()
:param name:
:param battle:
:param skill:
:param skill_usage:
:param requirements:
"""
super().__init__(name, battle)
self._requirements = requirements
self._skill_usage = skill_usage
self._skill = skill
def skill_usage_change(self, change_to: int):
"""
改变技能使用的类型
由于需要兼容群组类型,有时此方法可能无法通过代码提示调用,手动调用即可
:param change_to: 需要调整到的 SkillUsageType
:return:
"""
self._battle.add_action(self._add_conditions(Action(ActionType.SkillUsage, skill_usage=change_to)))
def to_dict(self) -> dict:
res = {
"name": self._name,
"skill": self._skill,
"skill_usage": self._skill_usage,
}
if self._skill_usage != SkillUsageType.NotAutoUse:
res["skill_usage"] = self._skill_usage
if self._requirements is not None:
res["requirements"] = self._requirements.to_dict()
return res
class Battle:
def __init__(self, stage_name: str, version="v4.0", doc: Document = None):
self._doc = doc
self._stage_name = stage_name
self._version = version
self._operators = []
self._groups = []
self._actions = []
self._wait_times = 0
self._wait_kills = 0
self._speedup = False
self._last_save_file_name = "battle.json"
def create_operator(self, name: str, skill: int, skill_usage: int = SkillUsageType.NotAutoUse,
requirements: Requirements = None) -> Operator:
"""
创建并添加一个干员
:param name: 干员名称
:param skill: 要使用的技能
:param skill_usage: 技能使用类型 参见 SkillUsageType
:param requirements: 可选
:return: 新生成的干员
"""
new_operator = Operator(name, self, skill, skill_usage, requirements)
self._add_operator(new_operator)
return new_operator
def create_group(self, name: str, *operators: Operator) -> Group:
"""
创建并添加一个群组
:param name: 群组名
:param operators: 所包含的干员,构造的时候 battle 一项置 None 即可
:return: 新创建的群组
"""
new_group = Group(name, self, operators)
self._add_group(new_group)
return new_group
def _add_operator(self, operator: Operator):
self._operators.append(operator)
def _add_group(self, group: Group):
self._groups.append(group)
def add_action(self, action: Action):
self._actions.append(action)
def speed_up(self):
"""
调整为二倍速
:return:
"""
if not self._speedup:
self._speed_change()
self._speedup = True
def speed_down(self):
"""
调整为一倍速
:return:
"""
if self._speedup:
self._speed_change()
self._speedup = False
def _speed_change(self):
self.add_action(Action(ActionType.SpeedUp))
def save_as_json(self, file_name: str = None):
"""
保存为 json 文件,生成在同目录下
:param file_name: 要保存的文件名
:return:
"""
if file_name is None:
file_name = self._last_save_file_name
else:
self._last_save_file_name = file_name
with open(file_name, 'w', encoding='utf-8') as f:
json.dump(self.to_dict(), f, ensure_ascii=False)
def to_dict(self) -> dict:
res = {
"stage_name": self._stage_name,
"minimum_required": self._version,
"opers": [operator.to_dict() for operator in self._operators],
"groups": [group.to_dict() for group in self._groups],
"actions": [action.to_dict() for action in self._actions],
}
if self._doc is not None:
res["doc"] = self._doc.to_dict()
return res
def test(self, enable_debug_output: bool = True):
"""
自动调用 MeoAssistantArknights 并测试生成自动战斗的文件
测试的是上次生成的 json 需先调用 save_as_json()
请自行配置 MeoAssistantArknights 所在地址等
同时确保能导入 asst.py
:param enable_debug_output: 是否开启 debug 输出
:return:
"""
# MeoAssistantArknights所在的路径
mma_path = "D:\\Program Files\\MeoAssistantArknights"
# adb的路径
adb_path = "D:\\Program Files\\MeoAssistantArknights\\platform-tools\\adb.exe"
# adb连接的地址
adb_address = "127.0.0.1:8873"
try:
from asst import Asst, Message
except ImportError:
import sys
import pathlib
sys.path.append(str(pathlib.Path.cwd().parent.parent/"src"/"Python"))
try:
from asst import Asst, Message
except ImportError:
print("asst导入失败请自行解决或者下载"
" https://github.com/MaaAssistantArknights/MaaAssistantArknights/blob/master/src/Python/asst.py "
"到同目录下")
return
@Asst.CallBackType
def callback(msg, details, arg):
print(Message(msg), json.loads(details.decode('utf-8')), arg)
if not Asst.load(mma_path):
print("加载MeoAssistantArknights失败请确定路径是否正确")
asst = Asst(callback) if enable_debug_output else Asst()
if asst.connect(adb_path, adb_address):
print('连接成功')
else:
print('连接失败')
print('请检查adb地址或者端口是否有误')
exit()
asst.append_task('Copilot', {
'stage_name': self._stage_name,
'filename': self._last_save_file_name,
'formation': False,
})
asst.start()
while True:
if "stop" == str(input('>')):
exit()
from __future__ import annotations
import json
# ======================= constants begin =========================
class Direction:
UP = ""
DOWN = ""
RIGHT = ""
LEFT = ""
class ActionType:
Deploy = "部署"
Skill = "技能"
Retreat = "撤退"
SpeedUp = "二倍速"
BulletTime = "子弹时间"
SkillUsage = "技能用法"
class SkillUsageType:
NotAutoUse = 0 # 0 - 不自动使用 默认
UseWhenOk = 1 # 1 - 好了就用,有多少次用多少次(例如干员 棘刺 3 技能、桃金娘 1 技能等)
UseWhenOkOnce = 2 # 2 - 好了就用,仅使用一次(例如干员 山 2 技能)
# AutoUse = 3 # 3 - 自动判断使用时机(画饼.jpg
# ========================= constants end =============================
class Document:
def __init__(self, title: str, details: str, title_color: str = "dark", details_color: str = "dark"):
self._details_color = details_color
self._details = details
self._title_color = title_color
self._title = title
def to_dict(self) -> dict:
return {
"title": self._title,
"title_color": self._title_color,
"details": self._details,
"details_color": self._details_color
}
class Requirements:
def __init__(self, elite: int, level: int, skill_level: int, module: int, potentiality: int):
self._potentiality = potentiality
self._module = module
self._skill_level = skill_level
self._level = level
self._elite = elite
def to_dict(self):
return {
"elite": self._elite,
"level": self._level,
"skill_level": self._skill_level,
"module": self._module,
"potentiality": self._potentiality
}
class Action:
def __init__(self, action_type: str, name: str = None, location: tuple = None, direction: str = "", kills: int = 0,
skill_usage: int = None, pre_delay: int = 0, rear_delay: int = 0, cost_changes: int = 0,
timeout: int = 999999999, doc: str = None, doc_color: str = None):
self._doc_color = doc_color
self._doc = doc
# self._timeout = timeout
self._cost_changes = cost_changes
self._rear_delay = rear_delay
self._pre_delay = pre_delay
self._skill_usage = skill_usage
self._kills = kills
self._direction = direction
self._name = name
self._location = location
self._action_type = action_type
def to_dict(self) -> dict:
res = {
"type": self._action_type,
}
if self._kills != 0:
res["kills"] = self._kills
if self._pre_delay != 0:
res["pre_delay"] = self._pre_delay
if self._rear_delay != 0:
res["rear_delay"] = self._rear_delay
if self._cost_changes != 0:
res["cost_changes"] = self._cost_changes
# 保留字段,暂未实现。
# if self._timeout != 0:
# res["timeout"] = self._timeout
if self._action_type == ActionType.Deploy or self._action_type == ActionType.Skill \
or self._action_type == ActionType.Retreat:
res["name"] = self._name
if self._action_type == ActionType.Deploy:
res["location"] = self._location
res["direction"] = self._direction
return res
class OperatorOrGroup:
def __init__(self, name: str, battle: Battle):
self._battle = battle
self._name = name
self._pre_delay = 0
self._rear_delay = 0
self._wait_kills = 0
self._cost_changes = 0
def pre_delay(self, times: int) -> OperatorOrGroup:
self._pre_delay = times
return self
def rear_delay(self, times: int) -> OperatorOrGroup:
self._rear_delay = times
return self
def cost_changes(self, costs: int) -> OperatorOrGroup:
self._cost_changes = costs
return self
def wait_kills(self, kills: int) -> OperatorOrGroup:
self._wait_kills = kills
return self
def deploy(self, location: tuple, direction: str):
self._battle.add_action(self._add_conditions(Action(ActionType.Deploy, self._name, location, direction)))
def use_skill(self):
self._battle.add_action(self._add_conditions(Action(ActionType.Skill, self._name)))
def retreat(self):
self._battle.add_action(self._add_conditions(Action(ActionType.Retreat, self._name)))
def _add_conditions(self, action: Action) -> Action:
if self._pre_delay != 0:
action._pre_delay = self._pre_delay
if self._rear_delay != 0:
action._rear_delay = self._rear_delay
if self._wait_kills != 0:
action._kills = self._wait_kills
if self._cost_changes != 0:
action._cost_changes = self._cost_changes
self._clear_conditions()
return action
def _clear_conditions(self):
self._pre_delay = 0
self._rear_delay = 0
self._wait_kills = 0
self._cost_changes = 0
def to_dict(self) -> dict:
# 此处不应该被执行到
raise Exception("请使用子类方法")
class Group(OperatorOrGroup):
def __init__(self, name: str, battle: Battle, operators: tuple):
"""
请使用 Battle.create_group()
:param name:
:param battle:
:param operators:
"""
super().__init__(name, battle)
self._operators = operators
def to_dict(self) -> dict:
return {
"name": self._name,
"opers": [operator.to_dict() for operator in self._operators]
}
class Operator(OperatorOrGroup):
def __init__(self, name: str, battle: Battle, skill: int, skill_usage: int = SkillUsageType.NotAutoUse,
requirements: Requirements = None):
"""
仅当要创建群组时才应该被调用
正常使用请用 Battle.create_operator()
:param name:
:param battle:
:param skill:
:param skill_usage:
:param requirements:
"""
super().__init__(name, battle)
self._requirements = requirements
self._skill_usage = skill_usage
self._skill = skill
self._character_name_list = None
def check_operator_name(self, auto_complete: bool = True) -> bool:
if self._character_name_list is None:
import os
if os.path.exists('./characters/character_name_list.json'):
with open('./characters/character_name_list.json', 'r', encoding='utf-8') as f:
self._character_name_list = json.load(f)
else:
raise Exception("无法进行名称检查,请检查名称文件是否存在")
if self._name in self._character_name_list:
return True
if auto_complete:
possible_character_name = None
for character_name in self._character_name_list:
if self._name in character_name:
if possible_character_name is None:
possible_character_name = character_name
else:
raise Exception("匹配到多个名字,无法自动补全,请手动补全")
if possible_character_name is not None:
self._name = possible_character_name
return True
else:
return False
else:
return False
def skill_usage_change(self, change_to: int):
"""
改变技能使用的类型
由于需要兼容群组类型,有时此方法可能无法通过代码提示调用,手动调用即可
:param change_to: 需要调整到的 SkillUsageType
:return:
"""
self._battle.add_action(self._add_conditions(Action(ActionType.SkillUsage, skill_usage=change_to)))
def to_dict(self) -> dict:
res = {
"name": self._name,
"skill": self._skill,
"skill_usage": self._skill_usage,
}
if self._skill_usage != SkillUsageType.NotAutoUse:
res["skill_usage"] = self._skill_usage
if self._requirements is not None:
res["requirements"] = self._requirements.to_dict()
return res
class Battle:
def __init__(self, stage_name: str, version="v4.0", doc: Document = None):
self._doc = doc
self._stage_name = stage_name
self._version = version
self._operators = []
self._groups = []
self._actions = []
self._wait_times = 0
self._wait_kills = 0
self._speedup = False
self._last_save_file_name = "battle.json"
def create_operator(self, name: str, skill: int, skill_usage: int = SkillUsageType.NotAutoUse,
requirements: Requirements = None, check_operator_name: bool = True) -> Operator:
"""
创建并添加一个干员
:param name: 干员名称
:param skill: 要使用的技能
:param skill_usage: 技能使用类型 参见 SkillUsageType
:param requirements: 可选
:param check_operator_name: 是否要检查输入的名字是否正确
:return: 新生成的干员
"""
new_operator = Operator(name, self, skill, skill_usage, requirements)
if check_operator_name:
if not new_operator.check_operator_name():
raise Exception("干员名称有误,请更改为正确的名称或关闭名称检查")
self._add_operator(new_operator)
return new_operator
def create_group(self, name: str, check_operator_name: bool = True, *operators: Operator) -> Group:
"""
创建并添加一个群组
:param name: 群组名
:param operators: 所包含的干员,构造的时候 battle 一项置 None 即可
:param check_operator_name: 是否要检查输入的名字是否正确
:return: 新创建的群组
"""
if check_operator_name:
for operator in operators:
if not operator.check_operator_name():
raise Exception("干员名称有误,请更改为正确的名称或关闭名称检查")
new_group = Group(name, self, operators)
self._add_group(new_group)
return new_group
def _add_operator(self, operator: Operator):
self._operators.append(operator)
def _add_group(self, group: Group):
self._groups.append(group)
def add_action(self, action: Action):
self._actions.append(action)
def speed_up(self):
"""
调整为二倍速
:return:
"""
if not self._speedup:
self._speed_change()
self._speedup = True
def speed_down(self):
"""
调整为一倍速
:return:
"""
if self._speedup:
self._speed_change()
self._speedup = False
def _speed_change(self):
self.add_action(Action(ActionType.SpeedUp))
def save_as_json(self, file_name: str = None):
"""
保存为 json 文件,生成在同目录下
:param file_name: 要保存的文件名
:return:
"""
if file_name is None:
file_name = self._last_save_file_name
else:
self._last_save_file_name = file_name
with open(file_name, 'w', encoding='utf-8') as f:
json.dump(self.to_dict(), f, ensure_ascii=False)
def to_dict(self) -> dict:
res = {
"stage_name": self._stage_name,
"minimum_required": self._version,
"opers": [operator.to_dict() for operator in self._operators],
"groups": [group.to_dict() for group in self._groups],
"actions": [action.to_dict() for action in self._actions],
}
if self._doc is not None:
res["doc"] = self._doc.to_dict()
return res
def test(self, enable_debug_output: bool = True):
"""
自动调用 MeoAssistantArknights 并测试生成自动战斗的文件
测试的是上次生成的 json 需先调用 save_as_json()
请自行配置 MeoAssistantArknights 所在地址等
同时确保能导入 asst.py
:param enable_debug_output: 是否开启 debug 输出
:return:
"""
# MeoAssistantArknights所在的路径
mma_path = "D:\\Program Files\\MeoAssistantArknights"
# adb的路径
adb_path = "D:\\Program Files\\MeoAssistantArknights\\platform-tools\\adb.exe"
# adb连接的地址
adb_address = "127.0.0.1:8873"
try:
from asst import Asst, Message
except ImportError:
import sys
import pathlib
sys.path.append(str(pathlib.Path.cwd().parent.parent / "src" / "Python"))
try:
from asst import Asst, Message
except ImportError:
print("asst导入失败请自行解决或者下载"
" https://github.com/MaaAssistantArknights/MaaAssistantArknights/blob/master/src/Python/asst.py "
"到同目录下")
return
@Asst.CallBackType
def callback(msg, details, arg):
print(Message(msg), json.loads(details.decode('utf-8')), arg)
if not Asst.load(mma_path):
print("加载MeoAssistantArknights失败请确定路径是否正确")
asst = Asst(callback) if enable_debug_output else Asst()
if asst.connect(adb_path, adb_address):
print('连接成功')
else:
print('连接失败')
print('请检查adb地址或者端口是否有误')
exit()
asst.append_task('Copilot', {
'stage_name': self._stage_name,
'filename': self._last_save_file_name,
'formation': False,
})
asst.start()
while True:
if "stop" == str(input('>')):
exit()

View File

@@ -0,0 +1 @@
["Lancet-2", "Castle-3", "THRM-EX", "正义骑士号", "夜刀", "黑角", "巡林者", "杜林", "12F", "芬", "香草", "预备干员-近战", "翎羽", "玫兰莎", "泡普卡", "卡缇", "米格鲁", "斑点", "克洛丝", "安德切尔", "预备干员-狙击", "炎熔", "芙蓉", "安赛尔", "预备干员-后勤", "史都华德", "预备干员-术师", "梓兰", "夜烟", "远山", "格雷伊", "卡达", "深靛", "布丁", "杰西卡", "流星", "红云", "梅", "白雪", "松果", "安比尔", "酸糖", "讯使", "清道夫", "红豆", "桃金娘", "豆苗", "杜宾", "缠丸", "断罪者", "霜叶", "艾丝黛尔", "慕斯", "刻刀", "宴", "芳汀", "砾", "孑", "暗索", "末药", "嘉维尔", "苏苏洛", "调香师", "清流", "褐果", "角峰", "蛇屠箱", "泡泡", "古米", "坚雷", "深海色", "地灵", "波登可", "罗比菈塔", "伊桑", "阿消", "白面鸮", "微风", "凛冬", "德克萨斯", "贾维", "苇草", "野鬃", "极境", "夜半", "诗怀雅", "鞭刃", "芙兰卡", "炎客", "Sharp", "因陀罗", "燧石", "拉普兰德", "断崖", "柏喙", "战车", "幽灵鲨", "布洛卡", "星极", "铸铁", "赤冬", "羽毛笔", "龙舌兰", "蓝毒", "白金", "灰喉", "Stormeye", "四月", "寒芒克洛丝", "陨星", "慑砂", "送葬人", "奥斯塔", "阿米娅", "苦艾", "特米米", "天火", "Pith", "惊蛰", "蜜蜡", "莱恩哈特", "薄绿", "爱丽丝", "炎狱炎熔", "蚀清", "耶拉", "洛洛", "梅尔", "稀音", "赫默", "华法琳", "亚叶", "Touch", "锡兰", "絮雨", "图耶", "桑葚", "蜜莓", "临光", "吽", "红", "槐琥", "卡夫卡", "乌有", "雷蛇", "可颂", "拜松", "火神", "石棉", "暮落", "暮落", "闪击", "暴雨", "灰毫", "极光", "普罗旺斯", "守林人", "安哲拉", "熔泉", "埃拉托 ", "崖心", "雪雉", "初雪", "巫恋", "真理", "格劳克斯", "掠风", "空", "海蒂", "月禾", "九色鹿", "夏栎", "狮蝎", "绮良", "食铁兽", "见行者", "罗宾", "霜华", "贝娜", "风丸", "能天使", "空弦", "灰烬", "黑", "远牙", "W", "菲亚梅塔", "早露", "迷迭香", "假日威龙陈", "推进之王", "风笛", "嵯峨", "琴柳", "焰尾", "伊芙利特", "莫斯提马", "艾雅法拉", "刻俄柏", "夕", "异客", "卡涅利安", "澄闪", "灵知", "安洁莉娜", "铃兰", "麦哲伦", "浊心斯卡蒂", "令", "傀影", "老鲤", "温蒂", "阿", "歌蕾蒂娅", "水月", "归溟幽灵鲨", "闪灵", "夜莺", "凯尔希", "流明", "星熊", "塞雷娅", "瑕光", "年", "泥岩", "森蚺", "号角", "山", "银灰", "棘刺", "陈", "艾丽妮", "煌", "史尔特尔", "赫拉格", "帕拉斯", "耀骑士临光", "医疗探机", "触手", "Mon3tr", "幻影", "机械水獭", "龙腾.F", "龙腾.L", "龙腾.A", "诅咒娃娃", "镜中虚影", "此面向敌", "工程蓄水炮", "移动摄影器", "沙之碑", "迷迭香的战术装备", "\"夹子\"", "磐蟹护卫队", "\"小自在\"", "迎宾踏垫", "斯卡蒂的海嗣", "全自动造型仪", "“耀阳”", "“清平”", "“逍遥”", "“弦惊”", "眠兽", "纸偶", "可靠电池", "障碍物", "震撼装置", "闸门", "侦测器", "干扰装置", "弩炮", "指挥终端", "便携式补给站", "源石冰晶", "源石祭坛", "干扰地雷", "源石流发生装置", "L-44\"留声机\"", "巨蕈", "罗德岛临时雇员", "轰隆隆先生", "梅什科线圈", "道路障碍物", "能量聚合体", "霜星的源石冰晶", "爱国者的源石祭坛", "盾卫", "禁锢装置", "改良型二踢脚", "碎石", "脉冲防御模组", "催泪瓦斯控制阀", "无人机工厂", "雪雉的安全起重机", "土石结构", "高能源石炸弹", "加固装置", "大帝", "沙尘暴", "可移动战术机库", "应急救治设施", "子代", "特制水上平台", "风筝", "涨潮控制", "破碎支柱", "战场废墟", "爆破装置", "防水蚀镀膜装置", "城市霓虹", "骑士之徽", "暴风雪", "无主的财富", "失修舞台雾机", "便携气罐", "“报幕助手”", "封印的地面", "丹田", "“冰淇淋机”", "伦蒂尼姆城防副炮", "断罪者", "圣徒之手", "溟痕", "暴行", "空爆", "月见夜", "猎蜂", "杰克", "夜魔", "格拉尼", "斯卡蒂"]

View File

@@ -0,0 +1,12 @@
# https://github.com/yuanyan3060/Arknights-Bot-Resource/blob/main/gamedata/excel/character_table.json
# 去除character_table中不需要的字段
import json
with open('./character_table.json', 'r', encoding='utf-8') as f:
character_table_dict = json.load(f)
character_name_list = []
for character_name in character_table_dict.keys():
character_name_list.append(character_table_dict[character_name]['name'])
# print(new_character_table_dict)
with open('./character_name_list.json', 'w', encoding='utf-8') as f:
json.dump(character_name_list, f, ensure_ascii=False)

View File

@@ -23,13 +23,14 @@
</div><!--JSON-->
<div class="row">
<div class="col-12">
<h1>关卡名</h1>
<input type="text" id="stage_name" class="form-control" placeholder="关卡名称">
</div><!--关卡名称-->
<h1>关卡中文</h1>
<span class="tips">小贴士:填完关卡名后开一局,会在目录下生成 map.png 地图坐标图片</span>
<input type="text" id="stage_name" class="form-control" placeholder="关卡中文名">
</div><!--关卡中文名-->
<div class="col-12">
<h2>描述</h2>
<textarea type="text" class="form-control" id="details" placeholder="描述"></textarea>
</div><!--描述-->
<h2>文本</h2>
<textarea type="text" class="form-control" id="details" placeholder="文本"></textarea>
</div><!--文本-->
<div class="col-12">
<h2>标题</h2>
<input type="text" id="title" class="form-control" placeholder="标题">
@@ -60,15 +61,15 @@
<thead>
<tr>
<th>类别</th>
<th>Kills</th>
<th>干员</th>
<th>击杀数</th>
<th>干员名字</th>
<th>x坐标</th>
<th>y坐标</th>
<th>方向</th>
<th>前延迟</th>
<th>后延迟</th>
<th>文本</th>
<th>颜色</th>
<th>文本颜色</th>
<th>删除</th>
</tr>
</thead>