Compare commits

...

4 commits

Author SHA1 Message Date
4c3eaef108 Merge branch 'clue'
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2025-06-29 20:29:12 +08:00
6d3640aea0 线索改进 2025-06-29 20:28:54 +08:00
fe6a59b0f6 线索数量获取场景修复
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2025-06-29 15:20:15 +08:00
706fa6a4c0 线索相关Solver使用TransitionOn新写法
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
2025-06-29 15:06:32 +08:00
13 changed files with 356 additions and 286 deletions

BIN
mower/resources/clue/filter_all_on.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -1,51 +1,53 @@
from mower.solvers.infra.base_mixin import BaseMixin from datetime import timedelta
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils.log import logger from mower.utils.log import logger
from mower.utils.recognize import Scene from mower.utils.recognize import Scene
from mower.utils.solver import BaseSolver from mower.utils.solver import BaseSolver, TransitionOn
from .utils import clue_cls from .utils import ClueMainSolver, clue_cls
class DailySolver(BaseSolver, BaseMixin): class DailySolver(BaseSolver):
solver_name = "每日线索领取" solver_name = "每日线索领取"
solver_max_duration = timedelta(minutes=2)
solver_default_scene = None
def run(self) -> None: def run(self) -> None:
self.success = False self.success = False
super().run() super().run()
def transition(self) -> bool: @TransitionOn(Scene.INFRA_CONFIDENTIAL)
if ( def _(self):
scene := self.scene() if self.success:
) == Scene.INFRA_DETAILS and self.detect_room() == "meeting": return True
self.tap((500, 1000)) if self.animation():
elif scene == Scene.INFRA_CONFIDENTIAL: return
if self.animation(): # 检查是否领过线索
return daily_scope = ((1822, 208), (1886, 243))
if self.success: if self.find("clue/badge_new", scope=daily_scope):
return True self.tap((1800, 270))
# 检查是否领过线索
daily_scope = ((1822, 208), (1886, 243))
if self.find("clue/badge_new", scope=daily_scope):
self.tap((1800, 270))
else:
return True
elif scene == Scene.CLUE_DAILY:
exit_pos = (1484, 152)
if self.find("clue/icon_notification"):
logger.info("当前获得线索数已经达到最大值")
self.tap(exit_pos)
self.success = True
elif clue := clue_cls("daily"):
logger.info(f"领取今日线索({clue}号)")
self.tap("clue/button_get")
else:
# 今日线索已领取,点X退出
self.tap(exit_pos)
self.success = True
elif scene == Scene.CLUE_MESSAGE_BOARD:
self.cback(3, id="infra_back")
elif scene in self.waiting_scene:
self.waiting_solver()
else: else:
EnterRoomSolver().run("meeting", detail=False) return True
@TransitionOn(Scene.CLUE_DAILY)
def _(self):
exit_pos = (1484, 152)
if self.find("clue/icon_notification"):
logger.info("当前获得线索数已经达到最大值")
self.tap(exit_pos)
self.success = True
elif clue := clue_cls("daily"):
logger.info(f"领取今日线索({clue}号)")
self.tap("clue/button_get")
else:
# 今日线索已领取,点X退出
self.tap(exit_pos)
self.success = True
@TransitionOn(BaseSolver.waiting_scene)
def _(self):
self.waiting_solver()
@TransitionOn()
def _(self):
ClueMainSolver().run()

View file

