Compare commits

...

22 commits

Author SHA1 Message Date
e8fb2f75a5 ruff
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2024-10-12 17:35:20 +08:00
2d28d8179e Merge branch 'main' of https://git.zhaozuohong.vip/mower-ng/mower-ng into dev_battle
Some checks failed
ci/woodpecker/push/check_format Pipeline failed
2024-10-12 17:20:26 +08:00
2485208fe5 fix:选人为空列表时报错
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2024-10-12 17:16:18 +08:00
636f5a5ae4 优化AutoFight细节 2024-10-12 17:15:56 +08:00
3d73367de2 减少未找到干员的滑动次数 2024-10-12 17:14:06 +08:00
e0fd51d155 简化credit_fight写法,预留自动编队选项 2024-10-12 14:18:23 +08:00
9ba2b6c902 fix:无人时indexError 2024-10-12 14:17:02 +08:00
aeb73eaeb2 AutoFight拆分BattleChoose整理为FightSolver
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2024-10-12 11:01:57 +08:00
25f47134f6 agent.json中新增干员数据
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2024-10-12 01:51:03 +08:00
de251e84b9 fix:选人时的IndexError
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2024-10-12 01:43:47 +08:00
779e5f184b 传group时,将结构转为opers
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2024-10-11 23:56:31 +08:00
2a40efcb7d fix:部署干员错误,补全Choose需要的参数
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2024-10-11 23:07:15 +08:00
73524fe58b 助战选择技能 2024-10-11 23:06:21 +08:00
224b69b3a1 Merge branch 'dev_battle' of https://git.zhaozuohong.vip/mower-ng/mower-ng into dev_battle 2024-10-11 19:54:29 +08:00
f42f171741 add:ChooseSupportSolver 2024-10-11 19:45:09 +08:00
cbbc16831d 检查放干员第二步是否成功 2024-10-11 19:44:03 +08:00
c1bf26572e add;判断选人完成
Some checks failed
ci/woodpecker/push/check_format Pipeline failed
2024-10-11 18:33:24 +08:00
f51709aa31 更改匹配Scene.OPERATOR_SUPPORT的元素 2024-10-11 13:43:00 +08:00
21fd28fc46 选技能Solver及其他细节
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2024-10-10 18:44:53 +08:00
275fbb692c OPERATOR_SELECT改为寻找choose_agent/fast_select ope_select_start改为模板匹配 2024-10-10 16:49:18 +08:00
52f0a667e6 Merge remote-tracking branch 'origin/dev_battle' into dev_battle 2024-10-10 14:30:06 +08:00
2aeff171ee add:战斗选人
Some checks failed
ci/woodpecker/push/check_format Pipeline failed
2024-10-10 13:32:53 +08:00
44 changed files with 2192 additions and 424 deletions

View file

@ -124,14 +124,17 @@ class Arknights数据处理器:
if not 干员数据["itemObtainApproach"]:
continue
干员名 = 干员数据["name"]
干员_名称列表.append(干员名)
干员_名称列表[干员名] = {
"key": 干员代码,
"profession": 干员数据["profession"],
}
干员头像路径 = f"./ArknightsGameResource/avatar/{干员代码}.png"
目标路径 = f"./ui/public/avatar/{干员数据['name']}.webp"
print(f"{干员名}: {干员代码}")
png_image = Image.open(干员头像路径)
png_image.save(目标路径, "WEBP")
干员_名称列表.sort(key=len)
干员_名称列表 = dict(sorted(干员_名称列表.items()))
with open("./mower/data/agent.json", "w", encoding="utf-8") as f:
json.dump(干员_名称列表, f, ensure_ascii=False, indent=4)
@ -485,7 +488,7 @@ class Arknights数据处理器:
def extract(name, bg, crop):
data = {}
data_path = f"./ArknightsGameResource/{name}"
data_path = f"D:\\Git_Repositories\\Pycharm_Project\\Mower\\ArknightsGameResource/{name}"
ORB = cv2.ORB_create(nfeatures=50, edgeThreshold=0, nlevels=1)
for i in os.listdir(data_path):
# i: char_285_medic2.png
@ -610,7 +613,7 @@ class Arknights数据处理器:
def 添加基建技能图标(self):
# 源目录和目标目录
source_dir = "./ArknightsGameResource/building_skill"
source_dir = "D:\\Git_Repositories\\Pycharm_Project\\Mower\\ArknightsGameResource/building_skill"
destination_dir = "./ui/public/building_skill"
# 创建目标目录(如果不存在)

File diff suppressed because it is too large Load diff

