Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
120c881
fix(MysteryShop): 蓝票多数量购买窗口改用 buy_more
kunkun008 Oct 23, 2026
be5f3c9
fix(MysteryShop): 蓝票按单价判断,高于 85 勾玉不买
kunkun008 Oct 24, 2026
47b9064
fix(MysteryShop): 蓝票余额不够买满时逐张购买
kunkun008 Oct 24, 2026
8563207
fix(AreaBoss): Handle fox battle scene settlement #1967
runhey Oct 24, 2026
cc65d55
Merge pull request #1966 from kunkun008/fix/mysteryshop-blue-buy-more
runhey Oct 24, 2026
6360d6a
修复道馆首次攻击后的任务间隔
weirj2016-ai Oct 24, 2026
cacc2af
御魂溢出处理升级
davidyangkunlong Oct 24, 2026
0b1c1a0
Merge pull request #1969 from weirj2016-ai/fix/dokan-first-attack-fai…
runhey Oct 25, 2026
6295765
Merge pull request #1970 from davidyangkunlong/SoulOverflowOnOldGener…
runhey Oct 25, 2026
329ecfc
修复永生之海队员锁定阵容检查
weirj2016-ai Oct 28, 2026
921a2b8
fix(WantedQuests): 适配悬赏封印新 UI 并清理孤立资产
LGG686 Oct 28, 2026
e68c2d2
fix(GlobalGame): 适配接受/拒绝按钮新 UI
LGG686 Oct 28, 2026
2db22cc
fix(RichMan): 海汐御魂开启自动选择时一次买完
LGG686 Oct 28, 2026
f77f4fe
Merge pull request #1973 from LGG686/WantedQuests
runhey Oct 28, 2026
dd860e0
Merge pull request #1974 from weirj2016-ai/fix/eternity-sea-member-lo…
runhey Oct 28, 2026
3f08515
道馆任务每次运行尝试开启道馆
weirj2016-ai Oct 28, 2026
d345e96
Merge pull request #1975 from weirj2016-ai/fix/dokan-create-every-run…
runhey Oct 28, 2026
506564c
fix(Costume): Refresh cached template image when replacing costume as…
runhey Oct 28, 2026
63b0d6d
Merge remote-tracking branch 'origin/dev' into dev
runhey Oct 28, 2026
1e18c25
道馆开启尝试移出查找循环
weirj2016-ai Oct 28, 2026
c6c724a
Merge pull request #1976 from weirj2016-ai/fix/dokan-create-on-entry-dev
runhey Oct 28, 2026
eddc617
fix(GameUi): 兼容商店两种招牌图片,修复反复进出商店的问题
leovefy Oct 29, 2026
65311c5
Merge pull request #1977 from leovefy/dev
runhey Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion module/atom/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pathlib import Path

from module.atom.RuleImageMallResourceMixin import RuleImageMallResourceMixin
from module.base.decorator import cached_property
from module.base.decorator import cached_property, del_cached_property
from module.logger import logger
from module.base.utils import is_approx_rectangle
from module.base.utils.utils import random_normal_distribution_int
Expand Down Expand Up @@ -233,6 +233,17 @@ def _sanitize_match_result(result: np.ndarray) -> np.ndarray:
logger.error("match result contains nan/inf (solid-color region), treated as not matched")
return np.nan_to_num(result, nan=-1.0, posinf=-1.0, neginf=-1.0)

def refresh(self) -> None:
self._image = None
self._kp = None
self._des = None
self._mask = None
self._mask_loaded = False
self._match_init = False
for cached_name in ('name', 'kp', 'des'):
del_cached_property(self, cached_name)


