feat(task): 培育支持指定P偶像
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
"""是否自动编成回忆。此选项优先级高于回忆编成编号。"""
|
||||
|
|
|
@ -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.藤田ことね_学園生活)
|
|
@ -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("设置"):
|
||||
|
|
|
@ -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}}]}
|
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 124 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 128 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 104 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 99 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 99 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 128 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 212 KiB |