@ -1,13 +1,18 @@
from datetime import timedelta
from mower.solvers.infra.base_mixin import BaseMixin 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 config
from mower.utils.log import logger from mower.utils.log import logger
from mower.utils.recognize import Scene from mower.utils.recognize import Scene
from mower.utils.solver import BaseSolver from mower.utils.solver import BaseSolver, TransitionOn
from .utils import MeetingSolver
class GetClueCountSolver(BaseSolver, BaseMixin): class GetClueCountSolver(BaseSolver, BaseMixin):
solver_name = "线索数量" solver_name = "线索数量"
solver_max_duration = timedelta(minutes=2)
solver_default_scene = None
def run(self) -> int: def run(self) -> int:
self.res = -1 self.res = -1
@ -16,16 +21,19 @@ class GetClueCountSolver(BaseSolver, BaseMixin):
return self.res return self.res
raise ValueError("未找到线索数量") raise ValueError("未找到线索数量")
def transition(self) -> bool: @TransitionOn(Scene.INFRA_DETAILS)
if self.find("meeting_arrange_check_in"): def _(self):
self.res = self.read_screen( MeetingSolver().run()
config.recog.img, limit=10, cord=((476, 985), (519, 1020)) self.res = self.read_screen(
) config.recog.img, limit=10, cord=((476, 985), (519, 1020))
logger.info(f"当前拥有线索数量为{self.res}") )
return True logger.info(f"当前拥有线索数量为{self.res}")
elif (scene := self.scene()) == Scene.INFRA_CONFIDENTIAL: return True
self.cback(3, id="infra_back")
elif scene in self.waiting_scene: @TransitionOn(BaseSolver.waiting_scene)
self.waiting_solver() def _(self):
else: self.waiting_solver()
EnterRoomSolver().run("meeting", detail=False)
@TransitionOn()
def _(self):
MeetingSolver().run()

View file

@ -1,18 +1,20 @@
from mower.solvers.infra.base_mixin import BaseMixin from datetime import timedelta
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config from mower.utils import config
from mower.utils.image import cmatch, crop2content, cropimg, loadres, thres2 from mower.utils.image import cmatch, crop2content, cropimg, loadres, thres2
from mower.utils.log import logger from mower.utils.log import logger
from mower.utils.rapidocr import ocr_rec from mower.utils.rapidocr import ocr_rec
from mower.utils.recognize import Scene from mower.utils.recognize import Scene
from mower.utils.solver import BaseSolver from mower.utils.solver import BaseSolver, TransitionOn
from mower.utils.vector import va from mower.utils.vector import va
from .utils import clue_cls, clue_scope from .utils import ClueMainSolver, clue_cls, clue_scope
class GiveAwaySolver(BaseSolver, BaseMixin): class GiveAwaySolver(BaseSolver):
solver_name = "传递线索" solver_name = "传递线索"
solver_max_duration = timedelta(minutes=2)
solver_default_scene = None
def run(self, clue_count) -> None: def run(self, clue_count) -> None:
self.clue_count = clue_count self.clue_count = clue_count
@ -20,50 +22,48 @@ class GiveAwaySolver(BaseSolver, BaseMixin):
return return
super().run() super().run()
def transition(self) -> bool: @TransitionOn(Scene.INFRA_CONFIDENTIAL)
if ( def _(self):
scene := self.scene() self.ctap((1799, 578))
) == Scene.INFRA_DETAILS and self.detect_room() == "meeting":
self.tap((500, 1000)) @TransitionOn(Scene.CLUE_GIVE_AWAY)
elif scene == Scene.INFRA_CONFIDENTIAL: def _(self):
self.ctap((1799, 578)) if not (config.conf.leifeng_mode or self.clue_count > 9):
elif scene == Scene.CLUE_GIVE_AWAY: return True
if not (config.conf.leifeng_mode or self.clue_count > 9): if self.animation():
self.tap((1868, 54)) return
return True if (c := clue_cls("give_away")) is None:
if self.animation(): return True
return if config.recog.gray[230][600] < 90:
if (c := clue_cls("give_away")) is None: self.ctap(clue_scope["give_away"], 5)
self.tap((1868, 54)) return
return True if self.find("product_complete"):
if config.recog.gray[230][600] < 90: return
self.ctap(clue_scope["give_away"], 5) orange = False
return for i in range(4):
if self.find("clue/icon_notification"): scope = (1285 + (c - 1) * 65, 137 + i * 225)
return bg = loadres("clue/give_away_digit_bg")
if self.detect_product_complete(): h, w, _ = bg.shape
return scope = scope, va(scope, (w, h))
orange = False img = cropimg(config.recog.img, scope)
for i in range(4): if cmatch(img, bg):
scope = (1285 + (c - 1) * 65, 137 + i * 225) orange = True
bg = loadres("clue/give_away_digit_bg") break
h, w, _ = bg.shape if not orange:
scope = scope, va(scope, (w, h)) i = 0
img = cropimg(config.recog.img, scope) name_top_left = (880, 125 + 225 * i)
if cmatch(img, bg): name_scope = (name_top_left, va(name_top_left, (390, 34)))
orange = True name = cropimg(config.recog.gray, name_scope)
break name = thres2(name, 250)
if not orange: name = crop2content(name)
i = 0 name = ocr_rec(name)
name_top_left = (880, 125 + 225 * i) logger.info(f"{name}送一张线索{c}")
name_scope = (name_top_left, va(name_top_left, (390, 34))) self.tap((1808, 200 + i * 255))
name = cropimg(config.recog.gray, name_scope)
name = thres2(name, 250) @TransitionOn(BaseSolver.waiting_scene)
name = crop2content(name) def _(self):
name = ocr_rec(name) self.waiting_solver()
logger.info(f"{name}送一张线索{c}")
self.tap((1808, 200 + i * 255)) @TransitionOn()
elif scene in self.waiting_scene: def _(self):
self.waiting_solver() ClueMainSolver().run()
else:
EnterRoomSolver().run("meeting", detail=False)