def corp(self, image: np.array, roi: list = None) -> np.array:
"""
截取图片
Expand Down
6 changes: 5 additions & 1 deletion module/device/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
from module.config.utils import get_server_next_update
from module.device.app_control import AppControl
from module.device.control import Control
from module.device.desktop_device import DesktopDevice
if IS_WINDOWS:
from module.device.desktop_device import DesktopDevice
else:
class DesktopDevice:
pass
from module.device.platform2 import Platform
from module.device.screenshot import Screenshot
from module.exception import (GameNotRunningError,
Expand Down
22 changes: 15 additions & 7 deletions tasks/AreaBoss/script_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from module.atom.click import RuleClick
from tasks.base_task import BaseTask
from tasks.Component.GeneralBattle.general_battle import GeneralBattle
from tasks.Component.GeneralBattle.battle import Battle
from tasks.Component.GeneralBattle.battle_wait import battle_wait_strategy, battle_wait_options
from tasks.GameUi.game_ui import GameUi
from tasks.GameUi.page import page_area_boss, page_shikigami_records
from tasks.Component.SwitchSoul.switch_soul import SwitchSoul
Expand All @@ -21,7 +23,7 @@
from typing import List


class ScriptTask(GeneralBattle, GameUi, SwitchSoul, AreaBossAssets):
class ScriptTask(Battle, GeneralBattle, GameUi, SwitchSoul, AreaBossAssets):

def _get_dynamic_boss_count(self) -> int:
"""
Expand Down Expand Up @@ -159,10 +161,10 @@ def boss_fight(self, battle: RuleImage, fileter_open: bool = True) -> bool:
return False

# 如果已经打过该BOSS,直接跳过不打了
if self.is_group_ranked():
logger.warning("There is no boss could be challenged")
self.ui_click_until_disappear(self.I_AB_CLOSE_RED, interval=3)
return True
# if self.is_group_ranked():
# logger.warning("There is no boss could be challenged")
# self.ui_click_until_disappear(self.I_AB_CLOSE_RED, interval=3)
# return True

# 根据 reward_floor 决定模式
if reward_floor in (AreaBossFloor.ONE, AreaBossFloor.TEN):
Expand Down Expand Up @@ -193,8 +195,12 @@ def start_fight(self) -> bool:
continue
if not self.appear(self.I_AB_CLOSE_RED): # 如果这个红色的关闭不见了才可以进行继续
break

return self.run_general_battle(self.config.area_boss.general_battle)
strategies, options = self.loadout_from_config(self.config.area_boss.general_battle)
options['completion'].check_imgs = [self.I_FIRE, self.I_FILTER]
self.loadout_show((strategies, options)) # 使用的是默认的策略: strategies
with battle_wait_options(**options):
win = self.battle_wait()
return win

def setup_ultra(self, floor: AreaBossFloor = AreaBossFloor.ONE) -> bool:
# 确定进入鬼王界面
Expand Down Expand Up @@ -493,6 +499,8 @@ def check_common_chars(self, bossName, name):
return 1
else:
return 0 # 如果交集的字符少于2个,可以根据需要返回其他值


if __name__ == '__main__':
from module.config.config import Config
from module.device.device import Device
Expand Down
4 changes: 4 additions & 0 deletions tasks/Component/Buy/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ class BuyAssets:
I_BUY_RMB = RuleImage(roi_front=(610,532,35,32), roi_back=(548,483,178,119), threshold=0.8, method="Template matching", file="./tasks/Component/Buy/buy/buy_buy_rmb.png")
# 购买成功
I_BUY_SUCCESS = RuleImage(roi_front=(512,53,261,44), roi_back=(449,0,405,167), threshold=0.8, method="Template matching", file="./tasks/Component/Buy/buy/buy_buy_success.png")
# 确认按钮上的勾玉
I_BUY_JADE = RuleImage(roi_front=(604,537,42,52), roi_back=(540,480,200,120), threshold=0.8, method="Template matching", file="./tasks/Component/Buy/buy/buy_buy_jade.png")


# Ocr Rule Assets
# Ocr-description
O_BUY_NUMBER = RuleOcr(roi=(578,416,52,71), area=(578,416,52,71), mode="Digit", method="Default", keyword="", name="buy_number")
# 确认按钮上的单价, roi 运行时按勾玉图标位置设置
O_BUY_PRICE = RuleOcr(roi=(646,537,60,52), area=(646,537,60,52), mode="Digit", method="Default", keyword="", name="buy_price")


