merge: MC-1 商店独立刷新机制(落码收官核心批;冒烟752/0+探针全绿+layout 0)
This commit is contained in:
commit
fdd32fe2eb
8 files changed
+83
-5
No files matched your search
@@ -58,7 +58,7 @@
|
||||
| Boss | 恒为图终点;行进至此开 Boss 战;胜利即关卡收束点 |
|
||||
| 奖励 | 行进至此触发节点奖励(兑付见下节);反馈呈现后回到选路 |
|
||||
| 恢复 | 行进至此向冒险结构发治疗封顶回报;反馈呈现后回到选路 |
|
||||
| 商店 | 行进至此打开商店覆盖层,用 GP(显示名「金币」,战斗掉落的冒险内货币)购买属性提升;离开后兑付确认回选路;不携带战斗行。**v1.5 兑付换代(pool 模式现行)**:进店从共享池抽 3 展示、买 1 后三件全重抽(未买不保留、无独立刷新动作——重抽绑购买)、货架买空=空架合法态;账本与定价归[冒险结构](冒险结构.md)共享池账本节 |
|
||||
| 商店 | 行进至此打开商店覆盖层,用 GP(显示名「金币」,战斗掉落的冒险内货币)购买属性提升;离开后兑付确认回选路;不携带战斗行。**v1.5 兑付换代(pool 模式现行)**:进店从共享池抽 3 展示、买 1 后三件全重抽(未买不保留)、货架买空=空架合法态。**v1.6(2026-10-04 P-C2+Q2 裁/MC-1)**:新增**独立刷新**——基础免费 0 次(天赋/营地侧刷新次数增益位预留)+ GP 付费刷新 ceil(20×1.8^已付费刷) provisional;重抽=共享池同源(shop_draw 同签),推翻本行旧「无独立刷新动作——重抽绑购买」口径;账本与定价归[冒险结构](冒险结构.md)共享池账本节 |
|
||||
|
||||
商店房的编排规则(2026-09-20 用户拍板):每关**恰 1 个**商店房,位置 = **关底 Boss 前一位**——哈迪斯式的"决战前最后一个补给点":买到的属性还有整段战斗可消费,错过的成长也没有第二家店可补。Boss 前一位同时是**必经位**:图拓扑收束使所有入口→Boss 的路径都穿过它,成长保底成为图结构事实而非玩家自觉。这条位置约束不由加载校验承载——图合法性六检零新增,商店房的"每关恰 1 + 必经"由冒烟断言把关。
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ GP(显示名「金币」)是冒险内的一次性属性提升货币:战斗
|
||||
|
||||
掉落掷定消费 gp_drop 封闭随机流(主种子派生、可回放)。**商店货架抽签消费 shop_draw 封闭随机流**(v0.13 扩签——每次抽 3〔进店+每次买后重抽〕恰一 scope,与 reward_pick 分签防取数序互扰)。掉落与价格的现行行值读商店表(registry 为准),数值所有权归[难度曲线与Boss](难度曲线与Boss.md)曲线调参批——本批骨架先行,全部数值 provisional。
|
||||
|
||||
**共享池账本(v0.13 P1 共享池单点;v0.14 令牌库换代)**:商店货架与提升三选一是**同一个池**——货架候选 = 池∖{GP 袋}(用钱买钱恒负交易)= {攻击, 生命上限, 护甲, 行动点} 四种(魔法道具件已随同调批出池);**库存 = 剩余令牌数**(TP0——「池消耗即上限」,种类次数上限退役,与奖励兑付摘牌同一令牌库承载,全冒险一次性跨关卡不补给):商店买一件,奖励面就少一枚;「无限购买」收敛为**钱尽或池空为止**(池空=硬闸、A 链爬价=软闸),空货架合法态。货架 = **令牌级抽签**(同 kind 去重每 kind 至多一卡,价格公式 tier-blind 维持批②裁定,购买消费在架令牌本体 kind+tier)。两渠道不得各自私记库存——消费走同一入口写同一令牌库(实现=AdventureRunner._bonus_deck 单点,冒险结构 owner;kind 维度派生计数 _bonus_taken 承载断点附随件与读数)。
|
||||
**共享池账本(v0.13 P1 共享池单点;v0.14 令牌库换代)**:商店货架与提升三选一是**同一个池**——货架候选 = 池∖{GP 袋}(用钱买钱恒负交易)= {攻击, 生命上限, 护甲, 行动点} 四种(魔法道具件已随同调批出池);**库存 = 剩余令牌数**(TP0——「池消耗即上限」,种类次数上限退役,与奖励兑付摘牌同一令牌库承载,全冒险一次性跨关卡不补给):商店买一件,奖励面就少一枚;「无限购买」收敛为**钱尽或池空为止**(池空=硬闸、A 链爬价=软闸),空货架合法态。货架 = **令牌级抽签**(同 kind 去重每 kind 至多一卡,价格公式 tier-blind 维持批②裁定,购买消费在架令牌本体 kind+tier)。两渠道不得各自私记库存——消费走同一入口写同一令牌库(实现=AdventureRunner._bonus_deck 单点,冒险结构 owner;kind 维度派生计数 _bonus_taken 承载断点附随件与读数)。**货架独立刷新(v0.15 MC-1 2026-10-04——P-C2+Q2 裁)**:进店免费刷新次数(基础 0,天赋/营地侧增益位预留)用尽后 GP 付费刷新(ceil(20×1.8^已付费刷) provisional——调参表旋钮 shop_refresh_*);重抽=同 pool_draw 共享池抽签(shop_draw 恰一新 scope),池空=不可刷新合法态;进店/离开双向清零付费刷计数。
|
||||
|
||||
### 关卡大奖励:通关回血 + 提升三选一(钥石退役出大奖励)
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ var _pool_deck: Array = [] # 共享池令牌库共享引用(Ad
|
||||
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_bonus_pick 同口写加算区+账本+摘牌)
|
||||
var _shop_shelf: Array = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡)
|
||||
var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零)
|
||||
var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零)
|
||||
|
||||
var hero_atk_pct := 0 # 测试面板覆盖值(切片专用)
|
||||
var hero_hp_pct := 0
|
||||
@@ -653,6 +654,7 @@ func continue_after_node() -> bool:
|
||||
if t == "shop":
|
||||
_shop_shelf = [] # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态——迭代 11 第②批 T2)
|
||||
_shop_visit_buys = 0
|
||||
_shop_paid_refreshes = 0 # MC-1
|
||||
return _present_choices()
|
||||
|
||||
|
||||
@@ -742,6 +744,7 @@ func set_shop_pool(deck_ref: Array, consume_handler: Callable) -> void:
|
||||
# 进店开店(_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()
|
||||
|
||||
|
||||
@@ -848,6 +851,31 @@ func shop_shelf() -> Array:
|
||||
return out
|
||||
|
||||
|
||||
# 商店独立刷新(MC-1——P-C2 裁 2026-10-04 推翻 v1.5「无独立刷新」):免费次数(天赋增益位,基础
|
||||
# knob=0——Q2 裁「基础免费 0 次」)用尽后 GP 付费刷新;刷新=货架整架重抽(shop_draw 恰一新 scope,
|
||||
# 与进店/买后重抽同签——重抽同源共享池);池空/非池模式=不可刷新合法态。
|
||||
func shop_refresh_free_left() -> int:
|
||||
return maxi(0, int(db.adventure.shop_refresh_free))
|
||||
|
||||
|
||||
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))))
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
# 购买(pool 模式唯一购买口):在架(kind→在架令牌)/余额前置校验零副作用 → handler 消费在架
|
||||
# 令牌本体(kind+tier——_apply_bonus_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买
|
||||
# 次计数 → wallet 事件 → 三件全重抽(未买不保留——重抽绑购买,无独立刷新动作)
|
||||
|
||||
@@ -31,6 +31,9 @@ const TIER_QUOTAS := {"common": 70, "rare": 25, "rarest": 3}
|
||||
|
||||
# —— 三选一候选数(2026-10-02 修复批 T2 魔法数结构化)——
|
||||
@export var bonus_candidate_count: int = 3 # [1,6] knob——令牌洗牌不放回抽取张数上界
|
||||
@export var shop_refresh_free: int = 0 # [0,9] knob(MC-1 provisional——进店免费刷新次数;基础 0=Q2 裁,天赋增益位预留)
|
||||
@export var shop_refresh_cost_base: int = 20 # [1,999] knob(MC-1 provisional——GP 刷新基价)
|
||||
@export var shop_refresh_cost_curve: float = 1.8 # [1.0,4.0] knob(MC-1 provisional——按已付费刷新数幂曲线,A 链同族)
|
||||
|
||||
# —— 令牌配额(kind × tier 计数;未列组合 = 0——heal 只入稀有/ap 只入罕见,v2.2 档位构成)——
|
||||
@export var token_attack_common: int = 18 # [0,99]
|
||||
|
||||
@@ -153,6 +153,7 @@ func on_shop_buy(product_id: String) -> void:
|
||||
toast.show_toast("金币不足——买不起这件商品", "warn")
|
||||
return
|
||||
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
|
||||
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost())
|
||||
_refresh_presentations.call()
|
||||
return
|
||||
if not runner.buy_product(product_id):
|
||||
@@ -164,6 +165,19 @@ func on_shop_buy(product_id: String) -> void:
|
||||
_refresh_presentations.call()
|
||||
|
||||
|
||||
# MC-1:刷新意图(shop_overlay.refresh_requested——核心 shop_refresh 受理后整架重投喂=重抽反馈)
|
||||
func on_shop_refresh() -> void:
|
||||
var runner = _runner_get.call()
|
||||
if runner == null or not runner.is_shop_pool():
|
||||
return
|
||||
if not runner.shop_refresh():
|
||||
toast.show_toast("金币不足——刷新失败", "warn")
|
||||
return
|
||||
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
|
||||
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost())
|
||||
_refresh_presentations.call()
|
||||
|
||||
|
||||
func on_shop_leave() -> void:
|
||||
_close_modal.call(shop_overlay)
|
||||
var runner = _runner_get.call()
|
||||
|
||||
@@ -289,6 +289,7 @@ func _build_ui() -> void:
|
||||
board_zone.toast, _start_overlay, logger, func() -> Variant: return runner,
|
||||
_open_modal, _close_modal, _refresh_presentations, _refresh_resume_button)
|
||||
shop_overlay.buy_requested.connect(_camp_pres.on_shop_buy)
|
||||
shop_overlay.refresh_requested.connect(_camp_pres.on_shop_refresh) # MC-1
|
||||
shop_overlay.leave_pressed.connect(_camp_pres.on_shop_leave)
|
||||
camp_overlay.buy_requested.connect(_camp_pres.on_camp_buy)
|
||||
camp_overlay.attune_toggled.connect(_camp_pres.on_camp_attune_toggle)
|
||||
@@ -520,6 +521,7 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
||||
_open_modal(shop_overlay)
|
||||
if runner.is_shop_pool():
|
||||
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
|
||||
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost()) # MC-1
|
||||
else:
|
||||
shop_overlay.open_shop(runner.shop_products(), int(runner.gp))
|
||||
log_view.append_bb(Narrator.shop_line(int(runner.gp)))
|
||||
|
||||
@@ -18,7 +18,8 @@ const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class":
|
||||
const KIND_LABELS := {"attack": "攻击", "max_hp": "生命上限", "armor": "护甲", "ap": "行动点"} # 货架件值文案轴(共享池 kind 词表——迭代 11 第②批)
|
||||
|
||||
var _gp_label: Label
|
||||
var _refresh_btn: Button # M6 刷新呈现位(禁用占位——机制由 MC-1 核心批接线)
|
||||
var _refresh_btn: Button # M6 呈现位——MC-1 起由 set_refresh_display 投喂启用
|
||||
signal refresh_requested # MC-1:刷新意图(pool 面;组合根接 presenter.on_shop_refresh)
|
||||
var _cards_box: VBoxContainer
|
||||
var _card_rows: Array[Dictionary] = [] # [{product_id, price, bought, btn}]——refresh_state 就地重刷(零重建回流)
|
||||
var _shelf_rows: Array[Dictionary] = [] # [{kind_id, btn}]——pool 面卡片(买后整架重建=重抽反馈)
|
||||
@@ -40,8 +41,8 @@ func _init() -> void:
|
||||
_refresh_btn = UiKit.button("")
|
||||
_refresh_btn.icon = UiKit.load_tex(UiKit.TEX_ICON_REFRESH)
|
||||
_refresh_btn.custom_minimum_size = Vector2(44, 44)
|
||||
_refresh_btn.tooltip_text = "刷新货架(机制接入中——商店刷新核心批 MC-1)"
|
||||
_refresh_btn.disabled = true
|
||||
_refresh_btn.disabled = true # MC-1 投喂后启用(set_refresh_display)
|
||||
_refresh_btn.pressed.connect(_emit_refresh_requested)
|
||||
top.add_child(_refresh_btn)
|
||||
var stock_badge := UiKit.label("0", 12, true, UiKit.INK_ON_GOLD)
|
||||
stock_badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
@@ -154,6 +155,21 @@ func refresh_shelf_state(gp: int) -> void:
|
||||
for card in _shelf_rows:
|
||||
card.btn.disabled = gp < int(card.price)
|
||||
card.card.modulate = Color(1, 1, 1, 1) if gp >= int(card.price) else Color(1, 1, 1, 0.55)
|
||||
if _refresh_btn != null and not _refresh_btn.disabled:
|
||||
_refresh_btn.disabled = gp < _refresh_gp_cost
|
||||
|
||||
|
||||
# MC-1 刷新呈现投喂(free_left=天赋位免费剩余[基础 0]/gp_cost=本次 GP 刷新价/can_afford=余额可付)
|
||||
var _refresh_gp_cost := 0
|
||||
|
||||
|
||||
func set_refresh_display(free_left: int, gp_cost: int, can_afford: bool) -> void:
|
||||
_refresh_gp_cost = gp_cost
|
||||
for lab in _refresh_btn.get_children():
|
||||
if lab is Label:
|
||||
(lab as Label).text = str(free_left)
|
||||
_refresh_btn.tooltip_text = "刷新货架——换一批商品\n免费 %d 次(天赋增益位,基础 0)\nGP 刷新 %d 金币(provisional)\n池源=共享池,与奖励三选一同源" % [free_left, gp_cost]
|
||||
_refresh_btn.disabled = not can_afford and free_left <= 0
|
||||
|
||||
|
||||
# 货架卡(bcard 竖卡:类型色带+图标+名+件值描述+价签+购买钮——M6 同构卡;demo ⑤)
|
||||
@@ -181,3 +197,7 @@ func _build_shelf_card(holder: Control, c: Dictionary) -> Dictionary:
|
||||
|
||||
func _emit_shelf_buy(kind: String) -> void:
|
||||
buy_requested.emit(kind)
|
||||
|
||||
|
||||
func _emit_refresh_requested() -> void:
|
||||
refresh_requested.emit()
|
||||
@@ -3729,6 +3729,17 @@ func _smoke_shop_pool() -> void:
|
||||
and _adv_drive_to_shop(a8b, _adv_st(), autoplay):
|
||||
first_shelf_b = (a8b.runner().shop_shelf() as Array).duplicate(true)
|
||||
check(first_shelf_a.size() == 3, "进店抽 3(货架 %d 卡——同构交互)" % first_shelf_a.size())
|
||||
# MC-1 商店独立刷新(P-C2+Q2 裁——基础免费 0;GP 刷新 ceil(20×1.8^n) provisional;重抽同源)
|
||||
if not scen.is_empty():
|
||||
var r8 = scen["r"]
|
||||
var gp0: int = int(r8.gp)
|
||||
var cost0: int = r8.shop_refresh_cost()
|
||||
check(cost0 == int(_db.adventure.shop_refresh_cost_base), "刷新首价=基价(%d)" % cost0)
|
||||
check(r8.shop_refresh_free_left() == 0, "基础免费 0 次(Q2——天赋增益位后续添加)")
|
||||
var shelf_pre: Array = (r8.shop_shelf() as Array).duplicate(true)
|
||||
check(r8.shop_refresh(), "GP 刷新受理(gp=%d cost=%d)" % [gp0, cost0])
|
||||
check(int(r8.gp) == gp0 - cost0, "GP 刷新扣费(%d→%d)" % [gp0, int(r8.gp)])
|
||||
check(int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))), "刷新爬价 ×curve(%d)" % int(r8.shop_refresh_cost()))
|
||||
var same_shelf := first_shelf_a.size() == first_shelf_b.size()
|
||||
if same_shelf:
|
||||
for i in first_shelf_a.size():
|
||||
|
||||
Reference in new issue
Block a user