kotones-auto-assistant/kotonebot/kaa/game_ui/schedule.py

163 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import logging
from dataclasses import dataclass
from cv2.typing import MatLike
from kotonebot.primitives import Rect
from kotonebot import ocr, device, image, action
from kotonebot.backend.core import HintBox
from kotonebot.kaa.config import ProduceAction
from kotonebot.kaa.tasks import R
logger = logging.getLogger(__name__)
# 三属性的当前值与最大值读取范围
CurVoValue = HintBox(x1=185, y1=680, x2=285, y2=720, source_resolution=(720, 1280))
CurDaValue = HintBox(x1=330, y1=680, x2=430, y2=720, source_resolution=(720, 1280))
CurViValue = HintBox(x1=475, y1=680, x2=575, y2=720, source_resolution=(720, 1280))
MaxDaValue = HintBox(x1=285, y1=720, x2=430, y2=750, source_resolution=(720, 1280))
@dataclass
class Lesson:
"""
:param rect: 课程位置
:param sp: 是否为 SP 课程
:param act: 课程类型
:param cur_attr_value: 课程属性的当前值
:param max_attr_value: 课程属性的最大值
"""
rect: Rect
sp: bool
act: ProduceAction
cur_attr_value: int
max_attr_value: int
class Schedule:
def __init__(self):
"""
初始化日程表
这里本来想传入本次培育中的全局信息,比如 难度偶像信息各个课程sp率
这些信息在整局培育中是相同的
"""
pass
@action('检测日程是否有课程', screenshot_mode='manual-inherit')
def have_lesson(self) -> bool:
"""
判断是否有课程,依据是课程的名字的图片
TODO: NIA 课程的名字的图片没放到这里后续若要支持NIA需要添加对应的图片
"""
device.screenshot()
result = image.find_multi([
R.InPurodyuusu.TextActionVocal,
R.InPurodyuusu.TextActionDance,
R.InPurodyuusu.TextActionVisual,
])
return result is not None
@action('识别日程,课程抉择,', screenshot_mode='manual-inherit')
def select_lesson(self) -> Lesson:
"""
选择课程,根据推荐、属性进行抉择
:return: 课程
"""
recommended = self.read_sensei_recommended()
lesson_data = self.read_lesson_data()
sp_lessons, nor_lessons = [], []
for l in lesson_data:
sp_lessons.append(l) if l.sp else nor_lessons.append(l)
have_sp = len(sp_lessons) > 0
for lesson in lesson_data:
if lesson.act == recommended and lesson.sp == have_sp:
logger.info(f'Recommended: {lesson}')
return lesson
# TODO: 各个课程的属性上限需要根据难度+偶像属性要求进行调整,之后再根据此上限选择课程.目前设置上限为 attr_limit_ratio
cal_lesson = None
attr_limit_ratio = 0.8
if sp_lessons:
lesson = min(sp_lessons, key=lambda x: x.cur_attr_value)
if lesson.cur_attr_value < lesson.max_attr_value * attr_limit_ratio:
cal_lesson = lesson
if cal_lesson is None and nor_lessons:
cal_lesson = min(nor_lessons, key=lambda x: x.cur_attr_value)
if cal_lesson is None:
logger.warning('Lesson calculate error, select vocal')
return lesson_data[0]
logger.info(f'Recommended is not sp, calculate result: {cal_lesson}')
return cal_lesson
@action('读取日程中课程数据', screenshot_mode='manual-inherit')
def read_lesson_data(self) -> list[Lesson]:
"""
读取当前课程数据包括位置是否为sp当前属性对应数值和最大值
:return: 课程数据列表
"""
img = device.screenshot()
sp_list = image.find_all(R.InPurodyuusu.IconSp)
vo_sp = da_sp = vi_sp = False
vo = image.expect(R.InPurodyuusu.ButtonPracticeVocal)
da = image.expect(R.InPurodyuusu.ButtonPracticeDance)
vi = image.expect(R.InPurodyuusu.ButtonPracticeVisual)
for cur_sp in sp_list:
if cur_sp.position[0] < vo.position[0]:
vo_sp = True
elif vo.position[0] < cur_sp.position[0] < da.position[0]:
da_sp = True
elif da.position[0] < cur_sp.position[0] < vi.position[0]:
vi_sp = True
max_value = self.read_number(img, MaxDaValue)
lesson_data = [
Lesson(vo.rect, vo_sp, ProduceAction.VOCAL, self.read_number(img, CurVoValue), max_value),
Lesson(da.rect, da_sp, ProduceAction.DANCE, self.read_number(img, CurDaValue), max_value),
Lesson(vi.rect, vi_sp, ProduceAction.VISUAL, self.read_number(img, CurViValue), max_value),
]
for lesson in lesson_data:
logger.info(f'Lesson: {lesson}')
return lesson_data
@action('读取日程中老师的推荐行动', screenshot_mode='manual-inherit')
def read_sensei_recommended(self) -> ProduceAction:
"""
读取老师的推荐行动
:return: 当前推荐行动,如果没推荐行动,返回 RECOMMENDED
"""
device.screenshot()
if image.find(R.InPurodyuusu.IconAsariSenseiAvatar):
logger.debug('Retrieving recommended lesson...')
result = image.find_multi([
R.InPurodyuusu.TextSenseiTipVocal,
R.InPurodyuusu.TextSenseiTipDance,
R.InPurodyuusu.TextSenseiTipVisual,
R.InPurodyuusu.TextSenseiTipRest,
R.InPurodyuusu.TextSenseiTipConsult,
])
if result:
match result.index:
case 0:
return ProduceAction.VOCAL
case 1:
return ProduceAction.DANCE
case 2:
return ProduceAction.VISUAL
case 3:
return ProduceAction.REST
case 4:
return ProduceAction.CONSULT
return ProduceAction.RECOMMENDED
def read_number(self, img: MatLike, box: HintBox) -> int:
"""
:param img: MatLike 图像
:param box: HintBox 需要读取数值的范围
:return: int 数值读取失败返回0
"""
all_number = ocr.raw().ocr(img, rect=box)
all_number = all_number.squash().numbers()
if all_number:
return int(all_number[0])
else:
return 0