BIN
mower/resources/choose_agent/battle_confirm.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/empty_skill_slot.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/fast_select.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/foldup.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/open_profession.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/perfer.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/perfer_agent.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/perfer_choosed.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/ALL.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/CASTER.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/MEDIC.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/PIONEER.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/SNIPER.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/SPECIAL.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/SUPPORT.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/TANK.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/WARRIOR.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/choose_arrow.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/profession/skill.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/rect.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/support_skill_be_choosen.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/support_status.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/choose_agent/trigger.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/fight/elite1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/fight/elite2.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/fight/profession_not_be_choosen.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
mower/resources/fight/skill_be_choosen.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -9,9 +9,9 @@ from typing import Literal
from mower.data import agent_list, base_room_list
from mower.solvers.credit import CreditSolver
from mower.solvers.credit_fight import CreditFight
from mower.solvers.cultivate_depot import cultivate as cultivateDepotSolver
from mower.solvers.depotREC import depotREC as DepotSolver
from mower.solvers.fight.credit_fight import CreditFight
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.clue import ClueSolver
from mower.solvers.infra.drone import DroneSolver
@ -655,7 +655,7 @@ class BaseSchedulerSolver(SceneGraphSolver, BaseMixin):
elif self.task.type == TaskTypes.FIAMMETTA:
self.plan_fia()
elif (
self.task.meta_data.split(",")[0] in agent_list
self.task.meta_data.split(",")[0] in list(agent_list.keys())
and self.task.type == TaskTypes.EXHAUST_OFF
):
self.overtake_room()
@ -2175,7 +2175,7 @@ class BaseSchedulerSolver(SceneGraphSolver, BaseMixin):
free_list.extend(
[
_name
for _name in agent_list
for _name in list(agent_list.keys())
if _name not in self.op_data.operators.keys()
and _name not in agents
]
@ -2255,7 +2255,9 @@ class BaseSchedulerSolver(SceneGraphSolver, BaseMixin):
_name = result[i]["agent"]
_mood = result[i]["mood"]
if _name != "":
if _name not in self.op_data.operators.keys() and _name in agent_list:
if _name not in self.op_data.operators.keys() and _name in list(
agent_list.keys()
):
self.op_data.add(Operator(_name, ""))
update_time = False
agent = self.op_data.operators[_name]

View file

@ -278,7 +278,6 @@ class depotREC(SceneGraphSolver):
截图列表.append(新的截图)
# saveimg(旧的截图, "depot_1_screenshot")
logger.info(f"仓库扫描: 把第{len(截图列表)}页保存进内存中等待识别")
if "养成材料" in 分类名称:
while True:

View file

@ -0,0 +1,43 @@
from .auto_fight import AutoFight
from .battle_choose import BattleChooseSolver
class FightSolver:
def run(
self,
level_name,
opers,
actions,
groups=[],
auto_choose: bool = True,
squad: int = None,
support_oper: list = [],
min_elite: int = 2,
min_level: int = 60,
retry_times: int = 10,
):
"""
Args:
level_name: 关卡名
opers: 干员列表
actions: 行动列表
groups: 干员组列表
auto_choose: 是否自动选择干员编队
squad: 队伍编号
support_oper: 助战干员
min_elite: 助战干员精英化等级的最低要求
min_level: 助战干员等级的最低要求
retry_times: 助战刷新次数
"""
if auto_choose:
BattleChooseSolver().run(
level_name,
opers,
groups,
squad,
support_oper,
min_elite,
min_level,
retry_times,
)
AutoFight().run(level_name, opers, actions, groups)

View file

@ -18,7 +18,20 @@ from mower.utils.vector import sa, va
class AutoFight(SceneGraphSolver):
def run(self, level_name, opers, actions, groups=None):
def run(
self,
level_name,
opers,
actions,
groups=[],
):
"""
Args:
level_name: 关卡名
opers: 干员列表
actions: 行动列表
groups: 干员组列表
"""
logger.info("Start: 自动战斗")
logger.info("地图坐标计算https://github.com/yuanyan3060/Arknights-Tile-Pos")
self.level_name = level_name
@ -43,16 +56,18 @@ class AutoFight(SceneGraphSolver):
def find_and_change_group_name(self, name: str) -> str:
if name in self.replacements:
return self.replacements[name]
for group in self.groups:
for i in range(len(self.groups) - 1, -1, -1):
group = self.groups[i]
if group["name"] == name:
for op in group["opers"]:
if op["name"] in self.operators:
self.replacements[name] = op["name"]
logger.debug(f"{name}替换为{op['name']}")
name = op["name"]
del op["name"]
self.watching[name] = op
self.watching[name]["location"] = None
del group
del self.groups[i]
return name
return name
@ -141,6 +156,8 @@ class AutoFight(SceneGraphSolver):
self.post_delay = 0
def check_condition(self) -> bool:
if "name" in self.action and self.action["name"] in self.replacements:
self.action["name"] = self.replacements[self.action["name"]]
if "kills" in self.action and self.action["kills"] > self.kills():
return False
if "costs" in self.action and self.action["costs"] > self.cost():
@ -247,7 +264,7 @@ class AutoFight(SceneGraphSolver):
def toggle_play(self):
"切换暂停与继续"
config.device.tap((1800, 80))
sleep(1)
sleep(0.5)
config.recog.update()
img = cropimg(config.recog.gray, ((740, 480), (1180, 665)))
img = thres2(img, 250)
@ -289,10 +306,10 @@ class AutoFight(SceneGraphSolver):
img = thres2(config.recog.gray, 200)
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rect = [cv2.boundingRect(c) for c in contours]
left = min(x for x, y, w, h in rect if w > 40 and h > 40)
right = max(x + w for x, y, w, h in rect if w > 40 and h > 40)
top = min(y for x, y, w, h in rect if w > 40 and h > 40)
bottom = max(y + h for x, y, w, h in rect if w > 40 and h > 40)
left = min(x for x, y, w, h in rect if w > 20 and h > 20)
right = max(x + w for x, y, w, h in rect if w > 20 and h > 20)
top = min(y for x, y, w, h in rect if w > 20 and h > 20)
bottom = max(y + h for x, y, w, h in rect if w > 20 and h > 20)
width = right - left
height = bottom - top
logger.debug(f"{width=} {height=}")
@ -300,9 +317,7 @@ class AutoFight(SceneGraphSolver):
def deploy(self):
"部署干员"
name = self.action["name"]
if self.groups or self.replacements:
name = self.find_and_change_group_name(name)
name = self.find_and_change_group_name(self.action["name"])
if name not in self.operators:
self.update_operators()
return
@ -331,14 +346,18 @@ class AutoFight(SceneGraphSolver):
self.location[name] = x, y
return
for _ in range(6):
for _ in range(5):
if not (flag := self.drag_success()):
self.sleep(0.05)
self.sleep(0.1)
else:
break
if flag:
config.device.swipe_ext([pos, dir], [200])
self.sleep(0.5)
if self.drag_success():
self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)
return
self.operators = {}
self.complete_action()
self.location[name] = x, y
@ -374,8 +393,8 @@ class AutoFight(SceneGraphSolver):
sleep(0.5)
pos = self.calc.get_skill_screen_pos(x, y)
pos = int(pos.x), int(pos.y)
config.device.tap(pos)
sleep(0.5)
self.tap(pos)
self.sleep(1)
def action_skill(self):
"使用action中指定位置的技能"
@ -417,11 +436,11 @@ class AutoFight(SceneGraphSolver):
del self.watching[w]
return
if self.action is None:
self.sleep(1)
return
if not self.condition_satified:
self.condition_satified = self.check_condition()
return
logger.debug(self.action)
if self.action["type"] in ["SpeedUp", "二倍速"]:
self.toggle_speed()
elif self.action["type"] in ["Deploy", "部署"]:

View file

@ -0,0 +1,35 @@
from .battle_agent_choose import BattleAgentChooseSolver
from .choose_squad import ChooseSquadSolver
from .choose_support import ChooseSupportSolver
class BattleChooseSolver:
def run(
self,
level_name: str,
opers: list,
groups: list = [],
squad: int = None,
support_oper: list = [],
min_elite: int = 2,
min_level: int = 60,
retry_times: int = 10,
):
"""
Args:
level_name: 关卡名
opers: 干员列表
groups: 干员组列表
squad: 队伍编号
support_oper: 助战干员
elite: 助战干员精英化等级的最低要求
level: 助战干员等级的最低要求
retry_times: 助战刷新次数
"""
if squad:
ChooseSquadSolver().run(level_name, squad)
lack = BattleAgentChooseSolver().run(opers + groups)
support_oper = support_oper if support_oper else lack
ChooseSupportSolver().run(
support_oper, min_elite, min_level, retry_times, level_name
)

View file

@ -0,0 +1,187 @@
import math
from copy import deepcopy
import cv2
from mower.data import agent_list
from mower.utils import config
from mower.utils.character_recognize import (
match_portrait,
operator_team_select,
)
from mower.utils.graph.utils import SceneGraphSolver
from mower.utils.log import logger
from mower.utils.scene import Scene
from .choose_skill import ChooseSkillSolver
class BattleAgentChooseSolver(SceneGraphSolver):
def run(self, agents: list) -> None:
if agents == []:
raise ValueError("干员列表为空")
self.agents = agents
self.agents_copy = deepcopy(agents)
self.cleard = False
self.filter = False
self.agent = None
self.choosed = []
self.final_check = False
self.check = []
self.lack = []
self.tmp_data = {}
self.swipe_time = 0
super().run()
return self.lack
def choose_agents(self):
if self.final_check:
if pos := self.find("choose_agent/foldup"):
self.tap(pos, interval=0.1)
return
check = self.get_agent_pos()
if check:
for i in check:
if i in self.choosed:
self.check.append(i)
if (len(self.check) + len(self.lack)) == len(self.agents):
self.tap_element("choose_agent/battle_confirm")
return True
elif (len(self.check) + len(self.lack)) < len(self.agents):
self.swipe_noinertia((1000, 540), (-1900, 0))
return
else:
logger.error(f"{self.choosed-self.check} 确认失败")
return False
if pos := self.find("choose_agent/open_profession"):
self.tap(pos, interval=0.1)
if self.cleard is False:
if self.find("choose_agent/battle_empty"):
self.cleard = True
else:
self.tap_element("choose_agent/clear_battle", interval=0.1)
return
if self.agent is None:
if len(self.agents_copy) > 0:
self.agent = self.agents_copy.pop(0)
else:
self.final_check = True
self.tag_choosed()
return
elif self.agent["name"] not in self.choosed:
now_choose = self.get_agent_pos()
if self.agent["name"] in now_choose:
ChooseSkillSolver().run(self.agent["skill"])
self.choosed.append(self.agent["name"])
if len(self.agents_copy) > 0:
tmp = self.agents_copy.pop(0)
else:
self.final_check = True
self.tag_choosed()
return
if "opers" in tmp:
tag = agent_list[tmp["opers"][-1]["name"]]["profession"]
else:
tag = agent_list[tmp["name"]]["profession"]
if agent_list[self.agent["name"]]["profession"] == tag:
self.tag_choosed()
self.tag_choosed(tag)
self.agent = tmp
config.recog.update()
agents = dict(operator_team_select(config.recog.img))
if "opers" in self.agent:
for i in self.agent["opers"]:
if i["name"] in agents.keys():
self.agent = i
self.tap(agents[i["name"]], interval=0.2)
return
else:
try:
self.tap(agents[self.agent["name"]], interval=0.2)
return
except KeyError:
if self.tag_choosed(agent_list[self.agent["name"]]["profession"]):
return
if set(agents.keys()).issubset(self.tmp_data.keys()):
self.swipe_time = self.swipe_time + 1
if self.swipe_time > 1:
logger.error(f"选择干员: {self.agent}失败 滑动次数过多")
self.lack.append(self.agent)
self.agent = None
return
else:
self.swipe_time = 0
self.swipe_noinertia((1000, 540), (-1900, 0))
self.tmp_data = agents
return
def get_agent_pos(self):
img = cv2.cvtColor(config.recog.img, cv2.COLOR_RGB2HSV)
img = cv2.inRange(img, (90, 200, 120), (200, 255, 255))
contours, _ = cv2.findContours(img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
segments = []
for contour in contours:
if cv2.contourArea(contour) < 40000 or cv2.contourArea(contour) > 100000:
continue
# 近似轮廓
x, y, w, h = cv2.boundingRect(contour)
current_segment = ((x, y), (x + w, y + h))
is_close = False
for segment in segments:
if (
math.sqrt(
math.pow(segment[0][0] - current_segment[0][0], 2)
+ math.pow(segment[0][1] - current_segment[0][1], 2)
)
< 40
):
is_close = True
break
# 如果不相近,则添加到列表中
if not is_close:
segments.append(current_segment)
logger.debug(f"segments:{segments}")
return list(dict(match_portrait(config.recog.gray, segment=segments)).keys())
def tag_choosed(self, profession: str = "ALL"):
str = "choose_agent/profession/" + profession
if pos := self.find(str):
if arrow := self.find("choose_agent/profession/choose_arrow"):
if (
math.sqrt(
math.pow(pos[0][0] - arrow[0][0], 2)
+ math.pow(pos[0][1] - arrow[0][1], 2)
)
< 100
):
return False
else:
self.tap(pos[0], interval=0.2)
return True
else:
self.tap(pos[0], interval=0.2)
return True
def transition(self) -> bool:
if (scene := self.scene()) == Scene.OPERATOR_AGENT_SELECT:
return self.choose_agents()
elif scene == Scene.OPERATOR_SELECT:
self.tap_element("choose_agent/fast_select")
elif scene in self.waiting_scene:
self.waiting_solver()
else:
return False

View file

@ -0,0 +1,59 @@
import cv2
from scipy.signal import argrelmax
from mower.utils import config
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cmatch, cropimg, loadres
from mower.utils.log import logger
from mower.utils.recognize import Scene
class ChooseSkillSolver(SceneGraphSolver):
"选择干员技能"
def run(self, skill: int):
logger.info(f"选择{skill}技能")
self.skill = skill
self.reverse = False
super().run()
def match_light(self):
"找闪电确定技能位置"
x = 88
img = cropimg(config.recog.img, ((x, 600), (112, 1080)))
threshold = 0.7
res = loadres("fight/light")
result = cv2.matchTemplate(img, res, cv2.TM_CCOEFF_NORMED).T[0]
print(result)
pos_list = []
for i in argrelmax(result, order=50)[0]:
if result[i] > threshold:
pos_list.append((x, i + 600))
logger.debug(pos_list)
return pos_list
def skill_be_choosen(self, y) -> bool:
"技能是否被选择"
img = cropimg(config.recog.img, ((539, y), (544, y + 5)))
res = loadres("fight/skill_be_choosen")
return cmatch(img, res, 70)
def transition(self):
if (scene := self.scene()) == Scene.OPERATOR_AGENT_SELECT:
if self.skill > len(pos_list := self.match_light()) and not self.reverse:
self.swipe_noinertia((300, 800), (0, -500), interval=0.5)
self.reverse = True
self.sleep(0.5)
elif self.reverse:
if self.skill_be_choosen(pos_list[-1][1]):
return True
else:
self.tap(pos_list[-1])
elif self.skill_be_choosen(pos_list[self.skill - 1][1]):
return True
else:
self.tap(pos_list[self.skill - 1])
elif scene in self.waiting_scene:
self.waiting_solver()
else:
return

View file

@ -0,0 +1,39 @@
import cv2
from mower.solvers.navigation import NavigationSolver
from mower.utils import config
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg
from mower.utils.log import logger
from mower.utils.recognize import Scene
class ChooseSquadSolver(SceneGraphSolver):
"选择编队"
def run(self, level_name, squad: int):
logger.info("Start: 选择编队")
self.level_name = level_name
self.squad = squad
super().run()
def current_squad(self):
count = []
for i in range(4):
hsv = cropimg(
config.recog.hsv, ((153 + i * 411, 990), (550 + i * 411, (1080)))
)
mask = cv2.inRange(hsv, (97, 0, 0), (101, 255, 255))
count.append(cv2.countNonZero(mask))
return count.index(max(count)) + 1
def transition(self):
if (scene := self.scene()) == Scene.OPERATOR_SELECT:
if self.current_squad() != self.squad:
self.tap((self.squad * 411 - 99, 1040))
else:
return True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
NavigationSolver().run(self.level_name, mode="copy")

View file

@ -0,0 +1,225 @@
import cv2
from scipy.signal import argrelmin
from skimage.metrics import structural_similarity
from mower.data import agent_list
from mower.models import secret_front
from mower.solvers.navigation import NavigationSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.character_recognize import match_portrait
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cmatch, cropimg, loadres, thres2
from mower.utils.log import logger
from mower.utils.recognize import Scene
profession_pos = {
"PIONEER": ((30, 150), (40, 160)),
"WARRIOR": ((30, 263), (40, 273)),
"TANK": ((30, 376), (40, 386)),
"SNIPER": ((30, 489), (40, 499)),
"CASTER": ((30, 602), (40, 612)),
"MEDIC": ((30, 715), (40, 725)),
"SUPPORT": ((30, 830), (40, 840)),
"SPECIAL": ((30, 944), (40, 954)),
}
skill_pos = (
((1237, 390), (1252, 405)),
((1409, 390), (1424, 405)),
((1582, 390), (1597, 405)),
)
class ChooseSupportSolver(SceneGraphSolver):
"选择助战干员"
def run(
self,
opers=[],
min_elite: int = 2,
min_level: int = 60,
retry_times: int = 10,
level_name: str = None,
):
"""
Args:
opers: 包含name和skill,若传入多个干员会选择其中练度最高的
elite: 干员精英化等级的最低要求
level: 干员等级的最低要求
retry_times: 重试次数,超过返回False
level_name: 关卡名称,用于导航,可不传
"""
self.opers_skill = {}
if len(opers) == 0:
return True
for i in range(len(opers) - 1, -1, -1): # 改变group的结构
op = opers[i]
if "opers" in op:
opers += op["opers"]
del opers[i]
logger.debug(f"选择助战干员: {opers}")
for op in opers:
self.opers_skill[op["name"]] = op["skill"]
logger.info(f"Start: 选择助战干员{self.opers_skill}")
self.min_progression = min_elite * 100 + min_level # 最低练度要求
self.retry_times = retry_times
self.level_name = level_name
self.profession = agent_list[opers[0]["name"]]["profession"] # 目标干员职业
self.matched = False # 是否匹配过干员
self.swiped = False # 是否滑动过
self.success = False # 是否成功选择到干员
self.skill = None # 选中干员需要的技能
super().run()
return self.success
def check_profession_focus(self) -> bool:
img = cropimg(config.recog.img, profession_pos[self.profession])
res = loadres("fight/profession_not_be_choosen")
return not cmatch(img, res, 70)
def get_oper_pos(self):
if not self.swiped:
img = cropimg(config.recog.gray, ((0, 908), (1839, 983)))
else:
img = cropimg(config.recog.gray, ((1600, 908), (1920, 983)))
res = loadres("fight/choose", True)
result = cv2.matchTemplate(img, res, cv2.TM_SQDIFF_NORMED)[0]
threshold = 0.1
match = []
for i in argrelmin(result, order=100)[0]:
if result[i] < threshold:
if self.swiped:
match.append(((i + 1600, 500), (i + 1794, 625)))
else:
match.append(((i, 500), (i + 194, 625)))
logger.debug(match)
match_opers = dict(match_portrait(config.recog.gray, segment=match))
result = {}
for op in self.opers_skill:
if op in match_opers:
result[op] = match_opers[op]
return result
def get_agent_elite(self, scope: tp.Scope) -> int:
"获取干员精英等级"
img = cropimg(
config.recog.gray,
(
(scope[0][0] + 55, scope[0][1] + 140),
(scope[1][0] - 54, scope[1][1] + 80),
),
)
img = thres2(img, 200)
e1 = loadres("fight/elite1", True)
e2 = loadres("fight/elite2", True)
ssim1 = structural_similarity(img, e1)
ssim2 = structural_similarity(img, e2)
if ssim2 > 0.8:
return 2
elif ssim1 > 0.8:
return 1
else:
return 0
def number(self, scope: tp.Scope, height: int, thres: int) -> int:
"数字识别"
img = cropimg(config.recog.gray, scope)
default_height = 25
if height != default_height:
scale = 25 / height
img = cv2.resize(img, None, None, scale, scale)
img = thres2(img, thres)
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rect = [cv2.boundingRect(c) for c in contours]
rect.sort(key=lambda c: c[0])
value = 0
for x, y, w, h in rect:
digit = cropimg(img, ((x, y), (x + w, y + h)))
digit = cv2.copyMakeBorder(
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
)
score = []
for i in range(10):
im = secret_front[i]
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
score.append(min_val)
value = value * 10 + score.index(min(score))
return value
def get_agent_progression(self, scope: tp.Scope) -> int:
"获取干员练度"
elite = self.get_agent_elite(scope)
level = self.number(
(
(scope[0][0] + 125, scope[0][1] + 220),
(scope[1][0] - 19, scope[1][1] + 130),
),
24,
180,
)
logger.debug(f"elite: {elite}, level: {level}")
return elite * 100 + level
def check_agent(self, cur_opers: dict):
"检查干员是否符合要求"
if cur_opers is None:
return None
result = None
max_progression = self.min_progression - 1
for op in cur_opers:
progression = self.get_agent_progression(cur_opers[op])
if progression > max_progression:
max_progression = progression
result = cur_opers[op]
self.skill = self.opers_skill[op]
return result
def transition(self):
if (scene := self.scene()) == Scene.OPERATOR_SUPPORT:
if self.retry_times < 0:
self.scene_graph_step(Scene.OPERATOR_SELECT)
if not self.check_profession_focus():
self.tap(self.get_pos(profession_pos[self.profession]))
if self.matched:
if self.find("fight/refresh"):
self.swipe_noinertia((800, 800), (150, 0))
self.tap_element("fight/refresh")
self.retry_times -= 1
self.matched = False
else:
self.sleep(0.5)
else:
if result := self.check_agent(self.get_oper_pos()):
self.tap(self.get_pos(result))
elif not self.swiped:
self.swiped = True
self.swipe_noinertia((800, 800), (-150, 0))
else:
self.matched = True
self.swiped = False
elif scene == Scene.OPERATOR_SUPPORT_AGENT:
if not self.find(
"choose_agent/support_skill_be_choosen", scope=skill_pos[self.skill - 1]
):
self.tap(self.get_pos(skill_pos[self.skill - 1]))
else:
self.tap_element("fight/use")
self.success = True
elif scene == Scene.OPERATOR_SELECT:
if self.success:
return True
elif self.retry_times < 0:
logger.warning("选择助战干员失败,请降低练度要求或添加好友")
return True
else:
self.ctap((1660, 315))
elif scene in self.waiting_scene:
self.waiting_solver()
else:
NavigationSolver().run(self.level_name, mode="copy")

View file

@ -1,7 +1,6 @@
import cv2
from scipy.signal import argrelmin
from mower.solvers.auto_fight import AutoFight
from mower.solvers.navigation import NavigationSolver
from mower.utils import config
from mower.utils.email import send_message
@ -10,6 +9,9 @@ from mower.utils.image import cropimg, loadres
from mower.utils.log import logger
from mower.utils.recognize import Scene
from .auto_fight import AutoFight
from .battle_choose import BattleChooseSolver
class CreditFight(SceneGraphSolver):
"""信用作战
@ -17,11 +19,25 @@ class CreditFight(SceneGraphSolver):
从首页导航至OF-1借助战并自动战斗
"""
def run(self):
def run(self, auto_choose: bool = False):
logger.info("Start: 信用作战")
self.support = False
navi_solver = NavigationSolver()
navi_solver.run("OF-1", mode="copy")
conf = config.conf.credit_fight
self.opers = [{"name": conf.operator, "skill": 1, "skill_usage": 1}]
self.actions = [
{"type": "SpeedUp"},
{
"type": "Deploy",
"name": conf.operator,
"location": [
conf.x,
conf.y,
],
"direction": conf.direction,
},
{"type": "SkillDaemon"},
]
self.auto_choose = auto_choose
super().run()
def choose_support(self):
@ -37,45 +53,17 @@ class CreditFight(SceneGraphSolver):
x = match[0]
return (x, 908), (x + 194, 983)
def current_squad(self):
count = []
for i in range(4):
hsv = cropimg(
config.recog.hsv, ((153 + i * 411, 990), (550 + i * 411, (1080)))
)
mask = cv2.inRange(hsv, (97, 0, 0), (101, 255, 255))
count.append(cv2.countNonZero(mask))
return count.index(max(count)) + 1
def transition(self):
if (scene := self.scene()) == Scene.OPERATOR_SELECT:
if self.auto_choose:
BattleChooseSolver().run("OF-1", self.opers)
self.auto_choose = False
if self.find("ope_select_start_empty"):
logger.info("编队内没有编入干员,停止OF-1")
return True
squad = self.current_squad()
target = config.conf.credit_fight.squad
if squad != target:
self.tap((target * 411 - 99, 1040))
return
if self.support:
# 开始行动
self.tap((1655, 781))
fight_solver = AutoFight()
conf = config.conf.credit_fight
actions = [
{"type": "SpeedUp"},
{
"type": "Deploy",
"name": conf.operator,
"location": [
conf.x,
conf.y,
],
"direction": conf.direction,
},
{"type": "SkillDaemon"},
]
fight_solver.run("OF-1", [], actions)
AutoFight().run("OF-1", self.opers, self.actions)
else:
# 借助战
self.ctap((1660, 315))
@ -94,5 +82,4 @@ class CreditFight(SceneGraphSolver):
elif scene in self.waiting_scene:
self.waiting_solver()
else:
navi_solver = NavigationSolver()
navi_solver.run("OF-1", mode="copy")
NavigationSolver().run("OF-1", "copy")

View file

@ -154,7 +154,7 @@ class Operators:
self.operators = {}
for room in self.plan.keys():
for idx, data in enumerate(self.plan[room]):
if data.agent not in agent_list and data.agent != "Free":
if data.agent not in list(agent_list.keys()) and data.agent != "Free":
return f"干员名输入错误: 房间->{room}, 干员->{data.agent}"
if data.agent in ["龙舌兰", "但书"]:
return f"高效组不可用龙舌兰,但书 房间->{room}, 干员->{data.agent}"
@ -196,7 +196,10 @@ class Operators:
):
missing_replacements.append(data.agent)
for _replacement in data.replacement:
if _replacement not in agent_list and data.agent != "Free":
if (
_replacement not in list(agent_list.keys())
and data.agent != "Free"
):
return f"干员名输入错误: 房间->{room}, 干员->{_replacement}"
if data.agent != "菲亚梅塔":
# 普通替换
@ -476,7 +479,7 @@ class Operators:
dorm.time = _agent.time_stamp + timedelta(seconds=sec_remaining)
else:
dorm.time = agent["time"]
elif _name in agent_list:
elif _name in list(agent_list.keys()):
dorm.name = _name
dorm.time = agent["time"]
break
@ -539,7 +542,7 @@ class Operators:
return None, None
def add(self, operator):
if operator.name not in agent_list:
if operator.name not in list(agent_list.keys()):
return
if self.config.is_resting_priority(operator.name):
operator.resting_priority = "low"
@ -610,7 +613,7 @@ class Operators:
ret += 1
if len(freeName) > 0:
for name in freeName:
if name in agent_list:
if name in list(agent_list.keys()):
self.operators[name].mood = self.operators[name].upper_limit
self.operators[name].depletion_rate = 0
self.operators[name].time_stamp = datetime.now()

