改写战斗中替换group的逻辑

This commit is contained in:
Elaina 2024-10-13 01:24:58 +08:00
commit 7f89eb0db8
3890 changed files with 82290 additions and 0 deletions

View file

View file

@ -0,0 +1,234 @@
from datetime import datetime
import cv2
import numpy as np
from mower.models import secret_front
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.solvers.infra.filter import RIIC_Filter
from mower.utils import config
from mower.utils.character_recognize import (
match_portrait,
operator_room_select,
)
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg
from mower.utils.log import logger
from mower.utils.scene import Scene
from mower.utils.vector import sa, va
class RIIC_ChooseSolver(SceneGraphSolver, BaseMixin):
def run(self, room: str, agent_list: list, wait_time: float = 0) -> bool:
self.room = room
self.agent_list = agent_list
self.wait_time = wait_time
self.wait_choose = {}
self.choosed = []
self.cleard = False
self.filter = RIIC_Filter()
self.swipe_time = 0
self.first_filter = True
self.in_adjust = False
self.tmp_data = {}
self.tmp_checked = {}
self.start_time = datetime.now()
self.success = False
self.check_room()
try:
return super().run()
except ValueError as e:
logger.error(e.__traceback__)
return False
def riic_agent_choose(self):
if (
len(list(set(self.choosed) & set(self.agent_list))) == len(self.agent_list)
and len(self.agent_list) > 1
):
if self.check_agent_order():
return True
else:
return
if self.first_filter:
if self.first_filter:
self.filter.run(技能=False)
self.first_filter = False
choosed_pos = self.get_agent_pos()
if choosed_pos:
if len(self.agent_list) > 1:
self.tap_element("choose_agent/clear", interval=0.1)
else:
tap_pos = choosed_pos.popitem()[1][0]
self.tap(self.tap(va(tap_pos, (100, 100))), interval=0.1)
self.first_filter = False
return
if len(self.wait_choose) > 0:
logger.debug(f"待选名单{self.wait_choose}")
choosed_pos = self.get_agent_pos()
for name, pos in list(self.wait_choose.items()):
if name in list(choosed_pos):
if len(self.agent_list) == 1:
return True
self.choosed.append(name)
del self.wait_choose[name]
else:
logger.info(f"tap:{name},{pos}")
self.tap(va(pos[0], (100, 100)), interval=0.2)
logger.debug(f"待选名单{self.wait_choose}")
return
agents = dict(operator_room_select(config.recog.img))
if set(agents.keys()).issubset(self.tmp_data.keys()):
self.swipe_time = self.swipe_time + 1
if self.swipe_time > 3:
raise ValueError("选择干员失败 滑动次数过多")
else:
self.swipe_time = 0
self.tmp_data = agents
logger.info(agents)
intersection = list(set(agents.keys()) & set(self.agent_list))
if intersection:
for i in intersection:
if i not in self.choosed:
self.wait_choose[i] = agents[i]
if self.wait_choose:
return
else:
# 加一个判断最右
self.swipe_noinertia((1000, 540), (-1900, 0), interval=0.1)
def check_agent_order(self):
choosed_agents = self.get_agent_pos(no_number=False)
logger.info(f"{choosed_agents}{self.agent_list}")
if len(choosed_agents) < len(self.agent_list):
if self.in_adjust is False:
self.filter.run(未进驻=None)
return False
agents = dict(operator_room_select(config.recog.img))
for i in self.agent_list:
self.tap(va(agents[i][0], (100, 0)), interval=0)
# 盲点结束等一下
self.sleep()
return False
elif len(choosed_agents) == len(self.agent_list):
for i in self.agent_list:
if choosed_agents[i][0] != (self.agent_list.index(i)) + 1:
self.tap_element("choose_agent/clear", interval=0.5)
self.in_adjust = True
return False
return True
def check_with_number(self, img, height=60):
_, im_w, _ = img.shape
img = cropimg(img, ((0, 0), (im_w, 100)))
kernel = np.ones((9, 9), np.uint8)
img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
img = cv2.inRange(img, (0, 0, 200), (225, 15, 255))
img = cv2.erode(img, kernel)
img = cv2.dilate(img, kernel)
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.shape[0] < 65 or digit.shape[1] < 40:
continue
score = []
kernel = np.ones((2, 2), np.uint8)
for i in range(10):
im = secret_front[i]
default_height = 25
if height and height != default_height:
scale = default_height / height
digit_1 = cv2.resize(digit, None, None, scale, scale)
im = cv2.dilate(im, kernel=kernel)
result = cv2.matchTemplate(digit_1, im, cv2.TM_SQDIFF_NORMED)
min_val, _, _, _ = cv2.minMaxLoc(result)
score.append(min_val)
value = value * 10 + score.index(min(score))
return value
def get_agent_pos(self, no_number=True):
res = {}
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)
segments.append(((x, y + 100), (x + w, y + h - 200)))
logger.debug(f"segments:{segments}")
opear = match_portrait(config.recog.gray, segment=segments)
if no_number:
return list(dict(opear).keys())
if len(self.agent_list) > 1:
for i in opear:
res[i[0]] = [
self.check_with_number(
cropimg(config.recog.img, sa([i[1][0], i[1][1]], (0, -100)))
),
i[1],
]
else:
for i in opear:
res[i[0]] = i[1]
return res
def check_wait_time(self) -> float:
return max(
self.wait_time - (datetime.now() - self.start_time).total_seconds(), 0
)
def check_room(self):
if (
(scene := self.scene()) == Scene.INFRA_DETAILS
) and not EnterRoomSolver().detect_room() == self.room:
EnterRoomSolver().run(self.room)
elif (
scene in [Scene.ORDER_LIST, Scene.FACTORY_ROOMS]
and not self.detect_room_inside() == self.room
):
EnterRoomSolver().run(self.room)
def transition(self) -> bool:
if (scene := self.scene()) == Scene.RIIC_OPERATOR_SELECT:
if self.riic_agent_choose():
self.sleep(self.check_wait_time())
self.tap_element("confirm_blue")
self.success = True
elif self.find("room_detail"):
if self.success:
return True
else:
self.tap((1600, 200))
elif scene in [Scene.ORDER_LIST, Scene.FACTORY_ROOMS]:
if self.success:
return True
else:
self.tap((500, 970))
elif scene == Scene.INFRA_ARRANGE_ORDER:
self.scene_graph_step(Scene.RIIC_OPERATOR_SELECT)
elif scene == Scene.INFRA_ARRANGE_CONFIRM:
self.scene_graph_step(Scene.RIIC_OPERATOR_SELECT)
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room)

View file

