diff --git a/tools/CopilotDesinger/CopilotDesinger.py b/tools/CopilotDesinger/CopilotDesinger.py index 8e7b6f62d0..d4dac93445 100644 --- a/tools/CopilotDesinger/CopilotDesinger.py +++ b/tools/CopilotDesinger/CopilotDesinger.py @@ -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() diff --git a/tools/CopilotDesinger/characters/character_name_list.json b/tools/CopilotDesinger/characters/character_name_list.json new file mode 100644 index 0000000000..5bbceefe5e --- /dev/null +++ b/tools/CopilotDesinger/characters/character_name_list.json @@ -0,0 +1 @@ +["Lancet-2", "Castle-3", "THRM-EX", "正义骑士号", "夜刀", "黑角", "巡林者", "杜林", "12F", "芬", "香草", "预备干员-近战", "翎羽", "玫兰莎", "泡普卡", "卡缇", "米格鲁", "斑点", "克洛丝", "安德切尔", "预备干员-狙击", "炎熔", "芙蓉", "安赛尔", "预备干员-后勤", "史都华德", "预备干员-术师", "梓兰", "夜烟", "远山", "格雷伊", "卡达", "深靛", "布丁", "杰西卡", "流星", "红云", "梅", "白雪", "松果", "安比尔", "酸糖", "讯使", "清道夫", "红豆", "桃金娘", "豆苗", "杜宾", "缠丸", "断罪者", "霜叶", "艾丝黛尔", "慕斯", "刻刀", "宴", "芳汀", "砾", "孑", "暗索", "末药", "嘉维尔", "苏苏洛", "调香师", "清流", "褐果", "角峰", "蛇屠箱", "泡泡", "古米", "坚雷", "深海色", "地灵", "波登可", "罗比菈塔", "伊桑", "阿消", "白面鸮", "微风", "凛冬", "德克萨斯", "贾维", "苇草", "野鬃", "极境", "夜半", "诗怀雅", "鞭刃", "芙兰卡", "炎客", "Sharp", "因陀罗", "燧石", "拉普兰德", "断崖", "柏喙", "战车", "幽灵鲨", "布洛卡", "星极", "铸铁", "赤冬", "羽毛笔", "龙舌兰", "蓝毒", "白金", "灰喉", "Stormeye", "四月", "寒芒克洛丝", "陨星", "慑砂", "送葬人", "奥斯塔", "阿米娅", "苦艾", "特米米", "天火", "Pith", "惊蛰", "蜜蜡", "莱恩哈特", "薄绿", "爱丽丝", "炎狱炎熔", "蚀清", "耶拉", "洛洛", "梅尔", "稀音", "赫默", "华法琳", "亚叶", "Touch", "锡兰", "絮雨", "图耶", "桑葚", "蜜莓", "临光", "吽", "红", "槐琥", "卡夫卡", "乌有", "雷蛇", "可颂", "拜松", "火神", "石棉", "暮落", "暮落", "闪击", "暴雨", "灰毫", "极光", "普罗旺斯", "守林人", "安哲拉", "熔泉", "埃拉托 ", "崖心", "雪雉", "初雪", "巫恋", "真理", "格劳克斯", "掠风", "空", "海蒂", "月禾", "九色鹿", "夏栎", "狮蝎", "绮良", "食铁兽", "见行者", "罗宾", "霜华", "贝娜", "风丸", "能天使", "空弦", "灰烬", "黑", "远牙", "W", "菲亚梅塔", "早露", "迷迭香", "假日威龙陈", "推进之王", "风笛", "嵯峨", "琴柳", "焰尾", "伊芙利特", "莫斯提马", "艾雅法拉", "刻俄柏", "夕", "异客", "卡涅利安", "澄闪", "灵知", "安洁莉娜", "铃兰", "麦哲伦", "浊心斯卡蒂", "令", "傀影", "老鲤", "温蒂", "阿", "歌蕾蒂娅", "水月", "归溟幽灵鲨", "闪灵", "夜莺", "凯尔希", "流明", "星熊", "塞雷娅", "瑕光", "年", "泥岩", "森蚺", "号角", "山", "银灰", "棘刺", "陈", "艾丽妮", "煌", "史尔特尔", "赫拉格", "帕拉斯", "耀骑士临光", "医疗探机", "触手", "Mon3tr", "幻影", "机械水獭", "龙腾.F", "龙腾.L", "龙腾.A", "诅咒娃娃", "镜中虚影", "此面向敌", "工程蓄水炮", "移动摄影器", "沙之碑", "迷迭香的战术装备", "\"夹子\"", "磐蟹护卫队", "\"小自在\"", "迎宾踏垫", "斯卡蒂的海嗣", "全自动造型仪", "“耀阳”", "“清平”", "“逍遥”", "“弦惊”", "眠兽", "纸偶", "可靠电池", "障碍物", "震撼装置", "闸门", "侦测器", "干扰装置", "弩炮", "指挥终端", "便携式补给站", "源石冰晶", "源石祭坛", "干扰地雷", "源石流发生装置", "L-44\"留声机\"", "巨蕈", "罗德岛临时雇员", "轰隆隆先生", "梅什科线圈", "道路障碍物", "能量聚合体", "霜星的源石冰晶", "爱国者的源石祭坛", "盾卫", "禁锢装置", "改良型二踢脚", "碎石", "脉冲防御模组", "催泪瓦斯控制阀", "无人机工厂", "雪雉的安全起重机", "土石结构", "高能源石炸弹", "加固装置", "大帝", "沙尘暴", "可移动战术机库", "应急救治设施", "子代", "特制水上平台", "风筝", "涨潮控制", "破碎支柱", "战场废墟", "爆破装置", "防水蚀镀膜装置", "城市霓虹", "骑士之徽", "暴风雪", "无主的财富", "失修舞台雾机", "便携气罐", "“报幕助手”", "封印的地面", "丹田", "“冰淇淋机”", "伦蒂尼姆城防副炮", "断罪者", "圣徒之手", "溟痕", "暴行", "空爆", "月见夜", "猎蜂", "杰克", "夜魔", "格拉尼", "斯卡蒂"] \ No newline at end of file diff --git a/tools/CopilotDesinger/characters/parse_character_table.py b/tools/CopilotDesinger/characters/parse_character_table.py new file mode 100644 index 0000000000..c0fd29b1c1 --- /dev/null +++ b/tools/CopilotDesinger/characters/parse_character_table.py @@ -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) diff --git a/tools/CopilotDesinger/index.js b/tools/CopilotDesinger/index.js new file mode 100644 index 0000000000..07eb54fe4e --- /dev/null +++ b/tools/CopilotDesinger/index.js @@ -0,0 +1,249 @@ +// 全局JSON数据 +const data = {}; + +// 模板 +const input_text = () => { + return $(''); +}; + +const skill_usage = () => { + return $(''); +}; + +const delete_icon = () => { + return $('') +}; + +const action_type = () => { + return $(''); +}; + +const direction = () => { + return $(''); +} + +const oper = (oper_data, delete_func) => { + const tr = $(''); + // 干员名字 + const name_input = input_text() + .val(oper_data.name ?? "") + .change(function () { oper_data.name = $(this).val(); }); + tr.append($('').append(name_input)); + // 技能 + const skill_input = input_text() + .attr('type', 'number') + .val(String(oper_data.skill ?? 0)) + .change(function () { oper_data.skill = Number($(this).val()); }); + tr.append($('').append(skill_input)); + // 技能用法 + const skill_usage_input = skill_usage() + .val(String(oper_data.skill_usage ?? 0)) + .change(function () { oper_data.skill_usage = Number($(this).val()); }); + tr.append($('').append(skill_usage_input)); + // 删除 + tr.append($('').append(delete_icon().click(delete_func))); + return tr; +} + +const action = (action_data) => { + const tr = $(''); + // 类别 + const type_input = action_type() + .val(action_data.type ?? "") + .change(function () { action_data.type = $(this).val(); }); + tr.append($('').append(type_input)); + // 击杀数 + const kills_input = input_text() + .attr('type', 'number') + .val(String(action_data.kills ?? "")) + .change(function () { action_data.kills = $(this).val !== "" ? Number($(this).val()) : undefined; }); + tr.append($('').append(kills_input)); + // 费用变化 + const cost_changes = input_text() + .attr('type', 'number') + .val(String(action_data.cost_changes ?? "")) + .change(function () { action_data.cost_changes = $(this).val !== "" ? Number($(this).val()) : undefined; }); + tr.append($('').append(cost_changes)); + // 干员 + const name_input = input_text() + .val(action_data.name ?? "") + .change(function () { action_data.name = $(this).val(); }); + tr.append($('').append(name_input)); + // x坐标 + const location_x_input = input_text() + .attr('type', 'number') + .val(String((action_data.location ?? [undefined, undefined])[0] ?? "")) + .change(function () { + action_data.location = action_data.location ?? [undefined, undefined]; + action_data.location[0] = $(this).val() !== "" ? Number($(this).val()) : undefined; + }); + tr.append($('').append(location_x_input)); + // y坐标 + const location_y_input = input_text() + .attr('type', 'number') + .val(String((action_data.location ?? [undefined, undefined])[1] ?? "")) + .change(function () { + action_data.location = action_data.location ?? [undefined, undefined]; + action_data.location[1] = $(this).val() !== "" ? Number($(this).val()) : undefined; + }); + tr.append($('').append(location_y_input)); + // 方向 + const direction_input = direction() + .val(action_data.direction ?? "") + .change(function () { action_data.direction = $(this).val(); }); + tr.append($('').append(direction_input)); + // 前延迟 + const pre_delay_input = input_text() + .attr('type', 'number') + .val(String(action_data.pre_delay ?? "")) + .change(function () { action_data.pre_delay = $(this).val() !== "" ? Number($(this).val()) : undefined; }); + tr.append($('').append(pre_delay_input)); + // 后延迟 + const rear_delay_input = input_text() + .attr('type', 'number') + .val(String(action_data.rear_delay ?? "")) + .change(function () { action_data.rear_delay = $(this).val() !== "" ? Number($(this).val()) : undefined; }); + tr.append($('').append(rear_delay_input)); + // 文本 + const doc_input = input_text() + .val(action_data.doc ?? "") + .change(function () { action_data.doc = $(this).val(); }); + tr.append($('').append(doc_input)); + // 颜色 + const doc_color_input = input_text() + .val(action_data.doc_color ?? "") + .change(function () { action_data.doc_color = $(this).val(); }); + tr.append($('').append(doc_color_input)); + // 删除 + tr.append($('').append(delete_icon().click(() => { + data.actions = data.actions.filter(a => a !== action_data); + loadData(); + }))); + return tr; +}; + +const group = (group_data, delete_func) => { + // 群组名 + const name_div_row = $('
'); + const name_div_col = $('
'); + const name_input = input_text() + .val(group_data.name ?? "") + .change(function () { group_data.name = $(this).val(); }); + name_div_col.append(name_input); + const delete_col = $('
'); + delete_col.append(delete_icon().click(delete_func)); + name_div_row.append(name_div_col).append(delete_col); + + // 群组干员列表 + const table = $('' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
干员名字技能技能用法删除
'); + (group_data.opers ?? []).forEach(oper_data => table.find('tbody').append(oper(oper_data, () => { + group_data.opers = group_data.opers.filter(o => o !== oper_data); + loadData(); + }))); + + return $('
') + .append(name_div_row) + .append(table); +}; + +// 加载数据 +const loadData = () => { + $('#stage_name').text(data.stage_name ?? ""); + $('#details').text(data.details ?? ""); + $('#title').text(data.title ?? ""); + // TODO: requirements + + $('#opers tbody').html(''); + (data.opers ?? []).forEach(oper_data => $('#opers tbody').append(oper(oper_data, () => { + data.opers = data.opers.filter(o => o !== oper_data); + loadData(); + }))); + + $('#groups').html(''); + (data.groups ?? []).forEach(group_data => $('#groups').append(group(group_data, () => { + data.groups = data.groups.filter(g => g !== group_data); + loadData(); + }))); + + $('#actions tbody').html(''); + (data.actions ?? []).forEach(action_data => $('#actions tbody').append(action(action_data))); +}; + +$(document).ready(() => { + // 绑定事件 + $('#stage_name').change(() => data.stage_name = $('#stage_name').val()); + $('#details').change(() => data.details = $('#details').val()); + $('#title').change(() => data.title = $('#title').val()); + + $('#opers_new').click(() => { + data.opers = data.opers ?? []; + data.opers.push({}); + loadData(); + }); + + $('#actions_new').click(() => { + data.actions = data.actions ?? []; + data.actions.push({}); + loadData(); + }); + + $('#groups_new').click(() => { + data.groups = data.groups ?? []; + data.groups.push({}); + loadData(); + }) + + $('#download_json').click(() => { + const result = JSON.stringify(data, null, 4); + const file = new Blob([result], { type: 'application/json' }); + const fileName = data.stage_name + '_' + data.opers.map(o => o.name).join('+') + '.json'; + const url = window.URL.createObjectURL(file); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', fileName); + link.click(); + }); + + $('#upload_json').click(() => $('#json_file').click()); + + $('#json_file').change(function (e) { + const files = e.target.files; + const f = files[0]; + const reader = new FileReader(); + reader.onload = (() => (e) => { + Object.assign(data, JSON.parse(e.target.result)); + loadData(); + })(f); + reader.readAsText(f); + }); + + loadData(); +}); \ No newline at end of file diff --git a/tools/CopilotDesinger/自动战斗作业生成器.html b/tools/CopilotDesinger/自动战斗作业生成器.html index 6f25f33f5f..627d437557 100644 --- a/tools/CopilotDesinger/自动战斗作业生成器.html +++ b/tools/CopilotDesinger/自动战斗作业生成器.html @@ -9,602 +9,77 @@ - + + -
-
-
-
-
-

