diff --git a/config/template.json b/config/template.json index 3c2eb57ce..5cd898cf6 100644 --- a/config/template.json +++ b/config/template.json @@ -1783,6 +1783,7 @@ "Refresh": false, "BuySkinBox": "disabled", "BuySkinBoxAmount": 0, + "BuySkinBoxPosition": null, "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..2e8b5c77c 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -9987,6 +9987,10 @@ "type": "input", "value": 0 }, + "BuySkinBoxPosition": { + "type": "input", + "value": "" + }, "ConsumeCoins": { "type": "checkbox", "value": false diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index cd2248e47..b05389dc2 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -679,6 +679,7 @@ GeneralShop: value: disabled option: [ disabled, unlimited, specified ] BuySkinBoxAmount: 0 + BuySkinBoxPosition: "" ConsumeCoins: false Filter: |- BookRedT3 > BookYellowT3 > BookBlueT3 > BookRedT2 diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 424d46876..6f1d0476f 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_BuySkinBoxPosition = None 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..70d64ed95 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." }, + "BuySkinBoxPosition": { + "name": "Specify Skin Box", + "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. Leave 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..3a06ef611 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" }, + "BuySkinBoxPosition": { + "name": "GeneralShop.BuySkinBoxPosition.name", + "help": "GeneralShop.BuySkinBoxPosition.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..ed9dd20ca 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -3393,6 +3393,10 @@ "name": "购买 X 个外观箱", "help": "在选择“购买指定数量”时生效" }, + "BuySkinBoxPosition": { + "name": "指定外观箱", + "help": "本设置用于商店中同时售卖多种外观箱时,通过填写外观箱位置指定需要购买的外观箱,位置从左至右,由1开始,格式同过滤器,如:1 > 2 > 3。留空表示无限制" + }, "ConsumeCoins": { "name": "消耗溢出物资", "help": "物资超过550000时购买所有消耗物资的物品" diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 2cd35db6a..722e34c72 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -3393,6 +3393,10 @@ "name": "購買 X 個外觀箱", "help": "在選擇「購買指定數量」時生效" }, + "BuySkinBoxPosition": { + "name": "指定外觀箱", + "help": "本設定用於商店中售賣多種外觀箱時,透過填寫外觀箱位置指定所需購買的外觀箱,位置從左至右,由1開始,格式同過濾器,如:1 > 2 > 3。留空表示無限制" + }, "ConsumeCoins": { "name": "消耗溢出物資", "help": "物資超過550000時購買所有消耗物資的物品" diff --git a/module/shop/shop_general.py b/module/shop/shop_general.py index 898369680..48235100d 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. + """ + position_raw = self.config.GeneralShop_BuySkinBoxPosition + position_str = str(position_raw).strip() if position_raw is not None else None + if position_str is None: + return None + SKINBOX_POSITION_FILTER.load(position_str) + allowed = {int(r) for r in SKINBOX_POSITION_FILTER.filter_raw if r.isdigit()} + return allowed if allowed else None + # New UI in 2025-08-14 @cached_property def shop_general_items(self): @@ -132,6 +151,12 @@ 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: + allowed = self.skinbox_allowed_positions + if allowed is not None: + grids = self.shop_general_items.grids + abs_pos = round((item.button[0] - grids.origin[0]) / grids.delta[0]) + 1 + if abs_pos not in allowed: + return False if mode == 'specified': self.config.GeneralShop_BuySkinBoxAmount -= 1 return True