165 changes: 165 additions & 0 deletions tasks/Component/Buy/buy.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,171 @@ def buy_more(self, start_click: Union[RuleImage, RuleOcr, RuleClick],
continue
return True

def buy_one_or_more(self, start_click: Union[RuleImage, RuleOcr, RuleClick],
check_image: RuleImage, number: int = None,
max_price: int = None, money: int = None, remain: int = None) -> bool:
"""
购买弹窗可能是单个购买窗口, 也可能是带数量选择(加号)的多数量购买窗口
先点开弹窗, 出现加号走 buy_more, 出现单个购买窗口走 buy_one
:param start_click: 开始点击
:param check_image: 单个购买窗口确认时候的图片
:param number: 多数量窗口的购买数量, 不指定就是拉满. 指定 max_price 时不用, 数量按 money 和 remain 算
:param max_price: 单价上限, 不为 None 时先读确认按钮上的勾玉单价, 没有勾玉、读不到或超过上限就取消购买
:param money: 当前余额, 指定 max_price 时用来算买得起几个
:param remain: 剩余购买次数, 指定 max_price 时用来算购买数量
:return: 超时没等到任何购买弹窗或取消购买返回 False
"""
timeout = Timer(10).start()
while 1:
self.screenshot()

if self.appear(self.I_BUY_PLUS):
if max_price is not None:
return self._buy_with_price(start_click, check_image, True, max_price, money, remain)
if number == 1:
# buy_more 指定数量时会先点一次加号再 OCR, 买 1 个会变成买 2 个
return self._buy_more_default(start_click)
logger.info('Buy window with number selector, use buy_more')
return self.buy_more(start_click, number)
if self.appear(check_image):
if max_price is not None:
return self._buy_with_price(start_click, check_image, False, max_price, money, remain)
logger.info('Single buy window, use buy_one')
return self.buy_one(start_click, check_image)
if timeout.reached():
logger.warning('Wait buy window timeout')
return False

if isinstance(start_click, RuleImage):
if self.appear_then_click(start_click, interval=1):
continue
elif isinstance(start_click, RuleOcr):
if self.ocr_appear_click(start_click, interval=1):
continue
elif isinstance(start_click, RuleClick):
if self.click(start_click, interval=1):
continue

def _buy_with_price(self, start_click: Union[RuleImage, RuleOcr, RuleClick], check_image: RuleImage,
more: bool, max_price: int, money: int, remain: int) -> bool:
"""
购买弹窗已打开, 读确认按钮上的勾玉单价, 不超过上限才买
:param more: True 为多数量购买窗口, False 为单个购买窗口
:return: 买到返回 True, 取消购买或达到购买上限返回 False
"""
price = self._buy_read_price()
if price is None or price > max_price:
logger.warning(f'Buy price {price} is unknown or over max price {max_price}, cancel buy')
self._buy_cancel(check_image)
return False
if not more:
logger.info(f'Single buy window, price {price}, buy number 1')
return self.buy_one(start_click, check_image)
if money is None or remain is None:
# 没传余额或剩余次数时只买 1 个
number = 1
else:
number = min(remain, money // price)
logger.info(f'Buy price {price}, money {money}, remain {remain}, buy number {number}')
if number < 1:
logger.warning('Money is not enough to buy one, cancel buy')
self._buy_cancel(check_image)
return False
if number == 1:
return self._buy_more_default(start_click)
if number == remain:
return self.buy_more(start_click, None)
# buy_more(start_click, number) 点加号后才读数量, 可能超过 number.
# 余额不够买满时每次只买 1 个, 调用方循环会重新读余额和剩余次数再买下一个
logger.info(f'Money is not enough to buy all {remain}, buy one by one')
return self._buy_more_default(start_click)

def _buy_read_price(self, min_price: int = 40) -> Union[int, None]:
"""
读购买确认按钮上的勾玉单价: 先等勾玉图标出现, 再读图标右边的数字
:param min_price: 单价下限, 低于它当作 OCR 读错(例如 90 少读一位成 9). 默认 40: 蓝票已知最低价 50, 留一点余量
:return: 单价, 没有勾玉图标、读不到或低于下限返回 None
"""
jade_timer = Timer(2).start()
while 1:
if self.appear(self.I_BUY_JADE):
break
if jade_timer.reached():
logger.warning('Jade icon is not found on buy button, maybe not paid by jade')
return None
self.screenshot()
x, y, w, h = self.I_BUY_JADE.roi_front
# 只取图标右边, 裁剪框碰到图标会把图标读成 1, 例如 90 读成 190
self.O_BUY_PRICE.roi = [x + w, y, 60, h]
price = self.O_BUY_PRICE.ocr(self.device.image)
try:
price = int(price)
except (TypeError, ValueError):
price = 0
if price == 0:
# Digit 读不到时返回 0
logger.warning('OCR buy price failed')
return None
if price < min_price:
logger.warning(f'Buy price {price} is lower than min price {min_price}, treat as OCR error')
return None
return price

def _buy_cancel(self, check_image: RuleImage) -> bool:
"""
点取消关掉购买弹窗, 直到多数量窗口的加号和单个购买窗口的确认图都消失
:return: 关掉返回 True, 超时返回 False
"""
timeout = Timer(10).start()
while 1:
self.screenshot()
if not self.appear(self.I_BUY_PLUS) and not self.appear(check_image):
return True
if timeout.reached():
logger.warning('Cancel buy window timeout')
return False
if self.click(self.C_BUY_CANCEL, interval=1):
continue

def _buy_more_default(self, start_click: Union[RuleImage, RuleOcr, RuleClick]) -> bool:
"""
多数量购买窗口不调整数量, 默认数量为 1 时直接确认买 1 个
:param start_click: 默认数量不是 1 时退回 buy_more(start_click, 1) 用
:return: 买到返回 True, 达到购买上限返回 False
"""
ocr_timer = Timer(2).start()
while 1:
current = self.O_BUY_NUMBER.ocr(self.device.image)
if current == 1:
break
if ocr_timer.reached():
logger.warning(f'Default buy number is {current}, fallback to buy_more')
return self.buy_more(start_click, 1)
self.screenshot()
logger.info('Buy window with number selector, buy one with default number')
while 1:
self.screenshot()

if self.ui_reward_appear_click():
while 1:
self.screenshot()
# 等待动画结束
if not self.appear(self.I_UI_REWARD, threshold=0.6):
logger.info('Get reward success')
break
# 一直点击
if self.ui_reward_appear_click():
continue
return True

if self.appear(self.I_UI_CONFIRM_SAMLL):
self.ui_click_until_disappear(self.I_UI_CONFIRM_SAMLL, interval=1)
logger.warning('Buy number limit')
return False

if self.click(self.C_BUY_MORE, interval=2):
continue

def buy_check_money(self, target: RuleOcr, minimum: int):
"""
检查钱是否足够
Expand Down
Binary file added tasks/Component/Buy/buy/buy_buy_jade.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions tasks/Component/Buy/buy/image.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,14 @@
"method": "Template matching",
"threshold": 0.8,
"description": "购买成功"
},
{
"itemName": "buy_jade",
"imageName": "buy_buy_jade.png",
"roiFront": "604,537,42,52",
"roiBack": "540,480,200,120",
"method": "Template matching",
"threshold": 0.8,
"description": "确认按钮上的勾玉"
}
]
9 changes: 9 additions & 0 deletions tasks/Component/Buy/buy/ocr.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,14 @@
"method": "Default",
"keyword": "",
"description": "Ocr-description"
},
{
"itemName": "buy_price",
"roiFront": "646,537,60,52",
"roiBack": "646,537,60,52",
"mode": "Digit",
"method": "Default",
"keyword": "",
"description": "确认按钮上的单价, roi 运行时按勾玉图标位置设置"
}
]
2 changes: 2 additions & 0 deletions tasks/Component/Costume/costume_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def replace_img(self,
asset_before_object.roi_back = asset_after.roi_back
asset_before_object.threshold = asset_after.threshold
asset_before_object.file = asset_after.file
# https://github.com/runhey/OnmyojiAutoScript/issues/1972
asset_before_object.refresh()

def check_costume_main(self, main_type: MainType):
if main_type == MainType.COSTUME_MAIN:
Expand Down
Loading
Loading