feat(task): 培育支持指定P偶像

This commit is contained in:
XcantloadX 2025-02-05 21:11:01 +08:00
parent 0d90ffd014
commit 58a8a8da72
83 changed files with 197 additions and 40 deletions

View File

@ -16,6 +16,7 @@ from typing import (
Concatenate,
Generic,
Type,
Sequence,
)
from typing_extensions import deprecated
@ -279,14 +280,15 @@ class ContextOcr:
def find_all(
self,
patterns: list[str | re.Pattern | StringMatchFunction],
patterns: Sequence[str | re.Pattern | StringMatchFunction],
*,
hint: HintBox | None = None,
rect: Rect | None = None,
) -> list[OcrResult | None]:
return self.__engine.find_all(
ContextStackVars.ensure_current().screenshot,
patterns,
list(patterns),
hint=hint,
rect=rect,
)

View File

@ -1,5 +1,5 @@
from enum import IntEnum, Enum
from typing import Literal
from enum import IntEnum, Enum
from pydantic import BaseModel
@ -22,10 +22,88 @@ class APShopItems(IntEnum):
"""回忆再生成券"""
class PIdols(Enum):
pass
class PIdol(Enum):
"""P偶像"""
倉本千奈_Campusmode = ["倉本千奈", "Campus mode!!"]
倉本千奈_WonderScale = ["倉本千奈", "Wonder Scale"]
倉本千奈_ようこそ初星温泉 = ["倉本千奈", "ようこそ初星温泉"]
倉本千奈_仮装狂騒曲 = ["倉本千奈", "仮装狂騒曲"]
倉本千奈_初心 = ["倉本千奈", "初心"]
倉本千奈_学園生活 = ["倉本千奈", "学園生活"]
倉本千奈_日々_発見的ステップ = ["倉本千奈", "日々、発見的ステップ!"]
倉本千奈_胸を張って一歩ずつ = ["倉本千奈", "胸を張って一歩ずつ"]
十王星南_Campusmode = ["十王星南", "Campus mode!!"]
十王星南_一番星 = ["十王星南", "一番星"]
十王星南_学園生活 = ["十王星南", "学園生活"]
十王星南_小さな野望 = ["十王星南", "小さな野望"]
姫崎莉波_clumsytrick = ["姫崎莉波", "clumsy trick"]
姫崎莉波_私らしさのはじまり = ["姫崎莉波", "『私らしさ』のはじまり"]
姫崎莉波_キミとセミブルー = ["姫崎莉波", "キミとセミブルー"]
姫崎莉波_Campusmode = ["姫崎莉波", "Campus mode!!"]
姫崎莉波_LUV = ["姫崎莉波", "L.U.V"]
姫崎莉波_ようこそ初星温泉 = ["姫崎莉波", "ようこそ初星温泉"]
姫崎莉波_ハッピーミルフィーユ = ["姫崎莉波", "ハッピーミルフィーユ"]
姫崎莉波_初心 = ["姫崎莉波", "初心"]
姫崎莉波_学園生活 = ["姫崎莉波", "学園生活"]
月村手毬_Lunasaymaybe = ["月村手毬", "Luna say maybe"]
月村手毬_一匹狼 = ["月村手毬", "一匹狼"]
月村手毬_Campusmode = ["月村手毬", "Campus mode!!"]
月村手毬_アイヴイ = ["月村手毬", "アイヴイ"]
月村手毬_初声 = ["月村手毬", "初声"]
月村手毬_学園生活 = ["月村手毬", "学園生活"]
月村手毬_仮装狂騒曲 = ["月村手毬", "仮装狂騒曲"]
有村麻央_Fluorite = ["有村麻央", "Fluorite"]
有村麻央_はじまりはカッコよく = ["有村麻央", "はじまりはカッコよく"]
有村麻央_Campusmode = ["有村麻央", "Campus mode!!"]
有村麻央_FeelJewelDream = ["有村麻央", "Feel Jewel Dream"]
有村麻央_キミとセミブルー = ["有村麻央", "キミとセミブルー"]
有村麻央_初恋 = ["有村麻央", "初恋"]
有村麻央_学園生活 = ["有村麻央", "学園生活"]
篠泽广_コントラスト = ["篠泽广", "コントラスト"]
篠泽广_一番向いていないこと = ["篠泽广", "一番向いていないこと"]
篠泽广_光景 = ["篠泽广", "光景"]
篠泽广_Campusmode = ["篠泽广", "Campus mode!!"]
篠泽广_仮装狂騒曲 = ["篠泽广", "仮装狂騒曲"]
篠泽广_ハッピーミルフィーユ = ["篠泽广", "ハッピーミルフィーユ"]
篠泽广_初恋 = ["篠泽广", "初恋"]
篠泽广_学園生活 = ["篠泽广", "学園生活"]
紫云清夏_TameLieOneStep = ["紫云清夏", "Tame-Lie-One-Step"]
紫云清夏_カクシタワタシ = ["紫云清夏", "カクシタワタシ"]
紫云清夏_夢へのリスタート = ["紫云清夏", "夢へのリスタート"]
紫云清夏_Campusmode = ["紫云清夏", "Campus mode!!"]
紫云清夏_キミとセミブルー = ["紫云清夏", "キミとセミブルー"]
紫云清夏_初恋 = ["紫云清夏", "初恋"]
紫云清夏_学園生活 = ["紫云清夏", "学園生活"]
花海佑芽_WhiteNightWhiteWish = ["花海佑芽", "White Night! White Wish!"]
花海佑芽_学園生活 = ["花海佑芽", "学園生活"]
花海佑芽_Campusmode = ["花海佑芽", "Campus mode!!"]
花海佑芽_TheRollingRiceball = ["花海佑芽", "The Rolling Riceball"]
花海佑芽_アイドル_はじめっ = ["花海佑芽", "アイドル、はじめっ!"]
花海咲季_BoomBoomPow = ["花海咲季", "Boom Boom Pow"]
花海咲季_Campusmode = ["花海咲季", "Campus mode!!"]
花海咲季_FightingMyWay = ["花海咲季", "Fighting My Way"]
花海咲季_わたしが一番 = ["花海咲季", "わたしが一番!"]
花海咲季_冠菊 = ["花海咲季", "冠菊"]
花海咲季_初声 = ["花海咲季", "初声"]
花海咲季_古今東西ちょちょいのちょい = ["花海咲季", "古今東西ちょちょいのちょい"]
花海咲季_学園生活 = ["花海咲季", "学園生活"]
葛城リーリヤ_一つ踏み出した先に = ["葛城リーリヤ", "一つ踏み出した先に"]
葛城リーリヤ_白線 = ["葛城リーリヤ", "白線"]
葛城リーリヤ_Campusmode = ["葛城リーリヤ", "Campus mode!!"]
葛城リーリヤ_WhiteNightWhiteWish = ["葛城リーリヤ", "White Night! White Wish!"]
葛城リーリヤ_冠菊 = ["葛城リーリヤ", "冠菊"]
葛城リーリヤ_初心 = ["葛城リーリヤ", "初心"]
葛城リーリヤ_学園生活 = ["葛城リーリヤ", "学園生活"]
藤田ことね_カワイイ_はじめました = ["藤田ことね", "カワイイ", "はじめました"]
藤田ことね_世界一可愛い私 = ["藤田ことね", "世界一可愛い私"]
藤田ことね_Campusmode = ["藤田ことね", "Campus mode!!"]
藤田ことね_YellowBigBang = ["藤田ことね", "Yellow Big Bang"]
藤田ことね_WhiteNightWhiteWish = ["藤田ことね", "White Night! White Wish!"]
藤田ことね_冠菊 = ["藤田ことね", "冠菊"]
藤田ことね_初声 = ["藤田ことね", "初声"]
藤田ことね_学園生活 = ["藤田ことね", "学園生活"]
class PurchaseConfig(BaseModel):
enabled: bool = False
"""是否启用商店购买"""
@ -74,11 +152,12 @@ class ProduceConfig(BaseModel):
"""培育模式。"""
produce_count: int = 1
"""培育的次数。"""
idols: list[str] = []
idols: list[PIdol] = []
"""要培育的偶像。将会按顺序循环选择培育。"""
memory_sets: list[int] = []
"""要使用的回忆编成编号,从 1 开始。将会按顺序循环选择使用。"""
support_card_sets: list[int] = []
"""要使用的支援卡编成编号,从 1 开始。将会按顺序循环选择使用。"""
auto_set_memory: bool = False
"""是否自动编成回忆。此选项优先级高于回忆编成编号。"""

