diff --git a/config/template.json b/config/template.json index 3c2eb57ce..3083c1feb 100644 --- a/config/template.json +++ b/config/template.json @@ -1783,6 +1783,7 @@ "Refresh": false, "BuySkinBox": "disabled", "BuySkinBoxAmount": 0, + "SkinBoxPositionFilter": "1 > 2 > 3", "ConsumeCoins": false, "Filter": "BookRedT3 > BookYellowT3 > BookBlueT3 > BookRedT2\n> Cube\n> FoodT6 > FoodT5" }, diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 074d3f9e2..2d50da662 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -9987,6 +9987,10 @@ "type": "input", "value": 0 }, + "SkinBoxPositionFilter": { + "type": "textarea", + "value": "1 > 2 > 3" + }, "ConsumeCoins": { "type": "checkbox", "value": false diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index cd2248e47..3102feaad 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -679,6 +679,8 @@ GeneralShop: value: disabled option: [ disabled, unlimited, specified ] BuySkinBoxAmount: 0 + SkinBoxPositionFilter: |- + 1 > 2 > 3 ConsumeCoins: false Filter: |- BookRedT3 > BookYellowT3 > BookBlueT3 > BookRedT2 diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 424d46876..d6231a810 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -417,6 +417,7 @@ class GeneratedConfig: GeneralShop_Refresh = False GeneralShop_BuySkinBox = 'disabled' # disabled, unlimited, specified GeneralShop_BuySkinBoxAmount = 0 + GeneralShop_SkinBoxPositionFilter = '1 > 2 > 3' GeneralShop_ConsumeCoins = False GeneralShop_Filter = 'BookRedT3 > BookYellowT3 > BookBlueT3 > BookRedT2\n> Cube\n> FoodT6 > FoodT5' diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 3396bee47..b3cd79a91 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -3393,6 +3393,10 @@ "name": "Purchase X Skin Boxes", "help": "Takes effect when \"Purchase Specified Quantity\" is selected." }, + "SkinBoxPositionFilter": { + "name": "Specific Skin Box Filter", + "help": "Used when multiple skin box types are sold simultaneously in the shop. Enter position(s) to target specific skin boxes; positions start from 1 (left to right). Uses the same format as filters, e.g. 1 > 2 > 3.\nLeave empty for no restriction." + }, "ConsumeCoins": { "name": "Consume Spilled Coins", "help": "Buy all items when the coins exceed 550000" diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index b16352348..ef4c6a2fc 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -3393,6 +3393,10 @@ "name": "GeneralShop.BuySkinBoxAmount.name", "help": "GeneralShop.BuySkinBoxAmount.help" }, + "SkinBoxPositionFilter": { + "name": "GeneralShop.SkinBoxPositionFilter.name", + "help": "GeneralShop.SkinBoxPositionFilter.help" + }, "ConsumeCoins": { "name": "GeneralShop.ConsumeCoins.name", "help": "GeneralShop.ConsumeCoins.help" diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 1d98fd6d9..31b7fdbdb 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -3393,6 +3393,10 @@ "name": "购买 X 个外观箱", "help": "在选择“购买指定数量”时生效" }, + "SkinBoxPositionFilter": { + "name": "外观箱位置过滤器", + "help": "当商店中同时售卖多种外观箱时,通过填写外观箱位置,指定需要购买的外观箱,位置从左至右,由1开始,如:1 > 2 > 3\n空格表示无限制" + }, "ConsumeCoins": { "name": "消耗溢出物资", "help": "物资超过550000时购买所有消耗物资的物品" diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 2cd35db6a..665d269b0 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -3393,6 +3393,10 @@ "name": "購買 X 個外觀箱", "help": "在選擇「購買指定數量」時生效" }, + "SkinBoxPositionFilter": { + "name": "外觀箱位置過濾器", + "help": "當商店中同時售賣多種外觀箱時,通過填寫外觀箱位置,指定需要購買的外觀箱,位置從左至右,由1開始,如:1 > 2 > 3\n空格表示無限制" + }, "ConsumeCoins": { "name": "消耗溢出物資", "help": "物資超過550000時購買所有消耗物資的物品" diff --git a/module/shop/shop_general.py b/module/shop/shop_general.py index 898369680..b213118f0 100644 --- a/module/shop/shop_general.py +++ b/module/shop/shop_general.py @@ -1,10 +1,15 @@ +import re + from module.base.decorator import cached_property +from module.base.filter import Filter from module.logger import logger from module.shop.base import ShopItemGrid, ShopItemGrid_250814 from module.shop.clerk import ShopClerk from module.shop.shop_status import ShopStatus from module.shop.ui import ShopUI +SKINBOX_POSITION_FILTER = Filter(re.compile(r'^(\d+)$'), ('position',)) + class GeneralShop_250814(ShopClerk, ShopUI, ShopStatus): gems = 0 @@ -18,6 +23,20 @@ class GeneralShop_250814(ShopClerk, ShopUI, ShopStatus): """ return self.config.GeneralShop_Filter.strip() + @cached_property + def skinbox_allowed_positions(self): + """ + Returns: + set[int]: allowed 1-based positions, or None if unrestricted. + """ + skin_box_filter = self.config.GeneralShop_SkinBoxPositionFilter.strip() + if not skin_box_filter: + return None + SKINBOX_POSITION_FILTER.load(skin_box_filter) + allowed = {int(pos) for pos in SKINBOX_POSITION_FILTER.filter_raw if pos.isdigit()} + logger.attr('Skin_box_filter', ' > '.join([str(pos) for pos in allowed])) + return allowed if allowed else None + # New UI in 2025-08-14 @cached_property def shop_general_items(self): @@ -132,12 +151,32 @@ class GeneralShop_250814(ShopClerk, ShopUI, ShopStatus): # and design constantly changes i.e. equip skin box logger.info(f'Item {item} is considered to be an equip skin box') if self._currency >= item.price: + if not self.skinbox_position_check(): + return False if mode == 'specified': self.config.GeneralShop_BuySkinBoxAmount -= 1 return True return False + def skinbox_position_check(self, item): + """ + Check if a skin box is in the allowed position where it should be bought. + + Args: + item: Item to check + + Returns: + bool: whether the skin box is in the allowed position + """ + allowed = self.skinbox_allowed_positions + if allowed is None: + return True + + grids = self.shop_general_items.grids + abs_pos = round((item.button[0] - grids.origin[0]) / grids.delta[0]) + 1 + return abs_pos in allowed + def run(self): """ Run General Shop