feat(task): 培育中增加对“活動支給”的处理

This commit is contained in:
XcantloadX 2025-01-10 13:54:22 +08:00
parent f0a3dadd71
commit c75bb49e4e
15 changed files with 245 additions and 208 deletions

View File

@ -1,65 +0,0 @@
import dearpygui.dearpygui as dpg
import cv2 as cv
import numpy as np
dpg.create_context()
dpg.create_viewport(title='Custom Title', width=600, height=800)
dpg.setup_dearpygui()
vid = cv.VideoCapture(0)
ret, frame = vid.read()
# image size or you can get this from image shape
frame_width = vid.get(cv.CAP_PROP_FRAME_WIDTH)
frame_height = vid.get(cv.CAP_PROP_FRAME_HEIGHT)
video_fps = vid.get(cv.CAP_PROP_FPS)
print(frame_width)
print(frame_height)
print(video_fps)
print("Frame Array:")
print("Array is of type: ", type(frame))
print("No. of dimensions: ", frame.ndim)
print("Shape of array: ", frame.shape)
print("Size of array: ", frame.size)
print("Array stores elements of type: ", frame.dtype)
data = np.flip(frame, 2) # because the camera data comes in as BGR and we need RGB
data = data.ravel() # flatten camera data to a 1 d stricture
data = np.asarray(data, dtype=np.float32) # change data type to 32bit floats
texture_data = np.true_divide(data, 255.0) # normalize image data to prepare for GPU
print("texture_data Array:")
print("Array is of type: ", type(texture_data))
print("No. of dimensions: ", texture_data.ndim)
print("Shape of array: ", texture_data.shape)
print("Size of array: ", texture_data.size)
print("Array stores elements of type: ", texture_data.dtype)
with dpg.texture_registry(show=True):
dpg.add_raw_texture(frame.shape[1], frame.shape[0], texture_data, tag="texture_tag", format=dpg.mvFormat_Float_rgb)
with dpg.window(label="Example Window"):
dpg.add_text("Hello, world")
dpg.add_image("texture_tag")
dpg.show_metrics()
dpg.show_viewport()
while dpg.is_dearpygui_running():
# updating the texture in a while loop the frame rate will be limited to the camera frame rate.
# commenting out the "ret, frame = vid.read()" line will show the full speed that operations and updating a texture can run at
ret, frame = vid.read()
data = np.flip(frame, 2)
data = data.ravel()
data = np.asarray(data, dtype=np.float32)
texture_data = np.true_divide(data, 255.0)
dpg.set_value("texture_tag", texture_data)
# to compare to the base example in the open cv tutorials uncomment below
#cv.imshow('frame', frame)
dpg.render_dearpygui_frame()
vid.release()
#cv.destroyAllWindows() # when using upen cv window "imshow" call this also
dpg.destroy_context()

View File

@ -1,15 +1,4 @@
from kotonebot.client.protocol import DeviceABC from .client.protocol import DeviceABC
from .backend.context import ContextOcr, ContextImage, ContextDebug, device, ocr, image, debug from .backend.context import ContextOcr, ContextImage, ContextDebug, device, ocr, image, debug
from .backend.util import Rect, fuzz, regex, contains, grayscale from .backend.util import Rect, fuzz, regex, contains, grayscaled, grayscale_cached
# device: DeviceProtocol
# ocr: ContextOcr
# image: ContextImage
# debug: ContextDebug
# def __getattr__(name: str):
# try:
# return getattr(_c, name)
# except AttributeError:
# return globals()[name]

View File

@ -28,7 +28,7 @@ class ContextOcr:
def raw(self, lang: OcrLanguage = 'jp') -> Ocr: def raw(self, lang: OcrLanguage = 'jp') -> Ocr:
""" """
返回 `kotonebot.backend.ocr` 中的 Ocr 对象\n 返回 `kotonebot.backend.ocr` 中的 Ocr 对象\n
Ocr 对象与此对象的区别是此对象会自动截图 Ocr 对象需要手动传入图像参数 Ocr 对象与此对象ContextOcr的区别是此对象会自动截图 Ocr 对象需要手动传入图像参数
""" """
match lang: match lang:
case 'jp': case 'jp':
@ -201,7 +201,7 @@ class ContextImage:
time.sleep(0.1) time.sleep(0.1)
def expect(self, template: str, mask: str | None = None, threshold: float = 0.9) -> TemplateMatchResult: def expect(self, template: str | MatLike, mask: str | None = None, threshold: float = 0.9) -> TemplateMatchResult:
""" """
寻找指定图像 寻找指定图像

View File

@ -76,12 +76,12 @@ def cropper_x(x1: float, x2: float) -> Callable[[MatLike], MatLike]:
return lambda img: crop_x(img, x1, x2) return lambda img: crop_x(img, x1, x2)
def grayscale(img: MatLike | str) -> MatLike: def grayscaled(img: MatLike | str) -> MatLike:
if isinstance(img, str): if isinstance(img, str):
img = cv2.imread(img) img = cv2.imread(img)
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
@lru_cache @lru_cache
def grayscale_cached(img: MatLike | str) -> MatLike: def grayscale_cached(img: MatLike | str) -> MatLike:
return grayscale(img) return grayscaled(img)

View File

@ -35,7 +35,7 @@ class AdbDevice(DeviceABC):
def __click_last(self) -> None: def __click_last(self) -> None:
if self.last_find is None: if self.last_find is None:
raise ValueError("No last find result") raise ValueError("No last find result. Make sure you are not calling the 'raw' functions.")
self.click(self.last_find) self.click(self.last_find)
def __click_rect(self, rect: Rect) -> None: def __click_rect(self, rect: Rect) -> None:

View File

@ -0,0 +1,132 @@
from typing import Literal
from logging import getLogger
from time import sleep
from kotonebot import (
ocr,
device,
contains,
image,
debug,
regex,
grayscaled,
grayscale_cached
)
from .. import R
from .pdorinku import acquire_pdorinku
logger = getLogger(__name__)
def acquire_skill_card():
"""获取技能卡(スキルカード)"""
# TODO: 识别卡片内容,而不是固定选卡
# TODO: 不硬编码坐标
CARD_POSITIONS = [
(157, 820, 128, 128),
(296, 820, 128, 128),
(435, 820, 128, 128),
]
logger.info("Click first skill card")
device.click(CARD_POSITIONS[0])
sleep(0.5)
# 确定
logger.info("Click 受け取る")
device.click(ocr.expect(contains("受け取る")).rect)
# 跳过动画
device.click(image.expect_wait_any([
R.InPurodyuusu.PSkillCardIconBlue,
R.InPurodyuusu.PSkillCardIconColorful
]))
AcquisitionType = Literal[
"PDrinkAcquire", # P饮料被动领取
"PDrinkSelect", # P饮料主动领取
"PDrinkMax", # P饮料到达上限
"PSkillCardAcquire", # 技能卡领取
"PSkillCardSelect", # 技能卡选择
"PItem", # P物品
"Clear", # 目标达成
]
def acquisitions() -> AcquisitionType | None:
"""处理行动开始前和结束后可能需要处理的事件,直到到行动页面为止"""
img = device.screenshot_raw()
gray_img = grayscaled(img)
logger.info("Acquisition stuffs...")
# P饮料被动领取
logger.info("Check PDrink acquisition...")
if image.raw().find(img, R.InPurodyuusu.PDrinkIcon):
logger.info("Click to finish animation")
device.click_center()
sleep(1)
return "PDrinkAcquire"
# P饮料主动领取
# if ocr.raw().find(img, contains("受け取るPドリンクを選れでください")):
if image.raw().find(img, R.InPurodyuusu.TextPleaseSelectPDrink):
logger.info("PDrink acquisition")
# 不领取
# device.click(ocr.expect(contains("受け取らない")))
# sleep(0.5)
# device.click(image.expect(R.InPurodyuusu.ButtonNotAcquire))
# sleep(0.5)
# device.click(image.expect(R.InPurodyuusu.ButtonConfirm))
acquire_pdorinku(index=0)
return "PDrinkSelect"
# P饮料到达上限
if image.raw().find(img, R.InPurodyuusu.TextPDrinkMax):
device.click(image.expect(R.InPurodyuusu.ButtonLeave))
sleep(0.7)
# 可能需要点击确认
device.click(image.expect(R.InPurodyuusu.ButtonConfirm, threshold=0.8))
return "PDrinkMax"
# 技能卡被动领取(支援卡效果)
logger.info("Check skill card acquisition...")
if image.raw().find_any(img, [
R.InPurodyuusu.PSkillCardIconBlue,
R.InPurodyuusu.PSkillCardIconColorful
]):
logger.info("Acquire skill card")
device.click_center()
return "PSkillCardAcquire"
# 技能卡选择
if ocr.raw().find(img, contains("受け取るスキルカードを選んでください")):
logger.info("Acquire skill card")
acquire_skill_card()
sleep(5)
return "PSkillCardSelect"
# 奖励箱技能卡
if res := image.raw().find(gray_img, grayscaled(R.InPurodyuusu.LootBoxSkillCard)):
logger.info("Acquire skill card from loot box")
device.click(res.rect)
# 下面就是普通的技能卡选择
return acquisitions()
# 目标达成
if image.raw().find(gray_img, grayscale_cached(R.InPurodyuusu.IconClearBlue)):
logger.debug("達成: clicked")
device.click_center()
sleep(5)
# TODO: 可能不存在 達成 NEXT
logger.debug("達成 NEXT: clicked")
device.click_center()
return "Clear"
# P物品
if image.raw().find(img, R.InPurodyuusu.PItemIconColorful):
logger.info("Click to finish PItem acquisition")
device.click_center()
sleep(1)
return "PItem"
# 支援卡
# logger.info("Check support card acquisition...")
# 记忆
# 未跳过剧情
return None
if __name__ == '__main__':
from logging import getLogger
import logging
from kotonebot.backend.context import init_context
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s')
getLogger('kotonebot').setLevel(logging.DEBUG)
getLogger(__name__).setLevel(logging.DEBUG)
init_context()
acquisitions()

View File

@ -1,20 +1,19 @@
import random import random
import re
import time import time
from typing import Literal from typing import Literal
from typing_extensions import deprecated from typing_extensions import deprecated
import numpy as np
import cv2 import cv2
import unicodedata import unicodedata
import logging import logging
from time import sleep from time import sleep
from kotonebot import ocr, device, fuzz, contains, image, debug, regex from kotonebot import ocr, device, contains, image, debug, regex
from kotonebot.backend.context import init_context from kotonebot.backend.context import init_context
from kotonebot.backend.util import crop_y, cropper_y, grayscale, grayscale_cached from kotonebot.backend.util import crop_y, cropper_y
from kotonebot.tasks import R from kotonebot.tasks import R
from kotonebot.tasks.actions import loading from kotonebot.tasks.actions import loading
from kotonebot.tasks.actions.pdorinku import acquire_pdorinku from .non_lesson_actions import enter_allowance, study_available, enter_study, allowance_available
from .common import acquisitions, AcquisitionType, acquire_skill_card
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -299,26 +298,7 @@ def remaing_turns_and_points():
turns_ocr = ocr.ocr(turns_img) turns_ocr = ocr.ocr(turns_img)
logger.debug("turns_ocr: %s", turns_ocr) logger.debug("turns_ocr: %s", turns_ocr)
def acquire_skill_card():
"""获取技能卡(スキルカード)"""
# TODO: 识别卡片内容,而不是固定选卡
# TODO: 不硬编码坐标
CARD_POSITIONS = [
(157, 820, 128, 128),
(296, 820, 128, 128),
(435, 820, 128, 128),
]
logger.info("Click first skill card")
device.click(CARD_POSITIONS[0])
sleep(0.5)
# 确定
logger.info("Click 受け取る")
device.click(ocr.expect(contains("受け取る")).rect)
# 跳过动画
device.click(image.expect_wait_any([
R.InPurodyuusu.PSkillCardIconBlue,
R.InPurodyuusu.PSkillCardIconColorful
]))
def rest(): def rest():
"""执行休息""" """执行休息"""
@ -328,83 +308,7 @@ def rest():
# 确定 # 确定
device.click(image.expect_wait(R.InPurodyuusu.RestConfirmBtn)) device.click(image.expect_wait(R.InPurodyuusu.RestConfirmBtn))
AcquisitionType = Literal[
"PDrinkAcquire", # P饮料被动领取
"PDrinkSelect", # P饮料主动领取
"PDrinkMax", # P饮料到达上限
"PSkillCardAcquire", # 技能卡被动领取
"PSkillCardSelect", # 技能卡主动领取
"PItem", # P物品
"Clear", # 目标达成
]
def acquisitions() -> AcquisitionType | None:
"""处理行动开始前和结束后可能需要处理的事件,直到到行动页面为止"""
img = device.screenshot_raw()
gray_img = grayscale(img)
logger.info("Acquisition stuffs...")
# P饮料被动领取
logger.info("Check PDrink acquisition...")
if image.raw().find(img, R.InPurodyuusu.PDrinkIcon):
logger.info("Click to finish animation")
device.click_center()
sleep(1)
return "PDrinkAcquire"
# P饮料主动领取
# if ocr.raw().find(img, contains("受け取るPドリンクを選れでください")):
if image.raw().find(img, R.InPurodyuusu.TextPleaseSelectPDrink):
logger.info("PDrink acquisition")
# 不领取
# device.click(ocr.expect(contains("受け取らない")))
# sleep(0.5)
# device.click(image.expect(R.InPurodyuusu.ButtonNotAcquire))
# sleep(0.5)
# device.click(image.expect(R.InPurodyuusu.ButtonConfirm))
acquire_pdorinku(index=0)
return "PDrinkSelect"
# P饮料到达上限
if image.raw().find(img, R.InPurodyuusu.TextPDrinkMax):
device.click(image.expect(R.InPurodyuusu.ButtonLeave))
sleep(0.7)
# 可能需要点击确认
device.click(image.expect(R.InPurodyuusu.ButtonConfirm, threshold=0.8))
return "PDrinkMax"
# 技能卡被动领取
logger.info("Check skill card acquisition...")
if image.raw().find_any(img, [
R.InPurodyuusu.PSkillCardIconBlue,
R.InPurodyuusu.PSkillCardIconColorful
]):
logger.info("Acquire skill card")
device.click_center()
return "PSkillCardAcquire"
# 技能卡主动领取
if ocr.raw().find(img, contains("受け取るスキルカードを選んでください")):
logger.info("Acquire skill card")
acquire_skill_card()
sleep(5)
return "PSkillCardSelect"
# 目标达成
if image.raw().find(gray_img, grayscale_cached(R.InPurodyuusu.IconClearBlue)):
logger.debug("達成: clicked")
device.click_center()
sleep(5)
# TODO: 可能不存在 達成 NEXT
logger.debug("達成 NEXT: clicked")
device.click_center()
return "Clear"
# P物品
if image.raw().find(img, R.InPurodyuusu.PItemIconColorful):
logger.info("Click to finish PItem acquisition")
device.click_center()
sleep(1)
return "PItem"
# 支援卡
# logger.info("Check support card acquisition...")
# 记忆
# 未跳过剧情
return None
def until_action_scene(): def until_action_scene():
"""等待进入行动场景""" """等待进入行动场景"""
@ -663,7 +567,7 @@ def hajime_regular(week: int = -1, start_from: int = 1):
if not enter_recommended_action(): if not enter_recommended_action():
rest() rest()
def week_common(): def week_lesson():
until_action_scene() until_action_scene()
executed_action = enter_recommended_action() executed_action = enter_recommended_action()
logger.info("Executed recommended action: %s", executed_action) logger.info("Executed recommended action: %s", executed_action)
@ -677,8 +581,16 @@ def hajime_regular(week: int = -1, start_from: int = 1):
rest() rest()
until_action_scene() until_action_scene()
def week_non_lesson():
"""非练习周。可能可用行动包括:おでかけ、相談、活動支給、授業"""
until_action_scene()
if allowance_available():
enter_allowance()
# elif study_available():
# enter_study()
until_action_scene()
def week_final(): def week_final_lesson():
if enter_recommended_action(final_week=True) != 'lesson': if enter_recommended_action(final_week=True) != 'lesson':
raise ValueError("Failed to enter recommended action on final week.") raise ValueError("Failed to enter recommended action on final week.")
sleep(5) sleep(5)
@ -709,19 +621,19 @@ def hajime_regular(week: int = -1, start_from: int = 1):
produce_end() produce_end()
weeks = [ weeks = [
week_common, # 1 week_lesson, # 1: Vo.レッスン、Da.レッスン、Vi.レッスン
week_common, # 2 week_lesson, # 2: 授業
week_common, # 3 week_lesson, # 3: Vo.レッスン、Da.レッスン、Vi.レッスン、授業
week_common, # 4 week_non_lesson, # 4: おでかけ、相談、活動支給
week_final, # 5 week_final_lesson, # 5: 追い込みレッスン
week_mid_exam, # 6 week_mid_exam, # 6: 中間試験
week_common, # 7 week_non_lesson, # 7: おでかけ、活動支給
week_common, # 8 week_non_lesson, # 8: 授業、活動支給
week_common, # 9 week_lesson, # 9: Vo.レッスン、Da.レッスン、Vi.レッスン
week_common, # 10 week_lesson, # 10: Vo.レッスン、Da.レッスン、Vi.レッスン、授業
week_common, # 11 week_non_lesson, # 11: おでかけ、相談、活動支給
week_final, # 12 week_final_lesson, # 12: 追い込みレッスン
week_final_exam, # 13 week_final_exam, # 13: 最終試験
] ]
if week != -1: if week != -1:
logger.info("Week %d started.", week) logger.info("Week %d started.", week)
@ -751,13 +663,13 @@ if __name__ == '__main__':
getLogger(__name__).setLevel(logging.DEBUG) getLogger(__name__).setLevel(logging.DEBUG)
init_context() init_context()
while not image.wait_for_any([ # while not image.wait_for_any([
R.InPurodyuusu.TextPDiary, # 普通周 # R.InPurodyuusu.TextPDiary, # 普通周
R.InPurodyuusu.ButtonFinalPracticeDance # 离考试剩余一周 # R.InPurodyuusu.ButtonFinalPracticeDance # 离考试剩余一周
], timeout=2): # ], timeout=2):
logger.info("Action scene not detected. Retry...") # logger.info("Action scene not detected. Retry...")
acquisitions() # acquisitions()
sleep(3) # sleep(3)
# image.wait_for_any([ # image.wait_for_any([
# R.InPurodyuusu.TextPDiary, # 普通周 # R.InPurodyuusu.TextPDiary, # 普通周
@ -775,7 +687,7 @@ if __name__ == '__main__':
# acquisitions() # acquisitions()
# acquire_pdorinku(0) # acquire_pdorinku(0)
# image.wait_for(R.InPurodyuusu.InPractice.PDorinkuIcon) # image.wait_for(R.InPurodyuusu.InPractice.PDorinkuIcon)
# hajime_regular(start_from=1) hajime_regular(start_from=9)
# until_practice_scene() # until_practice_scene()
# device.click(image.expect_wait_any([ # device.click(image.expect_wait_any([
# R.InPurodyuusu.PSkillCardIconBlue, # R.InPurodyuusu.PSkillCardIconBlue,