View File

@ -1,10 +1,10 @@
import logging
from . import R
from .common import conf
from .common import conf, PIdol
from .actions.loading import wait_loading_end
from .actions.in_purodyuusu import hajime_regular
from kotonebot import device, image, ocr, task, action, sleep
from kotonebot import device, image, ocr, task, action, sleep, contains
from .actions.scenes import loading, at_home, goto_home
logger = logging.getLogger(__name__)
@ -14,9 +14,90 @@ def format_time(seconds):
seconds = int(seconds % 60)
return f"{minutes}m {seconds}s"
def unify(arr: list[int]):
# 先对数组进行排序
arr.sort()
result = []
i = 0
while i < len(arr):
# 将当前元素加入结果
result.append(arr[i])
# 跳过所有与当前元素相似的元素
j = i + 1
while j < len(arr) and abs(arr[j] - arr[i]) <= 10:
j += 1
i = j
return result
@action('选择P偶像')
def select_idol(target_titles: list[str] | PIdol):
"""
选择目标P偶像
前置条件培育-偶像选择页面 1.アイドル選択\n
结束状态培育-偶像选择页面 1.アイドル選択\n
:param target_titles: 目标偶像的名称关键字选择时只会选择所有关键字都出现的偶像
"""
# 前置条件:[res/sprites/jp/produce/produce_preparation1.png]
# 结束状态:[res/sprites/jp/produce/produce_preparation1.png]
# 进入总览
device.update_screenshot()
device.click(image.expect(R.Produce.ButtonPIdolOverview))
while not image.find(R.Common.ButtonConfirmNoIcon):
device.update_screenshot()
if isinstance(target_titles, PIdol):
target_titles = target_titles.value
_target_titles = [contains(t) for t in target_titles]
device.update_screenshot()
# 定位滑动基准
results = image.find_all(R.Produce.IconPIdolLevel)
results.sort(key=lambda r: tuple(r.position))
ys = unify([r.position[1] for r in results])
min_y = ys[0]
max_y = ys[1]
found = False
max_tries = 5
tries = 0
# TODO: 加入 ScrollBar 类,判断滚动条进度
# 找到目标偶像
while not found:
# 首先检查当前选中的是不是已经是目标
if all(ocr.find_all(_target_titles, rect=R.Produce.KbIdolOverviewName)):
found = True
break
# 如果不是,就挨个选中,判断名称
for r in results:
device.click(r)
device.update_screenshot()
if all(ocr.find_all(_target_titles, rect=R.Produce.KbIdolOverviewName)):
found = True
break
if not found:
tries += 1
if tries > max_tries:
break
# 翻页
device.swipe(x1=100, x2=100, y1=max_y, y2=min_y)
sleep(2)
device.update_screenshot()
results = image.find_all(R.Produce.IconPIdolLevel)
results.sort(key=lambda r: tuple(r.position))
device.click(image.expect(R.Common.ButtonConfirmNoIcon))
return found
@action('单次培育')
def do_produce():
"""进行培育流程"""
def do_produce(idol: PIdol | None = None):
"""
进行培育流程
:param idol: 要培育的偶像如果为 None则使用配置文件中的偶像
"""
if not conf().produce.enabled:
logger.info('Produce is disabled.')
return
@ -34,7 +115,14 @@ def do_produce():
sleep(0.3)
wait_loading_end()
# 选择 PIdol [screenshots/produce/select_p_idol.png]
if idol:
select_idol(idol.value)
elif conf().produce.idols:
select_idol(conf().produce.idols[0].value) # TODO: 支持多次培育
else:
logger.warning('No PIdol specified. Using default idol.')
device.click(image.expect_wait(R.Common.ButtonNextNoIcon))
sleep(0.1)
# 选择支援卡 自动编成 [screenshots/produce/select_support_card.png]
device.click(image.expect_wait(R.Produce.ButtonAutoSet))
@ -89,5 +177,4 @@ if __name__ == '__main__':
logging.getLogger('kotonebot').setLevel(logging.DEBUG)
logger.setLevel(logging.DEBUG)
produce_task()
# select_idol(PIdol.藤田ことね_学園生活)

