mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-15 17:30:27 +08:00
471 lines
17 KiB
Python
471 lines
17 KiB
Python
from __future__ import annotations
|
||
|
||
import json
|
||
|
||
|
||
# ======================= constants begin =========================
|
||
class Direction:
|
||
UP = "上"
|
||
DOWN = "下"
|
||
RIGHT = "右"
|
||
LEFT = "左"
|
||
|
||
|
||
class ActionType:
|
||
Deploy = "部署"
|
||
Skill = "技能"
|
||
Retreat = "撤退"
|
||
SpeedUp = "二倍速"
|
||
BulletTime = "子弹时间"
|
||
SkillUsage = "技能用法"
|
||
SkillDaemon = "摆完挂机"
|
||
Output = "打印"
|
||
|
||
|
||
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 add_haisi_doc(self):
|
||
if self._doc is not None:
|
||
return
|
||
if self._action_type in [ActionType.SpeedUp, ActionType.BulletTime, ActionType.SkillDaemon, ActionType.Output]:
|
||
haisi_doc_list = [self._action_type, "执行"]
|
||
elif self._action_type in [ActionType.Deploy, ActionType.Skill, ActionType.Retreat]:
|
||
haisi_doc_list = [self._name]
|
||
if self._kills != 0:
|
||
haisi_doc_list.append("等待")
|
||
haisi_doc_list.append(str(self._kills) + "杀")
|
||
if self._pre_delay != 0:
|
||
haisi_doc_list.append("等待")
|
||
haisi_doc_list.append(str(self._pre_delay) + "秒")
|
||
if self._rear_delay != 0:
|
||
haisi_doc_list.append("延时")
|
||
haisi_doc_list.append(str(self._rear_delay) + "秒")
|
||
if self._cost_changes != 0:
|
||
haisi_doc_list.append("等待")
|
||
haisi_doc_list.append("费用")
|
||
haisi_doc_list.append("变化")
|
||
haisi_doc_list.append(str(self._cost_changes))
|
||
haisi_doc_list.append(self._action_type)
|
||
else:
|
||
return
|
||
self._doc = ",".join(list(filter(None, haisi_doc_list)))
|
||
self._doc_color = "orange"
|
||
|
||
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
|
||
if self._doc is not None:
|
||
res["doc"] = self._doc
|
||
if self._doc_color is not None:
|
||
res["doc_color"] = self._doc_color
|
||
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, skill: int, skill_usage: int = SkillUsageType.NotAutoUse, battle: Battle = None,
|
||
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_file_name = None
|
||
# 海嗣语 我,海嗣,回归大群,打钱
|
||
self._enable_haisi_doc = False
|
||
|
||
def enable_haisi_doc(self) -> Battle:
|
||
self._enable_haisi_doc = True
|
||
self._doc.details += "\n"
|
||
self._doc.details += "回归,大群,加入,光荣,进化"
|
||
return self
|
||
|
||
def create_operator(self, name: str, skill: int, skill_usage: int = SkillUsageType.NotAutoUse,
|
||
add_to_battle: bool = True, requirements: Requirements = None,
|
||
check_operator_name: bool = True) -> Operator:
|
||
"""
|
||
创建并添加一个干员
|
||
:param name: 干员名称
|
||
:param skill: 要使用的技能
|
||
:param skill_usage: 技能使用类型 参见 SkillUsageType
|
||
:param add_to_battle: 是否要加入自动编队,构造召唤物或关卡道具时等需设为 False
|
||
:param requirements: 可选
|
||
:param check_operator_name: 是否要检查输入的名字是否正确
|
||
:return: 新生成的干员
|
||
"""
|
||
new_operator = Operator(name, skill, skill_usage, self, requirements)
|
||
if check_operator_name:
|
||
if not new_operator.check_operator_name():
|
||
raise Exception("干员名称有误,请更改为正确的名称或关闭名称检查")
|
||
if add_to_battle:
|
||
self._add_operator(new_operator)
|
||
return new_operator
|
||
|
||
def create_group(self, name: str, *operators: Operator) -> Group:
|
||
return self._create_group(name, True, *operators)
|
||
|
||
def create_group_without_name_check(self, name: str, *operators: Operator) -> Group:
|
||
return self._create_group(name, False, *operators)
|
||
|
||
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):
|
||
if self._enable_haisi_doc:
|
||
action.add_haisi_doc()
|
||
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, stage_name: str = None):
|
||
"""
|
||
保存为 json 文件,生成在同目录下
|
||
:param file_name: 要保存的文件名
|
||
:param stage_name: 手动指定关卡名
|
||
:return:
|
||
"""
|
||
if file_name is None:
|
||
file_name = self._last_file_name = self._generate_file_name(self._stage_name) \
|
||
if stage_name is None else self._generate_file_name(stage_name)
|
||
else:
|
||
self._last_file_name = file_name
|
||
with open(file_name, 'w', encoding='utf-8') as f:
|
||
json.dump(self.to_dict(), f, ensure_ascii=False)
|
||
|
||
def _generate_file_name(self, stage_name: str) -> str:
|
||
res = stage_name
|
||
res += "-"
|
||
res += "+".join([operator.name for operator in self._operators])
|
||
res += ".json"
|
||
return res.replace("/", "_") # 防止文件名中含有 / 而使得文件打开失败
|
||
|
||
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_file_name,
|
||
'formation': False,
|
||
})
|
||
asst.start()
|
||
while True:
|
||
if "stop" == str(input('>')):
|
||
exit()
|