feat(task):修改领取技能卡的选择逻辑2

This commit is contained in:
WinterChimes 2025-06-28 22:37:24 +08:00
parent 00427292e0
commit 3e698a6ae7
15 changed files with 455 additions and 1000 deletions

View File

@ -1,16 +1,47 @@
from enum import Enum
class CharacterId(Enum):
"""偶像 ID。"""
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, 十王星南
hmsz = "hmsz" # Hataya Misuzu, 秦谷美鈴
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, 十王星南
hmsz = "hmsz" # Hataya Misuzu, 秦谷美鈴
class ExamEffectType(Enum):
"""
考试流派
温存根据 ShowExamEffectType 决定
"""
good_condition = "ProduceExamEffectType_ExamParameterBuff"
"""好调"""
focus = "ProduceExamEffectType_ExamLessonBuff"
"""集中"""
good_impression = "ProduceExamEffectType_ExamReview"
"""好印象"""
motivation = "ProduceExamEffectType_ExamCardPlayAggressive"
"""干劲"""
confidence = "ProduceExamEffectType_ExamConcentration"
"""强气"""
full_power = "ProduceExamEffectType_ExamFullPower"
"""全力"""
class ShowExamEffectType(Enum):
"""
若为 ProduceExamEffectType_ExamPreservation 则偶像卡推荐流派显示为 温存
目前分 全力-温存 强气-温存 两种选择卡牌还是根据 全力/强气 来选择
"""
unknown = "ProduceExamEffectType_Unknown"
"""推荐流派与ExamEffectType对应"""
conservation = "ProduceExamEffectType_ExamPreservation"
"""推荐流派显示为温存"""

View File

@ -1,13 +1,16 @@
from dataclasses import dataclass
from .sqlite import select, select_many
from .constants import CharacterId
from .constants import ExamEffectType, ShowExamEffectType
@dataclass
class IdolCard:
"""偶像卡"""
id: str
skin_id: str
exam_effect_type: ExamEffectType
show_exam_effect_type: ShowExamEffectType
is_another: bool
another_name: str | None
name: str
@ -21,6 +24,8 @@ class IdolCard:
SELECT
IC.id AS cardId,
ICS.id AS skinId,
IC.examEffectType AS examEffectType,
IC.showExamEffectType AS showExamEffectType,
Char.lastName || ' ' || Char.firstName || ' ' || IC.name AS name,
NOT (IC.originalIdolCardSkinId = ICS.id) AS isAnotherVer,
ICS.name AS anotherVerName
@ -34,11 +39,13 @@ class IdolCard:
return cls(
id=row["cardId"],
skin_id=row["skinId"],
exam_effect_type=ExamEffectType(row["examEffectType"]),
show_exam_effect_type=ShowExamEffectType(row["showExamEffectType"]),
is_another=bool(row["isAnotherVer"]),
another_name=row["anotherVerName"],
name=row["name"]
)
@classmethod
def all(cls) -> list['IdolCard']:
"""获取所有偶像卡"""
@ -46,6 +53,8 @@ class IdolCard:
SELECT
IC.id AS cardId,
ICS.id AS skinId,
IC.examEffectType AS examEffectType,
IC.showExamEffectType AS showExamEffectType,
Char.lastName || ' ' || Char.firstName || ' ' || IC.name AS name,
NOT (IC.originalIdolCardSkinId = ICS.id) AS isAnotherVer,
ICS.name AS anotherVerName
@ -58,13 +67,17 @@ class IdolCard:
results.append(cls(
id=row["cardId"],
skin_id=row["skinId"],
exam_effect_type=ExamEffectType(row["examEffectType"]),
show_exam_effect_type=ShowExamEffectType(row["showExamEffectType"]),
is_another=bool(row["isAnotherVer"]),
another_name=row["anotherVerName"],
name=row["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())

View File

@ -29,25 +29,12 @@ class PlanType(Enum):
ANOMALY = "ProducePlanType_Plan3"
"""非凡"""
class Archetype(Enum):
"""偶像流派"""
UNIDENTIFIED = "unidentified"
"""未识别"""
GOOD_CONDITION = "好調"
"""好调"""
FOCUS = "集中"
"""集中"""
GOOD_IMPRESSION = "好印象"
"""好印象"""
MOTIVATION = "やる気"
"""干劲"""
CONFIDENCE = "強気"
"""强气"""
CONSERVATION = "温存"
"""温存"""
FULL_POWER = "全力"
"""全力"""
class PlayMovePositionType(Enum):
"""卡牌打出后 除外 还是 进入弃牌堆"""
LOST = "ProduceCardMovePositionType_Lost"
"""除外,洗牌后无法抽到"""
GRAVE = "ProduceCardMovePositionType_Grave"
"""进入弃牌堆,洗牌后仍能抽到"""
@dataclass
@ -64,6 +51,8 @@ class SkillCard:
"""卡牌名称。"""
once: bool
"""此卡牌在考试或课程中是否只会出现一次。"""
play_move_position_type: PlayMovePositionType
"""此卡牌在考试或课程中使用后除外还是进入弃牌堆。"""
origin_idol_card: str | None
"""此卡牌所属的偶像卡。"""
origin_support_card: str | None
@ -80,12 +69,12 @@ class SkillCard:
def is_from_idol_card(self) -> bool:
"""此卡牌是否来自偶像卡。"""
return self.origin_idol_card is not None
@property
def is_from_support_card(self) -> bool:
"""此卡牌是否来自支援卡。"""
return self.origin_support_card is not None
@property
def asset_ids(self) -> list[str]:
"""
@ -94,7 +83,7 @@ class SkillCard:
if not self.is_character_asset:
return [self.asset_id]
return [f'{self.asset_id}-{ii.value}' for ii in CharacterId]
@classmethod
def all(cls) -> list['SkillCard']:
"""获取所有技能卡"""
@ -106,6 +95,7 @@ class SkillCard:
category AS cardType,
name,
noDeckDuplication AS once,
playMovePositionType,
originIdolCardId AS idolCardId,
originSupportCardId AS supportCardId,
isCharacterAsset
@ -120,12 +110,13 @@ class SkillCard:
card_type=CardType(row["cardType"]),
name=row["name"],
once=bool(row["once"]),
play_move_position_type=PlayMovePositionType(row["playMovePositionType"]),
origin_idol_card=row["idolCardId"],
origin_support_card=row["supportCardId"],
is_character_asset=bool(row["isCharacterAsset"])
))
return results
@classmethod
def from_asset_id(cls, asset_id: str) -> 'SkillCard | None':
"""根据资源 ID 查询 SkillCard。"""
@ -141,6 +132,7 @@ class SkillCard:
category AS cardType,
name,
noDeckDuplication AS once,
playMovePositionType,
originIdolCardId AS idolCardId,
originSupportCardId AS supportCardId,
isCharacterAsset
@ -156,6 +148,7 @@ class SkillCard:
card_type=CardType(row["cardType"]),
name=row["name"],
once=bool(row["once"]),
play_move_position_type=PlayMovePositionType(row["playMovePositionType"]),
origin_idol_card=row["idolCardId"],
origin_support_card=row["supportCardId"],
is_character_asset=bool(row["isCharacterAsset"])

View File

@ -17,9 +17,10 @@ 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)) # 白色背景
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[RectTuple]:
"""
@ -48,6 +49,7 @@ def extract_idols(img: MatLike) -> list[RectTuple]:
rects.append((x, y, w, h))
return rects
def display_rects(img: MatLike, rects: list[RectTuple]) -> MatLike:
"""Draw rectangles on the image and display them."""
result = img.copy()
@ -56,10 +58,11 @@ def display_rects(img: MatLike, rects: list[RectTuple]) -> MatLike:
# 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)
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[RectTuple], db: ImageDatabase, idol_path: str) -> MatLike:
"""
在预览图上绘制所有匹配到的偶像
@ -72,31 +75,32 @@ def draw_idol_preview(img: MatLike, rects: list[RectTuple], db: ImageDatabase, i
"""
# 创建一个与原图大小相同的白色背景图片
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]
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
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)
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:
@ -106,6 +110,7 @@ def idols_db() -> ImageDatabase:
_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()
@ -124,7 +129,7 @@ def locate_idol(skin_id: str):
img = device.screenshot()
# 只保留 BoxIdolOverviewIdols 区域
mask = np.zeros_like(img)
mask[y:y+h, x:x+w] = img[y:y+h, x:x+w]
mask[y:y + h, x:x + w] = img[y:y + h, x:x + w]
img = mask
# 检测 & 查询
rects = extract_idols(img)
@ -133,7 +138,7 @@ def locate_idol(skin_id: str):
# cv2.waitKey(0)
for rect in rects:
rx, ry, rw, rh = rect
idol_img = img[ry:ry+rh, rx:rx+rw]
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}
@ -146,7 +151,24 @@ def locate_idol(skin_id: str):
# # 使用新函数绘制预览图
# preview_img = draw_idol_preview(img, rects, db, path)
@action('重新培育页面识别偶像卡', screenshot_mode='manual-inherit')
def find_idol_skin_id_on_resume_produce(img: MatLike) -> str | None:
"""
继续培育 界面查找偶像皮肤id
默认 数据库中的key 偶像皮肤id_\d.png
:return:
"""
db = idols_db()
rx, ry, rw, rh = R.Produce.BoxResumeDialogIdolCard.xywh
idol_img = img[ry:ry + rh, rx:rx + rw]
match = db.match(idol_img, 20)
if match:
return match.key.rsplit("_", 1)[0]
else:
return None
def test():
from kotonebot.backend.context import init_context, manual_context
@ -154,7 +176,8 @@ def test():
manual_context().begin()
locate_idol('i_card-skin-fktn-3-006')
if __name__ == '__main__':
from kotonebot.util import cv2_imread
test()
test()

View File

@ -1,211 +0,0 @@
from dataclasses import dataclass
from kotonebot.kaa.skill_card.enum_constant import CardName, PlanType, CardType
@dataclass
class CardTemplate:
"""
卡牌模板
理论上应该保存到数据库上相关属性就不需要识别
"""
# 卡牌名称
name: CardName
# 卡牌职业分类,感性、理性等
plan_type: PlanType
# 卡牌作用类型,a卡m卡等
card_type: CardType
# 一回一次
once: bool = False
# 以下信息可能在以后会用到,这里先不写了
# good_condition: bool = False # 好调
# focus: bool = False # 集中
#
# good_impression: bool = False # 好印象
# motivation: bool = False # 干劲
#
# confidence: bool = False # 强气
# conservation: bool = False # 温存
# full_power: bool = False # 全力
#
# energy: bool = False # 护盾
# TODO 从数据库中读取对应的卡牌信息,读取完成后更新 card_template_dict
card_collection = [
# unidentified 当识别过程无法找到对应的卡时,使用这个而不是眠气卡
CardTemplate(CardName.unidentified, PlanType.common, CardType.trouble, once=False),
# common
CardTemplate(CardName.c_n_0, PlanType.common, CardType.trouble, once=True),
CardTemplate(CardName.c_n_1, PlanType.common, CardType.active, once=False),
CardTemplate(CardName.c_n_2, PlanType.common, CardType.active, once=False),
CardTemplate(CardName.c_n_3, PlanType.common, CardType.mental, once=True),
CardTemplate(CardName.c_r_1, PlanType.common, CardType.mental, once=False),
CardTemplate(CardName.c_r_2, PlanType.common, CardType.mental, once=True),
CardTemplate(CardName.c_sr_1, PlanType.common, CardType.active, once=False),
CardTemplate(CardName.c_sr_2, PlanType.common, CardType.mental, once=True),
CardTemplate(CardName.c_sr_3, PlanType.common, CardType.mental, once=True),
CardTemplate(CardName.c_ssr_1, PlanType.common, CardType.mental, once=True),
CardTemplate(CardName.c_ssr_2, PlanType.common, CardType.mental, once=True),
CardTemplate(CardName.c_ssr_3, PlanType.common, CardType.mental, once=True),
CardTemplate(CardName.c_ssr_4, PlanType.common, CardType.mental, once=True),
# sense
CardTemplate(CardName.s_n_1, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_n_2, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_n_3, PlanType.sense, CardType.mental, once=False),
CardTemplate(CardName.s_n_4, PlanType.sense, CardType.mental, once=False),
CardTemplate(CardName.s_r_1, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_r_2, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_r_3, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_r_4, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_r_5, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_r_6, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_r_7, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_r_8, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_r_9, PlanType.sense, CardType.mental, once=False),
CardTemplate(CardName.s_r_10, PlanType.sense, CardType.mental, once=False),
CardTemplate(CardName.s_r_11, PlanType.sense, CardType.mental, once=False),
CardTemplate(CardName.s_r_12, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_1, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_sr_2, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_sr_3, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_sr_4, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_sr_5, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_sr_6, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_sr_7, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_sr_8, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_sr_9, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_sr_10, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_11, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_12, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_13, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_14, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_15, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_16, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_17, PlanType.sense, CardType.mental, once=False),
CardTemplate(CardName.s_sr_18, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_sr_19, PlanType.sense, CardType.mental, once=False),
CardTemplate(CardName.s_ssr_1, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_ssr_2, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_ssr_3, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_ssr_4, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_ssr_5, PlanType.sense, CardType.active, once=True),
CardTemplate(CardName.s_ssr_6, PlanType.sense, CardType.active, once=False),
CardTemplate(CardName.s_ssr_7, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_ssr_8, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_ssr_9, PlanType.sense, CardType.mental, once=True),
CardTemplate(CardName.s_ssr_10, PlanType.sense, CardType.mental, once=True),
# logic
CardTemplate(CardName.l_n_1, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_n_2, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_n_3, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_n_4, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_r_1, PlanType.logic, CardType.active, once=False),
CardTemplate(CardName.l_r_2, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_r_3, PlanType.logic, CardType.active, once=False),
CardTemplate(CardName.l_r_4, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_r_5, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_r_6, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_r_7, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_r_8, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_r_9, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_r_10, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_r_11, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_r_12, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_r_13, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_sr_1, PlanType.logic, CardType.active, once=False),
CardTemplate(CardName.l_sr_2, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_sr_3, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_sr_4, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_sr_5, PlanType.logic, CardType.active, once=False),
CardTemplate(CardName.l_sr_6, PlanType.logic, CardType.active, once=False),
CardTemplate(CardName.l_sr_7, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_sr_8, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_sr_9, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_sr_10, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_sr_11, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_sr_12, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_sr_13, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_sr_14, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_sr_15, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_sr_16, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_sr_17, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_sr_18, PlanType.logic, CardType.mental, once=False),
CardTemplate(CardName.l_ssr_1, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_ssr_2, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_ssr_3, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_ssr_4, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_ssr_5, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_ssr_6, PlanType.logic, CardType.active, once=True),
CardTemplate(CardName.l_ssr_7, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_ssr_8, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_ssr_9, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_ssr_10, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_ssr_11, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_ssr_12, PlanType.logic, CardType.mental, once=True),
CardTemplate(CardName.l_ssr_13, PlanType.logic, CardType.mental, once=True),
# anomaly
CardTemplate(CardName.a_n_1, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_n_2, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_n_3, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_n_4, PlanType.anomaly, CardType.mental, once=False),
CardTemplate(CardName.a_n_5, PlanType.anomaly, CardType.mental, once=False),
CardTemplate(CardName.a_r_1, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_r_2, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_r_3, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_r_4, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_r_5, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_r_6, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_r_7, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_r_8, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_r_9, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_r_10, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_sr_1, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_sr_2, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_sr_3, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_sr_4, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_sr_5, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_sr_6, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_sr_7, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_sr_8, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_sr_9, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_sr_10, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_sr_11, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_sr_12, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_sr_13, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_sr_14, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_sr_15, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_sr_16, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_sr_17, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_sr_18, PlanType.anomaly, CardType.mental, once=False),
CardTemplate(CardName.a_sr_19, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_ssr_1, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_ssr_2, PlanType.anomaly, CardType.active, once=False),
CardTemplate(CardName.a_ssr_3, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_ssr_4, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_ssr_5, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_ssr_6, PlanType.anomaly, CardType.active, once=True),
CardTemplate(CardName.a_ssr_7, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_ssr_8, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_ssr_9, PlanType.anomaly, CardType.mental, once=True),
CardTemplate(CardName.a_ssr_10, PlanType.anomaly, CardType.mental, once=True),
]
card_template_dict = {card.name: card for card in card_collection}

View File

@ -1,25 +1,23 @@
from kotonebot.kaa.common import ConfigBaseModel
from kotonebot.kaa.skill_card.enum_constant import Archetype, CardName
from kotonebot.kaa.db.constants import ExamEffectType
# TODO: 这个文件应该写在 kotonebot.kaa.common 的 BaseConfig文件中
# TODO用户可以设置自己的流派卡组把卡填入下面的设置中未填入的卡默认不选。
# 单流派卡组设置
class SingleDeckConfig(ConfigBaseModel):
# 角色流派
archetype: Archetype
# 角色流派。目前只考虑ExamEffectType温存目前分全力温存和强气温存所以没有温存设置。
archetype: ExamEffectType
# 当可选卡不在设置中是否优先选择其中的一次性卡false会直接进行刷新
select_once_card_before_refresh: bool = True
# 核心卡,在商店页面会购买
core_cards: list[CardName] = []
core_cards: list[str] = []
# 高优先度卡
high_priority_cards: list[CardName] = []
high_priority_cards: list[str] = []
# 中优先度卡
medium_priority_cards: list[CardName] = []
medium_priority_cards: list[str] = []
# 低优先度卡
low_priority_cards: list[CardName] = []
low_priority_cards: list[str] = []
# 所有卡组设置

View File

@ -1,40 +1,4 @@
from enum import Enum, IntEnum
# 卡牌作用类型
class CardType(Enum):
mental = "MentalSkillCard" # M卡
active = "ActiveSkillCard" # A卡
trouble = "TroubleSkillCard" # T卡
unidentified = "Unidentified" # 未识别
# 卡牌职业分类
class PlanType(Enum):
common = "Common" # 通用
sense = "Sense" # 感性
logic = "Logic" # 理性
anomaly = "Anomaly" # 非凡
# 偶像流派
class Archetype(Enum):
# 未识别
unidentified = "unidentified"
# 好调
good_condition = "好調"
# 集中
focus = "集中"
# 好印象
good_impression = "好印象"
# 干劲
motivation = "やる気"
# 强气
confidence = "強気"
# 温存
conservation = "温存"
# 全力
full_power = "全力"
from enum import IntEnum
# 卡牌抉择优先度,越大优先度越低
@ -44,236 +8,3 @@ class CardPriority(IntEnum):
medium = 2
low = 3
other = 99
# 卡牌名称(标识)
class CardName(Enum):
unidentified = "Unidentified"
# commonN 卡
c_n_0 = "眠気"
c_n_1 = "アピールの基本"
c_n_2 = "ポーズの基本"
c_n_3 = "表現の基本"
# commonR 卡
c_r_1 = "気合十分!"
c_r_2 = "ファーストステップ"
# commonSR 卡
c_sr_1 = "前途洋々"
c_sr_2 = "アイドル宣言"
c_sr_3 = "ハイテンション"
# commonSSR 卡
c_ssr_1 = "テレビ出演"
c_ssr_2 = "叶えたい夢"
c_ssr_3 = "アイドル魂"
c_ssr_4 = "仕切り直し"
# senseN 卡
s_n_1 = "挑戦"
s_n_2 = "試行錯誤"
s_n_3 = "振る舞いの基本"
s_n_4 = "表情の基本"
# senseR 卡
s_r_1 = "軽い足取り"
s_r_2 = "愛嬌"
s_r_3 = "準備運動"
s_r_4 = "ファンサ"
s_r_5 = "勢い任せ"
s_r_6 = "ハイタッチ"
s_r_7 = "トークタイム"
s_r_8 = "エキサイト"
s_r_9 = "バランス感覚"
s_r_10 = "楽観的"
s_r_11 = "深呼吸"
s_r_12 = "ひと呼吸"
# senseSR 卡
s_sr_1 = "決めポーズ"
s_sr_2 = "アドリブ"
s_sr_3 = "情熱ターン"
s_sr_4 = "飛躍"
s_sr_5 = "祝福"
s_sr_6 = "スタートダッシュ"
s_sr_7 = "スタンドプレー"
s_sr_8 = "シュプレヒコール"
s_sr_9 = "立ち位置チェック"
s_sr_10 = "眼力"
s_sr_11 = "大声援"
s_sr_12 = "演出計画"
s_sr_13 = "願いの力"
s_sr_14 = "静かな意志"
s_sr_15 = "始まりの合図"
s_sr_16 = "意地"
s_sr_17 = "存在感"
s_sr_18 = "成功への道筋"
s_sr_19 = "スポットライト"
# senseSSR 卡
s_ssr_1 = "コール&レスポンス"
s_ssr_2 = "バズワード"
s_ssr_3 = "成就"
s_ssr_4 = "魅惑のパフォーマンス"
s_ssr_5 = "至高のエンタメ"
s_ssr_6 = "覚醒"
s_ssr_7 = "国民的アイドル"
s_ssr_8 = "魅惑の視線"
s_ssr_9 = "鳴り止まない拍手"
s_ssr_10 = "天真爛漫"
# logicN 卡
l_n_1 = "可愛い仕草"
l_n_2 = "気分転換"
l_n_3 = "目線の基本"
l_n_4 = "意識の基本"
# logicR 卡
l_r_1 = "今日もおはよう"
l_r_2 = "ゆるふわおしゃべり"
l_r_3 = "もう少しだけ"
l_r_4 = "手拍子"
l_r_5 = "元気な挨拶"
l_r_6 = "デイドリーミング"
l_r_7 = "リスタート"
l_r_8 = "えいえいおー"
l_r_9 = "リズミカル"
l_r_10 = "思い出し笑い"
l_r_11 = "パステル気分"
l_r_12 = "励まし"
l_r_13 = "幸せのおまじない"
# logicSR 卡
l_sr_1 = "ラブリーウインク"
l_sr_2 = "ありがとうの言葉"
l_sr_3 = "ハートの合図"
l_sr_4 = "キラメキ"
l_sr_5 = "みんな大好き"
l_sr_6 = "きらきら紙吹雪"
l_sr_7 = "あふれる思い出"
l_sr_8 = "ふれあい"
l_sr_9 = "幸せな時間"
l_sr_10 = "ファンシーチャーム"
l_sr_11 = "ワクワクが止まらない"
l_sr_12 = "本番前夜"
l_sr_13 = "ひなたぼっこ"
l_sr_14 = "イメトレ"
l_sr_15 = "やる気は満点"
l_sr_16 = "ゆめみごこち"
l_sr_17 = "止められない想い"
l_sr_18 = "オトメゴコロ"
# logicSSR 卡
l_ssr_1 = "200%スマイル"
l_ssr_2 = "開花"
l_ssr_3 = "届いて!"
l_ssr_4 = "輝くキミへ"
l_ssr_5 = "あのときの約束"
l_ssr_6 = "キセキの魔法"
l_ssr_7 = "私がスター"
l_ssr_8 = "星屑センセーション"
l_ssr_9 = "ノートの端の決意"
l_ssr_10 = "手書きのメッセージ"
l_ssr_11 = "トキメキ"
l_ssr_12 = "虹色ドリーマー"
l_ssr_13 = "夢色リップ"
# anomalyN 卡
a_n_1 = "挨拶の基本"
a_n_2 = "はげしく"
a_n_3 = "スパート"
a_n_4 = "メントレの基本"
a_n_5 = "イメージの基本"
# anomalyR 卡
a_r_1 = "ジャストアピール"
a_r_2 = "スターライト"
a_r_3 = "一歩"
a_r_4 = "ラッキー♪"
a_r_5 = "積み重ね"
a_r_6 = "精一杯"
a_r_7 = "ハッピー♪"
a_r_8 = "嬉しい誤算"
a_r_9 = "涙の思い出"
a_r_10 = "セッティング"
# anomalySR 卡
a_sr_1 = "せーのっ!"
a_sr_2 = "アッチェレランド"
a_sr_3 = "はじけるパッション"
a_sr_4 = "汗と成長"
a_sr_5 = "第一印象"
a_sr_6 = "オープニングアクト"
a_sr_7 = "始まりの笑顔"
a_sr_8 = "トレンドリーダー"
a_sr_9 = "理想のテンポ"
a_sr_10 = "トレーニングの成果"
a_sr_11 = "潜在能力"
a_sr_12 = "カウントダウン"
a_sr_13 = "モチベ"
a_sr_14 = "プライド"
a_sr_15 = "盛り上げ上手"
a_sr_16 = "インフルエンサー"
a_sr_17 = "忍耐力"
a_sr_18 = "切磋琢磨"
a_sr_19 = "タフネス"
# anomalySSR 卡
a_ssr_1 = "翔び立て!"
a_ssr_2 = "総合芸術"
a_ssr_3 = "心・技・体"
a_ssr_4 = "輝け!"
a_ssr_5 = "クライマックス"
a_ssr_6 = "全身全霊"
a_ssr_7 = "アイドルになります"
a_ssr_8 = "一心不乱"
a_ssr_9 = "頂点へ"
a_ssr_10 = "覚悟"
archetype_dict = {x.value: x for x in Archetype}
card_name_dict = {x.value: x for x in CardName}
# 一些因为ocr读取有问题而暂时添加的匹配
card_name_dict.update(
{
# 卡名读取有误
"天真燗漫": CardName.s_ssr_10,
"元気な挨": CardName.l_r_5,
"えいえいお一": CardName.l_r_8,
"フクワクが止まらない": CardName.l_sr_11,
"ラッキーの": CardName.a_r_4, # 名字后面的音符符号
"ハッピー・": CardName.a_r_7, # 名字后面的音符符号
"切礎琢磨": CardName.a_sr_18,
# 强化卡名读取有误
"デイドリーミングャ": CardName.l_r_6,
"ラッキー・": CardName.a_r_4, # 名字后面的音符符号
"切瑳琢磨": CardName.a_sr_18,
"バズワードャ": CardName.s_ssr_2, # 名字后面的音符符号
"輝くキミヘ": CardName.l_ssr_4, # 片假名
"天真欄漫": CardName.s_ssr_10,
"頂点ヘ": CardName.a_ssr_9, # 片假名
}
)
def get_archetype(s: str):
return archetype_dict.get(s, Archetype.unidentified)
def get_card_name(s: str):
return card_name_dict.get(s, CardName.unidentified)

View File

@ -1,8 +1,9 @@
from dataclasses import dataclass
from logging import getLogger
from kotonebot.kaa.db.constants import ExamEffectType
from kotonebot.kaa.skill_card.card_deck_config import DeckConfig, SingleDeckConfig
from kotonebot.kaa.skill_card.enum_constant import Archetype, CardName, CardPriority
from kotonebot.kaa.skill_card.enum_constant import CardPriority
logger = getLogger(__name__)
@ -12,17 +13,17 @@ class GlobalIdolSetting:
def __init__(self):
# 是否需要刷新全局配置,理论上新开培育、重新培育都需要更新
self.need_update: bool = True
self.idol_archetype: Archetype = Archetype.good_impression
self.idol_archetype: ExamEffectType = ExamEffectType.good_impression
self.card_deck: dict = {}
self.select_once_card_before_refresh = True
def new_play(self):
self.need_update = True
self.select_once_card_before_refresh = True
self.card_deck = {}
self.card_deck.clear()
logger.info("New game, wait for update")
def update_deck(self, idol_archetype: Archetype, config: DeckConfig):
def update_deck(self, idol_archetype: ExamEffectType, config: DeckConfig):
"""
根据流派选择初始化对应的卡组配置如果自定义有就使用自定义没有就使用预设
:param idol_archetype: 偶像流派
@ -54,10 +55,10 @@ class GlobalIdolSetting:
self.card_deck.update({card: CardPriority.core for card in card_deck_config.core_cards})
self.select_once_card_before_refresh = card_deck_config.select_once_card_before_refresh
def get_card_priority(self, card_name: CardName) -> CardPriority:
def get_card_priority(self, card_id: str) -> CardPriority:
"""
根据卡名来查看此卡的选卡优先级
:param card_name: 卡名
:param card_id: 卡id
:return: 优先级越小越高不在配置卡组则返回other(99)
"""
return self.card_deck.get(card_name, CardPriority.other)
return self.card_deck.get(card_id, CardPriority.other)

View File

@ -1,90 +1,41 @@
from logging import getLogger
from dataclasses import dataclass
from kotonebot.kaa.db.skill_card import SkillCard
from kotonebot.kaa.tasks import R
from kotonebot import Interval, device, Countdown, ocr, action, image
from kotonebot.backend.core import HintBox
from kotonebot.kaa.skill_card.enum_constant import CardName, get_card_name
from kotonebot.kaa.skill_card.card_data import CardTemplate, card_template_dict
from kotonebot.kaa.db.skill_card import PlayMovePositionType
from kotonebot.kaa.game_ui.skill_card_select import SkillCardElement
from kotonebot.kaa.skill_card.enum_constant import CardPriority
from kotonebot.kaa.skill_card_action.global_idol_setting_action import idol_setting
from kotonebot.primitives import Rect
# 技能卡选择页面,点击技能卡后,技能卡的名字的范围
SelectCardNameArea = HintBox(x1=70, y1=478, x2=570, y2=538, source_resolution=(720, 1280))
# 商店页面,选择商品后,商品名的范围
ShopItemNameArea = HintBox(x1=195, y1=170, x2=625, y2=215, source_resolution=(720, 1280))
# 强化或删除技能卡时,技能卡的名字的范围
EnhancementCardNameArea = HintBox(x1=180, y1=100, x2=630, y2=145, source_resolution=(720, 1280))
logger = getLogger(__name__)
@dataclass
class ActualCard:
rect: Rect
name: str
skill_card: CardTemplate
priority: int
skill_card_element: SkillCardElement
priority: CardPriority
@staticmethod
def create_by(rect: Rect, card: SkillCard | None) -> 'ActualCard':
def create_by(card: SkillCardElement) -> 'ActualCard':
"""
:param rect: 技能卡的位置
:param title: 读取到的技能卡的名字
:param card: 读取到的技能卡信息
:return: cls
"""
if card:
name = card.name.replace(' ', '').replace('+', '')
card_name = get_card_name(name)
else:
card_name = CardName.unidentified
priority = idol_setting.get_card_priority(card.skill_card.id)
return ActualCard(card, priority)
if card_name == CardName.unidentified:
logger.warning(f'Unrecognized skill card{name}, rect={rect}')
else:
logger.info(f'Recognized skill card{card_name.value}')
def __lt__(self, other):
return self.priority < other.priority
skill_card = card_template_dict[card_name]
priority = idol_setting.get_card_priority(card_name)
return ActualCard(rect, name, skill_card, priority)
# class CardReader:
# """
# 此类用于识别技能卡。
# """
# def __init__(self, rect: HintBox = SelectCardNameArea):
# """
# :param rect: 识别文本范围
# """
# self.rect = rect
# self.it = Interval()
# self.wait_cd = Countdown(sec=5)
# def click_and_read_cards(self, card_rects: list[Rect]) -> list[ActualCard]:
# """
# 对输入的card_rects进行点击再对点击后的图像的指定位置self.rect 读取文本实例化为ActualCard。
# :param card_rects: 技能卡点击位置
# :return: ActualCard list
# """
# result = []
# for rect in card_rects:
# actual_card = self.read_card(rect)
# logger.info(actual_card)
# result.append(actual_card)
# return result
# @action('读取技能卡', screenshot_mode='manual-inherit')
# def read_card(self, card_rect: Rect) -> ActualCard:
# title = []
# self.it.reset()
# self.wait_cd.reset()
# while len(title) == 0 and not self.wait_cd.expired():
# device.click(card_rect)
# self.it.wait()
# img = device.screenshot()
# title = ocr.raw().ocr(img, rect=self.rect)
# return ActualCard.create_by(card_rect, title.squash().text)
def select(self) -> bool:
"""
选卡时可以选择
:return:
"""
return self.priority != CardPriority.other
def lost(self):
"""
是否为 除外
:return:
"""
return self.skill_card_element.skill_card.play_move_position_type == PlayMovePositionType.LOST

View File

@ -1,297 +1,252 @@
{
"pre_built_deck": [
{
"archetype": "好調",
"archetype": "ProduceExamEffectType_ExamParameterBuff",
"select_once_card_before_refresh": false,
"core_cards": [
"至高のエンタメ",
"国民的アイドル",
"魅惑の視線"
"p_card-01-act-3_049",
"p_card-01-men-3_006",
"p_card-01-men-3_036"
],
"high_priority_cards": [
"アイドル宣言",
"アイドル魂",
"仕切り直し",
"ひと呼吸",
"願いの力",
"静かな意志",
"始まりの合図",
"存在感",
"シュプレヒコール",
"コール&レスポンス",
"成就",
"鳴り止まない拍手",
"天真爛漫",
"魅惑のパフォーマンス"
"p_card-00-men-2_012",
"p_card-00-men-3_003",
"p_card-00-men-3_005",
"p_card-01-men-1_034",
"p_card-01-men-2_038",
"p_card-01-men-2_035",
"p_card-01-men-2_036",
"p_card-01-men-2_037",
"p_card-01-act-2_001",
"p_card-01-act-3_030",
"p_card-01-act-3_029",
"p_card-01-men-3_033",
"p_card-01-men-3_035",
"p_card-01-act-3_010"
],
"medium_priority_cards": [
"眼力",
"大声援",
"演出計画",
"意地",
"ハイタッチ",
"トークタイム",
"エキサイト",
"祝福"
"p_card-01-men-2_039",
"p_card-01-men-2_043",
"p_card-01-men-2_034",
"p_card-01-men-2_040",
"p_card-01-act-1_020",
"p_card-01-act-1_021",
"p_card-01-act-1_036",
"p_card-01-act-2_042"
],
"low_priority_cards": [
"決めポーズ",
"飛躍",
"立ち位置チェック",
"成功への道筋",
"ファーストステップ",
"テレビ出演",
"叶えたい夢"
"p_card-01-act-2_003",
"p_card-01-act-2_033",
"p_card-01-act-2_059",
"p_card-01-men-2_041",
"p_card-00-men-1_007",
"p_card-00-men-3_012",
"p_card-00-men-3_011"
]
},
{
"archetype": "集中",
"archetype": "ProduceExamEffectType_ExamLessonBuff",
"select_once_card_before_refresh": false,
"core_cards": [
"至高のエンタメ",
"国民的アイドル",
"魅惑の視線"
"p_card-01-act-3_049",
"p_card-01-men-3_006",
"p_card-01-men-3_036"
],
"high_priority_cards": [
"アイドル宣言",
"アイドル魂",
"仕切り直し",
"ひと呼吸",
"願いの力",
"静かな意志",
"始まりの合図",
"存在感",
"シュプレヒコール",
"コール&レスポンス",
"成就",
"鳴り止まない拍手",
"天真爛漫",
"魅惑のパフォーマンス"
"p_card-00-men-2_012",
"p_card-00-men-3_003",
"p_card-00-men-3_005",
"p_card-01-men-1_034",
"p_card-01-men-2_038",
"p_card-01-men-2_035",
"p_card-01-men-2_036",
"p_card-01-men-2_037",
"p_card-01-act-2_001",
"p_card-01-act-3_030",
"p_card-01-act-3_029",
"p_card-01-men-3_033",
"p_card-01-men-3_035",
"p_card-01-act-3_010"
],
"medium_priority_cards": [
"眼力",
"大声援",
"演出計画",
"意地",
"ハイタッチ",
"トークタイム",
"エキサイト",
"祝福"
"p_card-01-men-2_039",
"p_card-01-men-2_043",
"p_card-01-men-2_034",
"p_card-01-men-2_040",
"p_card-01-act-1_020",
"p_card-01-act-1_021",
"p_card-01-act-1_036",
"p_card-01-act-2_042"
],
"low_priority_cards": [
"決めポーズ",
"飛躍",
"立ち位置チェック",
"成功への道筋",
"ファーストステップ",
"テレビ出演",
"叶えたい夢"
"p_card-01-act-2_003",
"p_card-01-act-2_033",
"p_card-01-act-2_059",
"p_card-01-men-2_041",
"p_card-00-men-1_007",
"p_card-00-men-3_012",
"p_card-00-men-3_011"
]
},
{
"archetype": "好印象",
"archetype": "ProduceExamEffectType_ExamReview",
"select_once_card_before_refresh": false,
"core_cards": [
"輝くキミへ",
"私がスター",
"星屑センセーション",
"夢色リップ"
"p_card-02-act-3_050",
"p_card-02-men-3_002",
"p_card-02-men-3_004",
"p_card-02-men-3_040"
],
"high_priority_cards": [
"アイドル宣言",
"アイドル魂",
"仕切り直し",
"止められない想い",
"キラメキ",
"ファンシーチャーム",
"ワクワクが止まらない",
"ノートの端の決意",
"トキメキ",
"虹色ドリーマー",
"みんな大好き",
"ゆめみごこち",
"オトメゴコロ"
"p_card-00-men-2_012",
"p_card-00-men-3_003",
"p_card-00-men-3_005",
"p_card-02-men-2_056",
"p_card-02-act-2_048",
"p_card-02-men-2_058",
"p_card-02-men-2_004",
"p_card-02-men-3_041",
"p_card-02-men-3_043",
"p_card-02-men-3_042",
"p_card-02-act-2_049",
"p_card-02-men-2_057",
"p_card-02-men-2_060"
],
"medium_priority_cards": [
"手拍子",
"幸せのおまじない",
"やる気は満点",
"本番前夜",
"200%スマイル",
"キセキの魔法"
"p_card-02-act-1_003",
"p_card-02-men-1_035",
"p_card-02-men-2_051",
"p_card-02-men-2_054",
"p_card-02-act-3_001",
"p_card-02-act-3_052"
],
"low_priority_cards": [
"ラブリーウインク",
"幸せな時間",
"ひなたぼっこ",
"イメトレ",
"ファーストステップ",
"テレビ出演",
"叶えたい夢"
"p_card-02-act-2_046",
"p_card-02-men-2_050",
"p_card-02-men-2_052",
"p_card-02-men-2_008",
"p_card-00-men-1_007",
"p_card-00-men-3_012",
"p_card-00-men-3_011"
]
},
{
"archetype": "やる気",
"archetype": "ProduceExamEffectType_ExamCardPlayAggressive",
"select_once_card_before_refresh": false,
"core_cards": [
"私がスター",
"夢色リップ",
"デイドリーミング",
"きらきら紙吹雪"
"p_card-02-men-3_002",
"p_card-02-men-3_040",
"p_card-02-act-1_037",
"p_card-02-act-2_062"
],
"high_priority_cards": [
"アイドル宣言",
"アイドル魂",
"仕切り直し",
"止められない想い",
"ワクワクが止まらない",
"ひなたぼっこ",
"イメトレ",
"届いて!",
"ノートの端の決意",
"トキメキ"
"p_card-00-men-2_012",
"p_card-00-men-3_003",
"p_card-00-men-3_005",
"p_card-02-men-2_056",
"p_card-02-men-2_004",
"p_card-02-men-2_052",
"p_card-02-men-2_008",
"p_card-02-act-3_039",
"p_card-02-men-3_041",
"p_card-02-men-3_043"
],
"medium_priority_cards": [
"開花",
"手書きのメッセージ",
"あのときの約束",
"ありがとうの言葉",
"本番前夜"
"p_card-02-act-3_038",
"p_card-02-men-3_044",
"p_card-02-act-3_045",
"p_card-02-act-2_047",
"p_card-02-men-2_054"
],
"low_priority_cards": [
"元気な挨拶",
"リズミカル",
"やる気は満点",
"ファーストステップ",
"テレビ出演",
"叶えたい夢"
"p_card-02-act-1_004",
"p_card-02-men-1_006",
"p_card-02-men-2_051",
"p_card-00-men-1_007",
"p_card-00-men-3_012",
"p_card-00-men-3_011"
]
},
{
"archetype": "強気",
"archetype": "ProduceExamEffectType_ExamConcentration",
"select_once_card_before_refresh": false,
"core_cards": [
"アイドルになります",
"一心不乱"
"p_card-03-men-3_058",
"p_card-03-men-3_061"
],
"high_priority_cards": [
"アイドル宣言",
"アイドル魂",
"仕切り直し",
"第一印象",
"プライド",
"盛り上げ上手",
"インフルエンサー",
"タフネス",
"総合芸術",
"心・技・体",
"クライマックス",
"全身全霊",
"頂点へ"
"p_card-00-men-2_012",
"p_card-00-men-3_003",
"p_card-00-men-3_005",
"p_card-03-act-2_081",
"p_card-03-men-2_066",
"p_card-03-men-2_076",
"p_card-03-men-2_080",
"p_card-03-men-2_074",
"p_card-03-act-3_054",
"p_card-03-act-3_056",
"p_card-03-act-3_055",
"p_card-03-act-3_065",
"p_card-03-men-3_062"
],
"medium_priority_cards": [
"精一杯",
"ハッピー♪",
"嬉しい誤算",
"オープニングアクト",
"カウントダウン",
"忍耐力"
"p_card-03-act-1_044",
"p_card-03-men-1_039",
"p_card-03-men-1_049",
"p_card-03-act-2_063",
"p_card-03-act-2_073",
"p_card-03-men-2_077"
],
"low_priority_cards": [
"涙の思い出",
"はじけるパッション",
"理想のテンポ",
"ファーストステップ",
"テレビ出演",
"叶えたい夢"
"p_card-03-men-1_048",
"p_card-03-act-2_068",
"p_card-03-act-2_067",
"p_card-00-men-1_007",
"p_card-00-men-3_012",
"p_card-00-men-3_011"
]
},
{
"archetype": "温存",
"archetype": "ProduceExamEffectType_ExamFullPower",
"select_once_card_before_refresh": false,
"core_cards": [
"モチベ",
"アイドルになります"
"p_card-03-men-2_075",
"p_card-03-men-3_058"
],
"high_priority_cards": [
"アイドル宣言",
"アイドル魂",
"仕切り直し",
"プライド",
"盛り上げ上手",
"インフルエンサー",
"タフネス",
"翔び立て!",
"心・技・体",
"輝け!",
"全身全霊",
"頂点へ",
"覚悟"
"p_card-00-men-2_012",
"p_card-00-men-3_003",
"p_card-00-men-3_005",
"p_card-03-men-2_066",
"p_card-03-men-2_076",
"p_card-03-men-2_080",
"p_card-03-men-2_074",
"p_card-03-act-3_057",
"p_card-03-act-3_056",
"p_card-03-act-3_053",
"p_card-03-act-3_065",
"p_card-03-men-3_062",
"p_card-03-men-3_063"
],
"medium_priority_cards": [
"ハッピー♪",
"嬉しい誤算",
"涙の思い出",
"セッティング",
"オープニングアクト",
"トレーニングの成果",
"潜在能力",
"カウントダウン",
"忍耐力",
"クライマックス"
"p_card-03-men-1_049",
"p_card-03-men-1_048",
"p_card-03-men-1_051",
"p_card-03-act-2_063",
"p_card-03-act-2_071",
"p_card-03-men-2_064",
"p_card-03-men-2_077",
"p_card-03-act-3_055"
],
"low_priority_cards": [
"ジャストアピール",
"積み重ね",
"アッチェレランド",
"はじけるパッション",
"トレンドリーダー",
"ファーストステップ",
"テレビ出演",
"叶えたい夢"
]
},
{
"archetype": "全力",
"select_once_card_before_refresh": false,
"core_cards": [
"モチベ",
"アイドルになります"
],
"high_priority_cards": [
"アイドル宣言",
"アイドル魂",
"仕切り直し",
"プライド",
"盛り上げ上手",
"インフルエンサー",
"タフネス",
"翔び立て!",
"心・技・体",
"輝け!",
"全身全霊",
"頂点へ",
"覚悟"
],
"medium_priority_cards": [
"嬉しい誤算",
"涙の思い出",
"セッティング",
"オープニングアクト",
"トレーニングの成果",
"潜在能力",
"忍耐力",
"クライマックス"
],
"low_priority_cards": [
"ジャストアピール",
"積み重ね",
"アッチェレランド",
"はじけるパッション",
"トレンドリーダー",
"ファーストステップ",
"テレビ出演",
"叶えたい夢"
"p_card-03-act-1_038",
"p_card-03-act-1_042",
"p_card-03-act-2_072",
"p_card-03-act-2_068",
"p_card-03-act-2_082",
"p_card-00-men-1_007",
"p_card-00-men-3_012",
"p_card-00-men-3_011"
]
}
],

View File

@ -2,10 +2,8 @@ import json
import os
from logging import getLogger
from kotonebot import Interval, Countdown, ocr, contains, device, action
from kotonebot.backend.core import HintBox
from kotonebot.kaa.db import IdolCard
from kotonebot.kaa.skill_card.card_deck_config import DeckConfig
from kotonebot.kaa.skill_card.enum_constant import Archetype, archetype_dict, get_archetype
from kotonebot.kaa.skill_card.global_idol_setting import GlobalIdolSetting
logger = getLogger(__name__)
@ -26,64 +24,16 @@ idol_setting = GlobalIdolSetting()
# TODO: 这里应该从配置文件中读取
deck_config = get_default_config()
# P手帳
PGuide = HintBox(x1=595, y1=610, x2=690, y2=735, source_resolution=(720, 1280))
# 編成
Builder = HintBox(x1=530, y1=1080, x2=610, y2=1140, source_resolution=(720, 1280))
# 流派
IdolArchetype = HintBox(x1=515, y1=455, x2=655, y2=495, source_resolution=(720, 1280))
# P手帳退出
PGuideExit = HintBox(x1=320, y1=1155, x2=400, y2=1235, source_resolution=(720, 1280))
@action('根据日程更新偶像流派', screenshot_mode='manual-inherit')
def update_archetype_by_schedule():
def update_archetype_by_idol_skin_id(idol_skin_id: str):
"""
根据日程更新偶像流派
根据偶像皮肤id更新全局偶像信息
:param idol_skin_id:
:return:
"""
if not idol_setting.need_update:
return
it = Interval()
wait_cd = Countdown(sec=5).start()
device.screenshot()
while not ocr.find(contains("P手帳"), rect=PGuide):
if wait_cd.expired():
logger.warning("not found: P手帳, update deck failed")
return
it.wait()
device.screenshot()
device.click(PGuide)
it.wait()
device.screenshot()
wait_cd.reset()
while not ocr.find(contains("編成"), rect=Builder):
if wait_cd.expired():
break
it.wait()
device.screenshot()
device.click(Builder)
it.wait()
wait_cd.reset()
while not wait_cd.expired():
it.wait()
img = device.screenshot()
if text := ocr.raw().ocr(img, rect=IdolArchetype):
archetype = get_archetype(text.squash().text)
if archetype != Archetype.unidentified:
idol_setting.update_deck(archetype, deck_config)
break
if idol_setting.need_update:
logger.warning("Not found archetype, update deck failed")
# 如果在手册页面,尝试退出
it.wait()
wait_cd.reset()
device.screenshot()
while ocr.find(contains("編成"), rect=Builder) and not wait_cd.expired():
device.click(PGuideExit)
it.wait()
device.screenshot()
idol_setting.new_play()
idol_card = IdolCard.from_skin_id(idol_skin_id)
if idol_card:
idol_setting.update_deck(idol_card.exam_effect_type, deck_config)
else:
logger.warning(f"Can`t found archetype by skin id: {idol_skin_id}")

View File

@ -4,9 +4,7 @@ from kotonebot import device, image, Interval, ocr, contains, Countdown, action
from kotonebot.backend.core import HintBox
from kotonebot.kaa.game_ui import dialog, badge
from kotonebot.kaa.game_ui.skill_card_select import extract_cards
from kotonebot.kaa.skill_card.enum_constant import CardPriority
from kotonebot.kaa.skill_card_action.card_reader import ActualCard
from kotonebot.kaa.skill_card_action.global_idol_setting_action import idol_setting
from kotonebot.kaa.tasks import R
from kotonebot.primitives import Rect
@ -39,33 +37,31 @@ def select_skill_card():
continue
img = device.screenshot()
match_results = image.find_multi([
R.InPurodyuusu.A,
R.InPurodyuusu.M
])
if match_results:
cards = extract_cards(img)
cards = [ActualCard.create_by(card.rect, card.skill_card) for card in cards]
skill_card_elements = extract_cards(img)
if skill_card_elements:
cards = [ActualCard.create_by(skill_card_element) for skill_card_element in skill_card_elements]
cards = sorted(cards, reverse=True)
target_card = cards[0]
select_suggest = False
target_card = sorted(cards, key=lambda x: x.priority)[0]
select_other = target_card.priority == CardPriority.other
# 非卡组配置的卡时,判断是否需要刷新
if select_other:
# 若考虑先刷新,则尝试刷新,刷新成功则重新识别卡
if not idol_setting.select_once_card_before_refresh:
if try_refresh_skill_card(target_card.rect):
it.wait()
continue
# 选择一回一次的卡,如果没有,尝试刷新,刷新成功则重新识别卡
if once_cards := [card for card in cards if card.skill_card.once]:
target_card = once_cards[0]
elif try_refresh_skill_card(target_card.rect):
# 非卡组配置的卡时,尝试刷新
if not target_card.select():
if try_refresh_skill_card(target_card.skill_card_element.rect):
it.wait()
continue
logger.info(f"select {target_card.name}")
device.click(target_card.rect)
# 如果没刷新次数,尝试选取除外卡
if once_cards := [card for card in cards if card.lost()]:
target_card = once_cards[0]
else:
select_suggest = True
if select_suggest:
# 既没有刷新,也没有除外卡,选择推荐卡
card_rect = find_recommended_card_rect([card.skill_card_element.rect for card in cards])
logger.info(f"select recommended card")
device.click(card_rect)
else:
logger.info(f"select {target_card.skill_card_element.skill_card.name}")
device.click(target_card.skill_card_element.rect)
it.wait()
else:
@ -81,6 +77,26 @@ def select_skill_card():
logger.warning("Skill card select failed")
@action('寻找推荐卡', screenshot_mode='manual-inherit')
def find_recommended_card_rect(cards: list[Rect]) -> Rect:
# 判断是否有推荐卡
rec_badges = image.find_all(R.InPurodyuusu.TextRecommend)
rec_badges = [card.rect for card in rec_badges]
if rec_badges:
matches = badge.match(cards, rec_badges, 'mb')
logger.debug("Recommend card badge matches: %s", matches)
# 选第一个推荐卡
target_match = next(filter(lambda m: m.badge is not None, matches), None)
if target_match:
target_card = target_match.object
else:
target_card = cards[0]
else:
logger.debug("No recommend badge found. Pick first card.")
target_card = cards[0]
return target_card
@action('刷新技能卡', screenshot_mode='manual-inherit')
def try_refresh_skill_card(first_card: Rect) -> bool:
"""

View File

@ -24,63 +24,62 @@ logger = getLogger(__name__)
@action('领取技能卡', screenshot_mode='manual-inherit')
def acquire_skill_card():
"""获取技能卡(スキルカード)"""
select_skill_card()
# TODO: 识别卡片内容,而不是固定选卡
# TODO: 不硬编码坐标
# logger.debug("Locating all skill cards...")
#
# it = Interval()
# cards = None
# card_clicked = False
# target_card = None
#
# while True:
# device.screenshot()
# it.wait()
#
# # 是否显示技能卡选择指导的对话框
# # [kotonebot-resource/sprites/jp/in_purodyuusu/screenshot_show_skill_card_select_guide_dialog.png]
# if image.find(R.InPurodyuusu.TextSkillCardSelectGuideDialogTitle):
# # 默认就是显示,直接确认
# dialog.yes()
# continue
# if not cards:
# cards = image.find_all_multi([
# R.InPurodyuusu.A,
# R.InPurodyuusu.M
# ])
# if not cards:
# logger.warning("No skill cards found. Skip acquire.")
# return
# cards = sorted(cards, key=lambda x: (x.position[0], x.position[1]))
# logger.info(f"Found {len(cards)} skill cards")
# # 判断是否有推荐卡
# rec_badges = image.find_all(R.InPurodyuusu.TextRecommend)
# rec_badges = [card.rect for card in rec_badges]
# if rec_badges:
# cards = [card.rect for card in cards]
# matches = badge.match(cards, rec_badges, 'mb')
# logger.debug("Recommend card badge matches: %s", matches)
# # 选第一个推荐卡
# target_match = next(filter(lambda m: m.badge is not None, matches), None)
# if target_match:
# target_card = target_match.object
# else:
# target_card = cards[0]
# else:
# logger.debug("No recommend badge found. Pick first card.")
# target_card = cards[0].rect
# continue
# if not card_clicked and target_card is not None:
# logger.debug("Click target skill card")
# device.click(target_card)
# card_clicked = True
# sleep(0.2)
# continue
# if acquire_btn := image.find(R.InPurodyuusu.AcquireBtnDisabled):
# logger.debug("Click acquire button")
# device.click(acquire_btn)
# break
logger.debug("Locating all skill cards...")
it = Interval()
cards = None
card_clicked = False
target_card = None
while True:
device.screenshot()
it.wait()
# 是否显示技能卡选择指导的对话框
# [kotonebot-resource/sprites/jp/in_purodyuusu/screenshot_show_skill_card_select_guide_dialog.png]
if image.find(R.InPurodyuusu.TextSkillCardSelectGuideDialogTitle):
# 默认就是显示,直接确认
dialog.yes()
continue
if not cards:
cards = image.find_all_multi([
R.InPurodyuusu.A,
R.InPurodyuusu.M
])
if not cards:
logger.warning("No skill cards found. Skip acquire.")
return
cards = sorted(cards, key=lambda x: (x.position[0], x.position[1]))
logger.info(f"Found {len(cards)} skill cards")
# 判断是否有推荐卡
rec_badges = image.find_all(R.InPurodyuusu.TextRecommend)
rec_badges = [card.rect for card in rec_badges]
if rec_badges:
cards = [card.rect for card in cards]
matches = badge.match(cards, rec_badges, 'mb')
logger.debug("Recommend card badge matches: %s", matches)
# 选第一个推荐卡
target_match = next(filter(lambda m: m.badge is not None, matches), None)
if target_match:
target_card = target_match.object
else:
target_card = cards[0]
else:
logger.debug("No recommend badge found. Pick first card.")
target_card = cards[0].rect
continue
if not card_clicked and target_card is not None:
logger.debug("Click target skill card")
device.click(target_card)
card_clicked = True
sleep(0.2)
continue
if acquire_btn := image.find(R.InPurodyuusu.AcquireBtnDisabled):
logger.debug("Click acquire button")
device.click(acquire_btn)
break
@action('选择P物品', screenshot_mode='auto')
def select_p_item():
@ -261,7 +260,7 @@ def fast_acquisitions() -> AcquisitionType | None:
logger.debug("Check skill card select...")
if image.find(R.InPurodyuusu.TextSkillCard):
logger.info("Acquire skill card found")
acquire_skill_card()
select_skill_card()
return "PSkillCardSelect"
# P物品选择
logger.debug("Check PItem select...")

View File

@ -3,7 +3,6 @@ from typing_extensions import assert_never
from typing import Literal
from kotonebot.kaa.game_ui.schedule import Schedule
from kotonebot.kaa.skill_card_action.global_idol_setting_action import update_archetype_by_schedule
from kotonebot.kaa.tasks import R
from ..actions import loading
from kotonebot.kaa.game_ui import WhiteFilter, dialog
@ -736,7 +735,6 @@ def hajime_from_stage(stage: ProduceStage, type: Literal['regular', 'pro', 'mast
remaining_week = texts.squash().replace('ó', '6').numbers()
if not remaining_week:
raise UnrecoverableError("Failed to detect week. text=" + repr(texts.squash()))
update_archetype_by_schedule()
# 判断阶段
match type:
case 'regular':

View File

@ -3,7 +3,7 @@ from itertools import cycle
from typing import Optional, Literal
from typing_extensions import assert_never
from kotonebot.kaa.skill_card_action.global_idol_setting_action import idol_setting
from kotonebot.kaa.skill_card_action.global_idol_setting_action import update_archetype_by_idol_skin_id
from kotonebot.ui import user
from kotonebot.util import Countdown, Interval
from kotonebot.backend.context.context import wait
@ -13,7 +13,7 @@ from kotonebot.kaa.tasks import R
from kotonebot.kaa.common import conf
from kotonebot.kaa.game_ui import dialog
from ..actions.scenes import at_home, goto_home
from kotonebot.kaa.game_ui.idols_overview import locate_idol
from kotonebot.kaa.game_ui.idols_overview import locate_idol, find_idol_skin_id_on_resume_produce
from ..produce.in_purodyuusu import hajime_pro, hajime_regular, hajime_master, resume_pro_produce, resume_regular_produce, \
resume_master_produce
from kotonebot import device, image, ocr, task, action, sleep, contains, regex
@ -168,6 +168,12 @@ def resume_produce():
raise ValueError('Failed to detect weeks after multiple retries.')
if current_week is None:
raise ValueError('Failed to detect current_week.')
# 更新全局偶像信息
img = device.screenshot()
skin_id = find_idol_skin_id_on_resume_produce(img)
update_archetype_by_idol_skin_id(skin_id)
# 点击 再開する
# [kotonebot-resource/sprites/jp/produce/produce_resume.png]
logger.info('Click resume button.')
@ -201,7 +207,6 @@ def do_produce(
if not at_home():
goto_home()
idol_setting.new_play()
device.screenshot()
# 有进行中培育的情况
if ocr.find(contains(''), rect=R.Produce.BoxProduceOngoing):
@ -209,6 +214,8 @@ def do_produce(
resume_produce()
return True
# 新培育时更新偶像信息
update_archetype_by_idol_skin_id(idol_skin_id)
# 0. 进入培育页面
mode_text = mode.upper()
if mode_text == 'MASTER':