View File

@ -10,7 +10,7 @@ from kotonebot.config.manager import load_config, save_config
from kotonebot.tasks.common import (
BaseConfig, APShopItems, PurchaseConfig, ActivityFundsConfig,
PresentsConfig, AssignmentConfig, ContestConfig, ProduceConfig,
MissionRewardConfig
MissionRewardConfig, PIdol
)
from kotonebot.config.base_config import UserConfig, BackendConfig
@ -115,6 +115,7 @@ class KotoneBotUI:
produce_enabled: bool,
produce_mode: Literal["regular"],
produce_count: int,
produce_idols: List[str],
auto_set_memory: bool,
auto_set_support: bool,
use_pt_boost: bool,
@ -163,6 +164,7 @@ class KotoneBotUI:
enabled=produce_enabled,
mode=produce_mode,
produce_count=produce_count,
idols=[PIdol[idol] for idol in produce_idols],
auto_set_memory=auto_set_memory,
auto_set_support_card=auto_set_support,
use_pt_boost=use_pt_boost,
@ -298,7 +300,7 @@ class KotoneBotUI:
)
return assignment_enabled, mini_live_reassign, mini_live_duration, online_live_reassign, online_live_duration
def _create_produce_settings(self) -> Tuple[gr.Checkbox, gr.Dropdown, gr.Number, gr.Checkbox, gr.Checkbox, gr.Checkbox, gr.Checkbox, gr.Checkbox]:
def _create_produce_settings(self) -> Tuple[gr.Checkbox, gr.Dropdown, gr.Number, gr.Dropdown, gr.Checkbox, gr.Checkbox, gr.Checkbox, gr.Checkbox, gr.Checkbox]:
with gr.Column():
gr.Markdown("### 培育设置")
produce_enabled = gr.Checkbox(
@ -317,6 +319,16 @@ class KotoneBotUI:
label="培育次数",
interactive=True
)
# 添加偶像选择
idol_choices = [idol.name for idol in PIdol]
selected_idols = [idol.name for idol in self.current_config.options.produce.idols]
produce_idols = gr.Dropdown(
choices=idol_choices,
value=selected_idols,
label="选择要培育的偶像",
multiselect=True,
interactive=True
)
auto_set_memory = gr.Checkbox(
label="自动编成回忆",
value=self.current_config.options.produce.auto_set_memory
@ -343,7 +355,7 @@ class KotoneBotUI:
inputs=[produce_enabled],
outputs=[produce_group]
)
return produce_enabled, produce_mode, produce_count, auto_set_memory, auto_set_support, use_pt_boost, use_note_boost, follow_producer
return produce_enabled, produce_mode, produce_count, produce_idols, auto_set_memory, auto_set_support, use_pt_boost, use_note_boost, follow_producer
def _create_settings_tab(self) -> None:
with gr.Tab("设置"):