View file

@ -154,11 +154,11 @@ class Recognizer:
self.scene = Scene.MAIL
elif self.find("navigation/record_restoration"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("fight/refresh"):
elif self.find("choose_agent/support_status"):
self.scene = Scene.OPERATOR_SUPPORT
elif self.find("fight/collection") or self.find("fight/collection_on"):
self.scene = Scene.OPERATOR_AGENT_SELECT
elif self.find("ope_select_start"):
elif self.find("choose_agent/fast_select"):
self.scene = Scene.OPERATOR_SELECT
elif self.find("ope_eliminate"):
self.scene = Scene.OPERATOR_ELIMINATE

View file

@ -4,6 +4,8 @@ color = {
"arrange_order_options": (1652, 23),
"arrange_order_options_scene": (369, 199),
"choose_agent/clear": (685, 996),
"choose_agent/fast_select": ((1296, 25), (1592, 98)),
"choose_agent/support_status": (1038, 41),
"clue": (1740, 855),
"clue/daily": (526, 623),
"clue/filter_all": (1297, 99),
@ -65,7 +67,6 @@ color = {
"ope_eliminate": (1332, 938),
"ope_recover_originite_on": (1514, 124),
"ope_recover_potion_on": (1046, 127),
"ope_select_start": (1579, 701),
"open_recruitment": (192, 143),
"order_label": (404, 137),
"pull_once": (1260, 950),
@ -107,11 +108,24 @@ template_matching = {
"arrange_confirm": (755, 903),
"biography": (768, 934),
"business_card": (55, 165),
"choose_product_options": (1174, 23),
"collection": (1005, 943),
"collection_small": (1053, 982),
"connecting": (1087, 978),
"credit_visiting": (78, 220),
"choose_agent/battle_confirm": (1591, 991),
"choose_agent/battle_empty": (92, 296),
"choose_agent/profession/ALL": (1828, 46),
"choose_agent/profession/choose_arrow": ((1850, 125), (1920, 1080)),
"choose_agent/profession/CASTER": (1825, 630),
"choose_agent/profession/MEDIC": (1832, 750),
"choose_agent/profession/PIONEER": (1831, 160),
"choose_agent/profession/SNIPER": (1829, 505),
"choose_agent/profession/SPECIAL": (1835, 994),
"choose_agent/profession/SUPPORT": (1828, 872),
"choose_agent/profession/TANK": (1832, 394),
"choose_agent/profession/WARRIOR": (1825, 271),
"choose_agent/support_skill_be_choosen": None,
"choose_product_options": (1174, 23),
"episode": (535, 937),
"factory_accelerate": (1800, 775),
"fight/gear": (82, 45),
@ -164,6 +178,7 @@ template_matching = {
"ope_failed": (183, 465),
"ope_finish": (87, 265),
"ope_plan": (1278, 24),
"ope_select_start": ((1579, 701), (1731, 921)),
"ope_select_start_empty": ((0, 0), (400, 400)),
"order_ready": (500, 664),
"order_switching_notice": (604, 900),
@ -219,11 +234,21 @@ template_matching = {
template_matching_score = {
"connecting": 0.7,
"choose_agent/profession/ALL": 0.6,
"choose_agent/profession/CASTER": 0.6,
"choose_agent/profession/MEDIC": 0.6,
"choose_agent/profession/PIONEER": 0.6,
"choose_agent/profession/SNIPER": 0.6,
"choose_agent/profession/SPECIAL": 0.6,
"choose_agent/profession/SUPPORT": 0.6,
"choose_agent/profession/TANK": 0.6,
"choose_agent/profession/WARRIOR": 0.6,
"navigation/episode": 0.7,
"navigation/ope_hard": 0.7,
"navigation/ope_hard_small": 0.7,
"navigation/ope_normal": 0.7,
"navigation/ope_normal_small": 0.7,
"ope_select_start": 0.7,
"recruit/agent_token": 0.8,
"recruit/agent_token_first": 0.8,
"recruit/lmb": 0.7,

View file

@ -14,11 +14,34 @@ Res = Literal[
"bill_accelerate",
"biography",
"business_card",
"choose_agent/battle_confirm",
"choose_agent/battle_empty",
"choose_agent/clear",
"choose_agent/clear_battle",
"choose_agent/confirm",
"choose_agent/empty_skill_slot",
"choose_agent/fast_select",
"choose_agent/foldup",
"choose_agent/open_profession",
"choose_agent/perfer",
"choose_agent/perfer_agent",
"choose_agent/perfer_choosed",
"choose_agent/profession/ALL",
"choose_agent/profession/CASTER",
"choose_agent/profession/choose_arrow",
"choose_agent/profession/MEDIC",
"choose_agent/profession/PIONEER",
"choose_agent/profession/skill",
"choose_agent/profession/SNIPER",
"choose_agent/profession/SPECIAL",
"choose_agent/profession/SUPPORT",
"choose_agent/profession/TANK",
"choose_agent/profession/WARRIOR",
"choose_agent/rect",
"choose_agent/riic_empty",
"choose_agent/support_skill_be_choosen",
"choose_agent/support_status",
"choose_agent/trigger",
"choose_product_options",
"clue/1",
"clue/2",
@ -91,13 +114,17 @@ Res = Literal[
"fight/collection",
"fight/collection_on",
"fight/complete",
"fight/elite1",
"fight/elite2",
"fight/enemy",
"fight/failed_text",
"fight/gear",
"fight/kills_separator",
"fight/light",
"fight/pause",
"fight/profession_not_be_choosen",
"fight/refresh",
"fight/skill_be_choosen",
"fight/skill_ready",
"fight/skill_stop",
"fight/use",

View file

@ -121,7 +121,7 @@ def load_plan_from_json():
def operator_list():
from mower.data import agent_list
return agent_list
return list(agent_list.keys())
@app.route("/shop")
@ -509,10 +509,9 @@ def get_count():
if new_task.type == TaskTypes.SKILL_UPGRADE:
supports = []
for s in req["upgrade_support"]:
if (
s["name"] not in agent_list
or s["swap_name"] not in agent_list
):
if s["name"] not in list(agent_list.keys()) or s[
"swap_name"
] not in list(agent_list.keys()):
raise Exception("干员名不正确")
supports.append(
SkillUpgradeSupport(