View file

@ -1,42 +1,42 @@
from datetime import timedelta from datetime import 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 import config
from mower.utils.scene import Scene from mower.utils.scene import Scene
from mower.utils.solver import BaseSolver from mower.utils.solver import BaseSolver, TransitionOn
from .utils import MeetingSolver
class MessageBoard(BaseSolver, BaseMixin): class MessageBoard(BaseSolver):
solver_name = "留言板" solver_name = "留言板"
solver_max_duration = timedelta(minutes=2) solver_max_duration = timedelta(minutes=2)
solver_default_scene = None
def transition(self): @TransitionOn(Scene.INFRA_DETAILS)
if ( def _(self):
scene := self.scene() MeetingSolver().run()
) == Scene.INFRA_DETAILS and self.detect_room() == "meeting": if pos := self.find("clue/title_party"):
if pos := self.find("clue/title_party"): self.tap(pos)
self.tap(pos) return
return score, scope = config.recog.match("clue/message_board_entry")
score, scope = config.recog.match("clue/message_board_entry") if score > 0.8:
if score > 0.8: self.ctap(scope)
self.ctap(scope) return
return self.tap("clue/interact")
self.tap("clue/interact")
elif scene == Scene.CLUE_MESSAGE_BOARD: @TransitionOn(Scene.CLUE_MESSAGE_BOARD)
if self.animation(): def _(self):
return if self.animation():
if pos := self.find("clue/message_board_collect"): return
self.tap(pos) if pos := self.find("clue/message_board_collect"):
return self.tap(pos)
return True return
elif scene in [ return True
Scene.INFRA_CONFIDENTIAL,
Scene.CLUE_MESSAGE_BOARD_FRIEND, @TransitionOn(BaseSolver.waiting_scene)
Scene.CLUE_ACCESS_RECORD, def _(self):
]: self.waiting_solver()
self.cback(3, id="infra_back")
elif scene in self.waiting_scene: @TransitionOn()
self.waiting_solver() def _(self):
else: MeetingSolver().run()
EnterRoomSolver().run("meeting", detail=False)

View file