View File

@ -1,24 +1 @@
{
"definitions": {
"30a6f399-6999-4f04-bb77-651e0214112f": {
"name": "Produce.IconPIdolLevel",
"displayName": "P偶像卡上的等级图标",
"type": "template",
"annotationId": "30a6f399-6999-4f04-bb77-651e0214112f",
"useHintRect": false
}
},
"annotations": [
{
"id": "30a6f399-6999-4f04-bb77-651e0214112f",
"type": "rect",
"data": {
"x1": 238,
"y1": 742,
"x2": 248,
"y2": 753
},
"_tip": "P偶像卡上的等级图标"
}
]
}
{"definitions":{"30a6f399-6999-4f04-bb77-651e0214112f":{"name":"Produce.IconPIdolLevel","displayName":"P偶像卡上的等级图标","type":"template","annotationId":"30a6f399-6999-4f04-bb77-651e0214112f","useHintRect":false},"b71165dd-7285-45a7-bb8c-d4fccee2b0ba":{"name":"Produce.KbIdolOverviewName","displayName":"P偶像总览界面偶像名称","type":"hint-box","annotationId":"b71165dd-7285-45a7-bb8c-d4fccee2b0ba","useHintRect":false}},"annotations":[{"id":"30a6f399-6999-4f04-bb77-651e0214112f","type":"rect","data":{"x1":238,"y1":742,"x2":248,"y2":753}},{"id":"b71165dd-7285-45a7-bb8c-d4fccee2b0ba","type":"rect","data":{"x1":140,"y1":16,"x2":615,"y2":97}}]}

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB