refactor(runner拆件): 令牌池与商店族拆出两件+断点字段登记单源(零行为变更)
- curio_deck.gd(新增):令牌多重集/抽签/cap/凭证三计数与判定唯一宿主,随机经注入 rg 面;宿主公开面委托保持原签名 - shop_session.gd(新增):双模式货架/定价/购买/刷新入件,钱包原语留宿主经回调落账 - adventure_runner:BREAKPOINT_FIELDS 27 键登记单源驱动写侧拼装与两处读侧回退;续玩段拆为六小函数 - level_runner:商店族 21 函数转委托层,9 个成员改转发属性(类变量计数保持 38) - breakpoint_resume_test:新增 P8 键集==写侧实拼键集与策略标签词表断言〔wr2〕
This commit is contained in:
1 parent
5c9c5f8083
commit
e5ee4d04f0
8 files changed
+925
-504
No files matched your search
+79
-221
@@ -1,5 +1,5 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 关卡域生命周期编排者:节点图行进、HP 跨战斗携带、商店房与恢复点兑付、关卡末收束(垂直切片验证:一关内数值提升可感知)。
|
||||
## 关卡域生命周期编排者:节点图行进、HP 跨战斗携带、恢复点兑付、关卡末收束(垂直切片验证:一关内数值提升可感知);商店族归 ShopSession 件。
|
||||
## Core 层零 UI 依赖(headless 可跑完整关卡);零自动推进——兑付确认后经 choices_presented,推进只走 pick_next_node。
|
||||
## 种子域:单关模式 start_level 自播种;冒险模式 start_level_external 不重播种(主种子恰一次归 AdventureRunner)。
|
||||
## hero_atk_pct/hero_hp_pct 为测试专用覆盖——经 encounter 显式传入战斗,零表写,db.hero 运行期只读。
|
||||
@@ -34,7 +34,7 @@ var gp: int: # GP 冒险钱包(局内货币—
|
||||
get: return int(_ledger["gp"])
|
||||
set(v): _ledger["gp"] = v
|
||||
var base_override := {"atk": 0, "max_hp": 0, "armor_class": 0} # 永久强化 flat 注入(元进度实现批——冒险模式 = AdventureRunner 共享字典经 start_level_external 注入;单关测试模式 = 私有全零)
|
||||
var _shop_purchased := {} # product_id → 已购档数(现价重算输入;冒险开始清零)
|
||||
var _shop: ShopSession = null # 商店件(_init 建;双模式货架/定价/购买/刷新唯一宿主)
|
||||
# 协作对象私有(E1/E3 封装收口):settlement 读取经 current_battle() 成文出口
|
||||
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
|
||||
var treasure_inventory: TreasureItems = null # 秘宝 关卡域库存(TreasureItems;跨战斗延续,
|
||||
@@ -46,14 +46,24 @@ var _attunement: Array[Variant] = [] # 同调装配集(adve
|
||||
var _hero_attack_type := ""
|
||||
var _hero_armor_type := ""
|
||||
# ---- 商店共享池模式态(缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池)----
|
||||
var _shop_pool_mode := false # pool 模式封印(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传)
|
||||
var _pool_deck: Array[Variant] = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点)
|
||||
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_curio_pick 同口写加算区+账本+摘牌)
|
||||
var _pool_ticket := Callable() # 专长凭证展示判定 handler(T5——AdventureRunner._feat_ticket_consider;节点与商店共享计数)
|
||||
var _pool_cap_check := Callable() # cap 检查回调(AdventureRunner._curio_cap_ok;cap 满剔出货架候选视图)
|
||||
var _shop_shelf: Array[Variant] = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡)
|
||||
var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零)
|
||||
var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零)
|
||||
# 件内状态白盒读面(断点写侧与既有取数面沿用原字段名;状态本体单点在 ShopSession)
|
||||
var _shop_pool_mode: bool:
|
||||
get: return _shop.pool_mode
|
||||
var _pool_deck: Array[Variant]:
|
||||
get: return _shop.pool_deck
|
||||
var _pool_consume: Callable:
|
||||
get: return _shop.pool_consume
|
||||
var _pool_ticket: Callable:
|
||||
get: return _shop.pool_ticket
|
||||
var _shop_shelf: Array[Variant]:
|
||||
get: return _shop.shelf
|
||||
var _shop_visit_buys: int:
|
||||
get: return _shop.visit_buys
|
||||
var _shop_paid_refreshes: int:
|
||||
get: return _shop.paid_refreshes
|
||||
var _shop_purchased: Dictionary[Variant, Variant]: # product_id → 已购档数(现价重算输入;冒险开始清零)
|
||||
get: return _shop.purchased
|
||||
set(v): _shop.purchased = v
|
||||
|
||||
var hero_atk_pct := 0 # 测试面板覆盖值(切片专用)
|
||||
var hero_hp_pct := 0
|
||||
@@ -70,6 +80,8 @@ var _generator # 参数化长图生成器(惰性建——NodeMapGenerator
|
||||
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
db = db_ref
|
||||
rng = rng_ref
|
||||
_shop = ShopSession.new(db, rng)
|
||||
_bind_shop()
|
||||
|
||||
|
||||
# 进行中谓词(RUNNING 或 AWAITING_CHOICE 两相位合计;内部守卫专用)
|
||||
@@ -150,6 +162,7 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary =
|
||||
run_ledger.bonus = {"atk": 0, "max_hp": 0, "armor_class": 0} # 加算区冒险开始全 0(单关入口零元进度——注入面见上)
|
||||
base_override = {"atk": 0, "max_hp": 0, "armor_class": 0} # 永久强化 flat:缺省全零白板(单关入口零元进度——注入面见上)
|
||||
_shop_purchased = {} # 已购档数清零(跨冒险不延续——递增定价按冒险内累计)
|
||||
_bind_shop() # 钱包引用换代重绑(_ledger 已重建)
|
||||
for k in base_override.keys():
|
||||
base_override[k] = maxi(0, int(base_override_in.get(k, 0))) # 注入规范化(契约同冒险开始参数面)
|
||||
hero_hp = effective_max_hp()
|
||||
@@ -189,6 +202,7 @@ func start_level_external(level_index: int, ledger: Dictionary,
|
||||
_ledger = ledger
|
||||
base_override = base_override_ref
|
||||
_shop_purchased = purchased_ref
|
||||
_bind_shop() # 钱包共享引用换代重绑(_ledger = 冒险账本)
|
||||
_rest_handler = rest_handler
|
||||
if not _assign_generated_graph(row, rng.master_seed):
|
||||
return false
|
||||
@@ -472,7 +486,7 @@ func _enter_node(node: Dictionary) -> bool:
|
||||
# 商店房兑付:到达即开店(覆盖层归呈现层),购买出账/
|
||||
# 离开归玩家意图——零自动推进;离开确认经 continue_after_node 回选路口
|
||||
if _shop_pool_mode:
|
||||
_shop_open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
|
||||
_shop.open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
|
||||
node_entered.emit(node, {"gp": gp, "shelf": true})
|
||||
return true
|
||||
node_entered.emit(node, {"gp": gp}) # legacy 固定三件(off 对照承载——零 RNG 零池消费)
|
||||
@@ -629,6 +643,7 @@ func set_treasure_unlocked(ids: Dictionary) -> void:
|
||||
# 同调装配集注入(冒险开始参数面;AdventureRunner 唯一调用方)
|
||||
func set_attunement(ids: Array) -> void:
|
||||
_attunement = ids.duplicate()
|
||||
_bind_shop() # 装配集共享引用换代重绑(刷新执照读数面)
|
||||
|
||||
|
||||
# 特性持有集注入(AdventureRunner._start_current_level 唯一调用方;begin_battle
|
||||
@@ -652,6 +667,7 @@ var _tree_rules: Array[Variant] = []
|
||||
|
||||
func set_tree_rules(rules: Array) -> void:
|
||||
_tree_rules = rules.duplicate()
|
||||
_bind_shop() # rule 键集共享引用换代重绑(散折价判定面)
|
||||
|
||||
|
||||
# 两根类型轴注入(adventure 参数面快照;空串=未注入→锻造配置默认值回退。
|
||||
@@ -669,6 +685,7 @@ var run_ledger: RunLedger = RunLedger.new()
|
||||
|
||||
func set_run_ledger(l: RunLedger) -> void:
|
||||
run_ledger = l
|
||||
_bind_shop() # 账类换代重绑(加算区 flat 落点面)
|
||||
|
||||
|
||||
# 凭证件三账注入(储备金袋 RAP/时沙 首轮 AP/完美保险 标错豁免;节点级一次性,
|
||||
@@ -713,9 +730,7 @@ func continue_after_node() -> bool:
|
||||
if t != "reward" and t != "rest" and t != "shop":
|
||||
return false
|
||||
if t == "shop":
|
||||
_shop_shelf = [] # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态)
|
||||
_shop_visit_buys = 0
|
||||
_shop_paid_refreshes = 0 # MC-1
|
||||
_shop.leave() # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态)
|
||||
return _present_choices()
|
||||
|
||||
|
||||
@@ -735,265 +750,108 @@ func close_wallet() -> void:
|
||||
|
||||
|
||||
# ---- 商店房兑付(GP+商店房实施批:呈现层只读投影 + 购买出账)----
|
||||
# 实装归 ShopSession 件(双模式货架/定价/购买/刷新唯一宿主);本类保留同签名委托与件注入/
|
||||
# 白盒入口——外部调用面与断点取数面零变化。
|
||||
|
||||
# 件注入(建件与宿主引用换代时调用——钱包/账类/图行/装配集为共享引用,出账直接落同一引用)
|
||||
func _bind_shop() -> void:
|
||||
_shop.bind({
|
||||
"wallet": _ledger,
|
||||
"run_ledger": run_ledger,
|
||||
"level_row": level_row,
|
||||
"tree_rules": _tree_rules,
|
||||
"attunement": _attunement,
|
||||
}, {
|
||||
"wallet_emit": Callable(self, "_emit_wallet_event"),
|
||||
"max_hp_gain": Callable(self, "_gain_max_hp_flat"),
|
||||
"battle": Callable(self, "current_battle"),
|
||||
})
|
||||
|
||||
|
||||
func _emit_wallet_event(event: String, gp_now: int, extra: Dictionary) -> void:
|
||||
wallet_changed.emit(event, gp_now, extra)
|
||||
|
||||
|
||||
# 购血(商店 max_hp 商品——当前生命 +Δ 并封顶新上限,与账本置满/通关回血同式)
|
||||
func _gain_max_hp_flat(flat: int) -> void:
|
||||
hero_hp = mini(hero_hp + flat, effective_max_hp())
|
||||
|
||||
|
||||
# 商店覆盖层数据源(只读投影——现价/已购档数在此现算,呈现层禁复算定价公式)
|
||||
func shop_products() -> Array:
|
||||
var out: Array[Dictionary] = []
|
||||
for prow in db.shop_products():
|
||||
out.append({
|
||||
"product_id": String(prow.get("product_id", "")),
|
||||
"display_name": String(prow.get("display_name", "")),
|
||||
"attr": String(prow.get("attr", "")),
|
||||
"flat_value": int(prow.get("flat_value", 0)),
|
||||
"price": current_price(prow),
|
||||
"purchased": purchased_tier(String(prow.get("product_id", ""))),
|
||||
})
|
||||
return out
|
||||
return _shop.shop_products()
|
||||
|
||||
|
||||
# 现价 = base_price + price_level_bonus(level_index) + price_increment × 该商品已购档数
|
||||
# (散折价:economy_spend 激活时商品标价 ×80%——见 _spend_discounted)
|
||||
func current_price(prow: Dictionary) -> int:
|
||||
return _spend_discounted(int(prow.get("base_price", 0)) + db.shop_price_level_bonus(int(level_row.level_index)) \
|
||||
+ int(prow.get("price_increment", 0)) * purchased_tier(String(prow.get("product_id", ""))))
|
||||
|
||||
|
||||
# 散折价单点(财富之道·散 economy_spend 激活时商品购买价 ×spend_discount〔表 knob,缺省 0.8〕,
|
||||
# floor 取整偏向玩家、下限 1 防 1 元商品折成 0;范围=商品标价定义边界〔legacy 商品面 current_price
|
||||
# + 池货架面 pool_price 同折〕——商店刷新价 shop_refresh_cost 与三选一付费刷新不折)
|
||||
func _spend_discounted(price: int) -> int:
|
||||
if _tree_rules.has("economy_spend"):
|
||||
return maxi(1, int(floor(float(price) * float(db.shop.spend_discount))))
|
||||
return price
|
||||
return _shop.current_price(prow)
|
||||
|
||||
|
||||
func purchased_tier(product_id: String) -> int:
|
||||
return int(_shop_purchased.get(product_id, 0))
|
||||
return _shop.purchased_tier(product_id)
|
||||
|
||||
|
||||
# 购买(未知商品/属性键出域/余额不足拒绝——返回 false 零副作用):
|
||||
# 出账 → 加算区 += flat_value → 购血同步当前生命(封顶新上限)→ 已购档数 +1
|
||||
func buy_product(product_id: String) -> bool:
|
||||
var prow: Dictionary[Variant, Variant] = db.shop_product_row(product_id)
|
||||
if prow.is_empty():
|
||||
push_error("[关卡节点地图] 商店房未知商品: %s" % product_id)
|
||||
return false
|
||||
var attr := String(prow.get("attr", ""))
|
||||
if not run_ledger.bonus.has(attr):
|
||||
push_error("[关卡节点地图] 商品属性键出域: %s(attr=%s)" % [product_id, attr])
|
||||
return false
|
||||
var price := current_price(prow)
|
||||
if gp < price:
|
||||
push_warning("[关卡节点地图] 购买拒绝——余额不足: %s(需 %d,钱包 %d)" % [product_id, price, gp])
|
||||
return false # 余额不足(覆盖层按钮禁用为常态防线——此处兜底拒绝+静默留痕)
|
||||
gp -= price
|
||||
var flat := int(prow.get("flat_value", 0))
|
||||
run_ledger.bonus[attr] = int(run_ledger.bonus[attr]) + flat
|
||||
if attr == "max_hp":
|
||||
hero_hp = mini(hero_hp + flat, effective_max_hp()) # 购血:上限与当前生命同步 +Δ(封顶新上限)
|
||||
_shop_purchased[product_id] = purchased_tier(product_id) + 1
|
||||
wallet_changed.emit("buy", gp, {"product": product_id, "price": price})
|
||||
return true
|
||||
return _shop.buy_product(product_id)
|
||||
|
||||
|
||||
func player_request_refresh() -> bool:
|
||||
if _settlement == null:
|
||||
return false
|
||||
return _settlement.player_request_refresh()
|
||||
return _shop.player_request_refresh()
|
||||
|
||||
|
||||
# ---- 商店共享池模式(legacy 固定三件路径保留为 off 对照承载)----
|
||||
# 散折价单点(测试取数面沿用本类调用名)
|
||||
func _spend_discounted(price: int) -> int:
|
||||
return _shop._spend_discounted(price)
|
||||
|
||||
|
||||
# 注入(AdventureRunner._start_current_level 唯一调用方——pool 模式专用):令牌库共享引用 +
|
||||
# 池消费 handler。legacy 模式不注入(_shop_pool_mode 恒 false——off 态零 RNG 零池消费零扰动)。
|
||||
# cap 检查回调(cap 满 kind 剔出货架候选视图)。
|
||||
# 池消费 handler + 专长凭证判定 + cap 检查回调(cap 满 kind 剔出货架候选视图)。
|
||||
func set_shop_pool(deck_ref: Array, consume_handler: Callable, ticket_consider: Callable = Callable(), cap_check: Callable = Callable()) -> void:
|
||||
_pool_deck = deck_ref
|
||||
_pool_consume = consume_handler
|
||||
_pool_ticket = ticket_consider # T5——专长凭证展示判定(AdventureRunner._feat_ticket_consider;同流取数复用 shop_draw scope)
|
||||
_pool_cap_check = cap_check
|
||||
_shop_pool_mode = true
|
||||
_shop.set_shop_pool(deck_ref, consume_handler, ticket_consider, cap_check)
|
||||
|
||||
|
||||
# 进店开店(_enter_node shop 分支 pool 模式):进店购买次计数清零 + 抽 3 落架(恰一 shop_draw scope)
|
||||
func _shop_open_pool() -> void:
|
||||
_shop_visit_buys = 0
|
||||
_shop_paid_refreshes = 0 # MC-1——进店付费刷新计数清零
|
||||
_shop_draw_shelf()
|
||||
|
||||
|
||||
# 货架抽签(进店 + 每次买后全重抽共用;TP0 令牌级货架):牌库 = 令牌库中货架词表内 kind 的剩余
|
||||
# 令牌(库存=剩余令牌数——池消耗即上限,与奖励三选一兑付摘牌同库承载);部分 Fisher-Yates 等概率
|
||||
# 洗牌不放回(逐枚等概率——kind 出现率∝剩余令牌数,无独立权重 knob;同 kind 去重每 kind 至多一卡,
|
||||
# 购买身份 kind 单键无歧义);消费 shop_draw 封闭流恰一 scope(每次抽 3 恰一取号)。牌库空 = 空货架
|
||||
# 合法态(池空硬闸——买光即枯竭既定后果)。
|
||||
# 货架抽签白盒入口(测试取数面沿用本类调用名)
|
||||
func _shop_draw_shelf() -> void:
|
||||
_shop_shelf = []
|
||||
var sellable: Array[Variant] = []
|
||||
for row in db.shop_pool_rows():
|
||||
var shelf_kind := String(row.get("kind_id", ""))
|
||||
if _pool_cap_check.is_valid() and not bool(_pool_cap_check.call(shelf_kind, "")):
|
||||
continue # cap 满剔除(货架候选视图剔除;牌库本体不动)
|
||||
for t in _pool_deck:
|
||||
var tok: Dictionary[Variant, Variant] = t as Dictionary
|
||||
if String(tok.get("kind", "")) == shelf_kind:
|
||||
sellable.append({"kind": shelf_kind, "tier": String(tok.get("tier", ""))})
|
||||
if sellable.is_empty():
|
||||
return
|
||||
var stream: RandomNumberGenerator = rng.open_stream("shop_draw")
|
||||
for i in sellable.size():
|
||||
var j := stream.randi_range(i, sellable.size() - 1)
|
||||
var tmp = sellable[i]
|
||||
sellable[i] = sellable[j]
|
||||
sellable[j] = tmp
|
||||
var seen: Array[Variant] = []
|
||||
for tok in sellable:
|
||||
var k := String((tok as Dictionary).get("kind", ""))
|
||||
if seen.has(k):
|
||||
continue
|
||||
seen.append(k)
|
||||
_shop_shelf.append(tok)
|
||||
if _shop_shelf.size() >= 3:
|
||||
break
|
||||
# T5 专长凭证注入(非 Boss 渠道唯一专长入口——货架与节点共享保底计数;判定含 pity 记账,替换末位占一席。
|
||||
# 取数复用本函数 shop_draw scope 的 stream——零新 scope,确定性序列不漂移)
|
||||
if _pool_ticket.is_valid() and not _shop_shelf.is_empty() and bool(_pool_ticket.call(stream)):
|
||||
_shop_shelf[_shop_shelf.size() - 1] = {"kind": "feat_ticket", "tier": "excellent"} # 专长凭证占位档名 = excellent
|
||||
# 商店渠道无专长直接投放(渠道白名单:商店渠道只出属性值提升+资源,专长在非 Boss
|
||||
# 渠道只以专长凭证形态出现——奇物.md v2.7/A4 断言;专长商店入口=T5 专长凭证)。
|
||||
_shop._draw_shelf()
|
||||
|
||||
|
||||
# 库存余量(TP0——令牌库内该 kind 剩余枚数单点读;未注入/防御读 = 0)
|
||||
func pool_stock(kind: String) -> int:
|
||||
var n := 0
|
||||
for t in _pool_deck:
|
||||
if String((t as Dictionary).get("kind", "")) == kind:
|
||||
n += 1
|
||||
return n
|
||||
return _shop.pool_stock(kind)
|
||||
|
||||
|
||||
# 现价 = ceil(基价 × b^(关序−1) × A^(本次进店已购次数))(P4 甲 ceil 向上单一取整——玩家不利向;
|
||||
# A/b 表 knob provisional;核心现算,呈现层禁复算——契约 5)。relic 卡(保底占位——shop 词表外)
|
||||
# 基价 provisional 15(表 knob relic_base_price——shop.tres 词表扩列归数值批)。
|
||||
func pool_price(kind: String) -> int:
|
||||
var row: Dictionary[Variant, Variant] = db.shop_pool_row(kind)
|
||||
if row.is_empty():
|
||||
if kind == "relic":
|
||||
return _spend_discounted(int(ceil(float(db.shop.relic_base_price) * pow(db.shop_price_b(), float(maxi(0, int(level_row.level_index) - 1))) * pow(db.shop_price_a(), float(_shop_visit_buys)))))
|
||||
return 999999
|
||||
var base := float(maxi(1, int(row.get("base_price", 1))))
|
||||
var price_f: float = base * pow(db.shop_price_b(), float(maxi(0, int(level_row.level_index) - 1))) \
|
||||
* pow(db.shop_price_a(), float(_shop_visit_buys))
|
||||
return _spend_discounted(int(ceil(price_f)))
|
||||
return _shop.pool_price(kind)
|
||||
|
||||
|
||||
# 货架只读投影(呈现层数据源——kind/显示名/件值/现价/库存余量齐备,UI 零复算)。
|
||||
# 件值=在架令牌本体值(TP0——令牌级货架,kind+tier 定值;价格公式 tier-blind〔不随档位变〕)。
|
||||
func shop_shelf() -> Array:
|
||||
var out: Array[Dictionary] = []
|
||||
for tok in _shop_shelf:
|
||||
var kind := String((tok as Dictionary).get("kind", ""))
|
||||
var tier := String((tok as Dictionary).get("tier", ""))
|
||||
var row: Dictionary[Variant, Variant] = db.shop_pool_row(kind)
|
||||
var display := String(row.get("display_name", kind))
|
||||
var value := int(db.adventure.curio_value(kind, tier))
|
||||
if row.is_empty() and kind == "relic":
|
||||
var rrow: Dictionary[Variant, Variant] = db.feat_row(String(tok.get("feat_id", "")))
|
||||
display = String(rrow.get("display_name", "特性"))
|
||||
value = 0
|
||||
out.append({
|
||||
"kind_id": kind,
|
||||
"tier": tier,
|
||||
"feat_id": String(tok.get("feat_id", "")),
|
||||
"display_name": display,
|
||||
"value": value,
|
||||
"price": pool_price(kind),
|
||||
"stock_left": pool_stock(kind),
|
||||
})
|
||||
return out
|
||||
return _shop.shop_shelf()
|
||||
|
||||
|
||||
# 商店独立刷新(MC-1):免费次数(天赋增益位,基础
|
||||
# knob=0=「基础免费 0 次」)用尽后 GP 付费刷新;刷新=货架整架重抽(shop_draw 恰一新 scope,
|
||||
# 与进店/买后重抽同签——重抽同源共享池);池空/非池模式=不可刷新合法态。
|
||||
func shop_refresh_free_left() -> int:
|
||||
# 刷新执照 shop_refresh_bonus 装配合计叠加(进店入账语义=读数即本店可用,
|
||||
# 与基础 knob 同生命周期;未装配=基值)
|
||||
return maxi(0, int(db.adventure.shop_refresh_free) + _attune_shop_refresh_bonus())
|
||||
|
||||
|
||||
# 刷新执照装配合计(装配集 kind 分发扫描;零装配=0)
|
||||
func _attune_shop_refresh_bonus() -> int:
|
||||
var total := 0
|
||||
for iid_v in _attunement:
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
|
||||
if (arow.get("effect_kinds", []) as Array).has("shop_refresh_bonus"):
|
||||
total += int(arow.get("magnitude", 0))
|
||||
return total
|
||||
return _shop.shop_refresh_free_left()
|
||||
|
||||
|
||||
func shop_refresh_cost() -> int:
|
||||
return int(ceil(float(db.adventure.shop_refresh_cost_base)
|
||||
* pow(db.adventure.shop_refresh_cost_curve, float(_shop_paid_refreshes))))
|
||||
return _shop.shop_refresh_cost()
|
||||
|
||||
|
||||
func shop_refresh() -> bool:
|
||||
if not _shop_pool_mode or _shop_shelf.is_empty():
|
||||
return false
|
||||
if shop_refresh_free_left() <= 0:
|
||||
var cost := shop_refresh_cost()
|
||||
if gp < cost:
|
||||
return false
|
||||
gp -= cost
|
||||
_shop_paid_refreshes += 1
|
||||
_shop_draw_shelf()
|
||||
return true
|
||||
return _shop.shop_refresh()
|
||||
|
||||
|
||||
# GP 冒险钱包单口支出(三选一 GP 付费刷新与商店刷新共用同一钱包;余额不足 false 零副作用)
|
||||
func spend_gp(amount: int) -> bool:
|
||||
if amount <= 0 or gp < amount:
|
||||
return false
|
||||
gp -= amount
|
||||
return true
|
||||
return _shop.spend_gp(amount)
|
||||
|
||||
|
||||
# 购买(pool 模式唯一购买口):在架(kind→在架令牌)/余额前置校验零副作用 → handler 消费在架
|
||||
# 令牌本体(kind+tier——_apply_curio_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买
|
||||
# 次计数 → wallet 事件 → 三件全重抽(未买不保留——重抽绑购买,无独立刷新动作)
|
||||
func buy_pool_item(kind: String) -> bool:
|
||||
var shelf_tok := {}
|
||||
for tok in _shop_shelf:
|
||||
if String((tok as Dictionary).get("kind", "")) == kind:
|
||||
shelf_tok = tok
|
||||
break
|
||||
if not _shop_pool_mode or shelf_tok.is_empty():
|
||||
push_error("[关卡节点地图] 商店购买拒绝——非池模式或件不在架: %s" % kind)
|
||||
return false
|
||||
var price := pool_price(kind)
|
||||
if gp < price:
|
||||
push_warning("[关卡节点地图] 购买拒绝——余额不足: %s(需 %d,钱包 %d)" % [kind, price, gp])
|
||||
return false
|
||||
var tier := String(shelf_tok.get("tier", ""))
|
||||
if _pool_consume.is_null() or not bool(_pool_consume.call(kind, tier)):
|
||||
push_error("[关卡节点地图] 购买拒绝——池库存消费失败: %s|%s" % [kind, tier])
|
||||
return false
|
||||
gp -= price
|
||||
_shop_visit_buys += 1
|
||||
wallet_changed.emit("buy", gp, {"product": kind, "price": price})
|
||||
_shop_draw_shelf() # 买 1 → 三件全重抽(恰一 shop_draw scope——同进店取号签)
|
||||
return true
|
||||
return _shop.buy_pool_item(kind)
|
||||
|
||||
|
||||
# 池模式谓词(呈现层组合根分发用——货架面 vs legacy 商品面)
|
||||
func is_shop_pool() -> bool:
|
||||
return _shop_pool_mode
|
||||
return _shop.is_shop_pool()
|
||||
|
||||
|
||||
# 断点快照读面(P2 甲货架+账本入断点——AdventureRunner._snapshot_breakpoint 消费;离开店即空架)
|
||||
func shop_bp_shelf() -> Array:
|
||||
return _shop_shelf.duplicate()
|
||||
return _shop.shop_bp_shelf()
|
||||
|
||||
|
||||
func shop_bp_visit_buys() -> int:
|
||||
return _shop_visit_buys
|
||||
return _shop.shop_bp_visit_buys()
|
||||
Reference in new issue
Block a user