- 关卡中文名 -

- - 小贴士:填完关卡名后开一局,会在目录下生成 map.png 地图坐标图片 -
-
-
-
-

- 文本 -

- -
-
-
-
-

- 标题 -

- -
-
-
- - - -
+
+
+
+ + +
-
-
-
-
-

选择干员

-
-
-
-
- - - - - - - - - - -
修改干员名字技能技能用法删除
-
-
-
-
-
-
-
- - -
-
- - -
-
- - -
-
- -
-
-
-
-
-
-
-

战斗流程 小贴士:可以拖拽更改顺序

-
-
-
-
- - - - - - - - - - - - - - - - - - -
修改类别Kills费用变化干员名字x坐标y坐标方向前延迟后延迟文本颜色删除
-
-
-
-
-
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - - -
-
- - -
-
- - -
- -
- - -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- -
-
+
+
+
+

关卡中文名

+ 小贴士:填完关卡名后开一局,会在目录下生成 map.png 地图坐标图片 + +
+
+

文本

+ +
+
+

标题

+ +
+
+

群组

+
+ +
+
+

干员

+ + + + + + + + + + +
干员名字技能技能用法删除
+ +
+
+

战斗流程

+ + + + + + + + + + + + + + + + + + +
类别击杀数费用变化干员名字x坐标y坐标方向前延迟后延迟文本文本颜色删除
+
- - + \ No newline at end of file