feat(*): 日常新增支持指定购买商品 & 部分优化
1. 日常新增支持指定购买商品 2. 新增 DispatcherContext.expand,允许在一个 dispatcher 函数内复用其他 dispatcher 函数 3. 修复 make_resources.py 生成结果中部分变量命名格式不正确的问题
This commit is contained in:
parent
58a8a8da72
commit
4154c5541e
|
@ -115,7 +115,7 @@ def action(
|
|||
pass_through: bool = False,
|
||||
priority: int = 0,
|
||||
screenshot_mode: ScreenshotMode | None = None,
|
||||
dispatcher: Literal[True] = True,
|
||||
dispatcher: Literal[True, 'fragment'] = True,
|
||||
) -> Callable[[Callable[Concatenate[DispatcherContext, P], R]], Callable[P, R]]:
|
||||
"""
|
||||
`action` 装饰器,用于标记一个函数为动作函数。
|
||||
|
@ -135,6 +135,9 @@ def action(
|
|||
"""
|
||||
...
|
||||
|
||||
# TODO: 需要找个地方统一管理这些属性名
|
||||
ATTR_ORIGINAL_FUNC = '_kb_inner'
|
||||
ATTR_ACTION_MARK = '__kb_action_mark'
|
||||
def action(*args, **kwargs):
|
||||
def _register(func: Callable, name: str, description: str|None = None, priority: int = 0) -> Action:
|
||||
description = description or func.__doc__ or ''
|
||||
|
@ -143,7 +146,6 @@ def action(*args, **kwargs):
|
|||
logger.debug(f'Action "{name}" registered.')
|
||||
return action
|
||||
|
||||
|
||||
if len(args) == 1 and isinstance(args[0], Callable):
|
||||
func = args[0]
|
||||
action = _register(_placeholder, func.__name__, func.__doc__)
|
||||
|
@ -154,6 +156,8 @@ def action(*args, **kwargs):
|
|||
ContextStackVars.pop()
|
||||
current_callstack.pop()
|
||||
return ret
|
||||
setattr(_wrapper, ATTR_ORIGINAL_FUNC, func)
|
||||
setattr(_wrapper, ATTR_ACTION_MARK, True)
|
||||
action.func = _wrapper
|
||||
return _wrapper
|
||||
else:
|
||||
|
@ -163,7 +167,7 @@ def action(*args, **kwargs):
|
|||
priority = kwargs.get('priority', 0)
|
||||
screenshot_mode = kwargs.get('screenshot_mode', None)
|
||||
dispatcher = kwargs.get('dispatcher', False)
|
||||
if dispatcher:
|
||||
if dispatcher == True or dispatcher == 'fragment':
|
||||
if not (screenshot_mode is None or screenshot_mode == 'manual'):
|
||||
raise ValueError('`screenshot_mode` must be None or "manual" when `dispatcher=True`.')
|
||||
screenshot_mode = 'manual'
|
||||
|
@ -175,7 +179,7 @@ def action(*args, **kwargs):
|
|||
return func
|
||||
else:
|
||||
if dispatcher:
|
||||
func = dispatcher_decorator(func) # type: ignore
|
||||
func = dispatcher_decorator(func, fragment=(dispatcher == 'fragment')) # type: ignore
|
||||
def _wrapper(*args: P.args, **kwargs: P.kwargs):
|
||||
current_callstack.append(action)
|
||||
vars = ContextStackVars.push(screenshot_mode=screenshot_mode)
|
||||
|
@ -183,6 +187,8 @@ def action(*args, **kwargs):
|
|||
ContextStackVars.pop()
|
||||
current_callstack.pop()
|
||||
return ret
|
||||
setattr(_wrapper, ATTR_ORIGINAL_FUNC, func)
|
||||
setattr(_wrapper, ATTR_ACTION_MARK, True)
|
||||
action.func = _wrapper
|
||||
return _wrapper
|
||||
return _action_decorator
|
||||
|
|
|
@ -3,7 +3,7 @@ import logging
|
|||
import inspect
|
||||
from logging import Logger
|
||||
from types import CodeType
|
||||
from typing import Any, Callable, Concatenate, TypeVar, ParamSpec, Literal, Protocol
|
||||
from typing import Annotated, Any, Callable, Concatenate, TypeVar, ParamSpec, Literal, Protocol, cast
|
||||
from typing_extensions import Self
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
@ -15,22 +15,75 @@ P = ParamSpec('P')
|
|||
R = TypeVar('R')
|
||||
ThenAction = Literal['click', 'log']
|
||||
DoAction = Literal['click']
|
||||
# TODO: 需要找个地方统一管理这些属性名
|
||||
ATTR_DISPATCHER_MARK = '__kb_dispatcher_mark'
|
||||
ATTR_ORIGINAL_FUNC = '_kb_inner'
|
||||
|
||||
|
||||
class DispatchFunc: pass
|
||||
|
||||
wrapper_to_func: dict[Callable, Callable] = {}
|
||||
|
||||
class DispatcherContext:
|
||||
def __init__(self):
|
||||
self.finished: bool = False
|
||||
self._first_run: bool = True
|
||||
|
||||
def finish(self):
|
||||
"""标记已完成 dispatcher 循环。循环将在下次条件检测时退出。"""
|
||||
self.finished = True
|
||||
|
||||
def dispatcher(func: Callable[Concatenate[DispatcherContext, P], R]) -> Callable[P, R]:
|
||||
"""
|
||||
def expand(self, func: Annotated[Callable[[], Any], DispatchFunc], ignore_finish: bool = True):
|
||||
"""
|
||||
调用其他 dispatcher 函数。
|
||||
|
||||
使用 `expand` 和直接调用的区别是:
|
||||
* 直接调用会执行 while 循环,直到满足结束条件
|
||||
* 而使用 `expand` 则只会执行一次。效果类似于将目标函数里的代码直接复制粘贴过来。
|
||||
"""
|
||||
# 获取原始函数
|
||||
original_func = func
|
||||
while not getattr(original_func, ATTR_DISPATCHER_MARK, False):
|
||||
original_func = getattr(original_func, ATTR_ORIGINAL_FUNC)
|
||||
original_func = getattr(original_func, ATTR_ORIGINAL_FUNC)
|
||||
|
||||
if not original_func:
|
||||
raise ValueError(f'{repr(func)} is not a dispatcher function.')
|
||||
elif not callable(original_func):
|
||||
raise ValueError(f'{repr(original_func)} is not callable.')
|
||||
original_func = cast(Callable[[DispatcherContext], Any], original_func)
|
||||
|
||||
old_finished = self.finished
|
||||
ret = original_func(self)
|
||||
if ignore_finish:
|
||||
self.finished = old_finished
|
||||
return ret
|
||||
|
||||
@property
|
||||
def beginning(self) -> bool:
|
||||
"""是否为第一次运行"""
|
||||
return self._first_run
|
||||
|
||||
@property
|
||||
def finishing(self) -> bool:
|
||||
"""是否即将结束运行"""
|
||||
return self.finished
|
||||
|
||||
def dispatcher(
|
||||
func: Callable[Concatenate[DispatcherContext, P], R],
|
||||
*,
|
||||
fragment: bool = False
|
||||
) -> Annotated[Callable[P, R], DispatchFunc]:
|
||||
"""
|
||||
注意:\n
|
||||
此装饰器必须在应用 @action/@task 装饰器后再应用,且 `screenshot_mode='manual'` 参数必须设置。
|
||||
或者也可以使用 @action/@task 装饰器中的 `dispatcher=True` 参数,
|
||||
那么就没有上面两个要求了。
|
||||
|
||||
:param fragment:
|
||||
片段模式,默认不启用。
|
||||
启用后,被装饰函数将会只执行依次,
|
||||
而不会一直循环到 ctx.finish() 被调用。
|
||||
"""
|
||||
ctx = DispatcherContext()
|
||||
def wrapper(*args: P.args, **kwargs: P.kwargs):
|
||||
|
@ -38,5 +91,19 @@ def dispatcher(func: Callable[Concatenate[DispatcherContext, P], R]) -> Callable
|
|||
from kotonebot import device
|
||||
device.update_screenshot()
|
||||
ret = func(ctx, *args, **kwargs)
|
||||
ctx._first_run = False
|
||||
return ret
|
||||
return wrapper
|
||||
def fragment_wrapper(*args: P.args, **kwargs: P.kwargs):
|
||||
from kotonebot import device
|
||||
device.update_screenshot()
|
||||
return func(ctx, *args, **kwargs)
|
||||
setattr(wrapper, ATTR_ORIGINAL_FUNC, func)
|
||||
setattr(fragment_wrapper, ATTR_ORIGINAL_FUNC, func)
|
||||
setattr(wrapper, ATTR_DISPATCHER_MARK, True)
|
||||
setattr(fragment_wrapper, ATTR_DISPATCHER_MARK, True)
|
||||
wrapper_to_func[wrapper] = func
|
||||
if fragment:
|
||||
return fragment_wrapper
|
||||
|
||||
else:
|
||||
return wrapper
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Literal
|
||||
from typing import Literal, Dict
|
||||
from enum import IntEnum, Enum
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
@ -102,13 +102,136 @@ class PIdol(Enum):
|
|||
藤田ことね_冠菊 = ["藤田ことね", "冠菊"]
|
||||
藤田ことね_初声 = ["藤田ことね", "初声"]
|
||||
藤田ことね_学園生活 = ["藤田ことね", "学園生活"]
|
||||
|
||||
class DailyMoneyShopItems(IntEnum):
|
||||
"""日常商店物品"""
|
||||
Recommendations = -1
|
||||
"""所有推荐商品"""
|
||||
LessonNote = 0
|
||||
"""レッスンノート"""
|
||||
VeteranNote = 1
|
||||
"""ベテランノート"""
|
||||
SupportEnhancementPt = 2
|
||||
"""サポート強化Pt 支援强化Pt"""
|
||||
SenseNoteVocal = 3
|
||||
"""センスノート(ボーカル)感性笔记(声乐)"""
|
||||
SenseNoteDance = 4
|
||||
"""センスノート(ダンス)感性笔记(舞蹈)"""
|
||||
SenseNoteVisual = 5
|
||||
"""センスノート(ビジュアル)感性笔记(形象)"""
|
||||
LogicNoteVocal = 6
|
||||
"""ロジックノート(ボーカル)理性笔记(声乐)"""
|
||||
LogicNoteDance = 7
|
||||
"""ロジックノート(ダンス)理性笔记(舞蹈)"""
|
||||
LogicNoteVisual = 8
|
||||
"""ロジックノート(ビジュアル)理性笔记(形象)"""
|
||||
AnomalyNoteVocal = 9
|
||||
"""アノマリーノート(ボーカル)非凡笔记(声乐)"""
|
||||
AnomalyNoteDance = 10
|
||||
"""アノマリーノート(ダンス)非凡笔记(舞蹈)"""
|
||||
AnomalyNoteVisual = 11
|
||||
"""アノマリーノート(ビジュアル)非凡笔记(形象)"""
|
||||
RechallengeTicket = 12
|
||||
"""再挑戦チケット 重新挑战券"""
|
||||
RecordKey = 13
|
||||
|
||||
# 碎片
|
||||
"""記録の鍵 解锁交流的物品"""
|
||||
IdolPiece_倉本千奈_WonderScale = 14
|
||||
"""倉本千奈 WonderScale 碎片"""
|
||||
IdolPiece_篠泽广_光景 = 15
|
||||
"""篠泽广 光景 碎片"""
|
||||
IdolPiece_紫云清夏_TameLieOneStep = 16
|
||||
"""紫云清夏 Tame-Lie-One-Step 碎片"""
|
||||
|
||||
@classmethod
|
||||
def to_ui_text(cls, item: "DailyMoneyShopItems") -> str:
|
||||
"""获取枚举值对应的UI显示文本"""
|
||||
MAP = {
|
||||
cls.Recommendations: "所有推荐商品",
|
||||
cls.LessonNote: "课程笔记",
|
||||
cls.VeteranNote: "老手笔记",
|
||||
cls.SupportEnhancementPt: "支援强化点数",
|
||||
cls.SenseNoteVocal: "感性笔记(声乐)",
|
||||
cls.SenseNoteDance: "感性笔记(舞蹈)",
|
||||
cls.SenseNoteVisual: "感性笔记(形象)",
|
||||
cls.LogicNoteVocal: "理性笔记(声乐)",
|
||||
cls.LogicNoteDance: "理性笔记(舞蹈)",
|
||||
cls.LogicNoteVisual: "理性笔记(形象)",
|
||||
cls.AnomalyNoteVocal: "非凡笔记(声乐)",
|
||||
cls.AnomalyNoteDance: "非凡笔记(舞蹈)",
|
||||
cls.AnomalyNoteVisual: "非凡笔记(形象)",
|
||||
cls.RechallengeTicket: "重新挑战券",
|
||||
cls.RecordKey: "记录钥匙",
|
||||
cls.IdolPiece_倉本千奈_WonderScale: "倉本千奈 WonderScale 碎片",
|
||||
cls.IdolPiece_篠泽广_光景: "篠泽广 光景 碎片",
|
||||
cls.IdolPiece_紫云清夏_TameLieOneStep: "紫云清夏 Tame-Lie-One-Step 碎片"
|
||||
}
|
||||
return MAP.get(item, str(item))
|
||||
|
||||
@classmethod
|
||||
def all(cls) -> list[tuple[str, 'DailyMoneyShopItems']]:
|
||||
"""获取所有枚举值及其对应的UI显示文本"""
|
||||
return [(cls.to_ui_text(item), item) for item in cls]
|
||||
|
||||
def to_resource(self):
|
||||
from . import R
|
||||
match self:
|
||||
case DailyMoneyShopItems.Recommendations:
|
||||
return R.Daily.TextShopRecommended
|
||||
case DailyMoneyShopItems.LessonNote:
|
||||
return R.Shop.ItemLessonNote
|
||||
case DailyMoneyShopItems.VeteranNote:
|
||||
return R.Shop.ItemVeteranNote
|
||||
case DailyMoneyShopItems.SupportEnhancementPt:
|
||||
return R.Shop.ItemSupportEnhancementPt
|
||||
case DailyMoneyShopItems.SenseNoteVocal:
|
||||
return R.Shop.ItemSenseNoteVocal
|
||||
case DailyMoneyShopItems.SenseNoteDance:
|
||||
return R.Shop.ItemSenseNoteDance
|
||||
case DailyMoneyShopItems.SenseNoteVisual:
|
||||
return R.Shop.ItemSenseNoteVisual
|
||||
case DailyMoneyShopItems.LogicNoteVocal:
|
||||
return R.Shop.ItemLogicNoteVocal
|
||||
case DailyMoneyShopItems.LogicNoteDance:
|
||||
return R.Shop.ItemLogicNoteDance
|
||||
case DailyMoneyShopItems.LogicNoteVisual:
|
||||
return R.Shop.ItemLogicNoteVisual
|
||||
case DailyMoneyShopItems.AnomalyNoteVocal:
|
||||
return R.Shop.ItemAnomalyNoteVocal
|
||||
case DailyMoneyShopItems.AnomalyNoteDance:
|
||||
return R.Shop.ItemAnomalyNoteDance
|
||||
case DailyMoneyShopItems.AnomalyNoteVisual:
|
||||
return R.Shop.ItemAnomalyNoteVisual
|
||||
case DailyMoneyShopItems.RechallengeTicket:
|
||||
return R.Shop.ItemRechallengeTicket
|
||||
case DailyMoneyShopItems.RecordKey:
|
||||
return R.Shop.ItemRecordKey
|
||||
case DailyMoneyShopItems.IdolPiece_倉本千奈_WonderScale:
|
||||
return R.Shop.IdolPiece.倉本千奈_WonderScale
|
||||
case DailyMoneyShopItems.IdolPiece_篠泽广_光景:
|
||||
return R.Shop.IdolPiece.篠泽广_光景
|
||||
case DailyMoneyShopItems.IdolPiece_紫云清夏_TameLieOneStep:
|
||||
return R.Shop.IdolPiece.紫云清夏_TameLieOneStep
|
||||
case _:
|
||||
raise ValueError(f"Unknown daily shop item: {self}")
|
||||
|
||||
|
||||
class PurchaseConfig(BaseModel):
|
||||
enabled: bool = False
|
||||
"""是否启用商店购买"""
|
||||
money_enabled: bool = False
|
||||
"""是否启用金币购买"""
|
||||
money_items: list[DailyMoneyShopItems] = []
|
||||
"""金币商店要购买的物品"""
|
||||
money_refresh_on: Literal['never', 'not_found', 'always'] = 'never'
|
||||
"""
|
||||
金币商店刷新逻辑。
|
||||
|
||||
* never: 从不刷新。
|
||||
* not_found: 仅当要购买的物品不存在时刷新。
|
||||
* always: 总是刷新。
|
||||
"""
|
||||
ap_enabled: bool = False
|
||||
"""是否启用AP购买"""
|
||||
ap_items: list[Literal[0, 1, 2, 3]] = []
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from . import R
|
||||
from .common import conf, PIdol
|
||||
|
@ -95,6 +96,9 @@ def select_idol(target_titles: list[str] | PIdol):
|
|||
def do_produce(idol: PIdol | None = None):
|
||||
"""
|
||||
进行培育流程
|
||||
|
||||
前置条件:可导航至首页的任意页面\n
|
||||
结束状态:游戏首页\n
|
||||
|
||||
:param idol: 要培育的偶像。如果为 None,则使用配置文件中的偶像。
|
||||
"""
|
||||
|
@ -162,14 +166,23 @@ def do_produce(idol: PIdol | None = None):
|
|||
wait_loading_end()
|
||||
hajime_regular()
|
||||
|
||||
|
||||
@task('培育')
|
||||
def produce_task():
|
||||
def produce_task(count: Optional[int] = None):
|
||||
"""
|
||||
培育任务
|
||||
|
||||
:param count:
|
||||
培育次数。若为 None,则从配置文件中读入。
|
||||
"""
|
||||
import time
|
||||
start_time = time.time()
|
||||
do_produce()
|
||||
end_time = time.time()
|
||||
logger.info(f"Produce time used: {format_time(end_time - start_time)}")
|
||||
if count is None:
|
||||
count = conf().produce.produce_count
|
||||
for _ in range(count):
|
||||
start_time = time.time()
|
||||
do_produce()
|
||||
|
||||
end_time = time.time()
|
||||
logger.info(f"Produce time used: {format_time(end_time - start_time)}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
import logging
|
||||
|
|
|
@ -1,14 +1,104 @@
|
|||
"""从商店购买物品"""
|
||||
import logging
|
||||
from typing import Optional
|
||||
from typing_extensions import deprecated
|
||||
|
||||
from . import R
|
||||
from .common import conf
|
||||
from .common import conf, DailyMoneyShopItems
|
||||
from kotonebot.backend.util import cropped
|
||||
from kotonebot import task, device, image, ocr, action, sleep
|
||||
from kotonebot.backend.dispatch import DispatcherContext, dispatcher
|
||||
from .actions.scenes import goto_home, goto_shop, at_daily_shop
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@action('购买 Money 物品', screenshot_mode='manual-inherit')
|
||||
def money_items2(items: Optional[list[DailyMoneyShopItems]] = None):
|
||||
"""
|
||||
购买 Money 物品
|
||||
|
||||
前置条件:商店页面的 マニー Tab\n
|
||||
结束状态:-
|
||||
|
||||
:param items: 要购买的物品列表,默认为 None。为 None 时使用配置文件里的设置。
|
||||
"""
|
||||
# 前置条件:[screenshots\shop\money1.png]
|
||||
logger.info(f'Purchasing マニー items.')
|
||||
|
||||
if items is None:
|
||||
items = conf().purchase.money_items
|
||||
|
||||
device.update_screenshot()
|
||||
if DailyMoneyShopItems.Recommendations in items:
|
||||
dispatch_recommended_items()
|
||||
items.remove(DailyMoneyShopItems.Recommendations)
|
||||
|
||||
finished = []
|
||||
max_scroll = 3
|
||||
scroll = 0
|
||||
while items:
|
||||
for item in items:
|
||||
if image.find(item.to_resource()):
|
||||
logger.info(f'Purchasing {item.to_ui_text(item)}...')
|
||||
device.click()
|
||||
dispatch_purchase_dialog()
|
||||
finished.append(item)
|
||||
items = [item for item in items if item not in finished]
|
||||
# 全都买完了
|
||||
if not items:
|
||||
break
|
||||
# 还有,翻页后继续
|
||||
else:
|
||||
device.swipe_scaled(x1=0.5, x2=0.5, y1=0.8, y2=0.5)
|
||||
sleep(0.5)
|
||||
device.update_screenshot()
|
||||
scroll += 1
|
||||
if scroll >= max_scroll:
|
||||
break
|
||||
logger.info(f'Purchasing money items completed. {len(finished)} item(s) purchased.')
|
||||
if items:
|
||||
logger.info(f'{len(items)} item(s) not purchased: {", ".join([item.to_ui_text(item) for item in items])}')
|
||||
|
||||
@action('购买推荐商品', dispatcher=True)
|
||||
def dispatch_recommended_items(ctx: DispatcherContext):
|
||||
"""
|
||||
购买推荐商品
|
||||
|
||||
前置条件:商店页面的 マニー Tab\n
|
||||
结束状态:-
|
||||
"""
|
||||
# 前置条件:[screenshots\shop\money1.png]
|
||||
if ctx.beginning:
|
||||
logger.info(f'Start purchasing recommended items.')
|
||||
|
||||
if image.find(R.Daily.TextShopRecommended):
|
||||
logger.info(f'Clicking on recommended item.') # TODO: 计数
|
||||
device.click()
|
||||
elif ctx.expand(dispatch_purchase_dialog):
|
||||
pass
|
||||
elif image.find(R.Daily.IconTitleDailyShop) and not image.find(R.Daily.TextShopRecommended):
|
||||
logger.info(f'No recommended item found. Finished.')
|
||||
ctx.finish()
|
||||
|
||||
@action('确认购买', dispatcher=True)
|
||||
def dispatch_purchase_dialog(ctx: DispatcherContext):
|
||||
"""
|
||||
确认购买
|
||||
|
||||
前置条件:购买确认对话框\n
|
||||
结束状态:对话框关闭后原来的界面
|
||||
"""
|
||||
# 前置条件:[screenshots\shop\dialog.png]
|
||||
if image.find(R.Daily.ButtonShopCountAdd, colored=True):
|
||||
logger.debug('Adjusting quantity(+1)...')
|
||||
device.click()
|
||||
elif image.find(R.Common.ButtonConfirm):
|
||||
logger.debug('Confirming purchase...')
|
||||
# device.click()
|
||||
device.click(image.expect(R.InPurodyuusu.ButtonCancel))
|
||||
ctx.finish()
|
||||
|
||||
@deprecated('改用 `money_items2`')
|
||||
@action('购买 Money 物品')
|
||||
def money_items():
|
||||
"""
|
||||
|
@ -99,7 +189,7 @@ def purchase():
|
|||
# 购买マニー物品
|
||||
if conf().purchase.money_enabled:
|
||||
image.expect_wait(R.Daily.IconShopMoney)
|
||||
money_items()
|
||||
money_items2()
|
||||
sleep(0.5)
|
||||
else:
|
||||
logger.info('Money purchase is disabled.')
|
||||
|
@ -121,6 +211,4 @@ if __name__ == '__main__':
|
|||
import logging
|
||||
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s] [%(lineno)d] %(message)s')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
# money_items()
|
||||
# ap_items([0, 1, 3])
|
||||
purchase()
|
||||
dispatch_recommended_items()
|
||||
|
|
|
@ -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, PIdol
|
||||
MissionRewardConfig, PIdol, DailyMoneyShopItems
|
||||
)
|
||||
from kotonebot.config.base_config import UserConfig, BackendConfig
|
||||
|
||||
|
@ -104,6 +104,7 @@ class KotoneBotUI:
|
|||
money_enabled: bool,
|
||||
ap_enabled: bool,
|
||||
ap_items: List[str],
|
||||
money_items: List[DailyMoneyShopItems],
|
||||
activity_funds_enabled: bool,
|
||||
presents_enabled: bool,
|
||||
assignment_enabled: bool,
|
||||
|
@ -125,9 +126,9 @@ class KotoneBotUI:
|
|||
) -> str:
|
||||
ap_items_enum: List[Literal[0, 1, 2, 3]] = []
|
||||
ap_items_map: Dict[str, APShopItems] = {
|
||||
"获取支援强化 Pt 提升": APShopItems.PRODUCE_PT_UP,
|
||||
"获取笔记数提升": APShopItems.PRODUCE_NOTE_UP,
|
||||
"再挑战券": APShopItems.RECHALLENGE,
|
||||
"支援强化点数提升": APShopItems.PRODUCE_PT_UP,
|
||||
"笔记数提升": APShopItems.PRODUCE_NOTE_UP,
|
||||
"重新挑战券": APShopItems.RECHALLENGE,
|
||||
"回忆再生成券": APShopItems.REGENERATE_MEMORY
|
||||
}
|
||||
for item in ap_items:
|
||||
|
@ -141,6 +142,7 @@ class KotoneBotUI:
|
|||
purchase=PurchaseConfig(
|
||||
enabled=purchase_enabled,
|
||||
money_enabled=money_enabled,
|
||||
money_items=money_items,
|
||||
ap_enabled=ap_enabled,
|
||||
ap_items=ap_items_enum
|
||||
),
|
||||
|
@ -217,7 +219,7 @@ class KotoneBotUI:
|
|||
outputs=[task_status]
|
||||
)
|
||||
|
||||
def _create_purchase_settings(self) -> Tuple[gr.Checkbox, gr.Checkbox, gr.Checkbox, gr.Dropdown]:
|
||||
def _create_purchase_settings(self) -> Tuple[gr.Checkbox, gr.Checkbox, gr.Checkbox, gr.Dropdown, gr.Dropdown]:
|
||||
with gr.Column():
|
||||
gr.Markdown("### 商店购买设置")
|
||||
purchase_enabled = gr.Checkbox(
|
||||
|
@ -229,6 +231,15 @@ class KotoneBotUI:
|
|||
label="启用金币购买",
|
||||
value=self.current_config.options.purchase.money_enabled
|
||||
)
|
||||
|
||||
# 添加金币商店商品选择
|
||||
money_items = gr.Dropdown(
|
||||
multiselect=True,
|
||||
choices=list(DailyMoneyShopItems.all()),
|
||||
value=self.current_config.options.purchase.money_items,
|
||||
label="金币商店购买物品"
|
||||
)
|
||||
|
||||
ap_enabled = gr.Checkbox(
|
||||
label="启用AP购买",
|
||||
value=self.current_config.options.purchase.ap_enabled
|
||||
|
@ -237,9 +248,9 @@ class KotoneBotUI:
|
|||
# 转换枚举值为显示文本
|
||||
selected_items: List[str] = []
|
||||
ap_items_map = {
|
||||
APShopItems.PRODUCE_PT_UP: "获取支援强化 Pt 提升",
|
||||
APShopItems.PRODUCE_NOTE_UP: "获取笔记数提升",
|
||||
APShopItems.RECHALLENGE: "再挑战券",
|
||||
APShopItems.PRODUCE_PT_UP: "支援强化点数提升",
|
||||
APShopItems.PRODUCE_NOTE_UP: "笔记数提升",
|
||||
APShopItems.RECHALLENGE: "重新挑战券",
|
||||
APShopItems.REGENERATE_MEMORY: "回忆再生成券"
|
||||
}
|
||||
for item_value in self.current_config.options.purchase.ap_items:
|
||||
|
@ -259,7 +270,7 @@ class KotoneBotUI:
|
|||
inputs=[purchase_enabled],
|
||||
outputs=[purchase_group]
|
||||
)
|
||||
return purchase_enabled, money_enabled, ap_enabled, ap_items
|
||||
return purchase_enabled, money_enabled, ap_enabled, ap_items, money_items
|
||||
|
||||
def _create_work_settings(self) -> Tuple[gr.Checkbox, gr.Checkbox, gr.Dropdown, gr.Checkbox, gr.Dropdown]:
|
||||
with gr.Column():
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 861 KiB |
|
@ -0,0 +1 @@
|
|||
{"definitions":{"0949c622-9067-4f0d-bac2-3f938a1d2ed2":{"name":"Shop.ItemLessonNote","displayName":"レッスンノート","type":"template","annotationId":"0949c622-9067-4f0d-bac2-3f938a1d2ed2","useHintRect":false},"b2af59e9-60e3-4d97-8c72-c7ba092113a3":{"name":"Shop.ItemVeteranNote","displayName":"ベテランノート","type":"template","annotationId":"b2af59e9-60e3-4d97-8c72-c7ba092113a3","useHintRect":false},"835489e2-b29b-426c-b4c9-3bb9f8eb6195":{"name":"Shop.ItemSupportEnhancementPt","displayName":"サポート強化Pt 支援强化Pt","type":"template","annotationId":"835489e2-b29b-426c-b4c9-3bb9f8eb6195","useHintRect":false},"c5b7d67e-7260-4f08-a0e9-4d31ce9bbecf":{"name":"Shop.ItemSenseNoteVocal","displayName":"センスノート(ボーカル)感性笔记(声乐)","type":"template","annotationId":"c5b7d67e-7260-4f08-a0e9-4d31ce9bbecf","useHintRect":false},"0f7d581d-cea3-4039-9205-732e4cd29293":{"name":"Shop.ItemSenseNoteDance","displayName":"センスノート(ダンス)感性笔记(舞蹈)","type":"template","annotationId":"0f7d581d-cea3-4039-9205-732e4cd29293","useHintRect":false},"d3cc3323-51af-4882-ae12-49e7384b746d":{"name":"Shop.ItemSenseNoteVisual","displayName":"センスノート(ビジュアル)感性笔记(形象)","type":"template","annotationId":"d3cc3323-51af-4882-ae12-49e7384b746d","useHintRect":false},"a1df3af1-a3e7-4521-a085-e4dc3cd1cc57":{"name":"Shop.ItemLogicNoteVocal","displayName":"ロジックノート(ボーカル)理性笔记(声乐)","type":"template","annotationId":"a1df3af1-a3e7-4521-a085-e4dc3cd1cc57","useHintRect":false},"a9fcaf04-0c1f-4b0f-bb5b-ede9da96180f":{"name":"Shop.ItemLogicNoteDance","displayName":"ロジックノート(ダンス)理性笔记(舞蹈)","type":"template","annotationId":"a9fcaf04-0c1f-4b0f-bb5b-ede9da96180f","useHintRect":false},"c3f536d6-a04a-4651-b3f9-dd2c22593f7f":{"name":"Shop.ItemLogicNoteVisual","displayName":"ロジックノート(ビジュアル)理性笔记(形象)","type":"template","annotationId":"c3f536d6-a04a-4651-b3f9-dd2c22593f7f","useHintRect":false},"eef25cf9-afd0-43b1-b9c5-fbd997bd5fe0":{"name":"Shop.ItemAnomalyNoteVocal","displayName":"アノマリーノート(ボーカル)非凡笔记(声乐)","type":"template","annotationId":"eef25cf9-afd0-43b1-b9c5-fbd997bd5fe0","useHintRect":false},"df991b42-ed8e-4f2c-bf0c-aa7522f147b6":{"name":"Shop.ItemAnomalyNoteDance","displayName":"アノマリーノート(ダンス)非凡笔记(舞蹈)","type":"template","annotationId":"df991b42-ed8e-4f2c-bf0c-aa7522f147b6","useHintRect":false}},"annotations":[{"id":"0949c622-9067-4f0d-bac2-3f938a1d2ed2","type":"rect","data":{"x1":243,"y1":355,"x2":313,"y2":441}},{"id":"b2af59e9-60e3-4d97-8c72-c7ba092113a3","type":"rect","data":{"x1":414,"y1":355,"x2":484,"y2":441}},{"id":"835489e2-b29b-426c-b4c9-3bb9f8eb6195","type":"rect","data":{"x1":574,"y1":363,"x2":662,"y2":438}},{"id":"c5b7d67e-7260-4f08-a0e9-4d31ce9bbecf","type":"rect","data":{"x1":71,"y1":594,"x2":142,"y2":667}},{"id":"0f7d581d-cea3-4039-9205-732e4cd29293","type":"rect","data":{"x1":241,"y1":593,"x2":309,"y2":667}},{"id":"d3cc3323-51af-4882-ae12-49e7384b746d","type":"rect","data":{"x1":417,"y1":586,"x2":481,"y2":668}},{"id":"a1df3af1-a3e7-4521-a085-e4dc3cd1cc57","type":"rect","data":{"x1":585,"y1":591,"x2":651,"y2":669}},{"id":"a9fcaf04-0c1f-4b0f-bb5b-ede9da96180f","type":"rect","data":{"x1":69,"y1":825,"x2":138,"y2":899}},{"id":"c3f536d6-a04a-4651-b3f9-dd2c22593f7f","type":"rect","data":{"x1":242,"y1":820,"x2":310,"y2":898}},{"id":"eef25cf9-afd0-43b1-b9c5-fbd997bd5fe0","type":"rect","data":{"x1":413,"y1":821,"x2":481,"y2":897}},{"id":"df991b42-ed8e-4f2c-bf0c-aa7522f147b6","type":"rect","data":{"x1":583,"y1":823,"x2":649,"y2":900}}]}
|
Binary file not shown.
After Width: | Height: | Size: 743 KiB |
|
@ -0,0 +1 @@
|
|||
{"definitions":{"9340b854-025c-40da-9387-385d38433bef":{"name":"Shop.ItemAnomalyNoteVisual","displayName":"アノマリーノート(ビジュアル)非凡笔记(形象)","type":"template","annotationId":"9340b854-025c-40da-9387-385d38433bef","useHintRect":false},"ea1ba124-9cb3-4427-969a-bacd47e7d920":{"name":"Shop.ItemRechallengeTicket","displayName":"再挑戦チケット 重新挑战券","type":"template","annotationId":"ea1ba124-9cb3-4427-969a-bacd47e7d920","useHintRect":false},"1926f2f9-4bd7-48eb-9eba-28ec4efb0606":{"name":"Shop.ItemRecordKey","displayName":"記録の鍵 解锁交流的物品","type":"template","annotationId":"1926f2f9-4bd7-48eb-9eba-28ec4efb0606","useHintRect":false},"6720b6e8-ae80-4cc0-a885-518efe12b707":{"name":"Shop.IdolPiece.倉本千奈_WonderScale","displayName":"倉本千奈 WonderScale 碎片","type":"template","annotationId":"6720b6e8-ae80-4cc0-a885-518efe12b707","useHintRect":false},"afa06fdc-a345-4384-b25d-b16540830256":{"name":"Shop.IdolPiece.篠泽广_光景","displayName":"篠泽广 光景 碎片","type":"template","annotationId":"afa06fdc-a345-4384-b25d-b16540830256","useHintRect":false},"278b7d9c-707e-4392-9677-74574b5cdf42":{"name":"Shop.IdolPiece.紫云清夏_TameLieOneStep","displayName":"紫云清夏 Tame-Lie-One-Step 碎片","type":"template","annotationId":"278b7d9c-707e-4392-9677-74574b5cdf42","useHintRect":false},"e9ee330d-dfca-440e-8b8c-0a3b4e8c8730":{"name":"Daily.IconTitleDailyShop","displayName":"日常商店标题图标","type":"template","annotationId":"e9ee330d-dfca-440e-8b8c-0a3b4e8c8730","useHintRect":false}},"annotations":[{"id":"9340b854-025c-40da-9387-385d38433bef","type":"rect","data":{"x1":72,"y1":611,"x2":138,"y2":693}},{"id":"ea1ba124-9cb3-4427-969a-bacd47e7d920","type":"rect","data":{"x1":227,"y1":639,"x2":316,"y2":674}},{"id":"1926f2f9-4bd7-48eb-9eba-28ec4efb0606","type":"rect","data":{"x1":385,"y1":591,"x2":508,"y2":694}},{"id":"6720b6e8-ae80-4cc0-a885-518efe12b707","type":"rect","data":{"x1":589,"y1":633,"x2":638,"y2":678}},{"id":"afa06fdc-a345-4384-b25d-b16540830256","type":"rect","data":{"x1":83,"y1":867,"x2":134,"y2":912}},{"id":"278b7d9c-707e-4392-9677-74574b5cdf42","type":"rect","data":{"x1":247,"y1":864,"x2":301,"y2":907}},{"id":"e9ee330d-dfca-440e-8b8c-0a3b4e8c8730","type":"rect","data":{"x1":17,"y1":35,"x2":59,"y2":76}}]}
|
|
@ -180,7 +180,7 @@ def load_metadata(root_path: str, png_file: str) -> list[Resource]:
|
|||
uuid=definition.annotationId,
|
||||
name=definition.name.split('.')[-1],
|
||||
display_name=definition.displayName,
|
||||
class_path=to_camel_cases(definition.name.split('.')[:-1]),
|
||||
class_path=definition.name.split('.')[:-1],
|
||||
|
||||
rel_path=png_file,
|
||||
abs_path=os.path.abspath(clips[definition.annotationId]),
|
||||
|
@ -192,7 +192,7 @@ def load_metadata(root_path: str, png_file: str) -> list[Resource]:
|
|||
hb = HintBox(
|
||||
name=definition.name.split('.')[-1],
|
||||
display_name=definition.displayName,
|
||||
class_path=to_camel_cases(definition.name.split('.')[:-1]),
|
||||
class_path=definition.name.split('.')[:-1],
|
||||
x1=annotation.data.x1,
|
||||
y1=annotation.data.y1,
|
||||
x2=annotation.data.x2,
|
||||
|
|
Loading…
Reference in New Issue