620 lines
22 KiB
Python
620 lines
22 KiB
Python
import lzma
|
|
import pickle
|
|
from itertools import combinations
|
|
|
|
import cv2
|
|
import numpy as np
|
|
|
|
from mower import __rootdir__
|
|
from mower.data import (
|
|
agent_with_tags,
|
|
recruit_agent,
|
|
)
|
|
from mower.models import Digtal
|
|
from mower.utils import config
|
|
from mower.utils.email import recruit_rarity, recruit_template, send_message
|
|
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
|
|
from mower.utils.vector import va
|
|
|
|
number = Digtal().riic_base_digits
|
|
with lzma.open(f"{__rootdir__}/models/recruit_result.pkl", "rb") as f:
|
|
recruit_res_template = pickle.load(f)
|
|
with lzma.open(f"{__rootdir__}/models/recruit.pkl", "rb") as f:
|
|
tag_template = pickle.load(f)
|
|
job_list = [
|
|
"recruit/riic_res/CASTER",
|
|
"recruit/riic_res/MEDIC",
|
|
"recruit/riic_res/PIONEER",
|
|
"recruit/riic_res/SPECIAL",
|
|
"recruit/riic_res/SNIPER",
|
|
"recruit/riic_res/SUPPORT",
|
|
"recruit/riic_res/TANK",
|
|
"recruit/riic_res/WARRIOR",
|
|
]
|
|
|
|
|
|
class RecruitSolver(SceneGraphSolver):
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self.find_scope = {
|
|
"recruit/begin_recruit": [(340, 200), (590, 300)],
|
|
"recruit/job_requirements": [(100, 20), (300, 100)],
|
|
"recruit/recruit_done": [(300, 250), (600, 340)],
|
|
"recruit/recruit_lock": [(400, 120), (540, 220)],
|
|
}
|
|
|
|
# 四个招募栏位的位置 固定1920*1080所以直接写死了
|
|
up = 270
|
|
down = 1060
|
|
left = 25
|
|
right = 1890
|
|
|
|
self.segments = {
|
|
1: [(left, up), (950, 650)],
|
|
2: [(970, up), (right, 650)],
|
|
3: [(left, 690), (950, 650)],
|
|
4: [(970, 690), (right, down)],
|
|
}
|
|
self.agent_choose = {}
|
|
self.recruit_index = 1
|
|
# 默认要支援机械
|
|
self.recruit_order_index = 2
|
|
self.recruit_order = [6, 5, 1, 4, 3, 2]
|
|
self.refresh = False
|
|
self.result_agent = {}
|
|
self.tags = {}
|
|
self.ticket_number = None
|
|
|
|
self.normal = loadres("recruit/choose_template/normal")
|
|
self.rare = loadres("recruit/choose_template/rare")
|
|
|
|
def run(self):
|
|
self.add_recruit_param()
|
|
super().run()
|
|
logger.info(self.result_agent)
|
|
recruit_results = {}
|
|
if self.agent_choose:
|
|
for pos in self.agent_choose:
|
|
if self.agent_choose[pos]["choosed"] is False:
|
|
continue
|
|
agent = []
|
|
if self.agent_choose[pos]["result"]:
|
|
for item in self.agent_choose[pos]["result"]:
|
|
agent.append(item["name"])
|
|
recruit_results[pos] = self.agent_choose[pos]
|
|
logger.info(
|
|
"{}:[".format(pos)
|
|
+ ",".join(self.agent_choose[pos]["tags"])
|
|
+ "]:{}".format(",".join(agent))
|
|
)
|
|
logger.info(recruit_results)
|
|
if self.agent_choose or self.result_agent:
|
|
logger.info("招募汇总如下")
|
|
send_message(
|
|
recruit_template.render(
|
|
recruit_results=recruit_results,
|
|
recruit_get_agent=self.result_agent,
|
|
permit_count=self.ticket_number,
|
|
title_text="公招汇总",
|
|
),
|
|
"公招汇总通知",
|
|
"INFO",
|
|
)
|
|
return recruit_results, self.result_agent
|
|
|
|
def transition(self) -> bool:
|
|
if (scene := self.scene()) == Scene.RECRUIT_MAIN:
|
|
if self.recruit_index > 4:
|
|
logger.info("结束公招")
|
|
return True
|
|
job_requirements_scope = [
|
|
va(
|
|
self.segments[self.recruit_index][0],
|
|
self.find_scope["recruit/job_requirements"][0],
|
|
),
|
|
va(
|
|
self.segments[self.recruit_index][0],
|
|
self.find_scope["recruit/job_requirements"][1],
|
|
),
|
|
]
|
|
begin_recruit_scope = [
|
|
va(
|
|
self.segments[self.recruit_index][0],
|
|
self.find_scope["recruit/begin_recruit"][0],
|
|
),
|
|
va(
|
|
self.segments[self.recruit_index][0],
|
|
self.find_scope["recruit/begin_recruit"][1],
|
|
),
|
|
]
|
|
recruit_done_scope = [
|
|
va(
|
|
self.segments[self.recruit_index][0],
|
|
self.find_scope["recruit/recruit_done"][0],
|
|
),
|
|
va(
|
|
self.segments[self.recruit_index][0],
|
|
self.find_scope["recruit/recruit_done"][1],
|
|
),
|
|
]
|
|
recruit_lock_scope = [
|
|
va(
|
|
self.segments[self.recruit_index][0],
|
|
self.find_scope["recruit/recruit_lock"][0],
|
|
),
|
|
va(
|
|
self.segments[self.recruit_index][0],
|
|
self.find_scope["recruit/recruit_lock"][1],
|
|
),
|
|
]
|
|
|
|
if self.find("recruit/job_requirements", scope=job_requirements_scope):
|
|
self.recruit_index = self.recruit_index + 1
|
|
if self.recruit_index in self.agent_choose.keys():
|
|
self.agent_choose[self.recruit_index]["choosed"] = True
|
|
logger.debug(f"{self.recruit_index}正在招募")
|
|
return
|
|
elif pos := self.find("recruit/recruit_lock", scope=recruit_lock_scope):
|
|
logger.debug(f"{self.recruit_index}锁定")
|
|
return True
|
|
elif pos := self.find("recruit/recruit_done", scope=recruit_done_scope):
|
|
logger.debug(f"{self.recruit_index}结束招募 开包")
|
|
self.tap(pos)
|
|
return
|
|
elif pos := self.find("recruit/begin_recruit", scope=begin_recruit_scope):
|
|
self.ticket_number = self.get_ticket_number()
|
|
if self.ticket_number == 0:
|
|
self.recruit_index = self.recruit_index + 1
|
|
logger.debug(f"{self.recruit_index} 张招募券")
|
|
return
|
|
self.tap(pos)
|
|
return
|
|
else:
|
|
self.sleep()
|
|
|
|
elif scene == Scene.RECRUIT_TAGS:
|
|
self.ticket_number = self.get_ticket_number()
|
|
|
|
if self.recruit_index not in self.tags.keys() or self.refresh:
|
|
tmp_tags = self.get_recruit_tag()
|
|
if tmp_tags is False:
|
|
self.back()
|
|
return
|
|
self.tags[self.recruit_index] = tmp_tags
|
|
self.refresh = False
|
|
logger.info(
|
|
f"{self.recruit_index}号位置的tag识别结果{self.tags[self.recruit_index]}"
|
|
)
|
|
|
|
if self.recruit_index in self.agent_choose.keys():
|
|
if self.agent_choose[self.recruit_index]["level"] == 3:
|
|
if pos := self.find("recruit/refresh"):
|
|
self.tap(pos)
|
|
del self.tags[self.recruit_index]
|
|
del self.agent_choose[self.recruit_index]
|
|
self.refresh = True
|
|
return
|
|
|
|
choose = self.agent_choose[self.recruit_index]["tags"]
|
|
tags = self.tags[self.recruit_index]
|
|
logger.info(f"选择标签:{choose}")
|
|
tag_all_choose = True
|
|
for x in choose:
|
|
h, w, _ = tag_template[x].shape
|
|
tag_img = cropimg(config.recog.img, [tags[x], va(tags[x], (w, h))])
|
|
|
|
if self.tag_not_choosed(tag_img):
|
|
tag_all_choose = False
|
|
self.tap(tags[x])
|
|
|
|
if tag_all_choose is False:
|
|
return
|
|
|
|
if self.ticket_number == 0:
|
|
self.recruit_index = self.recruit_index + 1
|
|
self.back()
|
|
return
|
|
|
|
# 默认三星招募时长是9:00
|
|
recruit_time_choose = 540
|
|
recruit_result_level = self.agent_choose[self.recruit_index]["level"]
|
|
# 默认一星招募时长是3:50
|
|
if recruit_result_level == 1:
|
|
recruit_time_choose = 230
|
|
|
|
if (
|
|
self.ticket_number < config.conf.recruitment_permit
|
|
and recruit_result_level == 3
|
|
):
|
|
self.recruit_index = self.recruit_index + 1
|
|
logger.info("没券 返回")
|
|
self.back()
|
|
return
|
|
|
|
recruit_time = [9, 0]
|
|
if recruit_time_choose == 230:
|
|
recruit_time = [3, 50]
|
|
elif recruit_time_choose == 460:
|
|
recruit_time = [7, 40]
|
|
|
|
now_time = [
|
|
self.get_recruit_time("hour"),
|
|
self.get_recruit_time("minute"),
|
|
]
|
|
|
|
if now_time[1] != recruit_time[1]:
|
|
self.choose_time(now_time[1], recruit_time[1], mode="minute")
|
|
return
|
|
|
|
if now_time[0] != recruit_time[0]:
|
|
self.choose_time(now_time[0], recruit_time[0], mode="hour")
|
|
return
|
|
|
|
# # start recruit
|
|
self.tap_element("recruit/start_recruit")
|
|
self.agent_choose[self.recruit_index]["choosed"] = True
|
|
self.ticket_number = self.ticket_number - 1
|
|
self.recruit_index = self.recruit_index + 1
|
|
return
|
|
else:
|
|
self.recruit_tags(self.tags[self.recruit_index])
|
|
elif scene == Scene.REFRESH_TAGS:
|
|
self.tap_element("recruit/refresh_comfirm")
|
|
elif scene == Scene.RECRUIT_AGENT:
|
|
return self.recruit_result()
|
|
else:
|
|
self.scene_graph_step(Scene.RECRUIT_MAIN)
|
|
|
|
def recruit_result(self):
|
|
# 存在读完一次没退完再读一次
|
|
if str(self.recruit_index) in self.result_agent.keys():
|
|
self.tap((950, 150))
|
|
return
|
|
|
|
job_pt = None
|
|
for i in job_list:
|
|
if job_pt := self.find(i):
|
|
break
|
|
if job_pt is None:
|
|
self.sleep()
|
|
return
|
|
|
|
img = cropimg(config.recog.gray, ((job_pt[1][0], 730), (1800, 860)))
|
|
img = thres2(img, 200)
|
|
|
|
score = []
|
|
for id, res in recruit_res_template.items():
|
|
result = cv2.matchTemplate(img, res, cv2.TM_SQDIFF_NORMED, None, res)
|
|
min_val, _, _, _ = cv2.minMaxLoc(result)
|
|
if min_val <= 0.05:
|
|
score.append(recruit_agent[id]["name"])
|
|
self.result_agent[self.recruit_index] = max(score, key=len)
|
|
self.tap((500, 500))
|
|
|
|
def recruit_tags(self, tags):
|
|
tem_res = self.recruit_cal(sorted(tags))
|
|
recruit_cal_result = None
|
|
recruit_result_level = -1
|
|
|
|
for index in self.recruit_order:
|
|
if tem_res[index]:
|
|
recruit_result_level = index
|
|
break
|
|
if recruit_result_level == -1:
|
|
logger.error("筛选结果为 {}".format(tem_res))
|
|
raise ValueError("筛选tag失败")
|
|
elif recruit_result_level != 3 and self.all_same_res(
|
|
tem_res, recruit_result_level
|
|
):
|
|
recruit_cal_result = [tem_res[recruit_result_level][-1]]
|
|
else:
|
|
recruit_cal_result = tem_res[recruit_result_level]
|
|
logger.debug(f"recruit_cal_result:{recruit_cal_result}")
|
|
|
|
if self.recruit_order.index(recruit_result_level) <= self.recruit_order_index:
|
|
logger.info("稀有tag,发送邮件")
|
|
send_message(
|
|
recruit_rarity.render(
|
|
recruit_results=recruit_cal_result,
|
|
title_text="稀有tag通知",
|
|
),
|
|
"出稀有标签辣",
|
|
"WARNING",
|
|
)
|
|
if recruit_result_level == 6 or recruit_result_level == 1:
|
|
logger.debug(f"{recruit_result_level}星稀有tag ,不选")
|
|
self.recruit_index = self.recruit_index + 1
|
|
self.back()
|
|
return
|
|
elif recruit_result_level == 5:
|
|
if config.conf.recruit_auto_5 == 2:
|
|
if config.conf.recruit_auto_only5 and len(recruit_cal_result) > 1:
|
|
logger.debug(
|
|
f"{recruit_result_level}星稀有tag,但不止一个或纯手动选择"
|
|
)
|
|
self.recruit_index = self.recruit_index + 1
|
|
self.back()
|
|
return
|
|
|
|
if recruit_result_level != 3:
|
|
self.agent_choose[self.recruit_index] = {
|
|
"tags": list(recruit_cal_result[-1]["tag"]),
|
|
"result": list(recruit_cal_result[-1]["result"]),
|
|
"level": recruit_result_level,
|
|
"choosed": False,
|
|
}
|
|
return
|
|
|
|
self.agent_choose[self.recruit_index] = {
|
|
"tags": [],
|
|
"result": [{"id": "", "name": "随机三星干员", "star": 3}],
|
|
"level": recruit_result_level,
|
|
"choosed": False,
|
|
}
|
|
|
|
return
|
|
|
|
def all_same_res(self, recruit_cal_res, index):
|
|
tmp_list = recruit_cal_res[index]
|
|
last_res = tmp_list[-1]
|
|
for i in range(len(tmp_list) - 2, -1, -1):
|
|
if tmp_list[i]["result"] != last_res["result"]:
|
|
return False
|
|
return True
|
|
|
|
def recruit_cal(self, tags: list[str]):
|
|
logger.debug(f"选择标签{tags}")
|
|
index_dict = {k: i for i, k in enumerate(self.recruit_order)}
|
|
combined_agent = {}
|
|
if "新手" in tags:
|
|
tags.remove("新手")
|
|
for item in combinations(tags, 1):
|
|
tmp = agent_with_tags[item[0]]
|
|
|
|
if len(tmp) == 0:
|
|
continue
|
|
tmp.sort(key=lambda k: k["star"], reverse=True)
|
|
combined_agent[item] = tmp
|
|
for item in combinations(tags, 2):
|
|
tmp = [j for j in agent_with_tags[item[0]] if j in agent_with_tags[item[1]]]
|
|
|
|
if len(tmp) == 0:
|
|
continue
|
|
tmp.sort(key=lambda k: k["star"])
|
|
combined_agent[item] = tmp
|
|
for item in combinations(tags, 3):
|
|
tmp1 = [
|
|
j for j in agent_with_tags[item[0]] if j in agent_with_tags[item[1]]
|
|
]
|
|
tmp = [j for j in tmp1 if j in agent_with_tags[item[2]]]
|
|
|
|
if len(tmp) == 0:
|
|
continue
|
|
tmp.sort(key=lambda k: k["star"], reverse=True)
|
|
combined_agent[item] = tmp
|
|
|
|
sorted_list = sorted(
|
|
combined_agent.items(), key=lambda x: index_dict[x[1][0]["star"]]
|
|
)
|
|
|
|
result_dict = {}
|
|
for item in sorted_list:
|
|
result_dict[item[0]] = []
|
|
max_star = -1
|
|
min_star = 7
|
|
for agent in item[1]:
|
|
if "高级资深干员" not in item[0] and agent["star"] == 6:
|
|
continue
|
|
if agent["star"] > max_star:
|
|
max_star = agent["star"]
|
|
if agent["star"] < min_star:
|
|
min_star = agent["star"]
|
|
for agent in item[1]:
|
|
if max_star > 1 and agent["star"] == 2:
|
|
continue
|
|
if max_star > 1 and agent["star"] == 1:
|
|
continue
|
|
if max_star < 6 and agent["star"] == 6:
|
|
continue
|
|
result_dict[item[0]].append(agent)
|
|
|
|
try:
|
|
for key in list(result_dict.keys()):
|
|
if len(result_dict[key]) == 0:
|
|
result_dict.pop(key)
|
|
|
|
result_dict[item[0]] = sorted(
|
|
result_dict[item[0]], key=lambda x: x["star"], reverse=True
|
|
)
|
|
min_star = result_dict[item[0]][-1]["star"]
|
|
for res in result_dict[item[0]][:]:
|
|
if res["star"] > min_star:
|
|
result_dict[item[0]].remove(res)
|
|
except KeyError:
|
|
logger.debug("Recruit Cal Key Error :{}".format(result_dict))
|
|
continue
|
|
result = {
|
|
6: [],
|
|
5: [],
|
|
4: [],
|
|
3: [],
|
|
2: [],
|
|
1: [],
|
|
}
|
|
for tag in result_dict:
|
|
result[result_dict[tag][0]["star"]].append(
|
|
{"tag": tag, "result": result_dict[tag]}
|
|
)
|
|
for item in result:
|
|
if result[item]:
|
|
logger.debug("{}:{}".format(item, result[item]))
|
|
return result
|
|
|
|
def get_recruit_tag(self) -> dict | bool:
|
|
up = 520
|
|
down = 740
|
|
left = 530
|
|
right = 1300
|
|
|
|
img = config.recog.img[up:down, left:right]
|
|
tags_img = self.split_tags(img)
|
|
tags = {}
|
|
h, w, _ = img.shape
|
|
|
|
for index, value in enumerate(tags_img):
|
|
if self.tag_not_choosed(value) is False:
|
|
return False
|
|
max_v = -1
|
|
tag_res = None
|
|
for key in tag_template:
|
|
res = cv2.matchTemplate(
|
|
value,
|
|
tag_template[key],
|
|
cv2.TM_CCORR_NORMED,
|
|
)
|
|
|
|
_, max_val, _, _ = cv2.minMaxLoc(res)
|
|
if max_val > max_v:
|
|
tag_res = {"tag": key, "val": max_val}
|
|
max_v = max_val
|
|
tag_pos = (
|
|
int(left + (index % 3) * int(w / 3) + 30),
|
|
int(up + int(index / 3) * int(h / 2) + 30),
|
|
)
|
|
tags[tag_res["tag"]] = tag_pos
|
|
return tags
|
|
|
|
def split_tags(self, img):
|
|
tag_img = []
|
|
h, w, _ = img.shape
|
|
ori_img = img
|
|
tag_h, tag_w = int(h / 2), int(w / 3)
|
|
for i in range(0, 2):
|
|
for j in range(0, 3):
|
|
if i * j == 2:
|
|
continue
|
|
tag_img.append(img[0:tag_h, 0:tag_w])
|
|
img = img[0:h, tag_w:w]
|
|
img = ori_img[tag_h:h, 0:w]
|
|
|
|
return tag_img
|
|
|
|
def get_ticket_number(self, height: int | None = 0, thres: int | None = 180):
|
|
p1, p2 = self.find("recruit/ticket")
|
|
p3, _ = self.find("recruit/stone")
|
|
p1 = (p2[0], p1[1] + 10)
|
|
p3 = (p3[0] - 30, p2[1] - 5)
|
|
img = cropimg(config.recog.gray, (p1, p3))
|
|
default_height = 29
|
|
if height and height != default_height:
|
|
scale = default_height / 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,)
|
|
)
|
|
if digit.size < 800:
|
|
continue
|
|
score = []
|
|
for i in range(10):
|
|
im = number[i]
|
|
if digit.shape[0] < im.shape[0] or digit.shape[1] < im.shape[1]:
|
|
continue
|
|
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
|
min_val, _, _, _ = cv2.minMaxLoc(result)
|
|
score.append(min_val)
|
|
value = value * 10 + score.index(min(score))
|
|
return value
|
|
|
|
def add_recruit_param(self):
|
|
if not config.conf.recruit_robot:
|
|
self.recruit_order = [6, 5, 4, 3, 2, 1]
|
|
self.recruit_order_index = 1
|
|
|
|
def tag_not_choosed(self, tag: np.ndarray):
|
|
if cmatch(tag, self.normal, thresh=80) or cmatch(tag, self.rare, thresh=80):
|
|
return False
|
|
|
|
return True
|
|
|
|
def get_recruit_time(
|
|
self, mode="hour" or "minute", height: int | None = 84, thres: int | None = 100
|
|
):
|
|
area = []
|
|
if mode == "hour":
|
|
area = [(610, 280), (750, 400)]
|
|
elif mode == "minute":
|
|
area = [(850, 280), (980, 400)]
|
|
|
|
img = cropimg(config.recog.gray, area)
|
|
templates = Digtal().noto_sans
|
|
default_height = 28
|
|
|
|
if height and height != default_height:
|
|
scale = default_height / 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
|
|
img = cv2.bitwise_not(img)
|
|
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,)
|
|
)
|
|
if digit.size < 900:
|
|
continue
|
|
score = []
|
|
for i in range(10):
|
|
im = templates[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 choose_time(self, now_time: int, to_time: int, mode="hour" or "minute"):
|
|
subtract_time = now_time - to_time
|
|
|
|
click_time = 0
|
|
max_click = 0
|
|
tap_pos = 0
|
|
if mode == "minute":
|
|
click_time = int(abs(subtract_time) / 10)
|
|
tap_pos = 0.5
|
|
[self.tap_element("one_hour", tap_pos, 0.8, 0.1) for _ in range(click_time)]
|
|
return
|
|
|
|
if mode == "hour":
|
|
click_time = abs(subtract_time)
|
|
max_click = 9
|
|
tap_pos = 0.2
|
|
|
|
if abs(subtract_time) > (max_click / 2):
|
|
if subtract_time > 0:
|
|
[self.tap_element("one_hour", 0.2, 0.2, 0.1) for _ in range(click_time)]
|
|
else:
|
|
[
|
|
self.tap_element("one_hour", 0.2, 0.8, 0.1)
|
|
for _ in range(max_click - click_time)
|
|
]
|
|
else:
|
|
if subtract_time < 0:
|
|
[self.tap_element("one_hour", 0.2, 0.2, 0.1) for _ in range(click_time)]
|
|
else:
|
|
[self.tap_element("one_hour", 0.2, 0.8, 0.1) for _ in range(click_time)]
|
|
|
|
return
|