feat(task): 重构并优化培育偶像选择
原来采用逐个选择 + OCR 方法,现在采用裁剪出画面中所有卡片图像 + 直方图对比查找。新方法比原有方法快切准确。
This commit is contained in:
parent
97688c62c8
commit
b00db58f9a
|
@ -17,6 +17,7 @@ logs/
|
|||
traces/
|
||||
version
|
||||
kotonebot/tasks/resources
|
||||
cache/
|
||||
##########################
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
graft kotonebot/tasks/sprites
|
||||
graft kotonebot/tasks/resources
|
||||
prune tests
|
||||
prune tools
|
||||
prune experiments
|
|
@ -1 +1 @@
|
|||
{"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}}]}
|
||||
{"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},"46607060-cefc-43a0-abaf-9d4887d46cb8":{"name":"Produce.BoxIdolOverviewIdols","displayName":"偶像总览 偶像列表区域","type":"hint-box","annotationId":"46607060-cefc-43a0-abaf-9d4887d46cb8","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}},{"id":"46607060-cefc-43a0-abaf-9d4887d46cb8","type":"rect","data":{"x1":26,"y1":568,"x2":696,"y2":992}}]}
|
|
@ -35,271 +35,6 @@ class APShopItems(IntEnum):
|
|||
REGENERATE_MEMORY = 3
|
||||
"""回忆再生成券"""
|
||||
|
||||
|
||||
倉本千奈_BASE = 0
|
||||
十王星南_BASE = 100
|
||||
姫崎莉波_BASE = 200
|
||||
月村手毬_BASE = 300
|
||||
有村麻央_BASE = 400
|
||||
篠泽广_BASE = 500
|
||||
紫云清夏_BASE = 600
|
||||
花海佑芽_BASE = 700
|
||||
花海咲季_BASE = 800
|
||||
葛城リーリヤ_BASE = 900
|
||||
藤田ことね_BASE = 1000
|
||||
|
||||
class PIdol(IntEnum):
|
||||
"""P偶像"""
|
||||
倉本千奈_Campusmode = 倉本千奈_BASE + 0
|
||||
倉本千奈_WonderScale = 倉本千奈_BASE + 1
|
||||
倉本千奈_ようこそ初星温泉 = 倉本千奈_BASE + 2
|
||||
倉本千奈_仮装狂騒曲 = 倉本千奈_BASE + 3
|
||||
倉本千奈_初心 = 倉本千奈_BASE + 4
|
||||
倉本千奈_学園生活 = 倉本千奈_BASE + 5
|
||||
倉本千奈_日々_発見的ステップ = 倉本千奈_BASE + 6
|
||||
倉本千奈_胸を張って一歩ずつ = 倉本千奈_BASE + 7
|
||||
|
||||
十王星南_Campusmode = 十王星南_BASE + 0
|
||||
十王星南_一番星 = 十王星南_BASE + 1
|
||||
十王星南_学園生活 = 十王星南_BASE + 2
|
||||
十王星南_小さな野望 = 十王星南_BASE + 3
|
||||
|
||||
姫崎莉波_clumsytrick = 姫崎莉波_BASE + 0
|
||||
姫崎莉波_私らしさのはじまり = 姫崎莉波_BASE + 1
|
||||
姫崎莉波_キミとセミブルー = 姫崎莉波_BASE + 2
|
||||
姫崎莉波_Campusmode = 姫崎莉波_BASE + 3
|
||||
姫崎莉波_LUV = 姫崎莉波_BASE + 4
|
||||
姫崎莉波_ようこそ初星温泉 = 姫崎莉波_BASE + 5
|
||||
姫崎莉波_ハッピーミルフィーユ = 姫崎莉波_BASE + 6
|
||||
姫崎莉波_初心 = 姫崎莉波_BASE + 7
|
||||
姫崎莉波_学園生活 = 姫崎莉波_BASE + 8
|
||||
|
||||
月村手毬_Lunasaymaybe = 月村手毬_BASE + 0
|
||||
月村手毬_一匹狼 = 月村手毬_BASE + 1
|
||||
月村手毬_Campusmode = 月村手毬_BASE + 2
|
||||
月村手毬_アイヴイ = 月村手毬_BASE + 3
|
||||
月村手毬_初声 = 月村手毬_BASE + 4
|
||||
月村手毬_学園生活 = 月村手毬_BASE + 5
|
||||
月村手毬_仮装狂騒曲 = 月村手毬_BASE + 6
|
||||
|
||||
有村麻央_Fluorite = 有村麻央_BASE + 0
|
||||
有村麻央_はじまりはカッコよく = 有村麻央_BASE + 1
|
||||
有村麻央_Campusmode = 有村麻央_BASE + 2
|
||||
有村麻央_FeelJewelDream = 有村麻央_BASE + 3
|
||||
有村麻央_キミとセミブルー = 有村麻央_BASE + 4
|
||||
有村麻央_初恋 = 有村麻央_BASE + 5
|
||||
有村麻央_学園生活 = 有村麻央_BASE + 6
|
||||
|
||||
篠泽广_コントラスト = 篠泽广_BASE + 0
|
||||
篠泽广_一番向いていないこと = 篠泽广_BASE + 1
|
||||
篠泽广_光景 = 篠泽广_BASE + 2
|
||||
篠泽广_Campusmode = 篠泽广_BASE + 3
|
||||
篠泽广_仮装狂騒曲 = 篠泽广_BASE + 4
|
||||
篠泽广_ハッピーミルフィーユ = 篠泽广_BASE + 5
|
||||
篠泽广_初恋 = 篠泽广_BASE + 6
|
||||
篠泽广_学園生活 = 篠泽广_BASE + 7
|
||||
|
||||
紫云清夏_TameLieOneStep = 紫云清夏_BASE + 0
|
||||
紫云清夏_カクシタワタシ = 紫云清夏_BASE + 1
|
||||
紫云清夏_夢へのリスタート = 紫云清夏_BASE + 2
|
||||
紫云清夏_Campusmode = 紫云清夏_BASE + 3
|
||||
紫云清夏_キミとセミブルー = 紫云清夏_BASE + 4
|
||||
紫云清夏_初恋 = 紫云清夏_BASE + 5
|
||||
紫云清夏_学園生活 = 紫云清夏_BASE + 6
|
||||
|
||||
花海佑芽_WhiteNightWhiteWish = 花海佑芽_BASE + 0
|
||||
花海佑芽_学園生活 = 花海佑芽_BASE + 1
|
||||
花海佑芽_Campusmode = 花海佑芽_BASE + 2
|
||||
花海佑芽_TheRollingRiceball = 花海佑芽_BASE + 3
|
||||
花海佑芽_アイドル_はじめっ = 花海佑芽_BASE + 4
|
||||
|
||||
花海咲季_BoomBoomPow = 花海咲季_BASE + 0
|
||||
花海咲季_Campusmode = 花海咲季_BASE + 1
|
||||
花海咲季_FightingMyWay = 花海咲季_BASE + 2
|
||||
花海咲季_わたしが一番 = 花海咲季_BASE + 3
|
||||
花海咲季_冠菊 = 花海咲季_BASE + 4
|
||||
花海咲季_初声 = 花海咲季_BASE + 5
|
||||
花海咲季_古今東西ちょちょいのちょい = 花海咲季_BASE + 6
|
||||
花海咲季_学園生活 = 花海咲季_BASE + 7
|
||||
|
||||
葛城リーリヤ_一つ踏み出した先に = 葛城リーリヤ_BASE + 0
|
||||
葛城リーリヤ_白線 = 葛城リーリヤ_BASE + 1
|
||||
葛城リーリヤ_Campusmode = 葛城リーリヤ_BASE + 2
|
||||
葛城リーリヤ_WhiteNightWhiteWish = 葛城リーリヤ_BASE + 3
|
||||
葛城リーリヤ_冠菊 = 葛城リーリヤ_BASE + 4
|
||||
葛城リーリヤ_初心 = 葛城リーリヤ_BASE + 5
|
||||
葛城リーリヤ_学園生活 = 葛城リーリヤ_BASE + 6
|
||||
|
||||
藤田ことね_カワイイ_はじめました = 藤田ことね_BASE + 0
|
||||
藤田ことね_世界一可愛い私 = 藤田ことね_BASE + 1
|
||||
藤田ことね_Campusmode = 藤田ことね_BASE + 2
|
||||
藤田ことね_YellowBigBang = 藤田ことね_BASE + 3
|
||||
藤田ことね_WhiteNightWhiteWish = 藤田ことね_BASE + 4
|
||||
藤田ことね_冠菊 = 藤田ことね_BASE + 5
|
||||
藤田ことね_初声 = 藤田ことね_BASE + 6
|
||||
藤田ことね_学園生活 = 藤田ことね_BASE + 7
|
||||
|
||||
def to_title(self) -> list[str]:
|
||||
match self:
|
||||
case PIdol.倉本千奈_Campusmode:
|
||||
return ["倉本", "千奈", "Campus", "mode"]
|
||||
case PIdol.倉本千奈_WonderScale:
|
||||
return ["倉本", "千奈", "Wonder", "Scale"]
|
||||
case PIdol.倉本千奈_ようこそ初星温泉:
|
||||
return ["倉本", "千奈", "ようこそ初星温泉"]
|
||||
case PIdol.倉本千奈_仮装狂騒曲:
|
||||
return ["倉本", "千奈", "仮装狂騒曲"]
|
||||
case PIdol.倉本千奈_初心:
|
||||
return ["倉本", "千奈", "初心"]
|
||||
case PIdol.倉本千奈_学園生活:
|
||||
return ["倉本", "千奈", "学園生活"]
|
||||
case PIdol.倉本千奈_日々_発見的ステップ:
|
||||
return ["倉本", "千奈", "日々、発見的ステップ"]
|
||||
case PIdol.倉本千奈_胸を張って一歩ずつ:
|
||||
return ["倉本", "千奈", "胸を張って一歩ずつ"]
|
||||
case PIdol.十王星南_Campusmode:
|
||||
return ["十王", "星南", "Campus", "mode"]
|
||||
case PIdol.十王星南_一番星:
|
||||
return ["十王", "星南", "一番星"]
|
||||
case PIdol.十王星南_学園生活:
|
||||
return ["十王", "星南", "学園生活"]
|
||||
case PIdol.十王星南_小さな野望:
|
||||
return ["十王", "星南", "小さな野望"]
|
||||
case PIdol.姫崎莉波_clumsytrick:
|
||||
return ["姫崎", "莉波", "clumsy", "trick"]
|
||||
case PIdol.姫崎莉波_私らしさのはじまり:
|
||||
return ["姫崎", "莉波", "『私らしさ』のはじまり"]
|
||||
case PIdol.姫崎莉波_キミとセミブルー:
|
||||
return ["姫崎", "莉波", "キミとセミブルー"]
|
||||
case PIdol.姫崎莉波_Campusmode:
|
||||
return ["姫崎", "莉波", "Campus", "mode"]
|
||||
case PIdol.姫崎莉波_LUV:
|
||||
return ["姫崎", "莉波", "L", "U", "V"]
|
||||
case PIdol.姫崎莉波_ようこそ初星温泉:
|
||||
return ["姫崎", "莉波", "ようこそ初星温泉"]
|
||||
case PIdol.姫崎莉波_ハッピーミルフィーユ:
|
||||
return ["姫崎", "莉波", "ハッピーミルフィーユ"]
|
||||
case PIdol.姫崎莉波_初心:
|
||||
return ["姫崎", "莉波", "初心"]
|
||||
case PIdol.姫崎莉波_学園生活:
|
||||
return ["姫崎", "莉波", "学園生活"]
|
||||
case PIdol.月村手毬_Lunasaymaybe:
|
||||
return ["月村", "手毬", "Luna", "say", "maybe"]
|
||||
case PIdol.月村手毬_一匹狼:
|
||||
return ["月村", "手毬", "一匹狼"]
|
||||
case PIdol.月村手毬_Campusmode:
|
||||
return ["月村", "手毬", "Campus", "mode"]
|
||||
case PIdol.月村手毬_アイヴイ:
|
||||
return ["月村", "手毬", "アイヴイ"]
|
||||
case PIdol.月村手毬_初声:
|
||||
return ["月村", "手毬", "初声"]
|
||||
case PIdol.月村手毬_学園生活:
|
||||
return ["月村", "手毬", "学園生活"]
|
||||
case PIdol.月村手毬_仮装狂騒曲:
|
||||
return ["月村", "手毬", "仮装狂騒曲"]
|
||||
case PIdol.有村麻央_Fluorite:
|
||||
return ["有村", "麻央", "Fluorite"]
|
||||
case PIdol.有村麻央_はじまりはカッコよく:
|
||||
return ["有村", "麻央", "はじまりはカッコよく"]
|
||||
case PIdol.有村麻央_Campusmode:
|
||||
return ["有村", "麻央", "Campus", "mode"]
|
||||
case PIdol.有村麻央_FeelJewelDream:
|
||||
return ["有村", "麻央", "Feel", "Jewel", "Dream"]
|
||||
case PIdol.有村麻央_キミとセミブルー:
|
||||
return ["有村", "麻央", "キミとセミブルー"]
|
||||
case PIdol.有村麻央_初恋:
|
||||
return ["有村", "麻央", "初恋"]
|
||||
case PIdol.有村麻央_学園生活:
|
||||
return ["有村", "麻央", "学園生活"]
|
||||
case PIdol.篠泽广_コントラスト:
|
||||
return ["篠泽", "広", "コントラスト"]
|
||||
case PIdol.篠泽广_一番向いていないこと:
|
||||
return ["篠泽", "広", "一番向いていないこと"]
|
||||
case PIdol.篠泽广_光景:
|
||||
return ["篠泽", "広", "光景"]
|
||||
case PIdol.篠泽广_Campusmode:
|
||||
return ["篠泽", "広", "Campus", "mode"]
|
||||
case PIdol.篠泽广_仮装狂騒曲:
|
||||
return ["篠泽", "広", "仮装狂騒曲"]
|
||||
case PIdol.篠泽广_ハッピーミルフィーユ:
|
||||
return ["篠泽", "広", "ハッピーミルフィーユ"]
|
||||
case PIdol.篠泽广_初恋:
|
||||
return ["篠泽", "広", "初恋"]
|
||||
case PIdol.篠泽广_学園生活:
|
||||
return ["篠泽", "広", "学園生活"]
|
||||
case PIdol.紫云清夏_TameLieOneStep:
|
||||
return ["紫云", "清夏", "Tame", "Lie", "One", "Step"]
|
||||
case PIdol.紫云清夏_カクシタワタシ:
|
||||
return ["紫云", "清夏", "カクシタワタシ"]
|
||||
case PIdol.紫云清夏_夢へのリスタート:
|
||||
return ["紫云", "清夏", "夢へのリスタート"]
|
||||
case PIdol.紫云清夏_Campusmode:
|
||||
return ["紫云", "清夏", "Campus", "mode"]
|
||||
case PIdol.紫云清夏_キミとセミブルー:
|
||||
return ["紫云", "清夏", "キミとセミブルー"]
|
||||
case PIdol.紫云清夏_初恋:
|
||||
return ["紫云", "清夏", "初恋"]
|
||||
case PIdol.紫云清夏_学園生活:
|
||||
return ["紫云", "清夏", "学園生活"]
|
||||
case PIdol.花海佑芽_WhiteNightWhiteWish:
|
||||
return ["花海", "佑芽", "White", "Night", "Wish"]
|
||||
case PIdol.花海佑芽_学園生活:
|
||||
return ["花海", "佑芽", "学園生活"]
|
||||
case PIdol.花海佑芽_Campusmode:
|
||||
return ["花海", "佑芽", "Campus", "mode"]
|
||||
case PIdol.花海佑芽_TheRollingRiceball:
|
||||
return ["花海", "佑芽", "The", "Rolling", "Riceball"]
|
||||
case PIdol.花海佑芽_アイドル_はじめっ:
|
||||
return ["花海", "佑芽", "アイドル、はじめっ"]
|
||||
case PIdol.花海咲季_BoomBoomPow:
|
||||
return ["花海", "咲季", "Boom", "Boom", "Pow"]
|
||||
case PIdol.花海咲季_Campusmode:
|
||||
return ["花海", "咲季", "Campus", "mode"]
|
||||
case PIdol.花海咲季_FightingMyWay:
|
||||
return ["花海", "咲季", "Fighting", "My", "Way"]
|
||||
case PIdol.花海咲季_わたしが一番:
|
||||
return ["花海", "咲季", "わたしが一番"]
|
||||
case PIdol.花海咲季_冠菊:
|
||||
return ["花海", "咲季", "冠菊"]
|
||||
case PIdol.花海咲季_初声:
|
||||
return ["花海", "咲季", "初声"]
|
||||
case PIdol.花海咲季_古今東西ちょちょいのちょい:
|
||||
return ["花海", "咲季", "古今東西ちょちょいのちょい"]
|
||||
case PIdol.花海咲季_学園生活:
|
||||
return ["花海", "咲季", "学園生活"]
|
||||
case PIdol.葛城リーリヤ_一つ踏み出した先に:
|
||||
return ["葛城", "リーリヤ", "一つ踏み出した先に"]
|
||||
case PIdol.葛城リーリヤ_白線:
|
||||
return ["葛城", "リーリヤ", "白線"]
|
||||
case PIdol.葛城リーリヤ_Campusmode:
|
||||
return ["葛城", "リーリヤ", "Campus", "mode"]
|
||||
case PIdol.葛城リーリヤ_WhiteNightWhiteWish:
|
||||
return ["葛城", "リーリヤ", "White", "Night", "Wish"]
|
||||
case PIdol.葛城リーリヤ_冠菊:
|
||||
return ["葛城", "リーリヤ", "冠菊"]
|
||||
case PIdol.葛城リーリヤ_初心:
|
||||
return ["葛城", "リーリヤ", "初心"]
|
||||
case PIdol.葛城リーリヤ_学園生活:
|
||||
return ["葛城", "リーリヤ", "学園生活"]
|
||||
case PIdol.藤田ことね_カワイイ_はじめました:
|
||||
return ["藤田", "ことね", "カワイイ", "はじめました"]
|
||||
case PIdol.藤田ことね_世界一可愛い私:
|
||||
return ["藤田", "ことね", "世界一可愛い私"]
|
||||
case PIdol.藤田ことね_Campusmode:
|
||||
return ["藤田", "ことね", "Campus", "mode"]
|
||||
case PIdol.藤田ことね_YellowBigBang:
|
||||
return ["藤田", "ことね", "Yellow", "Big", "Bang"]
|
||||
case PIdol.藤田ことね_WhiteNightWhiteWish:
|
||||
return ["藤田", "ことね", "White", "Night", "Wish"]
|
||||
case PIdol.藤田ことね_冠菊:
|
||||
return ["藤田", "ことね", "冠菊"]
|
||||
case PIdol.藤田ことね_初声:
|
||||
return ["藤田", "ことね", "初声"]
|
||||
case PIdol.藤田ことね_学園生活:
|
||||
return ["藤田", "ことね", "学園生活"]
|
||||
case _:
|
||||
assert_never(self)
|
||||
|
||||
class DailyMoneyShopItems(IntEnum):
|
||||
"""日常商店物品"""
|
||||
Recommendations = -1
|
||||
|
@ -582,9 +317,9 @@ class ProduceConfig(ConfigBaseModel):
|
|||
"""
|
||||
produce_count: int = 1
|
||||
"""培育的次数。"""
|
||||
idols: list[PIdol] = []
|
||||
idols: list[str] = []
|
||||
"""
|
||||
要培育的偶像。将会按顺序循环选择培育。
|
||||
要培育偶像的 IdolCardSkin.id。将会按顺序循环选择培育。
|
||||
若未选择任何偶像,则使用游戏默认选择的偶像(为上次培育偶像)。
|
||||
"""
|
||||
memory_sets: list[int] = []
|
||||
|
@ -760,7 +495,114 @@ def upgrade_config() -> str | None:
|
|||
with open('config.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(root, f, ensure_ascii=False, indent=4)
|
||||
return '\n'.join(messages)
|
||||
|
||||
|
||||
倉本千奈_BASE = 0
|
||||
十王星南_BASE = 100
|
||||
姫崎莉波_BASE = 200
|
||||
月村手毬_BASE = 300
|
||||
有村麻央_BASE = 400
|
||||
篠泽广_BASE = 500
|
||||
紫云清夏_BASE = 600
|
||||
花海佑芽_BASE = 700
|
||||
花海咲季_BASE = 800
|
||||
葛城リーリヤ_BASE = 900
|
||||
藤田ことね_BASE = 1000
|
||||
|
||||
class PIdol(IntEnum):
|
||||
"""
|
||||
P偶像。已废弃,仅为 upgrade_v1_to_v2() 使用而保留。
|
||||
"""
|
||||
倉本千奈_Campusmode = 倉本千奈_BASE + 0
|
||||
倉本千奈_WonderScale = 倉本千奈_BASE + 1
|
||||
倉本千奈_ようこそ初星温泉 = 倉本千奈_BASE + 2
|
||||
倉本千奈_仮装狂騒曲 = 倉本千奈_BASE + 3
|
||||
倉本千奈_初心 = 倉本千奈_BASE + 4
|
||||
倉本千奈_学園生活 = 倉本千奈_BASE + 5
|
||||
倉本千奈_日々_発見的ステップ = 倉本千奈_BASE + 6
|
||||
倉本千奈_胸を張って一歩ずつ = 倉本千奈_BASE + 7
|
||||
|
||||
十王星南_Campusmode = 十王星南_BASE + 0
|
||||
十王星南_一番星 = 十王星南_BASE + 1
|
||||
十王星南_学園生活 = 十王星南_BASE + 2
|
||||
十王星南_小さな野望 = 十王星南_BASE + 3
|
||||
|
||||
姫崎莉波_clumsytrick = 姫崎莉波_BASE + 0
|
||||
姫崎莉波_私らしさのはじまり = 姫崎莉波_BASE + 1
|
||||
姫崎莉波_キミとセミブルー = 姫崎莉波_BASE + 2
|
||||
姫崎莉波_Campusmode = 姫崎莉波_BASE + 3
|
||||
姫崎莉波_LUV = 姫崎莉波_BASE + 4
|
||||
姫崎莉波_ようこそ初星温泉 = 姫崎莉波_BASE + 5
|
||||
姫崎莉波_ハッピーミルフィーユ = 姫崎莉波_BASE + 6
|
||||
姫崎莉波_初心 = 姫崎莉波_BASE + 7
|
||||
姫崎莉波_学園生活 = 姫崎莉波_BASE + 8
|
||||
|
||||
月村手毬_Lunasaymaybe = 月村手毬_BASE + 0
|
||||
月村手毬_一匹狼 = 月村手毬_BASE + 1
|
||||
月村手毬_Campusmode = 月村手毬_BASE + 2
|
||||
月村手毬_アイヴイ = 月村手毬_BASE + 3
|
||||
月村手毬_初声 = 月村手毬_BASE + 4
|
||||
月村手毬_学園生活 = 月村手毬_BASE + 5
|
||||
月村手毬_仮装狂騒曲 = 月村手毬_BASE + 6
|
||||
|
||||
有村麻央_Fluorite = 有村麻央_BASE + 0
|
||||
有村麻央_はじまりはカッコよく = 有村麻央_BASE + 1
|
||||
有村麻央_Campusmode = 有村麻央_BASE + 2
|
||||
有村麻央_FeelJewelDream = 有村麻央_BASE + 3
|
||||
有村麻央_キミとセミブルー = 有村麻央_BASE + 4
|
||||
有村麻央_初恋 = 有村麻央_BASE + 5
|
||||
有村麻央_学園生活 = 有村麻央_BASE + 6
|
||||
|
||||
篠泽广_コントラスト = 篠泽广_BASE + 0
|
||||
篠泽广_一番向いていないこと = 篠泽广_BASE + 1
|
||||
篠泽广_光景 = 篠泽广_BASE + 2
|
||||
篠泽广_Campusmode = 篠泽广_BASE + 3
|
||||
篠泽广_仮装狂騒曲 = 篠泽广_BASE + 4
|
||||
篠泽广_ハッピーミルフィーユ = 篠泽广_BASE + 5
|
||||
篠泽广_初恋 = 篠泽广_BASE + 6
|
||||
篠泽广_学園生活 = 篠泽广_BASE + 7
|
||||
|
||||
紫云清夏_TameLieOneStep = 紫云清夏_BASE + 0
|
||||
紫云清夏_カクシタワタシ = 紫云清夏_BASE + 1
|
||||
紫云清夏_夢へのリスタート = 紫云清夏_BASE + 2
|
||||
紫云清夏_Campusmode = 紫云清夏_BASE + 3
|
||||
紫云清夏_キミとセミブルー = 紫云清夏_BASE + 4
|
||||
紫云清夏_初恋 = 紫云清夏_BASE + 5
|
||||
紫云清夏_学園生活 = 紫云清夏_BASE + 6
|
||||
|
||||
花海佑芽_WhiteNightWhiteWish = 花海佑芽_BASE + 0
|
||||
花海佑芽_学園生活 = 花海佑芽_BASE + 1
|
||||
花海佑芽_Campusmode = 花海佑芽_BASE + 2
|
||||
花海佑芽_TheRollingRiceball = 花海佑芽_BASE + 3
|
||||
花海佑芽_アイドル_はじめっ = 花海佑芽_BASE + 4
|
||||
|
||||
花海咲季_BoomBoomPow = 花海咲季_BASE + 0
|
||||
花海咲季_Campusmode = 花海咲季_BASE + 1
|
||||
花海咲季_FightingMyWay = 花海咲季_BASE + 2
|
||||
花海咲季_わたしが一番 = 花海咲季_BASE + 3
|
||||
花海咲季_冠菊 = 花海咲季_BASE + 4
|
||||
花海咲季_初声 = 花海咲季_BASE + 5
|
||||
花海咲季_古今東西ちょちょいのちょい = 花海咲季_BASE + 6
|
||||
花海咲季_学園生活 = 花海咲季_BASE + 7
|
||||
|
||||
葛城リーリヤ_一つ踏み出した先に = 葛城リーリヤ_BASE + 0
|
||||
葛城リーリヤ_白線 = 葛城リーリヤ_BASE + 1
|
||||
葛城リーリヤ_Campusmode = 葛城リーリヤ_BASE + 2
|
||||
葛城リーリヤ_WhiteNightWhiteWish = 葛城リーリヤ_BASE + 3
|
||||
葛城リーリヤ_冠菊 = 葛城リーリヤ_BASE + 4
|
||||
葛城リーリヤ_初心 = 葛城リーリヤ_BASE + 5
|
||||
葛城リーリヤ_学園生活 = 葛城リーリヤ_BASE + 6
|
||||
|
||||
藤田ことね_カワイイ_はじめました = 藤田ことね_BASE + 0
|
||||
藤田ことね_世界一可愛い私 = 藤田ことね_BASE + 1
|
||||
藤田ことね_Campusmode = 藤田ことね_BASE + 2
|
||||
藤田ことね_YellowBigBang = 藤田ことね_BASE + 3
|
||||
藤田ことね_WhiteNightWhiteWish = 藤田ことね_BASE + 4
|
||||
藤田ことね_冠菊 = 藤田ことね_BASE + 5
|
||||
藤田ことね_初声 = 藤田ことね_BASE + 6
|
||||
藤田ことね_学園生活 = 藤田ことね_BASE + 7
|
||||
|
||||
|
||||
def upgrade_v1_to_v2(options: dict[str, Any]) -> tuple[dict[str, Any], str | None]:
|
||||
"""
|
||||
v1 -> v2 变更:
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
from .idol_card import IdolCard
|
||||
from .constants import CharacterId
|
|
@ -0,0 +1,14 @@
|
|||
from enum import Enum
|
||||
|
||||
class CharacterId(Enum):
|
||||
hski = "hski" # Hanami Saki, 花海咲季
|
||||
ttmr = "ttmr" # Tsukimura Temari, 月村手毬
|
||||
fktn = "fktn" # Fujita Kotone, 藤田ことね
|
||||
amao = "amao" # Arimura Mao, 有村麻央
|
||||
kllj = "kllj" # Katsuragi Lilja, 葛城リーリヤ
|
||||
kcna = "kcna" # Kuramoto China, 倉本千奈
|
||||
ssmk = "ssmk" # Shiun Sumika, 紫云清夏
|
||||
shro = "shro" # Shinosawa Hiro, 篠澤廣
|
||||
hrnm = "hrnm" # Himesaki Rinami, 姫崎莉波
|
||||
hume = "hume" # Hanami Ume, 花海佑芽
|
||||
jsna = "jsna" # Juo Sena, 十王星南
|
|
@ -0,0 +1,60 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from .sqlite import select, select_many
|
||||
from .constants import CharacterId
|
||||
|
||||
@dataclass
|
||||
class IdolCard:
|
||||
"""偶像卡"""
|
||||
id: str
|
||||
skin_id: str
|
||||
is_another: bool
|
||||
another_name: str | None
|
||||
name: str
|
||||
|
||||
@classmethod
|
||||
def from_skin_id(cls, sid: str) -> 'IdolCard | None':
|
||||
"""
|
||||
根据 skin_id 查询 IdolCard。
|
||||
"""
|
||||
row = select("""
|
||||
SELECT
|
||||
IC.id AS cardId,
|
||||
ICS.id AS skinId,
|
||||
Char.lastName || ' ' || Char.firstName || ' ' || IC.name AS name,
|
||||
NOT (IC.originalIdolCardSkinId = ICS.id) AS isAnotherVer,
|
||||
ICS.name AS anotherVerName
|
||||
FROM IdolCard IC
|
||||
JOIN Character Char ON characterId = Char.id
|
||||
JOIN IdolCardSkin ICS ON IC.id = ICS.idolCardId
|
||||
WHERE ICS.id = ?;
|
||||
""", sid)
|
||||
if row is None:
|
||||
return None
|
||||
card_id, skin_id, name, is_another, another_name = row
|
||||
return cls(card_id, skin_id, is_another, another_name, name)
|
||||
|
||||
@classmethod
|
||||
def all(cls) -> list['IdolCard']:
|
||||
"""获取所有偶像卡"""
|
||||
rows = select_many("""
|
||||
SELECT
|
||||
IC.id AS cardId,
|
||||
ICS.id AS skinId,
|
||||
Char.lastName || ' ' || Char.firstName || ' ' || IC.name AS name,
|
||||
NOT (IC.originalIdolCardSkinId = ICS.id) AS isAnotherVer,
|
||||
ICS.name AS anotherVerName
|
||||
FROM IdolCard IC
|
||||
JOIN Character Char ON characterId = Char.id
|
||||
JOIN IdolCardSkin ICS ON IC.id = ICS.idolCardId;
|
||||
""")
|
||||
results = []
|
||||
for row in rows:
|
||||
card_id, skin_id, name, is_another, another_name = row
|
||||
results.append(cls(card_id, skin_id, is_another, another_name, name))
|
||||
return results
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pprint import pprint as print
|
||||
print(IdolCard.from_skin_id('i_card-skin-fktn-3-006'))
|
||||
print(IdolCard.all())
|
|
@ -0,0 +1,25 @@
|
|||
import os
|
||||
import sqlite3
|
||||
from typing import Any, cast
|
||||
|
||||
from kotonebot.tasks import resources as res
|
||||
|
||||
_db: sqlite3.Connection | None = None
|
||||
_db_path = cast(str, res.__path__)[0] + '/game.db'
|
||||
|
||||
def select_many(query: str, *args) -> list[Any]:
|
||||
global _db
|
||||
if not _db:
|
||||
_db = sqlite3.connect(_db_path)
|
||||
c = _db.cursor()
|
||||
c.execute(query, args)
|
||||
return c.fetchall()
|
||||
|
||||
|
||||
def select(query: str, *args) -> Any:
|
||||
global _db
|
||||
if not _db:
|
||||
_db = sqlite3.connect(_db_path)
|
||||
c = _db.cursor()
|
||||
c.execute(query, args)
|
||||
return c.fetchone()
|
|
@ -1,3 +1,4 @@
|
|||
from .toolbar import toolbar_home, toolbar_menu
|
||||
from .commu_event_buttons import CommuEventButtonUI
|
||||
from .common import WhiteFilter
|
||||
from .scrollable import Scrollable, ScrollableIterator
|
|
@ -0,0 +1,166 @@
|
|||
import os
|
||||
import logging
|
||||
from typing import cast
|
||||
from importlib import resources
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from cv2.typing import MatLike
|
||||
|
||||
from kotonebot.tasks import R
|
||||
from kotonebot.tasks.util import paths
|
||||
from kotonebot.util import Rect, cv2_imread
|
||||
from kotonebot.tasks.game_ui import Scrollable
|
||||
from kotonebot.backend.debug import result, img
|
||||
from kotonebot import device, color, action, sleep, contains
|
||||
from kotonebot.tasks.image_db import ImageDatabase, HistDescriptor, FileDataSource
|
||||
from kotonebot.backend.preprocessor import HsvColorRemover, HsvColorsRemover
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_db: ImageDatabase | None = None
|
||||
|
||||
# OpenCV HSV 颜色范围
|
||||
RED_DOT = ((157, 205, 255), (179, 255, 255)) # 红点
|
||||
ORANGE_SELECT_BORDER = ((9, 50, 106), (19, 255, 255)) # 当前选中的偶像的橙色边框
|
||||
WHITE_BACKGROUND = ((0, 0, 234), (179, 40, 255)) # 白色背景
|
||||
|
||||
def extract_idols(img: MatLike) -> list[Rect]:
|
||||
"""
|
||||
寻找给定图像中的所有偶像。
|
||||
|
||||
:img: 输入图像,格式为 BGR 720x1280。
|
||||
:return: 所有偶像的矩形区域 `(x, y, w, h)`,如果未找到则返回空列表。
|
||||
"""
|
||||
# 移除不需要的颜色
|
||||
remover = HsvColorsRemover([RED_DOT, ORANGE_SELECT_BORDER, WHITE_BACKGROUND])
|
||||
img = remover.process(img)
|
||||
# 灰度、查找轮廓
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
contours, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
# 筛选面积、比例约为 140x190 的轮廓
|
||||
rects = []
|
||||
target_ratio = 140 / 190 # 目标宽高比
|
||||
target_area = 140 * 190 # 目标面积
|
||||
ratio_tolerance = 0.1 # 允许的误差范围
|
||||
for contour in contours:
|
||||
x, y, w, h = cv2.boundingRect(contour)
|
||||
if h == 0:
|
||||
continue
|
||||
ratio = w / h
|
||||
if abs(ratio - target_ratio) <= ratio_tolerance and w * h >= target_area:
|
||||
rects.append((x, y, w, h))
|
||||
return rects
|
||||
|
||||
def display_rects(img: MatLike, rects: list[Rect]) -> MatLike:
|
||||
"""Draw rectangles on the image and display them."""
|
||||
result = img.copy()
|
||||
for rect in rects:
|
||||
x, y, w, h = rect
|
||||
# Draw rectangle with green color and 2px thickness
|
||||
cv2.rectangle(result, (x, y), (x + w, y + h), (0, 255, 0), 2)
|
||||
# Optionally add text label
|
||||
cv2.putText(result, f"{w}x{h}", (x, y - 5),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
|
||||
return result
|
||||
|
||||
def draw_idol_preview(img: MatLike, rects: list[Rect], db: ImageDatabase, idol_path: str) -> MatLike:
|
||||
"""
|
||||
在预览图上绘制所有匹配到的偶像。
|
||||
|
||||
:param img: 原始图像
|
||||
:param rects: 检测到的偶像矩形区域列表
|
||||
:param db: 偶像图像数据库
|
||||
:param idol_path: 偶像图像文件路径
|
||||
:return: 带有匹配偶像的预览图
|
||||
"""
|
||||
# 创建一个与原图大小相同的白色背景图片
|
||||
preview_img = np.ones_like(img) * 255
|
||||
|
||||
# 在预览图上绘制所有匹配到的偶像
|
||||
for rect in rects:
|
||||
x, y, w, h = rect
|
||||
idol_img = img[y:y+h, x:x+w]
|
||||
match = db.match(idol_img, 20)
|
||||
if not match:
|
||||
continue
|
||||
file = os.path.join(idol_path, match.key)
|
||||
found_img = cv2_imread(file)
|
||||
|
||||
# 将找到的偶像图片缩放至与检测到的矩形大小相同
|
||||
resized_found_img = cv2.resize(found_img, (w, h))
|
||||
|
||||
# 将缩放后的图片放到预览图上对应位置
|
||||
preview_img[y:y+h, x:x+w] = resized_found_img
|
||||
|
||||
# 在预览图上绘制矩形框
|
||||
cv2.rectangle(preview_img, (x, y), (x + w, y + h), (0, 255, 0), 2)
|
||||
|
||||
# 可选:添加偶像ID标签
|
||||
cv2.putText(preview_img, match.key.split('.')[0], (x, y - 5),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
|
||||
return preview_img
|
||||
|
||||
def idols_db() -> ImageDatabase:
|
||||
global _db
|
||||
if _db is None:
|
||||
logger.info('Loading idols database...')
|
||||
path = paths.resource('idol_cards')
|
||||
db_path = paths.cache('idols.pkl')
|
||||
_db = ImageDatabase(FileDataSource(str(path)), db_path, HistDescriptor(8), name='idols')
|
||||
return _db
|
||||
|
||||
@action('定位偶像', screenshot_mode='manual-inherit')
|
||||
def locate_idol(skin_id: str):
|
||||
device.screenshot()
|
||||
logger.info('Locating idol %s', skin_id)
|
||||
x, y, w, h = R.Produce.BoxIdolOverviewIdols
|
||||
db = idols_db()
|
||||
sc = Scrollable(color_schema='light')
|
||||
|
||||
sc.update()
|
||||
logger.debug('Idol preview pages count: %s', repr(sc.page_count))
|
||||
pc = sc.page_count
|
||||
assert pc is not None
|
||||
# 1280x720 分辨率下,一行 4 个,一页共 12 个。
|
||||
# 一次只翻 0.8 行。
|
||||
for _ in sc(4 / (pc * 12) * 0.8):
|
||||
img = device.screenshot()
|
||||
# 只保留 BoxIdolOverviewIdols 区域
|
||||
mask = np.zeros_like(img)
|
||||
mask[y:y+h, x:x+w] = img[y:y+h, x:x+w]
|
||||
img = mask
|
||||
# 检测 & 查询
|
||||
rects = extract_idols(img)
|
||||
# cv2.imshow('Detected Idols', cv2.resize(display_rects(img, rects), (0, 0), fx=0.5, fy=0.5))
|
||||
# cv2.imshow('Idols Preview', cv2.resize(draw_idol_preview(img, rects, db, paths.resource('idol_cards')), (0, 0), fx=0.5, fy=0.5))
|
||||
# cv2.waitKey(0)
|
||||
for rect in rects:
|
||||
rx, ry, rw, rh = rect
|
||||
idol_img = img[ry:ry+rh, rx:rx+rw]
|
||||
match = db.match(idol_img, 20)
|
||||
logger.debug('Result rect: %s, match: %s', repr(rect), repr(match))
|
||||
# Key 格式:{skin_id}_{index}
|
||||
# 同一张卡升级前后图片不一样,index 分别为 0 和 1
|
||||
if match and match.key.startswith(skin_id):
|
||||
logger.info('Found idol %s', skin_id)
|
||||
return rx, ry, rw, rh
|
||||
return None
|
||||
# cv2.imshow('Detected Idols', cv2.resize(display_rects(img, rects), (0, 0), fx=0.5, fy=0.5))
|
||||
|
||||
# # 使用新函数绘制预览图
|
||||
# preview_img = draw_idol_preview(img, rects, db, path)
|
||||
|
||||
|
||||
def test():
|
||||
from kotonebot.backend.context import init_context, manual_context, device
|
||||
init_context()
|
||||
manual_context().begin()
|
||||
locate_idol('i_card-skin-fktn-3-006')
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pprint import pprint as print
|
||||
|
||||
from kotonebot.util import cv2_imread
|
||||
from kotonebot.backend.preprocessor import HsvColorFilter
|
||||
|
||||
test()
|
|
@ -1,4 +1,4 @@
|
|||
from .db import ImageDatabase, Db, DatabaseQueryResult
|
||||
from .db import ImageDatabase, Db, DatabaseQueryResult, FileDataSource, DataSource
|
||||
from .descriptors import HistDescriptor
|
||||
|
||||
__all__ = ['ImageDatabase', 'Db', 'DatabaseQueryResult', 'HistDescriptor']
|
||||
__all__ = ['ImageDatabase', 'Db', 'DatabaseQueryResult', 'HistDescriptor', 'FileDataSource', 'DataSource']
|
||||
|
|
|
@ -52,6 +52,9 @@ class DatabaseQueryResult(NamedTuple):
|
|||
feature: Any
|
||||
distance: float
|
||||
|
||||
def __repr__(self):
|
||||
return f'DatabaseQueryResult(key={self.key}, distance={self.distance})'
|
||||
|
||||
def chi2_distance(hist1: np.ndarray, hist2: np.ndarray, eps=1e-10):
|
||||
return 0.5 * np.sum((hist1 - hist2) ** 2 / (hist1 + hist2 + eps))
|
||||
|
||||
|
@ -163,7 +166,7 @@ class ImageDatabase:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from kotonebot.tasks.db.image_db.db import Db
|
||||
from kotonebot.tasks.image_db.db import Db
|
||||
logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s] [%(lineno)d] %(message)s')
|
||||
imgs_path = r'E:\GithubRepos\KotonesAutoAssistant.worktrees\dev\kotonebot\tasks\resources\idol_cards'
|
||||
needle_path = r'D:\05.png'
|
||||
|
|
|
@ -11,6 +11,7 @@ from kotonebot.backend.dispatch import SimpleDispatcher
|
|||
from .. import R
|
||||
from ..common import conf, PIdol
|
||||
from ..actions.scenes import at_home, goto_home
|
||||
from ..game_ui.idols_overview import locate_idol
|
||||
from ..produce.in_purodyuusu import hajime_pro, hajime_regular, resume_regular_produce
|
||||
from kotonebot import device, image, ocr, task, action, sleep, contains
|
||||
|
||||
|
@ -37,69 +38,33 @@ def unify(arr: list[int]):
|
|||
return result
|
||||
|
||||
@action('选择P偶像', screenshot_mode='manual-inherit')
|
||||
def select_idol(target_titles: list[str] | PIdol):
|
||||
def select_idol(skin_id: str):
|
||||
"""
|
||||
选择目标P偶像
|
||||
|
||||
前置条件:培育-偶像选择页面 1.アイドル選択\n
|
||||
结束状态:培育-偶像选择页面 1.アイドル選択\n
|
||||
|
||||
:param target_titles: 目标偶像的名称关键字。选择时只会选择所有关键字都出现的偶像。
|
||||
前置条件:偶像选择页面 1.アイドル選択\n
|
||||
结束状态:偶像选择页面 1.アイドル選択\n
|
||||
"""
|
||||
# 前置条件:[res/sprites/jp/produce/produce_preparation1.png]
|
||||
# 结束状态:[res/sprites/jp/produce/produce_preparation1.png]
|
||||
|
||||
logger.info(f"Find and select idol: {target_titles}")
|
||||
logger.info("Find and select idol: %s", skin_id)
|
||||
# 进入总览
|
||||
device.screenshot()
|
||||
device.click(image.expect(R.Produce.ButtonPIdolOverview))
|
||||
it = Interval()
|
||||
while not image.find(R.Common.ButtonConfirmNoIcon):
|
||||
if image.find(R.Produce.ButtonPIdolOverview):
|
||||
device.click()
|
||||
device.screenshot()
|
||||
|
||||
if isinstance(target_titles, PIdol):
|
||||
target_titles = target_titles.to_title()
|
||||
_target_titles = [contains(t, ignore_case=True) for t in target_titles]
|
||||
device.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
|
||||
sc = Scrollable()
|
||||
# 找到目标偶像
|
||||
while not found:
|
||||
# 首先检查当前选中的是不是已经是目标
|
||||
if all(ocr.find_all(_target_titles, rect=R.Produce.KbIdolOverviewName)):
|
||||
found = True
|
||||
break
|
||||
# 如果不是,就挨个选中,判断名称
|
||||
for r in results:
|
||||
device.click(r)
|
||||
sleep(0.3)
|
||||
device.screenshot(force=True)
|
||||
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)
|
||||
sc.next(page=0.8)
|
||||
sleep(0.4)
|
||||
device.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
|
||||
it.wait()
|
||||
# 选择偶像
|
||||
pos = locate_idol(skin_id)
|
||||
if pos is None:
|
||||
raise ValueError(f"Idol {skin_id} not found.")
|
||||
# 确认
|
||||
it.reset()
|
||||
while btn_confirm := image.find(R.Common.ButtonConfirmNoIcon):
|
||||
device.click(pos)
|
||||
sleep(0.3)
|
||||
device.click(btn_confirm)
|
||||
it.wait()
|
||||
|
||||
@action('培育开始.编成翻页', screenshot_mode='manual-inherit')
|
||||
def select_set(index: int):
|
||||
|
@ -172,7 +137,7 @@ def resume_produce():
|
|||
|
||||
@action('执行培育', screenshot_mode='manual-inherit')
|
||||
def do_produce(
|
||||
idol: PIdol,
|
||||
idol_skin_id: str,
|
||||
mode: Literal['regular', 'pro'],
|
||||
memory_set_index: Optional[int] = None
|
||||
) -> bool:
|
||||
|
@ -209,7 +174,7 @@ def do_produce(
|
|||
device.click(image.expect_wait(R.InPurodyuusu.ButtonCancel))
|
||||
return False
|
||||
# 1. 选择 PIdol [screenshots/produce/select_p_idol.png]
|
||||
select_idol(idol.to_title())
|
||||
select_idol(idol_skin_id)
|
||||
device.click(image.expect_wait(R.Common.ButtonNextNoIcon))
|
||||
# 2. 选择支援卡 自动编成 [screenshots/produce/select_support_card.png]
|
||||
ocr.expect_wait(contains('サポート'), rect=R.Produce.BoxStepIndicator)
|
||||
|
@ -272,7 +237,7 @@ def do_produce(
|
|||
def produce_task(
|
||||
mode: Optional[Literal['regular', 'pro']] = None,
|
||||
count: Optional[int] = None,
|
||||
idols: Optional[list[PIdol]] = None,
|
||||
idols: Optional[list[str]] = None,
|
||||
memory_sets: Optional[list[int]] = None
|
||||
):
|
||||
"""
|
||||
|
@ -280,7 +245,7 @@ def produce_task(
|
|||
|
||||
:param mode: 培育模式。若为 None,则从配置文件中读入。
|
||||
:param count: 培育次数。若为 None,则从配置文件中读入。
|
||||
:param idols: 要培育的偶像。若为 None,则从配置文件中读入。
|
||||
:param idols: 要培育的偶像的 IdolCardSkin.id。若为 None,则从配置文件中读入。
|
||||
"""
|
||||
if not conf().produce.enabled:
|
||||
logger.info('Produce is disabled.')
|
||||
|
@ -310,7 +275,7 @@ def produce_task(
|
|||
memory_set = next(memory_set_iterator, None)
|
||||
logger.info(
|
||||
f'Produce start with: '
|
||||
f'idol: {idol.value}, mode: {mode}, memory_set: #{memory_set}'
|
||||
f'idol: {idol}, mode: {mode}, memory_set: #{memory_set}'
|
||||
)
|
||||
if not do_produce(idol, mode, memory_set):
|
||||
user.info('AP 不足', f'由于 AP 不足,跳过了 {count - i} 次培育。')
|
||||
|
@ -340,13 +305,13 @@ if __name__ == '__main__':
|
|||
conf().produce.enabled = True
|
||||
conf().produce.mode = 'pro'
|
||||
conf().produce.produce_count = 1
|
||||
conf().produce.idols = [PIdol.月村手毬_アイヴイ]
|
||||
conf().produce.idols = ['i_card-skin-hski-3-002']
|
||||
conf().produce.memory_sets = [5]
|
||||
conf().produce.auto_set_memory = False
|
||||
# do_produce(PIdol.月村手毬_初声, 'pro', 5)
|
||||
produce_task()
|
||||
# a()
|
||||
# select_idol(PIdol.藤田ことね_学園生活)
|
||||
# select_idol()
|
||||
# select_set(10)
|
||||
# manual_context().begin()
|
||||
# print(ocr.ocr(rect=R.Produce.BoxSetCountIndicator).squash().numbers())
|
|
@ -0,0 +1,18 @@
|
|||
import os
|
||||
from typing import cast
|
||||
|
||||
from kotonebot.tasks import resources as res
|
||||
|
||||
CACHE = os.path.join('cache')
|
||||
RESOURCE = cast(list[str], res.__path__)[0]
|
||||
|
||||
if not os.path.exists(CACHE):
|
||||
os.makedirs(CACHE)
|
||||
|
||||
def cache(path: str) -> str:
|
||||
p = os.path.join(CACHE, path)
|
||||
os.makedirs(os.path.dirname(p), exist_ok=True)
|
||||
return p
|
||||
|
||||
def resource(path: str) -> str:
|
||||
return os.path.join(RESOURCE, path)
|
|
@ -9,8 +9,11 @@ from typing import List, Dict, Tuple, Literal, Generator
|
|||
import cv2
|
||||
import gradio as gr
|
||||
|
||||
from kotonebot.backend.context import task_registry, ContextStackVars
|
||||
from kotonebot.tasks.db import IdolCard
|
||||
from kotonebot.backend.bot import KotoneBot
|
||||
from kotonebot.config.manager import load_config, save_config
|
||||
from kotonebot.config.base_config import UserConfig, BackendConfig
|
||||
from kotonebot.backend.context import task_registry, ContextStackVars
|
||||
from kotonebot.tasks.common import (
|
||||
BaseConfig, APShopItems, CapsuleToysConfig, ClubRewardConfig, PurchaseConfig, ActivityFundsConfig,
|
||||
PresentsConfig, AssignmentConfig, ContestConfig, ProduceConfig,
|
||||
|
@ -18,8 +21,6 @@ from kotonebot.tasks.common import (
|
|||
RecommendCardDetectionMode, TraceConfig, StartGameConfig, UpgradeSupportCardConfig,
|
||||
upgrade_config
|
||||
)
|
||||
from kotonebot.config.base_config import UserConfig, BackendConfig
|
||||
from kotonebot.backend.bot import KotoneBot
|
||||
|
||||
# 初始化日志
|
||||
os.makedirs('logs', exist_ok=True)
|
||||
|
@ -330,7 +331,7 @@ class KotoneBotUI:
|
|||
enabled=produce_enabled,
|
||||
mode=produce_mode,
|
||||
produce_count=produce_count,
|
||||
idols=[PIdol[idol] for idol in produce_idols],
|
||||
idols=produce_idols,
|
||||
memory_sets=[int(i) for i in memory_sets],
|
||||
auto_set_memory=auto_set_memory,
|
||||
auto_set_support_card=auto_set_support,
|
||||
|
@ -662,8 +663,13 @@ class KotoneBotUI:
|
|||
info=ProduceConfig.model_fields['produce_count'].description
|
||||
)
|
||||
# 添加偶像选择
|
||||
idol_choices = [idol.name for idol in PIdol]
|
||||
selected_idols = [idol.name for idol in self.current_config.options.produce.idols]
|
||||
idol_choices = []
|
||||
for idol in IdolCard.all():
|
||||
if idol.is_another:
|
||||
idol_choices.append((f'{idol.name} 「{idol.another_name}」', idol.skin_id))
|
||||
else:
|
||||
idol_choices.append((f'{idol.name}', idol.skin_id))
|
||||
selected_idols = self.current_config.options.produce.idols
|
||||
produce_idols = gr.Dropdown(
|
||||
choices=idol_choices,
|
||||
value=selected_idols,
|
||||
|
|
Loading…
Reference in New Issue