## ShopManager — 商店管理(Autoload: ShopManager) ## S2 范围:3 选 1 法术、金币扣减、刷新费用公式(P6-N23) ## 权威来源:development_plan.md S2、numerical_design.md §2.3 extends Node signal shop_refreshed signal shop_closed const SLOT_COUNT: int = 3 const REROLL_BASE_COST: int = 20 # 第 1 次刷新费用 const REROLL_STEP: int = 10 # 每次递增价格 var current_slots: Array = [] # Array[SpellNode] (null 表示已售出) var reroll_count: int = 0 # 本波已刷新次数(WAVE_COMPLETE 后重置) var _shop_seed: int = 0 # 随机种(ADR-A2 P-S2-07) var _rng: RandomNumberGenerator = RandomNumberGenerator.new() func _ready() -> void: EventBus.subscribe(EventID.WAVE_COMPLETE, _on_wave_complete) func _on_wave_complete(_payload: Dictionary) -> void: reroll_count = 0 # P6-N23:波次结算后重置刷新次数 func open_shop(seed_override: int = -1) -> void: if seed_override >= 0: _shop_seed = seed_override else: _shop_seed = randi() _rng.seed = _shop_seed _refresh_slots() EventBus.emit(EventID.SHOP_OPENED, {"seed": _shop_seed}) ## 商店可抽池:shop_cost>0 且(未门控 unlock_cost==0 或 已解锁) func _available_pool() -> Array: return SpellRegistry.get_all_ids().filter(func(id): var s: SpellNode = SpellRegistry.get_spell(id) if s == null or int(s.meta.get("shop_cost", 0)) <= 0: return false var uc: int = int(s.meta.get("unlock_cost", 0)) return uc == 0 or MetaProgress.is_unlocked(id)) func _refresh_slots() -> void: var pool: Array = _available_pool() current_slots.clear() for _i in SLOT_COUNT: if pool.is_empty(): current_slots.append(null) continue var idx: int = _rng.randi_range(0, pool.size() - 1) current_slots.append(SpellRegistry.get_spell(pool[idx])) pool.remove_at(idx) shop_refreshed.emit() ## 购买指定槽位的法术,返回购买的 SpellNode、失败返回 null func buy_spell(slot_idx: int) -> SpellNode: if slot_idx < 0 or slot_idx >= current_slots.size(): return null var spell: SpellNode = current_slots[slot_idx] if spell == null: return null var cost: int = int(spell.meta.get("shop_cost", 20)) if not PlayerStats.spend_gold(cost): return null current_slots[slot_idx] = null shop_refreshed.emit() return spell ## 刷新商店,返回 true 表示成功 func reroll() -> bool: var cost: int = get_reroll_cost() if not PlayerStats.spend_gold(cost): return false reroll_count += 1 _rng.seed = _shop_seed + reroll_count * 31337 _refresh_slots() return true ## 刷新费用公式:base + step * reroll_count(P6-N23) func get_reroll_cost() -> int: return REROLL_BASE_COST + reroll_count * REROLL_STEP func close_shop() -> void: current_slots.clear() shop_closed.emit() func get_shop_seed() -> int: return _shop_seed func reset() -> void: current_slots.clear() reroll_count = 0 _shop_seed = 0