@ -1,12 +1,13 @@
from datetime import timedelta from datetime import timedelta
from mower.solvers.infra.base_mixin import BaseMixin 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 config
from mower.utils.log import logger from mower.utils.log import logger
from mower.utils.recognize import Scene from mower.utils.recognize import Scene
from mower.utils.solver import BaseSolver, TransitionOn from mower.utils.solver import BaseSolver, TransitionOn
from .utils import MeetingSolver
class PartyTimeSolver(BaseSolver, BaseMixin): class PartyTimeSolver(BaseSolver, BaseMixin):
solver_name = "线索交流结束时间" solver_name = "线索交流结束时间"
@ -15,9 +16,7 @@ class PartyTimeSolver(BaseSolver, BaseMixin):
@TransitionOn(Scene.INFRA_DETAILS) @TransitionOn(Scene.INFRA_DETAILS)
def _(self): def _(self):
if self.detect_room() != "meeting": MeetingSolver().run()
EnterRoomSolver().run("meeting", detail=False)
return
if not self.find("clue/party_on"): if not self.find("clue/party_on"):
config.party_time = None config.party_time = None
logger.info("线索交流未开启") logger.info("线索交流未开启")
@ -28,7 +27,6 @@ class PartyTimeSolver(BaseSolver, BaseMixin):
return True return True
if pos := self.find("clue/show_party_details"): if pos := self.find("clue/show_party_details"):
self.tap(pos) self.tap(pos)
return
@TransitionOn(BaseSolver.waiting_scene) @TransitionOn(BaseSolver.waiting_scene)
def _(self): def _(self):
@ -36,8 +34,4 @@ class PartyTimeSolver(BaseSolver, BaseMixin):
@TransitionOn() @TransitionOn()
def _(self): def _(self):
EnterRoomSolver().run("meeting", detail=False) MeetingSolver().run()
@TransitionOn(Scene.INFRA_CONFIDENTIAL)
def _(self):
self.cback(3, id="infra_back")

View file

@ -1,19 +1,19 @@
from datetime import timedelta
import cv2 import cv2
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 config
from mower.utils.image import crop2content, cropimg, loadres, thres2 from mower.utils.image import crop2content, cropimg, loadres, thres2
from mower.utils.log import logger from mower.utils.log import logger
from mower.utils.rapidocr import ocr_rec from mower.utils.rapidocr import ocr_rec
from mower.utils.recognize import Scene from mower.utils.recognize import Scene
from mower.utils.solver import BaseSolver from mower.utils.solver import BaseSolver, TransitionOn
from mower.utils.vector import va from mower.utils.vector import va
from .utils import ( from .utils import (
ClueMainSolver,
clue_cls, clue_cls,
clue_scope, clue_scope,
exit_pos,
is_orange, is_orange,
main_dots, main_dots,
main_scope, main_scope,
@ -27,8 +27,10 @@ filter_receive = (1900, 45)
filter_self = (1610, 70) filter_self = (1610, 70)
class PlaceSolver(BaseSolver, BaseMixin): class PlaceSolver(BaseSolver):
solver_name = "放置线索" solver_name = "放置线索"
solver_max_duration = timedelta(minutes=2)
solver_default_scene = None
def run(self) -> None: def run(self) -> None:
self.clue_status = {} self.clue_status = {}
@ -49,104 +51,101 @@ class PlaceSolver(BaseSolver, BaseMixin):
return cl, st return cl, st
return None, None return None, None
def transition(self) -> bool: @TransitionOn(Scene.INFRA_CONFIDENTIAL)
if ( def _(self):
scene := self.scene() if self.animation():
) == Scene.INFRA_DETAILS and self.detect_room() == "meeting": return
self.tap((500, 1000)) if unlock_pos := self.detect_unlock():
elif scene == Scene.INFRA_CONFIDENTIAL: self.tap(unlock_pos)
if self.animation(): return
return for i in range(1, 8):
if is_orange(self.get_color(main_dots[i]).tolist()):
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.ctap(main_scope[cl], 1)
return
return True
@TransitionOn(Scene.CLUE_PLACE)
def _(self):
cl, st = self.place_index()
if cl is None:
if unlock_pos := self.detect_unlock(): if unlock_pos := self.detect_unlock():
self.tap(unlock_pos) self.tap(unlock_pos)
return return
for i in range(1, 8): return True
if is_orange(self.get_color(main_dots[i]).tolist()): if self.get_color((1328 + 77 * cl, 114))[0] < 150:
self.clue_status[i] = "available" # 右上角 1-7
elif clue_cls(i): self.tap(clue_scope[cl])
hsv = config.recog.hsv return
if 160 < hsv[main_time[i][1]][main_time[i][0]][0] < 180: receive = st in ["available", "self"]
self.clue_status[i] = "friend" filter_pos = filter_receive if receive else filter_self
else: if not all(self.get_color(filter_pos) > [252] * 3):
self.clue_status[i] = "self" 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 = ocr_rec(name_img)
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 = crop2content(time_img)
time = ocr_rec(time_img)
else: else:
self.clue_status[i] = None time = None
cl, st = self.place_index() self.clue_list.append({"name": name, "time": time, "scope": tl2p(cp)})
if st in ["available", "self", "available_self_only"]:
self.ctap(main_scope[cl], 1)
return
else: else:
return True break
elif scene == Scene.CLUE_PLACE: if self.clue_list:
cl, st = self.place_index() list_name = "接收库" if receive else "自有库"
logger.info(f"{cl}号线索{list_name}{self.clue_list}")
if cl is None: selected = None
if unlock_pos := self.detect_unlock(): for c in self.clue_list:
self.tap(unlock_pos) if c["time"]:
return selected = c
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 = ocr_rec(name_img)
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 = crop2content(time_img)
time = ocr_rec(time_img)
else:
time = None
self.clue_list.append(
{"name": name, "time": time, "scope": tl2p(cp)}
)
else:
break break
if self.clue_list: selected = selected or self.clue_list[0]
list_name = "接收库" if receive else "自有库" self.tap(selected["scope"])
logger.info(f"{cl}号线索{list_name}{self.clue_list}") if self.clue_status[cl] == "available":
selected = None self.clue_status[cl] = "friend"
for c in self.clue_list: elif self.clue_status[cl] == "available_self_only":
if c["time"]: self.clue_status[cl] = "self_only"
selected = c elif self.clue_status[cl] == "self":
break self.clue_status[cl] = "friend"
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: else:
if self.clue_status[cl] == "available": self.clue_status[cl] = None
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: else:
EnterRoomSolver().run("meeting", detail=False) 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
@TransitionOn(BaseSolver.waiting_scene)
def _(self):
self.waiting_solver()
@TransitionOn()
def _(self):
ClueMainSolver().run()

View file

@ -1,45 +1,44 @@
from mower.solvers.infra.base_mixin import BaseMixin from datetime import timedelta
from mower.solvers.infra.enter_room import EnterRoomSolver
from mower.utils import config from mower.utils import config
from mower.utils.image import cropimg from mower.utils.image import cropimg
from mower.utils.log import logger from mower.utils.log import logger
from mower.utils.rapidocr import ocr_rec from mower.utils.rapidocr import ocr_rec
from mower.utils.recognize import Scene from mower.utils.recognize import Scene
from mower.utils.solver import BaseSolver from mower.utils.solver import BaseSolver, TransitionOn
from .utils import clue_cls, exit_pos from .utils import ClueMainSolver, clue_cls
class ReceiveSolver(BaseSolver, BaseMixin): class ReceiveSolver(BaseSolver):
solver_name = "接收好友线索" solver_name = "接收好友线索"
solver_max_duration = timedelta(minutes=2)
solver_default_scene = None
def run(self): @TransitionOn(Scene.INFRA_CONFIDENTIAL)
super().run() def _(self):
receive_scope = ((1822, 365), (1886, 400))
if self.find("clue/badge_new", scope=receive_scope):
self.ctap((1800, 430))
return
return True
def transition(self) -> bool: @TransitionOn(Scene.CLUE_RECEIVE)
if ( def _(self):
scene := self.scene() if self.find("product_complete"):
) == Scene.INFRA_DETAILS and self.detect_room() == "meeting": return
self.tap((500, 1000)) if (clue := clue_cls("receive")) is None:
elif scene == Scene.INFRA_CONFIDENTIAL: return True
receive_scope = ((1822, 365), (1886, 400)) name_scope = ((1580, 220), (1880, 255))
if self.find("clue/badge_new", scope=receive_scope): name_img = cropimg(config.recog.gray, name_scope)
self.ctap((1800, 430)) name = ocr_rec(name_img) or "好友"
else: logger.info(f"接收{name}{clue}号线索")
return True self.tap(name_scope)
elif scene == Scene.CLUE_RECEIVE:
if self.find("infra_complete/信用", scope=((1440, 130), (1920, 193))): @TransitionOn(BaseSolver.waiting_scene)
self.sleep() def _(self):
return self.waiting_solver()
if clue := clue_cls("receive"):
name_scope = ((1580, 220), (1880, 255)) @TransitionOn()
name_img = cropimg(config.recog.gray, name_scope) def _(self):
name = ocr_rec(name_img) or "好友" ClueMainSolver().run()
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

@ -1,7 +1,15 @@
import cv2 from datetime import timedelta
import cv2
import networkx as nx
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 config
from mower.utils.graph.utils import DG
from mower.utils.image import cropimg, loadres from mower.utils.image import cropimg, loadres
from mower.utils.recognize import Scene
from mower.utils.solver import BaseSolver, TransitionOn
from mower.utils.vector import va from mower.utils.vector import va
clue_size = (162, 216) clue_size = (162, 216)
@ -45,7 +53,6 @@ for i in range(1, 8):
main_time[i] = va(clue_top_left[i], main_time_offset) main_time[i] = va(clue_top_left[i], main_time_offset)
main_scope[i] = tl2p(va(clue_top_left[i], main_offset)) main_scope[i] = tl2p(va(clue_top_left[i], main_offset))
tm_thres = 0.6 tm_thres = 0.6
exit_pos = (1239, 144)
def clue_cls(scope): def clue_cls(scope):
@ -58,3 +65,53 @@ def clue_cls(scope):
if max_val > tm_thres: if max_val > tm_thres:
return i return i
return None return None
class MeetingSolver(BaseSolver, BaseMixin):
solver_name = "会客室"
solver_max_duration = timedelta(minutes=2)
solver_default_scene = None
@TransitionOn(Scene.INFRA_MAIN)
def _(self):
EnterRoomSolver().run("meeting", detail=False)
@TransitionOn(Scene.INFRA_DETAILS)
def _(self):
if self.detect_room() != "meeting":
self.scene_graph_step(Scene.INFRA_MAIN)
return
if self.find("meeting_arrange_check_in"):
return True
self.tap((960, 540))
@TransitionOn()
def _(self):
try:
nx.shortest_path(DG, self.scene(), Scene.INFRA_DETAILS)
self.scene_graph_step(Scene.INFRA_DETAILS)
except nx.NetworkXNoPath:
self.scene_graph_step(Scene.INFRA_MAIN)
class ClueMainSolver(BaseSolver):
solver_name = "主界面"
solver_max_duration = timedelta(minutes=2)
solver_default_scene = None
@TransitionOn(Scene.INFRA_DETAILS)
def _(self):
MeetingSolver().run()
self.tap((500, 1000))
@TransitionOn(Scene.INFRA_CONFIDENTIAL)
def _(self):
return True
@TransitionOn()
def _(self):
try:
nx.shortest_path(DG, self.scene(), Scene.INFRA_CONFIDENTIAL)
self.scene_graph_step(Scene.INFRA_CONFIDENTIAL)
except nx.NetworkXNoPath:
self.scene_graph_step(Scene.INFRA_MAIN)

View file

@ -18,10 +18,10 @@ def todo_complete(solver: BaseSolver):
@edge(Scene.ACTIVITY_ROOM_DETAILS, Scene.INFRA_MAIN) @edge(Scene.ACTIVITY_ROOM_DETAILS, Scene.INFRA_MAIN)
@edge(Scene.CTRLCENTER_ASSISTANT, Scene.INFRA_MAIN) @edge(Scene.CTRLCENTER_ASSISTANT, Scene.INFRA_MAIN)
@edge(Scene.CLUE_DAILY, Scene.INFRA_CONFIDENTIAL) @edge(Scene.CLUE_DAILY, Scene.INFRA_CONFIDENTIAL)
@edge(Scene.CLUE_RECEIVE, Scene.INFRA_CONFIDENTIAL) @edge(Scene.CLUE_RECEIVE, Scene.INFRA_DETAILS)
@edge(Scene.CLUE_GIVE_AWAY, Scene.INFRA_CONFIDENTIAL) @edge(Scene.CLUE_GIVE_AWAY, Scene.INFRA_CONFIDENTIAL)
@edge(Scene.CLUE_SUMMARY, Scene.INFRA_CONFIDENTIAL) @edge(Scene.CLUE_SUMMARY, Scene.INFRA_CONFIDENTIAL)
@edge(Scene.CLUE_PLACE, Scene.INFRA_CONFIDENTIAL) @edge(Scene.CLUE_PLACE, Scene.INFRA_DETAILS)
@edge(Scene.CLUE_MESSAGE_BOARD, Scene.INFRA_CONFIDENTIAL) @edge(Scene.CLUE_MESSAGE_BOARD, Scene.INFRA_CONFIDENTIAL)
@edge(Scene.CLUE_MESSAGE_BOARD_FRIEND, Scene.CLUE_MESSAGE_BOARD) @edge(Scene.CLUE_MESSAGE_BOARD_FRIEND, Scene.CLUE_MESSAGE_BOARD)
@edge(Scene.CLUE_ACCESS_RECORD, Scene.CLUE_MESSAGE_BOARD) @edge(Scene.CLUE_ACCESS_RECORD, Scene.CLUE_MESSAGE_BOARD)
@ -33,6 +33,12 @@ def infra_back(solver: BaseSolver):
solver.cback(3, id="infra_back") solver.cback(3, id="infra_back")
@edge(Scene.CLUE_RECEIVE, Scene.INFRA_CONFIDENTIAL)
@edge(Scene.CLUE_PLACE, Scene.INFRA_CONFIDENTIAL)
def clue_receive_back(solver: BaseSolver):
solver.tap((960, 1000))
@edge(Scene.RIIC_OPERATOR_SELECT, Scene.INFRA_DETAILS) @edge(Scene.RIIC_OPERATOR_SELECT, Scene.INFRA_DETAILS)
def riic_operator_select(solver: BaseSolver): def riic_operator_select(solver: BaseSolver):
solver.ctap("confirm_blue", 3) solver.ctap("confirm_blue", 3)

View file

@ -283,7 +283,7 @@ class Recognizer:
self.scene = Scene.CLUE_GIVE_AWAY self.scene = Scene.CLUE_GIVE_AWAY
elif self.find("clue/summary"): elif self.find("clue/summary"):
self.scene = Scene.CLUE_SUMMARY self.scene = Scene.CLUE_SUMMARY
elif self.find("clue/filter_all"): elif self.find("clue/filter_all") or self.find("clue/filter_all_on"):
self.scene = Scene.CLUE_PLACE self.scene = Scene.CLUE_PLACE
elif self.find("clue/message_board_banner"): elif self.find("clue/message_board_banner"):
self.scene = Scene.CLUE_MESSAGE_BOARD self.scene = Scene.CLUE_MESSAGE_BOARD

View file

@ -18,6 +18,7 @@ color = {
"clue/access_records.jpg": (843, 279), "clue/access_records.jpg": (843, 279),
"clue/daily": (526, 623), "clue/daily": (526, 623),
"clue/filter_all": (1297, 99), "clue/filter_all": (1297, 99),
"clue/filter_all_on": (1299, 100),
"clue/give_away": (25, 18), "clue/give_away": (25, 18),
"clue/message_board_banner": (19, 1006), "clue/message_board_banner": (19, 1006),
"clue/message_board_collect": (1657, 876), "clue/message_board_collect": (1657, 876),

View file

@ -69,6 +69,7 @@ Res = Literal[
"clue/button_unlock", "clue/button_unlock",
"clue/daily", "clue/daily",
"clue/filter_all", "clue/filter_all",
"clue/filter_all_on",
"clue/give_away", "clue/give_away",
"clue/give_away_digit_bg", "clue/give_away_digit_bg",
"clue/icon_notification", "clue/icon_notification",