View File

@ -0,0 +1,69 @@
"""
此文件包含非练习周的行动
具体包括おでかけ相談活動支給授業
"""
from time import sleep
from logging import getLogger
from kotonebot import device, image, ocr, debug
from kotonebot.tasks import R
from .common import acquisitions, AcquisitionType
logger = getLogger(__name__)
def allowance_available():
"""
判断是否可以执行活動支給
"""
return image.expect(R.InPurodyuusu.ButtonTextAllowance) is not None
def study_available():
"""
判断是否可以执行授業
"""
return image.expect(R.InPurodyuusu.ButtonTextStudy) is not None
def enter_study():
"""
执行授業
"""
raise NotImplementedError("授業功能未实现")
def enter_allowance():
"""
执行活動支給
前置条件位于行动页面且所有行动按钮清晰可见 \n
结束状态
"""
logger.info("Executing 活動支給.")
# 点击活動支給 [screenshots\allowance\step_1.png]
logger.info("Double clicking on 活動支給.")
device.double_click(image.expect(R.InPurodyuusu.ButtonTextAllowance), interval=1)
# 等待进入页面
sleep(3)
# 第一个箱子 [screenshots\allowance\step_2.png]
logger.info("Clicking on the first lootbox.")
device.click(image.expect_wait_any([
R.InPurodyuusu.LootboxSliverLock
]))
while acquisitions() is None:
logger.info("Waiting for acquisitions finished.")
sleep(2)
# 第二个箱子
logger.info("Clicking on the second lootbox.")
device.click(image.expect_wait_any([
R.InPurodyuusu.LootboxSliverLock
]))
while acquisitions() is None:
logger.info("Waiting for acquisitions finished.")
sleep(2)
logger.info("活動支給 completed.")
# 可能会出现的新动画
# 技能卡:[screenshots\allowance\step_4.png]
def study():
"""授業"""
pass

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 KiB

View File

@ -14,7 +14,7 @@ class {{ class_name }}:
""" """
路径:{{ attr.rel_path }}<br> 路径:{{ attr.rel_path }}<br>
模块:`{{ attr.class_path|join('.') }}.{{ attr.name }}`<br> 模块:`{{ attr.class_path|join('.') }}.{{ attr.name }}`<br>
<img src="vscode-file://vscode-app/{{ attr.abspath }}" style="max-width: 200px;"> <img src="vscode-file://vscode-app/{{ attr.abspath }}" style="max-width: 70%; max-height: 200px;">
""" """
{%- elif attr.type == 'next_class' -%} {%- elif attr.type == 'next_class' -%}
{{ attr.name }} = {{ attr.value }} {{ attr.name }} = {{ attr.value }}