@ -0,0 +1,309 @@
import lzma
import pickle
from datetime import datetime, timedelta
import cv2
import numpy as np
from mower import __rootdir__
from mower.utils import config, rapidocr
from mower.utils.character_recognize import operator_room_select
from mower.utils.csleep import MowerExit
from mower.utils.image import cropimg, loadres, thres2
from mower.utils.log import logger
with lzma.open(f"{__rootdir__}/models/operator_room.model", "rb") as f:
OP_ROOM = pickle.loads(f.read())
kernel = np.ones((12, 12), np.uint8)
class BaseMixin:
def detect_arrange_order(self):
name_list = ["工作状态", "技能", "心情", "信赖值"]
x_list = (1196, 1320, 1445, 1572)
y = 70
mask = cv2.inRange(config.recog.hsv, (99, 200, 0), (100, 255, 255))
for idx, x in enumerate(x_list):
if np.count_nonzero(mask[y : y + 3, x : x + 5]):
return (name_list[idx], True)
if np.count_nonzero(mask[y + 10 : y + 13, x : x + 5]):
return (name_list[idx], False)
def switch_arrange_order(self, name, ascending=False):
name_x = {"工作状态": 1197, "技能": 1322, "心情": 1447, "信赖值": 1575}
if isinstance(name, int):
name = list(name_x.keys())[name - 1]
if isinstance(ascending, str):
ascending = ascending == "true"
name_y = 60
self.tap((name_x[name], name_y), interval=0.5)
while True:
n, s = self.detect_arrange_order()
if n == name and s == ascending:
break
self.tap((name_x[name], name_y), interval=0.5)
def scan_agent(self, agent: list[str], error_count=0, max_agent_count=-1):
try:
# 识别干员
while self.find("connecting"):
logger.info("等待网络连接")
self.sleep()
# 返回的顺序是从左往右从上往下
ret = operator_room_select(config.recog.img)
# 提取识别出来的干员的名字
select_name = []
for name, scope in ret:
if name in agent:
select_name.append(name)
# self.get_agent_detail((y[1][0]))
self.tap(scope, interval=0)
agent.remove(name)
# 如果是按照个数选择 Free
if max_agent_count != -1:
if len(select_name) >= max_agent_count:
return select_name, ret
return select_name, ret
except MowerExit:
raise
except Exception as e:
logger.exception(e)
error_count += 1
if error_count < 3:
self.sleep(3)
return self.scan_agent(agent, error_count, max_agent_count)
else:
raise e
def verify_agent(self, agent: list[str], error_count=0, max_agent_count=-1):
try:
# 识别干员
while self.find("connecting"):
logger.info("等待网络连接")
self.sleep()
ret = operator_room_select(config.recog.img) # 返回的顺序是从左往右从上往下
# 提取识别出来的干员的名字
index = 0
for name, scope in ret:
if index >= len(agent):
return True
if name != agent[index]:
return False
index += 1
return True
except Exception as e:
logger.exception(e)
error_count += 1
self.switch_arrange_order("技能")
if error_count < 3:
self.sleep(3)
return self.verify_agent(agent, error_count, max_agent_count)
else:
raise e
def swipe_left(self, right_swipe):
if right_swipe > 3:
self.detail_filter(控制中枢=True)
self.detail_filter(控制中枢=False)
else:
swipe_time = 2 if right_swipe == 3 else right_swipe
for i in range(swipe_time):
self.swipe_noinertia((650, 540), (1900, 0))
return 0
def detail_filter(self, **kwargs):
if kwargs:
text = "".join(
f"{'打开' if value else '关闭'}{label}筛选"
for label, value in kwargs.items()
)
text += ",关闭其余筛选"
logger.info(text)
else:
logger.info("关闭所有筛选")
labels = [
"未进驻",
"产出设施",
"功能设施",
"自定义设施",
"控制中枢",
"生产类后勤",
"功能类后勤",
"恢复类后勤",
]
label_x = (560, 815, 1070, 1330)
label_y = (540, 645)
label_pos = []
for y in label_y:
for x in label_x:
label_pos.append((x, y))
label_pos_map = dict(zip(labels, label_pos))
target_state = dict(zip(labels, [False] * len(labels)))
target_state.update(kwargs)
filter_pos = (config.recog.w * 0.95, config.recog.h * 0.05)
self.tap(filter_pos)
err_cnt = 0
while not self.find("arrange_order_options_scene"):
self.ctap(filter_pos)
err_cnt += 1
if err_cnt > 3:
raise Exception("未进入筛选页面")
for label, pos in label_pos_map.items():
current_state = self.get_color(pos)[2] > 100
if target_state[label] != current_state:
self.tap(pos, interval=0.1)
config.recog.update()
confirm_pos = (config.recog.w * 0.8, config.recog.h * 0.8)
self.tap(confirm_pos)
err_cnt = 0
while self.find("arrange_order_options_scene"):
self.ctap(confirm_pos)
err_cnt += 1
if err_cnt > 3:
raise Exception("筛选确认失败")
def double_read_time(self, cord, upperLimit=None, use_digit_reader=False):
config.recog.update()
time_in_seconds = self.read_time(cord, upperLimit, use_digit_reader)
if time_in_seconds is None:
return datetime.now()
execute_time = datetime.now() + timedelta(seconds=(time_in_seconds))
return execute_time
def read_accurate_mood(self, img):
try:
img = thres2(img, 200)
return cv2.countNonZero(img) * 24 / 310
except Exception as e:
logger.exception(e)
return 24
def detect_product_complete(self) -> str:
"""
用于识别基建内产物收取信息
"""
for product in [
"赤金",
"经验",
"龙门币",
"源石碎片",
"合成玉",
"信用",
"先锋双芯片",
"狙击双芯片",
"医疗双芯片",
"术师双芯片",
"近卫双芯片",
"重装双芯片",
"辅助双芯片",
"特种双芯片",
]:
if self.find(f"infra_complete/{product}", scope=((1440, 130), (1920, 193))):
return product
def read_operator_in_room(self, img):
img = thres2(img, 200)
img = cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,))
dilation = cv2.dilate(img, kernel, iterations=1)
contours, _ = cv2.findContours(dilation, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
rect = map(lambda c: cv2.boundingRect(c), contours)
x, y, w, h = sorted(rect, key=lambda c: c[0])[0]
img = img[y : y + h, x : x + w]
tpl = np.zeros((46, 265), dtype=np.uint8)
tpl[: img.shape[0], : img.shape[1]] = img
tpl = cv2.copyMakeBorder(tpl, 2, 2, 2, 2, cv2.BORDER_CONSTANT, None, (0,))
max_score = 0
best_operator = None
for operator, template in OP_ROOM.items():
result = cv2.matchTemplate(tpl, template, cv2.TM_CCORR_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if max_val > max_score:
max_score = max_val
best_operator = operator
return best_operator
def read_screen(self, img, type="mood", limit=24, cord=None):
if cord is not None:
img = cropimg(img, cord)
if type == "name":
img = cropimg(img, ((169, 22), (513, 80)))
return self.read_operator_in_room(img)
try:
ret = rapidocr.engine(img, use_det=False, use_cls=False, use_rec=True)[0]
logger.debug(ret)
if not ret or not ret[0][0]:
raise Exception("识别失败")
ret = ret[0][0]
if "mood" in type:
if (f"/{limit}") in ret:
ret = ret.replace(f"/{limit}", "")
if len(ret) > 0:
if "." in ret:
ret = ret.replace(".", "")
return int(ret)
else:
return -1
elif "time" in type:
if "." in ret:
ret = ret.replace(".", ":")
return ret.strip()
else:
return ret
except Exception as e:
logger.exception(e)
return limit + 1
def read_time(self, cord, upperlimit, error_count=0, use_digit_reader=False):
# 刷新图片
config.recog.update()
try:
if use_digit_reader:
time_str = self.digit_reader.get_time(config.recog.gray)
else:
time_str = self.read_screen(config.recog.img, type="time", cord=cord)
h, m, s = str(time_str).split(":")
if int(m) > 60 or int(s) > 60:
raise Exception("读取错误")
res = int(h) * 3600 + int(m) * 60 + int(s)
if upperlimit is not None and res > upperlimit:
raise Exception("超过读取上限")
else:
return res
except Exception:
if error_count > 3:
logger.exception(f"读取失败{error_count}次超过上限")
return None
else:
logger.exception("读取失败")
return self.read_time(
cord, upperlimit, error_count + 1, use_digit_reader
)
def detect_room_number(self, img) -> int:
score = []
for i in range(1, 4):
digit = loadres(f"room/{i}")
result = cv2.matchTemplate(img, digit, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
score.append(max_val)
return score.index(max(score)) + 1
def detect_room_inside(self) -> str:
"""
贸易站订单列表或制造站设施列表的房间号
"""
img = config.recog.img
digit_1 = cropimg(img, ((783, 45), (803, 75)))
digit_2 = cropimg(img, ((825, 45), (845, 75)))
digit_1 = self.detect_room_number(digit_1)
digit_2 = self.detect_room_number(digit_2)
return f"room_{digit_1}_{digit_2}"

View file

@ -0,0 +1,22 @@
from mower.solvers.shop import CreditShop
from mower.utils.log import logger
from .daily import DailySolver
from .get_clue_count import GetClueCountSolver
from .give_away import GiveAwaySolver
from .party_time import PartyTimeSolver
from .place import PlaceSolver
from .receive import ReceiveSolver
class ClueSolver:
def run(self) -> None:
logger.info("基建:线索")
DailySolver().run()
ReceiveSolver().run()
PlaceSolver().run()
clue_count = GetClueCountSolver().run()
GiveAwaySolver().run(clue_count)
party_time = PartyTimeSolver().run()
CreditShop().run()
return party_time

View file

@ -0,0 +1,37 @@
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils.graph import SceneGraphSolver
from mower.utils.log import logger
from mower.utils.recognize import Scene
from .utils import clue_cls
class DailySolver(SceneGraphSolver):
def run(self) -> None:
self.success = False
super().run()
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.tap((725, 850))
elif scene == Scene.INFRA_CONFIDENTIAL:
# 检查是否领过线索
daily_scope = ((1815, 200), (1895, 250))
if self.find("clue/badge_new", scope=daily_scope) and not self.success:
self.tap((1800, 270))
else:
return True
elif scene == Scene.CLUE_DAILY:
if not self.find(
"clue/icon_notification", scope=((1400, 0), (1920, 400))
) and (clue := clue_cls("daily")):
logger.info(f"领取今日线索({clue}号)")
self.tap_element("clue/button_get")
else:
# 今日线索已领取,点X退出
self.tap((1484, 152))
self.success = True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run("meeting", detail=False)

View file

@ -0,0 +1,27 @@
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils.graph import SceneGraphSolver
from mower.utils.log import logger
from mower.utils.recognize import Scene
class GetClueCountSolver(SceneGraphSolver, BaseMixin):
def run(self) -> int:
self.res = -1
super().run()
if self.res >= 0:
return self.res
raise ValueError("未找到线索数量")
def transition(self) -> bool:
if self.find("arrange_check_in"):
self.res = self.read_screen(
config.recog.img, limit=10, cord=((645, 977), (755, 1018))
)
logger.info(f"当前拥有线索数量为{self.res}")
return True
elif self.scene() == Scene.INFRA_CONFIDENTIAL:
self.back()
else:
EnterRoomSolver().run("meeting", detail=False)

View file

@ -0,0 +1,72 @@
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config, rapidocr
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg
from mower.utils.log import logger
from mower.utils.recognize import Scene
from mower.utils.vector import va
from .utils import clue_cls, clue_scope
class GiveAwaySolver(SceneGraphSolver):
def run(self, clue_count) -> None:
self.clue_count = clue_count
self.friend_clue = []
if not (config.conf.leifeng_mode or self.clue_count > 9):
return
super().run()
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.tap((725, 850))
elif scene == Scene.INFRA_CONFIDENTIAL:
self.ctap((1799, 578))
elif scene == Scene.CLUE_GIVE_AWAY:
if (c := clue_cls("give_away")) and (
config.conf.leifeng_mode or self.clue_count > 9
):
if not self.friend_clue:
if self.find(
"clue/icon_notification", scope=((1400, 0), (1920, 400))
):
self.sleep()
return
for i in range(4):
label_scope = ((1450, 228 + i * 222), (1580, 278 + i * 222))
if not self.find("clue/label_give_away", scope=label_scope):
break
name_top_left = (870, 127 + 222 * i)
name_scope = (name_top_left, va(name_top_left, (383, 62)))
name = rapidocr.engine(
cropimg(config.recog.gray, name_scope),
use_det=True,
use_cls=False,
use_rec=True,
)[0][0][1]
if name:
name = name.strip()
data = {"name": name}
for j in range(1, 8):
pos = (1230 + j * 64, 142 + i * 222)
data[j] = self.get_color(pos)[0] < 137
self.friend_clue.append(data)
logger.debug(self.friend_clue)
friend = None
for idx, fc in enumerate(self.friend_clue):
if not fc[c]:
friend = idx
fc[c] = True
break
friend = friend or 0
logger.info(f"{self.friend_clue[friend]['name']}送一张线索{c}")
self.tap(clue_scope["give_away"])
self.clue_count -= 1
self.tap((1790, 200 + friend * 222))
else:
self.tap((1868, 54))
return True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run("meeting", detail=False)

View file

@ -0,0 +1,26 @@
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils.graph import SceneGraphSolver
from mower.utils.log import logger
from mower.utils.recognize import Scene
class PartyTimeSolver(SceneGraphSolver, BaseMixin):
def run(self):
super().run()
return self.party_time
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
if self.find("clue/title_party", scope=((1600, 190), (1880, 260))):
self.party_time = self.double_read_time(((1768, 438), (1902, 480)))
logger.info(f"线索交流结束时间:{self.party_time}")
return True
else:
self.party_time = None
logger.info("线索交流未开启")
return True
elif scene == Scene.INFRA_CONFIDENTIAL:
self.back()
else:
EnterRoomSolver().run("meeting", detail=False)

View file

@ -0,0 +1,163 @@
import cv2
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config, rapidocr
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, loadres, thres2
from mower.utils.log import logger
from mower.utils.recognize import Scene
from mower.utils.vector import va
from .utils import (
clue_cls,
clue_scope,
exit_pos,
is_orange,
main_dots,
main_scope,
main_time,
tl2p,
tm_thres,
)
clue_pos = ((1305, 208), (1305, 503), (1305, 797))
filter_receive = (1900, 45)
filter_self = (1610, 70)
class PlaceSolver(SceneGraphSolver):
def run(self) -> None:
self.clue_status = {}
super().run()
def detect_unlock(self):
unlock_pos = self.find("clue/button_unlock")
if unlock_pos is None:
return None
color = self.get_color(self.get_pos(unlock_pos))
if all(color > [252] * 3):
return unlock_pos
return None
def place_index(self):
for cl, st in self.clue_status.items():
if st in ["available", "self", "available_self_only"]:
return cl, st
return None, None
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.tap((725, 850))
elif scene == Scene.INFRA_CONFIDENTIAL:
if unlock_pos := self.detect_unlock():
self.tap(unlock_pos)
return
for i in range(1, 8):
if is_orange(self.get_color(main_dots[i])):
self.clue_status[i] = "available"
elif clue_cls(i):
hsv = config.recog.hsv
if 160 < hsv[main_time[i][1]][main_time[i][0]][0] < 180:
self.clue_status[i] = "friend"
else:
self.clue_status[i] = "self"
else:
self.clue_status[i] = None
cl, st = self.place_index()
if st in ["available", "self", "available_self_only"]:
self.tap(main_scope[cl])
return
else:
return True
elif scene == Scene.CLUE_PLACE:
cl, st = self.place_index()
if cl is None:
if unlock_pos := self.detect_unlock():
self.tap(unlock_pos)
return
else:
self.tap(exit_pos)
return True
if self.get_color((1328 + 77 * cl, 114))[0] < 150:
# 右上角 1-7
self.tap(clue_scope[cl])
return
receive = st in ["available", "self"]
filter_pos = filter_receive if receive else filter_self
if not all(self.get_color(filter_pos) > [252] * 3):
self.tap(filter_pos)
return
self.clue_list = []
for cp in clue_pos:
clue_img = cropimg(config.recog.img, tl2p(cp))
res = loadres(f"clue/{cl}")
result = cv2.matchTemplate(clue_img, res, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if max_val > tm_thres:
name_scope = (va(cp, (274, 99)), va(cp, (580, 134)))
name_img = cropimg(config.recog.gray, name_scope)
name_img = cv2.copyMakeBorder(
name_img, 48, 48, 48, 48, cv2.BORDER_REPLICATE
)
name = rapidocr.engine(
name_img,
use_det=True,
use_cls=False,
use_rec=True,
)[0][0][1]
if name:
name = name.strip()
time_scope = (va(cp, (45, 222)), va(cp, (168, 255)))
time_hsv = cropimg(config.recog.hsv, time_scope)
if 165 < time_hsv[0][0][0] < 175:
time_img = thres2(cropimg(config.recog.gray, time_scope), 180)
time_img = cv2.copyMakeBorder(
time_img, 48, 48, 48, 48, cv2.BORDER_REPLICATE
)
time = rapidocr.engine(
time_img,
use_det=True,
use_cls=False,
use_rec=True,
)[0][0][1]
if time:
time = time.strip()
else:
time = None
self.clue_list.append(
{"name": name, "time": time, "scope": tl2p(cp)}
)
else:
break
if self.clue_list:
list_name = "接收库" if receive else "自有库"
logger.info(f"{cl}号线索{list_name}{self.clue_list}")
selected = None
for c in self.clue_list:
if c["time"]:
selected = c
break
selected = selected or self.clue_list[0]
self.tap(selected["scope"])
if self.clue_status[cl] == "available":
self.clue_status[cl] = "friend"
elif self.clue_status[cl] == "available_self_only":
self.clue_status[cl] = "self_only"
elif self.clue_status[cl] == "self":
self.clue_status[cl] = "friend"
else:
self.clue_status[cl] = None
else:
if self.clue_status[cl] == "available":
self.clue_status[cl] = "available_self_only"
elif self.clue_status[cl] == "available_self_only":
self.clue_status[cl] = None
elif self.clue_status[cl] == "self":
self.clue_status[cl] = "self_only"
else:
self.clue_status[cl] = None
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run("meeting", detail=False)

View file

@ -0,0 +1,50 @@
import cv2
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config, rapidocr
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg
from mower.utils.log import logger
from mower.utils.recognize import Scene
from .utils import clue_cls, exit_pos
class ReceiveSolver(SceneGraphSolver):
def run(self) -> None:
super().run()
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.tap((725, 850))
elif scene == Scene.INFRA_CONFIDENTIAL:
receive_scope = ((1815, 360), (1895, 410))
if self.find("clue/badge_new", scope=receive_scope):
self.ctap((1800, 430))
else:
return True
elif scene == Scene.CLUE_RECEIVE:
if self.find("infra_complete\信用", scope=((1440, 130), (1920, 193))):
self.sleep()
return
if clue := clue_cls("receive"):
name_scope = ((1580, 220), (1880, 255))
name_img = cropimg(config.recog.gray, name_scope)
name_img = cv2.copyMakeBorder(
name_img, 48, 48, 48, 48, cv2.BORDER_REPLICATE
)
name = rapidocr.engine(
name_img,
use_det=True,
use_cls=False,
use_rec=True,
)[0][0][1]
name = name.strip() if name else "好友"
logger.info(f"接收{name}{clue}号线索")
self.tap(name_scope)
else:
self.tap(exit_pos)
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run("meeting", detail=False)

View file

@ -0,0 +1,60 @@
import cv2
from mower.utils import config
from mower.utils.image import cropimg, loadres
from mower.utils.vector import va
clue_size = (162, 216)
clue_top_left = {
"daily": (1118, 334),
"receive": (1305, 122),
"give_away": (30, 208),
# 摆放线索界面,线索框的左上角
1: (72, 228),
2: (374, 334),
3: (679, 198),
4: (1003, 265),
5: (495, 660),
6: (805, 573),
7: (154, 608),
}
dot_offset = (168, -8)
main_offset = (425, 0)
main_time_offset = (443, 257)
def tl2p(top_left):
return top_left, va(top_left, clue_size)
def is_orange(dot):
orange_dot = (255, 104, 1)
return all([abs(dot[i] - orange_dot[i]) < 3 for i in range(3)])
clue_scope = {}
for index, top_left in clue_top_left.items():
clue_scope[index] = tl2p(top_left)
clue_dots = {}
main_dots = {}
main_time = {}
main_scope = {}
for i in range(1, 8):
clue_dots[i] = va(clue_top_left[i], dot_offset)
main_dots[i] = va(clue_dots[i], main_offset)
main_time[i] = va(clue_top_left[i], main_time_offset)
main_scope[i] = tl2p(va(clue_top_left[i], main_offset))
tm_thres = 0.6
exit_pos = (1239, 144)
def clue_cls(scope):
scope_dict = clue_scope if isinstance(scope, str) else main_scope
img = cropimg(config.recog.img, scope_dict[scope])
for i in range(1, 8):
res = loadres(f"clue/{i}")
result = cv2.matchTemplate(img, res, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if max_val > tm_thres:
return i
return None

View file

@ -0,0 +1,132 @@
from datetime import datetime, timedelta
import cv2
from mower.models import riic_base_digits
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.digit_reader import DigitReader
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, thres2
from mower.utils.log import logger
from mower.utils.recognize import Scene
class DroneSolver(SceneGraphSolver, BaseMixin):
def run(
self,
room: str,
count: int = None,
all_in: bool = False,
cur_count: int = None,
) -> bool:
"""
Args:
room: 房间名
count:消耗无人机数量
all_in:贸易站-加速完成一笔订单制造站-消耗全部无人机消耗数量
cur_count:当前无人机数量
"""
logger.info("Start:无人机加速")
self.room = room
while cur_count is None and not all_in:
try:
cur_count = DigitReader().get_drone(config.recog.gray)
except Exception:
EnterRoomSolver().run(self.room, detail=False)
if count is None and not all_in:
count = cur_count - config.conf.drone_count_limit
if count <= 0:
return True
self.count = count
self.all_in = all_in
self.success = False
if cur_count < self.count and not self.all_in:
logger.info("无人机数量不足")
return False
if (
self.scene() in [Scene.ORDER_LIST, Scene.FACTORY_ROOMS]
and not self.detect_room_inside() == self.room
):
EnterRoomSolver().run(self.room, detail=False)
self.wait_start()
super().run()
return True
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 = riic_base_digits[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 timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=20)
def wait_start(self):
self.start_time = datetime.now()
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene in [Scene.ORDER_LIST, Scene.FACTORY_ROOMS]:
self.wait_start()
if self.success:
return True
elif pos := self.find("factory_accelerate"):
self.tap(pos)
elif pos := self.find("bill_accelerate"):
self.tap(pos)
elif scene == Scene.DRONE_ACCELERATE:
if self.all_in:
self.tap((1450, 500))
self.tap((1450, 900))
self.success = True
elif self.timeout():
logger.info("加速时间超过订单剩余时间")
self.tap((1450, 900))
self.success = True
elif (
tap_count := self.count - self.number(((240, 650), (350, 720)), 40, 150)
) == 0:
self.tap((1450, 900))
self.success = True
elif tap_count > 0:
for _ in range(tap_count):
self.tap((1300, 500), interval=0.07)
self.sleep(0.2)
elif tap_count < 0:
for _ in range(-tap_count):
self.tap((900, 500), interval=0.07)
self.sleep(0.2)
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,219 @@
from datetime import datetime, timedelta
import cv2
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, loadres
from mower.utils.log import logger
from mower.utils.recognize import Scene
from mower.utils.translate_room import translate_room
from mower.utils.vector import sm, va
facility = {
"central": (
(-0.6551724137931034, -0.26436781609195403),
(2.3524904214559386, 1.1340996168582376),
),
"dormitory_1": (
(-0.6615384615384616, 0.9692307692307693),
(1.7538461538461538, 0.5115384615384616),
),
"dormitory_2": (
(-0.046153846153846156, 1.580769230769231),
(1.7461538461538462, 0.5115384615384616),
),
"dormitory_3": (
(-0.6615384615384616, 2.1884615384615387),
(1.7538461538461538, 0.5192307692307693),
),
"dormitory_4": (
(-0.046153846153846156, 2.7961538461538464),
(1.7461538461538462, 0.5230769230769231),
),
"factory": (
(2.689655172413793, 0.9540229885057471),
(1.1379310344827585, 0.5172413793103448),
),
"meeting": (
(2.0804597701149423, 0.3486590038314176),
(1.747126436781609, 0.5172413793103448),
),
"contact": (
(2.689655172413793, 1.5632183908045976),
(1.1379310344827585, 0.5172413793103448),
),
"room_1_1": (
(-4.626923076923077, 0.9692307692307693),
(1.126923076923077, 0.5115384615384616),
),
"room_1_2": (
(-3.403846153846154, 0.9692307692307693),
(1.123076923076923, 0.5115384615384616),
),
"room_1_3": (
(-2.184615384615385, 0.9692307692307693),
(1.1307692307692307, 0.5115384615384616),
),
"room_2_1": (
(-5.2384615384615385, 1.580769230769231),
(1.1346153846153846, 0.5115384615384616),
),
"room_2_2": (
(-4.015384615384615, 1.580769230769231),
(1.1307692307692307, 0.5115384615384616),
),
"room_2_3": (
(-2.7923076923076926, 1.580769230769231),
(1.126923076923077, 0.5115384615384616),
),
"room_3_1": (
(-4.6230769230769235, 2.1884615384615387),
(1.123076923076923, 0.5192307692307693),
),
"room_3_2": (
(-3.4000000000000004, 2.1884615384615387),
(1.123076923076923, 0.5192307692307693),
),
"room_3_3": (
(-2.180769230769231, 2.1884615384615387),
(1.126923076923077, 0.5192307692307693),
),
"train": (
(2.689655172413793, 2.18007662835249),
(1.1379310344827585, 0.5172413793103448),
),
}
class EnterRoomSolver(SceneGraphSolver):
def run(self, room: str, detail: bool = True):
"""
Args:
room: 房间名
detail: 打开进驻信息
"""
logger.info(f"进入房间:{translate_room(room)}")
self.room = room
self.detail = detail
self.wait_start()
super().run()
def timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=3)
def wait_start(self):
self.start_time = datetime.now()
def detect_room_number(self, img) -> int:
score = []
for i in range(1, 5):
digit = loadres(f"room/{i}")
result = cv2.matchTemplate(img, digit, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
score.append(max_val)
return score.index(max(score)) + 1
def detect_room(self) -> str:
color_map = {
"制造站": 25,
"贸易站": 99,
"发电站": 36,
"训练室": 178,
"加工站": 32,
}
img = cropimg(config.recog.img, ((568, 18), (957, 95)))
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
colored_room = None
for room, color in color_map.items():
mask = cv2.inRange(hsv, (color - 1, 0, 0), (color + 2, 255, 255))
if cv2.countNonZero(mask) > 1000:
colored_room = room
break
if colored_room in ["制造站", "贸易站", "发电站"]:
digit_1 = cropimg(img, ((211, 24), (232, 54)))
digit_2 = cropimg(img, ((253, 24), (274, 54)))
digit_1 = self.detect_room_number(digit_1)
digit_2 = self.detect_room_number(digit_2)
logger.debug(f"{colored_room}B{digit_1}0{digit_2}")
return f"room_{digit_1}_{digit_2}"
elif colored_room == "训练室":
logger.debug("训练室B305")
return "train"
elif colored_room == "加工站":
logger.debug("加工站B105")
return "factory"
white_room = ["central", "dormitory", "meeting", "contact"]
score = []
for room in white_room:
tpl = loadres(f"room/{room}")
result = cv2.matchTemplate(img, tpl, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
score.append(max_val)
room = white_room[score.index(max(score))]
if room == "central":
logger.debug("控制中枢")
elif room == "dormitory":
digit = cropimg(img, ((174, 24), (195, 54)))
digit = self.detect_room_number(digit)
if digit == 4:
logger.debug("宿舍B401")
else:
logger.debug(f"宿舍B{digit}04")
return f"dormitory_{digit}"
elif room == "meeting":
logger.debug("会客室1F02")
else:
logger.debug("办公室B205")
return room
@staticmethod
def segment(central: tp.Scope) -> dict[str, tp.Rectangle]:
top_left = central[0]
width = central[1][0] - central[0][0]
result = {}
for name, (position, size) in facility.items():
facility_top_left = va(top_left, sm(width, position))
scope = facility_top_left, va(facility_top_left, sm(width, size))
result[name] = (
(max(scope[0][0], 0), scope[0][1]),
(min(scope[1][0], 1920), scope[1][1]),
)
logger.debug(result)
return result
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_MAIN:
score, scope = config.recog.match2d("control_central")
if score >= 0.9:
pos = self.segment(scope)[self.room]
self.ctap(pos, 1, config.screenshot_avg / 1000)
self.wait_start()
else:
config.recog.update()
elif scene in [Scene.CTRLCENTER_ASSISTANT, Scene.INFRA_DETAILS]:
if self.detect_room() == self.room:
if not self.detail:
self.tap((960, 540), interval=0)
return True
if self.find("room_detail"):
return True
if pos := self.find("arrange_check_in"):
if self.find("infra_overview_top_right"):
self.ctap(pos, 1, config.screenshot_avg / 1000)
else:
config.recog.update()
elif self.timeout():
self.scene_graph_step(Scene.INFRA_MAIN)
else:
config.recog.update()
else:
if self.timeout():
self.scene_graph_step(Scene.INFRA_MAIN)
else:
config.recog.update()
else:
self.scene_graph_step(Scene.INFRA_MAIN)

View file

@ -0,0 +1,129 @@
import cv2
import numpy as np
from mower.utils import config
from mower.utils.graph import SceneGraphSolver
from mower.utils.log import logger
from mower.utils.scene import Scene
class RIIC_Filter(SceneGraphSolver):
def __init__(self) -> None:
self.labels = [
"未进驻",
"产出设施",
"功能设施",
"自定义设施",
"控制中枢",
"生产类后勤",
"功能类后勤",
"恢复类后勤",
]
self.orders = [
"工作状态",
"技能",
"心情",
"信赖值",
]
label_x = (560, 815, 1070, 1330)
label_y = (540, 645)
label_pos = []
for y in label_y:
for x in label_x:
label_pos.append((x, y))
self.label_pos_map = dict(zip(self.labels, label_pos))
def run(self, **kwargs):
# 筛选标签 + True/False (开关标签) /None(切换状态)
# 排序 + True/False 箭头朝(上/下)
self.target_state = None
self.order_label = None
self.labels_chooed_done = False
if kwargs:
text = ""
for self.label, value in kwargs.items():
if self.label in self.orders and self.order_label is None:
self.order_label = self.label
elif self.order_label is not None:
raise ValueError("排序参数仅有一个")
text.join(f"{'打开' if value else '关闭'}{self.label}筛选")
text += ",关闭其余筛选"
logger.info(text)
else:
logger.info("关闭所有筛选")
if self.target_state is None:
self.target_state = dict(zip(self.labels, [False] * len(self.labels)))
self.target_state.update(kwargs)
logger.info(f"target_state :{self.target_state}")
try:
super().run()
except ValueError as e:
logger.error(e.__traceback__)
def transition(self) -> bool:
if (scene := self.scene()) == Scene.RIIC_OPERATOR_SELECT:
if self.labels_chooed_done:
if self.order_label in self.orders:
n, s = self.detect_arrange_order()
logger.info(f"detect_arrange_order {n}{s}")
logger.info(
f"order_label {self.order_label}{self.target_state[self.order_label]}"
)
if (
n != self.order_label
or s != self.target_state[self.order_label]
):
self.switch_arrange_order(
self.order_label, self.target_state[self.order_label]
)
return
return True
else:
self.tap_element("arrange_order_options")
elif scene == Scene.INFRA_ARRANGE_ORDER:
logger.debug(self.target_state)
not_taped = True
for label, pos in self.label_pos_map.items():
current_state = self.get_color(pos)[2] > 100
if self.target_state[label] is None:
self.target_state[label] = not current_state
if self.target_state[label] != current_state:
not_taped = False
self.tap(pos, interval=0.1)
if not_taped:
confirm_pos = (config.recog.w * 0.8, config.recog.h * 0.8)
self.tap(confirm_pos)
self.labels_chooed_done = True
else:
return
elif scene in self.waiting_scene:
self.waiting_solver()
else:
return False
def detect_arrange_order(self):
x_list = (1196, 1320, 1445, 1572)
y = 70
hsv = cv2.cvtColor(config.recog.img, cv2.COLOR_RGB2HSV)
mask = cv2.inRange(hsv, (99, 200, 0), (100, 255, 255))
for idx, x in enumerate(x_list):
if np.count_nonzero(mask[y : y + 3, x : x + 5]):
return (self.orders[idx], True)
if np.count_nonzero(mask[y + 10 : y + 13, x : x + 5]):
return (self.orders[idx], False)
def switch_arrange_order(self, name, ascending=False):
name_x = {"工作状态": 1197, "技能": 1322, "心情": 1447, "信赖值": 1575}
if isinstance(name, int):
name = list(name_x.keys())[name - 1]
if isinstance(ascending, str):
ascending = ascending == "true"
name_y = 60
self.tap((name_x[name], name_y), interval=0.5)

View file

@ -0,0 +1,137 @@
from datetime import datetime, timedelta
import cv2
from mower.models import riic_base_digits
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, thres2
from mower.utils.log import logger
name_x = (1288, 1869)
name_y = [(135, 326), (344, 535), (553, 744), (532, 723), (741, 932)]
name_p = [tuple(zip(name_x, y)) for y in name_y]
mood_x = (1470, 1780)
mood_y = [(219, 220), (428, 429), (637, 638), (615, 616), (823, 824)]
mood_p = [tuple(zip(mood_x, y)) for y in mood_y]
time_x = (1650, 1780)
time_y = [(270, 305), (480, 515), (690, 725), (668, 703), (877, 912)]
time_p = [tuple(zip(time_x, y)) for y in time_y]
class GetAgentFromRoomSolver(SceneGraphSolver, BaseMixin):
def run(self, room: str, read_agent_time=False):
"""
Args:
room: 房间名
read_agent_time: 读取工作或休息时间
"""
self.room = room
self.read_agent_time = read_agent_time
self.index = 0
self.result = []
EnterRoomSolver().run(self.room)
self.length = self.number(((1867, 1020), (1885, 1045)), 19, 200)
self.need_swipe = True if self.length > 3 else False
self.wait_start()
super().run()
return self.result
def timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=3)
def wait_start(self):
self.start_time = datetime.now()
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)
img = cv2.bitwise_not(img)
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(1, 6):
im = riic_base_digits[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 = score.index(min(score)) + 1
return value
@staticmethod
def is_power_station() -> bool:
img = cropimg(config.recog.img, ((568, 18), (957, 95)))
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
mask = cv2.inRange(hsv, (35, 0, 0), (38, 255, 255))
if cv2.countNonZero(mask) > 1000:
return True
return False
def read_agent_data(self, index):
data = {}
if self.find("infra_no_operator", scope=name_p[index]):
data["agent"] = ""
data["mood"] = -1
else:
data["agent"] = self.read_screen(
cropimg(config.recog.gray, name_p[index]), type="name"
)
data["mood"] = self.read_accurate_mood(
cropimg(config.recog.gray, mood_p[index])
)
if self.read_agent_time:
if (
data["mood"] == 24
or self.room in ["central", "meeting", "factory"]
or self.is_power_station()
):
data["time"] = datetime.now()
else:
data["time"] = self.double_read_time(time_p[index])
self.result.append(data)
def transition(self) -> bool:
if self.detect_product_complete():
logger.info("检测到产物收取提示")
self.sleep(0.5)
elif self.find("room_detail"):
self.wait_start()
if self.index >= 3 and self.need_swipe:
if self.get_color((1800, 930))[0] > 51:
self.swipe(
(config.recog.w * 0.8, config.recog.h * 0.5),
(0, -config.recog.h * 0.45),
duration=500,
interval=1,
)
elif self.timeout():
EnterRoomSolver().run(self.room)
else:
self.need_swipe = False
elif self.index == self.length:
return True
elif self.index < self.length:
self.read_agent_data(self.index)
self.index += 1
else:
EnterRoomSolver().run(self.room)
else:
EnterRoomSolver().run(self.room)

View file

@ -0,0 +1,91 @@
from datetime import datetime, timedelta
import cv2
from mower.models import riic_base_digits
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, thres2
from mower.utils.log import logger
from mower.utils.recognize import Scene
class GetOrderRemainingTimeSolver(SceneGraphSolver, BaseMixin):
def run(self, room: str):
logger.info("Start:读取订单剩余时间")
self.room = room
self.res = None
if (
self.scene() == Scene.ORDER_LIST
and not self.detect_room_inside() == self.room
):
EnterRoomSolver().run(self.room, detail=False)
self.wait_start()
super().run()
return self.res
def number(self, scope: tp.Scope, height: int, thres: int) -> str:
"数字识别"
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 = ""
for x, y, w, h in rect:
if h < 10 and w < 10:
value += ":"
continue
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 = riic_base_digits[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 += str(score.index(min(score)))
return value
def read_remain_time(self, pos) -> int:
h, m, s = self.number(pos, 19, 115).split("::")
return int(h) * 3600 + int(m) * 60 + int(s)
def timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=10)
def wait_start(self):
self.start_time = datetime.now()
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
self.wait_start()
elif scene == Scene.ORDER_LIST:
if self.timeout():
logger.error("读取订单失败")
return True
elif pos := self.find("bill_accelerate"):
pos[0][0] += 69
pos[0][1] -= 214
pos[1][0] -= 52
pos[1][1] -= 220
if res := self.read_remain_time(pos):
self.res = res
return True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,234 @@
# 用于记录Mower操作行为
import sqlite3
from datetime import datetime
from mower.utils.log import logger
from mower.utils.path import get_path
# 记录干员进出站以及心情数据,将记录信息存入agent_action表里
def save_action_to_sqlite_decorator(func):
def wrapper(self, name, mood, current_room, current_index, update_time=False):
agent = self.operators[name] # 干员
agent_current_room = agent.current_room # 干员所在房间
agent_is_high = agent.is_high() # 是否高优先级
# 调用原函数
result = func(self, name, mood, current_room, current_index, update_time)
if not update_time:
return
# 保存到数据库
current_time = datetime.now()
database_path = get_path("@app/tmp/data.db")
try:
# Create 'tmp' directory if it doesn't exist
get_path("@app/tmp").mkdir(exist_ok=True)
connection = sqlite3.connect(database_path)
cursor = connection.cursor()
# Create a table if it doesn't exist
cursor.execute(
"CREATE TABLE IF NOT EXISTS agent_action ("
"name TEXT,"
"agent_current_room TEXT,"
"current_room TEXT,"
"is_high INTEGER,"
"agent_group TEXT,"
"mood REAL,"
"current_time TEXT"
")"
)
# Insert data
cursor.execute(
"INSERT INTO agent_action VALUES (?, ?, ?, ?, ?, ?, ?)",
(
name,
agent_current_room,
current_room,
int(agent_is_high),
agent.group,
mood,
str(current_time),
),
)
connection.commit()
connection.close()
# Log the action
logger.debug(
f"Saved action to SQLite: Name: {name}, Agent's Room: {agent_current_room}, Agent's group: {agent.group}, "
f"Current Room: {current_room}, Is High: {agent_is_high}, Current Time: {current_time}"
)
except sqlite3.Error as e:
logger.error(f"SQLite error: {e}")
return result
return wrapper
def get_work_rest_ratios():
# TODO 整理数据计算工休比
database_path = get_path("@app/tmp/data.db")
try:
# 连接到数据库
conn = sqlite3.connect(database_path)
# conn = sqlite3.connect('../../tmp/data.db')
cursor = conn.cursor()
# 查询数据
cursor.execute("""
SELECT a.*
FROM agent_action a
JOIN (
SELECT DISTINCT b.name
FROM agent_action b
WHERE DATE(b.current_time) >= DATE('now', '-7 day', 'localtime')
AND b.is_high = 1 AND b.current_room NOT LIKE 'dormitory%'
UNION
SELECT '菲亚梅塔' AS name
) AS subquery ON a.name = subquery.name
WHERE DATE(a.current_time) >= DATE('now', '-1 month', 'localtime')
ORDER BY a.current_time;
""")
data = cursor.fetchall()
# 关闭数据库连接
conn.close()
except sqlite3.Error:
data = []
processed_data = {}
grouped_data = {}
for row in data:
name = row[0]
current_room = row[2]
current_time = row[6] # Assuming index 6 is the current_time column
agent = grouped_data.get(
name,
{
"agent_data": [
{"current_time": current_time, "current_room": current_room}
],
"difference": [],
},
)
difference = {
"time_diff": calculate_time_difference(
agent["agent_data"][-1]["current_time"], current_time
),
"current_room": agent["agent_data"][-1]["current_room"],
}
agent["agent_data"].append(
{"current_time": current_time, "current_room": current_room}
)
agent["difference"].append(difference)
grouped_data[name] = agent
for name in grouped_data:
work_time = 0
rest_time = 0
for difference in grouped_data[name]["difference"]:
if difference["current_room"].startswith("dormitory"):
rest_time += difference["time_diff"]
else:
work_time += difference["time_diff"]
processed_data[name] = {
"labels": ["休息时间", "工作时间"],
"datasets": [{"data": [rest_time, work_time]}],
}
return processed_data
# 整理心情曲线
def get_mood_ratios():
database_path = get_path("@app/tmp/data.db")
try:
# 连接到数据库
conn = sqlite3.connect(database_path)
cursor = conn.cursor()
# 查询数据(筛掉宿管和替班组的数据)
cursor.execute("""
SELECT a.*
FROM agent_action a
JOIN (
SELECT DISTINCT b.name
FROM agent_action b
WHERE DATE(b.current_time) >= DATE('now', '-7 day', 'localtime')
AND b.is_high = 1 AND b.current_room NOT LIKE 'dormitory%'
UNION
SELECT '菲亚梅塔' AS name
) AS subquery ON a.name = subquery.name
WHERE DATE(a.current_time) >= DATE('now', '-7 day', 'localtime')
ORDER BY a.agent_group DESC, a.current_time;
""")
data = cursor.fetchall()
# 关闭数据库连接
conn.close()
except sqlite3.Error:
data = []
work_rest_data_ratios = get_work_rest_ratios()
grouped_data = {}
grouped_work_rest_data = {}
for row in data:
group_name = row[4] # Assuming 'agent_group' is at index 4
if not group_name:
group_name = row[0]
mood_data = grouped_data.get(group_name, {"labels": [], "datasets": []})
work_rest_data = grouped_work_rest_data.get(
group_name, work_rest_data_ratios[row[0]]
)
grouped_work_rest_data[group_name] = work_rest_data
timestamp_datetime = datetime.strptime(
row[6], "%Y-%m-%d %H:%M:%S.%f"
) # Assuming 'current_time' is at index 6
# 创建 Luxon 格式的字符串
current_time = f"{timestamp_datetime.year:04d}-{timestamp_datetime.month:02d}-{timestamp_datetime.day:02d}T{timestamp_datetime.hour:02d}:{timestamp_datetime.minute:02d}:{timestamp_datetime.second:02d}.{timestamp_datetime.microsecond:06d}+08:00"
mood_label = row[0] # Assuming 'name' is at index 0
mood_value = row[5] # Assuming 'mood' is at index 5
if mood_label in [dataset["label"] for dataset in mood_data["datasets"]]:
# if mood_label == mood_data['datasets'][0]['label']:
mood_data["labels"].append(current_time)
# If mood label already exists, find the corresponding dataset
for dataset in mood_data["datasets"]:
if dataset["label"] == mood_label:
dataset["data"].append({"x": current_time, "y": mood_value})
break
else:
# If mood label doesn't exist, create a new dataset
mood_data["labels"].append(current_time)
mood_data["datasets"].append(
{"label": mood_label, "data": [{"x": current_time, "y": mood_value}]}
)
grouped_data[group_name] = mood_data
print(grouped_work_rest_data)
# 将数据格式整理为数组
formatted_data = []
for group_name, mood_data in grouped_data.items():
formatted_data.append(
{
"groupName": group_name,
"moodData": mood_data,
"workRestData": grouped_work_rest_data[group_name],
}
)
return formatted_data
def calculate_time_difference(start_time, end_time):
time_format = "%Y-%m-%d %H:%M:%S.%f"
start_datetime = datetime.strptime(start_time, time_format)
end_datetime = datetime.strptime(end_time, time_format)
time_difference = end_datetime - start_datetime
return time_difference.total_seconds()

View file

@ -0,0 +1,105 @@
from datetime import datetime, timedelta
import cv2
from mower.models import riic_base_digits
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.email import send_message
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, thres2
from mower.utils.log import logger
from mower.utils.recognize import Scene
class ReloadSolver(SceneGraphSolver, BaseMixin):
def run(self, room) -> None:
logger.info(f"开始补货:{room}")
self.room = room
self.success = False
if (
self.scene() == Scene.FACTORY_ROOMS
and not self.detect_room_inside() == self.room
):
EnterRoomSolver().run(self.room, detail=False)
self.wait_start()
super().run()
def timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=10)
def wait_start(self):
self.start_time = datetime.now()
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 = riic_base_digits[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 transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.wait_start()
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene == Scene.FACTORY_ROOMS:
reload_num = 99 - self.number(((1306, 462), (1391, 521)), 50, 200)
if self.find("reload_check"):
self.ctap((1400, 850), 1, config.screenshot_avg / 1000)
self.success = True
elif self.timeout():
logger.warning(f"材料已耗尽:{self.room} 时间为:{datetime.now()}")
send_message(f"材料已耗尽:{self.room}", level="WARNING")
return True
elif not self.success:
if reload_num == 0:
logger.info(f"货已满:{self.room} 时间为:{datetime.now()}")
send_message(f"货已满:{self.room}")
return True
else:
self.last_num = reload_num
self.tap((1450, 300))
else:
if reload_num == 0:
logger.info(
f"补货成功:{self.room} 本次补货数量:{self.last_num} 时间:{datetime.now()}"
)
send_message(f"补货成功:{self.room} 本次补货数量:{self.last_num}")
return True
else:
logger.warning(
f"补货成功:{self.room} 剩余数量:{99-reload_num} 剩余材料不足! 时间:{datetime.now()}"
)
send_message(
f"补货成功:{self.room} 剩余数量:{99-reload_num} 剩余材料不足!",
level="WARNING",
)
return True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,331 @@
import datetime
import cv2
from sqlalchemy import Column, Date, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from mower.models import noto_sans
from mower.utils import config
from mower.utils.digit_reader import DigitReader
from mower.utils.email import report_template, send_message
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, thres2
from mower.utils.log import logger
from mower.utils.path import get_path
from mower.utils.recognize import Scene, tp
Base = declarative_base()
# Database model for reports
class Report(Base):
__tablename__ = "reports"
id = Column(Integer, primary_key=True, autoincrement=True)
date = Column(Date, nullable=False, unique=True)
作战录像 = Column(Integer)
赤金 = Column(Integer)
龙门币订单 = Column(Integer)
龙门币订单数 = Column(Integer)
合成玉 = Column(Integer)
合成玉订单数量 = Column(Integer)
class ReportSolver(SceneGraphSolver):
def __init__(self) -> None:
super().__init__()
self.db_path = get_path("@app/tmp/report.db")
self.low_range_gray = (100, 100, 100)
self.high_range_gray = (255, 255, 255)
self.date = (datetime.datetime.now() - datetime.timedelta(hours=4)).date()
self.digitReader = DigitReader()
self.report_res = {
"作战录像": None,
"赤金": None,
"龙门币订单": None,
"龙门币订单数": None,
"合成玉": None,
"合成玉订单数量": None,
}
self.reload_time = 0
# Setup SQLAlchemy with dynamic DATABASE_URL
self.DATABASE_URL = f"sqlite:///{self.db_path}" # Dynamic DB URL
self.engine = create_engine(self.DATABASE_URL)
Base.metadata.create_all(self.engine)
self.Session = sessionmaker(bind=self.engine)
def run(self):
if self.has_record():
logger.info("今天的基报看过了")
return True
logger.info("康康大基报捏~")
try:
super().run()
return True
except Exception as e:
logger.exception(e)
return False
def transition(self) -> bool:
if self.scene() == Scene.RIIC_REPORT:
return self.read_report()
else:
self.scene_graph_step(Scene.RIIC_REPORT)
def read_report(self):
if self.find("riic/manufacture"):
try:
self.crop_report()
logger.info(self.report_res)
self.record_report()
except Exception as e:
logger.exception("基报读取失败:{}".format(e))
return True
else:
if self.reload_time > 3:
logger.info("未加载出基报")
return True
self.reload_time += 1
self.sleep(1)
def record_report(self):
logger.info(f"存入{self.date}的数据{self.report_res}")
try:
with self.Session() as session:
report = Report(
date=self.date,
作战录像=self.report_res["作战录像"],
赤金=self.report_res["赤金"],
龙门币订单=self.report_res["龙门币订单"],
龙门币订单数=self.report_res["龙门币订单数"],
合成玉=self.report_res["合成玉"],
合成玉订单数量=self.report_res["合成玉订单数量"],
)
session.merge(report)
session.commit()
except Exception as e:
logger.exception(f"存入数据失败:{e}")
self.tap((1253, 81), interval=2)
try:
send_message(
report_template.render(
report_data=self.report_res, title_text="基建报告"
),
"基建报告",
"INFO",
attach_image=config.recog.img,
)
except Exception as e:
logger.exception(f"基报邮件发送失败:{e}")
self.tap((40, 80), interval=2)
def has_record(self):
try:
with self.Session() as session: # 使用上下文管理器
record_exists = session.query(Report).filter_by(date=self.date).first()
return record_exists is not None
except Exception as e:
logger.exception(f"查询数据库失败:{e}")
return False
def crop_report(self):
exp_area = [[1625, 200], [1800, 230]]
iron_pos = self.find("riic/iron")
iron_area = [
[iron_pos[1][0], iron_pos[0][1]],
[1800, iron_pos[1][1]],
]
trade_pt = self.find("riic/trade")
assist_pt = self.find("riic/assistants")
area = {
"iron_order": [[1620, trade_pt[1][1] + 10], [1740, assist_pt[0][1] - 50]],
"iron_order_number": [
[1820, trade_pt[1][1] + 10],
[1870, assist_pt[0][1] - 65],
],
"orundum": [[1620, trade_pt[1][1] + 45], [1870, assist_pt[0][1]]],
"orundum_number": [
[1820, trade_pt[1][1] + 55],
[1860, assist_pt[0][1] - 20],
],
}
img = cv2.cvtColor(config.recog.img, cv2.COLOR_RGB2HSV)
img = cv2.inRange(img, (98, 0, 150), (102, 255, 255))
self.report_res["作战录像"] = self.get_number(img, exp_area, height=19)
self.report_res["赤金"] = self.get_number(img, iron_area, height=19)
self.report_res["龙门币订单"] = self.get_number(
img, area["iron_order"], height=19
)
self.report_res["合成玉"] = self.get_number(img, area["orundum"], height=19)
logger.info("蓝字读取完成")
img = cv2.cvtColor(config.recog.img, cv2.COLOR_RGB2HSV)
img = cv2.inRange(img, (0, 0, 50), (100, 100, 170))
self.report_res["龙门币订单数"] = self.get_number(
img, area["iron_order_number"], height=19, thres=200
)
self.report_res["合成玉订单数量"] = self.get_number(
img, area["orundum_number"], height=19, thres=200
)
logger.info("订单数读取完成")
def get_number(
self, img, scope: tp.Scope, height: int | None = 18, thres: int | None = 100
):
img = cropimg(img, scope)
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,)
)
score = []
for i in range(10):
im = noto_sans[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_report_data(self):
# 连接数据库
try:
with self.Session() as session:
format_data = []
# 查询所有报告数据
records = session.query(Report).all()
# 将记录转化为所需格式
for record in records:
format_data.append(
{
"日期": record.date.strftime(
"%Y-%m-%d"
), # 日期格式化为字符串
"作战录像": record.作战录像,
"赤金": record.赤金,
"制造总数": int(record.赤金 + record.作战录像),
"龙门币订单": record.龙门币订单,
"反向作战录像": -record.作战录像,
"龙门币订单数": record.龙门币订单数,
"每单获取龙门币": int(
record.龙门币订单 / record.龙门币订单数
)
if record.龙门币订单数
else 0,
}
)
# 如果格式化后的数据少于15条,则添加缺失的日期
earliest_date = (
min(record.date for record in records)
if records
else datetime.date.today()
)
if len(format_data) < 15:
for i in range(1, 16 - len(format_data)):
format_data.insert(
0,
{
"日期": (
earliest_date - datetime.timedelta(days=i + 1)
).strftime("%Y-%m-%d"),
"作战录像": "-",
"赤金": "-",
"龙门币订单": "-",
"龙门币订单数": "-",
"每单获取龙门币": "-",
},
)
logger.debug(format_data)
return format_data
except Exception as e:
logger.exception(f"读取数据库失败: {e}")
def get_orundum_data(self):
try:
format_data = []
with self.Session() as session:
# 查询所有报告数据
records = session.query(Report).all()
earliest_date = datetime.datetime.now()
# 初始化制造合成玉的开始日期
begin_make_orundum = (earliest_date + datetime.timedelta(days=1)).date()
if len(records) >= 15:
for i in range(len(records) - 1, -1, -1):
record = records[i]
if 0 < i < len(records) - 15:
continue
if record.合成玉 > 0:
begin_make_orundum = record.date
else:
for record in records:
if record.合成玉 > 0:
begin_make_orundum = record.date
if begin_make_orundum > earliest_date.date():
return format_data
total_orundum = 0
for record in records:
total_orundum += record.合成玉
format_data.append(
{
"日期": record.date.strftime("%Y-%m-%d"),
"合成玉": record.合成玉,
"合成玉订单数量": record.合成玉订单数量,
"抽数": round((record.合成玉 / 600), 1),
"累计制造合成玉": total_orundum,
}
)
if len(format_data) < 15:
earliest_date = records[0].date
for i in range(1, 16 - len(format_data)):
format_data.insert(
0,
{
"日期": (
earliest_date - datetime.timedelta(days=i + 1)
).strftime("%Y-%m-%d"),
"合成玉": "-",
"合成玉订单数量": "-",
"抽数": "-",
"累计制造合成玉": 0,
},
)
logger.debug(format_data)
return format_data
except Exception as e:
logger.exception(f"获取合成玉数据失败:{e}")
return []
def close_engine(self):
self.engine.dispose()
def __del__(self):
self.close_engine()

View file

@ -0,0 +1,54 @@
from mower.solvers.infra.base_choose import RIIC_ChooseSolver
from mower.solvers.infra.drone import DroneSolver
from mower.solvers.infra.get_agent_from_room import GetAgentFromRoomSolver
from mower.solvers.infra.get_order_remaining_time import (
GetOrderRemainingTimeSolver,
)
from mower.utils import config
from mower.utils.digit_reader import DigitReader
from mower.utils.graph import SceneGraphSolver
from mower.utils.log import logger
from .read_original_order_remaining_time import ReadOriginalOrderRemainTimeSolver
from .wait_for_order import WaitForOrderSolver
limit_time = 1800
class RunOrderSolver(SceneGraphSolver):
def run(
self,
room: str,
agent_list: list,
drone: bool = False,
) -> None:
logger.info("Start:跑单插拔")
# 读取原来的干员列表
original_agent_list = [
data["agent"] for data in GetAgentFromRoomSolver().run(room)
]
# 使用无人机加速
if drone:
# 读取当前无人机数量避免重复开关
cur_drone_count = DigitReader().get_drone(config.recog.gray)
drone_count = ReadOriginalOrderRemainTimeSolver().run(room) // 180 - 1
DroneSolver().run(room, drone_count, cur_count=cur_drone_count)
wait_time = max(
GetOrderRemainingTimeSolver().run(room) - config.conf.run_order_buffer_time,
0,
)
if wait_time > limit_time:
logger.error("检测到漏单")
return
logger.info(f"等待时间:{wait_time}")
# 换上跑单干员等待
RIIC_ChooseSolver().run(room, agent_list, wait_time)
if (wait_time := GetOrderRemainingTimeSolver().run(room)) > limit_time:
logger.error("检测到漏单")
return
logger.info(f"等待时间:{wait_time}")
# 等待订单完成
WaitForOrderSolver().run(room, wait_time)
# 换回原来的干员
RIIC_ChooseSolver().run(room, original_agent_list)

View file

@ -0,0 +1,80 @@
import cv2
from mower.models import riic_base_digits
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, thres2
from mower.utils.recognize import Scene
class ReadOriginalOrderRemainTimeSolver(SceneGraphSolver, BaseMixin):
"""
返回剩余时间,换算为秒
"""
def run(
self,
room: str,
):
self.room = room
if (
self.scene() == Scene.DRONE_ACCELERATE
and not self.detect_room_inside() == self.room
):
EnterRoomSolver().run(self.room, detail=False)
super().run()
return self.res
def number(self, scope: tp.Scope, height: int, thres: int) -> str:
"数字识别"
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 = ""
for x, y, w, h in rect:
if h < 7 and w < 7:
value += ":"
continue
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 = riic_base_digits[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 += str(score.index(min(score)))
return value
def read_remain_time(self) -> int:
h, m, s = self.number(((758, 670), (960, 705)), 30, 100).split("::")
return int(h) * 3600 + int(m) * 60 + int(s)
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene == Scene.ORDER_LIST:
if pos := self.find("bill_accelerate"):
self.tap(pos)
elif scene == Scene.DRONE_ACCELERATE:
if res := self.read_remain_time():
self.res = res
return True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,37 @@
from datetime import datetime, timedelta
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils.graph import SceneGraphSolver
from mower.utils.recognize import Scene
class WaitForOrderSolver(SceneGraphSolver, BaseMixin):
def run(self, room, wait_time) -> None:
self.room = room
self.wait_start()
self.sleep(wait_time)
self.success = False
super().run()
def timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=180)
def wait_start(self):
self.start_time = datetime.now()
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene == Scene.ORDER_LIST:
if pos := self.find("order_ready"):
self.tap(pos)
self.success = True
elif self.success:
return True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,47 @@
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils.graph import SceneGraphSolver
from mower.utils.log import logger
from mower.utils.recognize import Scene
check_order_scope = {"lmb": ((750, 796), (803, 849)), "oru": ((1111, 796), (1164, 849))}
class SwitchOrderSolver(SceneGraphSolver, BaseMixin):
def run(self, room: str, tar_order: str):
"""
Args:
room: 房间名
tar_order: 目标订单 lmb或oru
"""
order_type = "龙门币" if tar_order == "lmb" else "合成玉"
logger.info(f"Start:切换为{order_type}订单 ")
self.tar_order = tar_order
self.scope = check_order_scope[tar_order]
self.room = room
if (
self.scene() == Scene.ORDER_LIST
and not self.detect_room_inside() == self.room
):
EnterRoomSolver().run(self.room, detail=False)
super().run()
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene == Scene.ORDER_LIST:
if self.find(f"switch_order/{self.tar_order}"):
return True
else:
self.tap((1500, 1000))
elif scene == Scene.SWITCH_ORDER:
if self.find("switch_order/check", scope=self.scope):
self.scene_graph_step(Scene.ORDER_LIST)
else:
self.tap(self.get_pos(self.scope))
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,116 @@
from datetime import datetime
import cv2
from mower.models import riic_base_digits
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.drone import DroneSolver
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.digit_reader import DigitReader
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, thres2
from mower.utils.log import logger
from .check_current_product import CheckCurrentProductSolver
from .choose_product import ChooseProductSolver
from .get_remain_time import GetRemainTimeSolver
from .wait_for_product import WaitForProductSolver
production_time = {
"经验": 180 * 60,
"赤金": 72 * 60,
"源石碎片": 60 * 60,
"先锋双芯片": 60 * 60,
"狙击双芯片": 60 * 60,
"医疗双芯片": 60 * 60,
"术师双芯片": 60 * 60,
"近卫双芯片": 60 * 60,
"重装双芯片": 60 * 60,
"辅助双芯片": 60 * 60,
"特种双芯片": 60 * 60,
}
class SwitchProductSolver(SceneGraphSolver, BaseMixin):
def run(self, room, tar_product, only_get_time=False):
logger.info(f"Start:切换产物 房间:{room} 目标产物:{tar_product}")
EnterRoomSolver().run(room, detail=False)
# 检查当前产物
cur_product = CheckCurrentProductSolver().run(room)
logger.info(f"当前产物:{cur_product}")
if cur_product == tar_product:
logger.info("当前产物已为目标产物")
return True
# 读取当前无人机数量避免重复开关
cur_drone_count = DigitReader().get_drone(config.recog.gray)
# 读取生产速度
speed = self.read_speed()
# 获取无人机加速的剩余时间
remain_time = GetRemainTimeSolver().run(room)
if only_get_time:
return remain_time % production_time[cur_product] / speed
# 使用无人机加速
start_time = datetime.now()
drone_count = remain_time % production_time[cur_product] // 180
logger.info(f"应使用无人机数量:{drone_count}")
if not DroneSolver().run(room, drone_count, cur_count=cur_drone_count):
return False
# 等待产物完成
wait_time = max(
remain_time % production_time[cur_product] % 180 / speed
- (datetime.now() - start_time).total_seconds(),
0,
)
logger.info(f"等待时间:{wait_time}")
WaitForProductSolver().run(room, wait_time)
# 选择目标产物
if not ChooseProductSolver().run(room, tar_product):
return False
return True
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)
img = cv2.bitwise_not(img)
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 = ""
for x, y, w, h in rect:
if h < 8 and w < 8:
value += "."
continue
elif h < 20 and w < 20:
continue
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 = riic_base_digits[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 += str(score.index(min(score)))
return value
def read_speed(self) -> float:
speed1 = self.number(((1185, 955), (1255, 977)), 17, 120)
speed2 = self.number(((1285, 955), (1355, 977)), 17, 150)
return float(speed1) + float(speed2) + 1.0

View file

@ -0,0 +1,49 @@
from datetime import datetime, timedelta
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils.graph import SceneGraphSolver
from mower.utils.recognize import Scene
from .utils import product
class CheckCurrentProductSolver(SceneGraphSolver, BaseMixin):
def run(self, room) -> None:
self.room = room
if (
self.scene() == Scene.FACTORY_ROOMS
and not self.detect_room_inside() == self.room
):
EnterRoomSolver().run(self.room, detail=False)
self.wait_start()
super().run()
return self.res
def timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=5)
def wait_start(self):
self.start_time = datetime.now()
def check_product(self) -> str:
for p in product:
if self.find(f"product/{p}"):
return p
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.wait_start()
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene == Scene.FACTORY_ROOMS:
if res := self.check_product():
self.res = res
return True
elif self.timeout():
self.res = None
return True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,119 @@
from datetime import datetime, timedelta
import cv2
from mower.models import riic_base_digits
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.email import 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
production_index = {
"经验": [0, 1],
"赤金": [1, 0],
"源石碎片": [3, 0],
"先锋双芯片": [2, 0],
"狙击双芯片": [2, 5],
"医疗双芯片": [2, 6],
"术师双芯片": [2, 2],
"近卫双芯片": [2, 4],
"重装双芯片": [2, 1],
"辅助双芯片": [2, 3],
"特种双芯片": [2, 7],
}
first_pos = [((180, 215 + 140 * i), (190, 225 + 140 * i)) for i in range(4)]
second_pos = [(500 + 742 * j, 200 + 270 * i) for j in range(2) for i in range(4)]
class ChooseProductSolver(SceneGraphSolver):
def run(self, room, tar_product) -> bool:
self.room = room
self.tar_product = tar_product
self.first_pos = first_pos[production_index[self.tar_product][0]]
self.second_pos = second_pos[production_index[self.tar_product][1]]
self.success = False
self.num_flag = False
self.wait_start()
super().run()
return self.success
def be_choosen(self) -> bool:
img = cropimg(config.recog.img, self.first_pos)
res = loadres("product_be_choosen")
return cmatch(img, res, 60)
def timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=10)
def wait_start(self):
self.start_time = datetime.now()
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 = riic_base_digits[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 transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene == Scene.FACTORY_ROOMS:
if self.success:
return True
elif self.find("reload_check"):
if self.num_flag:
self.ctap((1400, 850), 1, config.screenshot_avg / 1000)
elif self.timeout():
logger.warning("切换成功,剩余材料不足1")
send_message(f"切换成功,剩余材料不足1:{self.room}", level="WARNING")
self.num_flag = True
else:
self.tap((1450, 300))
if self.number(((1306, 462), (1391, 521)), 50, 200) != 1:
self.num_flag = True
else:
self.tap((1750, 400))
elif scene == Scene.CHOOSE_PRODUCT:
if self.find("icon_notification_black"):
logger.warning("切换产物材料不足")
send_message(f"切换产物材料不足:{self.room}", level="WARNING")
return True
elif not self.be_choosen():
self.tap(self.get_pos(self.first_pos))
else:
self.tap(self.second_pos)
elif scene == Scene.PRODUCT_SWITCHING_CONFIRM:
self.tap_element("double_confirm/main", x_rate=1)
self.success = True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,80 @@
import cv2
from mower.models import riic_base_digits
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, thres2
from mower.utils.recognize import Scene
class GetRemainTimeSolver(SceneGraphSolver, BaseMixin):
"""
返回剩余时间,换算为秒
"""
def run(
self,
room: str,
):
self.room = room
if (
self.scene() == Scene.FACTORY_ROOMS
and not self.detect_room_inside() == self.room
):
EnterRoomSolver().run(self.room, detail=False)
super().run()
return self.res
def number(self, scope: tp.Scope, height: int, thres: int) -> str:
"数字识别"
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 = ""
for x, y, w, h in rect:
if h < 7 and w < 7:
value += ":"
continue
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 = riic_base_digits[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 += str(score.index(min(score)))
return value
def read_remain_time(self) -> int:
h, m, s = self.number(((758, 670), (960, 705)), 30, 100).split("::")
return int(h) * 3600 + int(m) * 60 + int(s)
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene == Scene.FACTORY_ROOMS:
if pos := self.find("factory_accelerate"):
self.tap(pos)
elif scene == Scene.DRONE_ACCELERATE:
if res := self.read_remain_time():
self.res = res
return True
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)

View file

@ -0,0 +1,13 @@
product = [
"经验",
"赤金",
"源石碎片",
"先锋双芯片",
"狙击双芯片",
"医疗双芯片",
"术师双芯片",
"近卫双芯片",
"重装双芯片",
"辅助双芯片",
"特种双芯片",
]

View file

@ -0,0 +1,36 @@
from datetime import datetime, timedelta
from mower.solvers.infra.base_mixin import BaseMixin
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config
from mower.utils.graph import SceneGraphSolver
from mower.utils.recognize import Scene
class WaitForProductSolver(SceneGraphSolver, BaseMixin):
def run(self, room, wait_time) -> None:
self.room = room
self.wait_start()
self.sleep(wait_time)
super().run()
def timeout(self) -> bool:
return datetime.now() > self.start_time + timedelta(seconds=180)
def wait_start(self):
self.start_time = datetime.now()
def transition(self) -> bool:
if (scene := self.scene()) == Scene.INFRA_DETAILS:
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
elif scene == Scene.FACTORY_ROOMS:
if self.detect_product_complete():
return True
elif self.timeout():
return True
else:
self.tap((1750, 950))
elif scene in self.waiting_scene:
self.waiting_solver()
else:
EnterRoomSolver().run(self.room, detail=False)