From 2f3a87e98f75ea39ba1c63c6db91fc8b79ff5ada Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 29 Sep 2026 07:21:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(t4):=20=E9=AD=94=E6=B3=95=E9=81=93?= =?UTF-8?q?=E5=85=B7=20HUD=20=E9=9D=A2=E2=80=94=E2=80=94C=20=E5=8C=BA?= =?UTF-8?q?=E5=9B=9B=E6=A7=BD=E6=8E=A5=E7=BA=BF+=E4=B8=A4=E6=AD=A5?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E4=BA=A4=E4=BA=92+=E5=8F=A0=E5=8A=A0?= =?UTF-8?q?=E5=B1=82=E5=91=88=E7=8E=B0+=E6=B8=85=E7=A9=BA=E5=8F=8D?= =?UTF-8?q?=E9=A6=88=20=E2=91=A0hero=5Finfo=5Fpanel:=E5=9B=9B=E6=A7=BD=20g?= =?UTF-8?q?ui=5Finput=20=E5=8F=AF=E7=82=B9+aux=5Fslot=5Fclicked=20?= =?UTF-8?q?=E4=BF=A1=E5=8F=B7+set=5Faux=5Finventory=20=E6=8A=95=E5=BD=B1?= =?UTF-8?q?=E6=B8=B2=E6=9F=93(=E7=A9=BA=E6=A7=BD=E8=99=9A=E4=BD=8D/used=20?= =?UTF-8?q?=E6=9A=97=E6=80=81/=E4=B8=8D=E5=8F=AF=E7=94=A8=E7=BD=AE?= =?UTF-8?q?=E7=81=B0/=E9=80=89=E4=B8=AD=E9=87=91=E6=A1=86/tooltip=20?= =?UTF-8?q?=E5=85=A8=E5=90=8D+=E8=AF=AD=E4=B9=89)=20=E2=91=A1main:?= =?UTF-8?q?=E9=80=89=E4=B8=AD=E6=80=81=20=5Faux=5Fuse=5Fselection+?= =?UTF-8?q?=E6=A7=BD=E7=82=B9=E5=87=BB(AP=20=E7=B1=BB=20target=3Dnone=20?= =?UTF-8?q?=E5=8D=B3=E7=94=A8/=E7=A9=BA=E9=97=B4=E7=B1=BB=20toggle=20?= =?UTF-8?q?=E9=80=89=E4=B8=AD)+=5Fon=5Fboard=5Faction=20dig=20=E5=8A=AB?= =?UTF-8?q?=E6=8C=81(=E9=80=89=E4=B8=AD=E6=9C=9F=E7=82=B9=E6=A0=BC=3D?= =?UTF-8?q?=E7=9B=AE=E6=A0=87=E6=8B=BE=E5=8F=96=E4=B8=8D=E8=90=BD=E6=8C=96?= =?UTF-8?q?=E6=A0=BC;move/flag=20=E4=B8=8D=E5=86=B2=E7=AA=81)+=5Fattempt?= =?UTF-8?q?=5Faux=5Fuse(=E6=8B=92=E7=BB=9D=E9=9B=B6=E6=B6=88=E8=80=97=20to?= =?UTF-8?q?ast=20=E5=8F=AF=E8=BE=A8=E8=AE=A4)+=5Faux=5Finventory=5Fproject?= =?UTF-8?q?ion(=E5=AD=97=E5=BD=A2=3D=E5=90=8D=E9=A6=96=E5=AD=97;usable=20?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=8E=A5=E6=8E=92=E9=9B=B7=E7=8E=A9=E6=B3=95?= =?UTF-8?q?=E5=8F=AA=E8=AF=BB=E9=9D=A2)+use=20=E6=88=90=E5=8A=9F=E5=90=8E?= =?UTF-8?q?=E6=A7=BD=E4=BD=8D/=E5=8F=A0=E5=8A=A0=E5=B1=82=E5=8F=8C?= =?UTF-8?q?=E5=88=B7+=5Fon=5Fboard=5Faction=20=E8=A1=8C=E5=B0=BE=E6=A7=BD?= =?UTF-8?q?=E4=BD=8D=E9=87=8D=E6=9F=A5(=E6=8E=88=E4=BA=88=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E8=B5=B0=E5=AE=8C)=20=E2=91=A2=E5=8F=A0=E5=8A=A0?= =?UTF-8?q?=E5=B1=82=E5=91=88=E7=8E=B0:CellUnit.aux=5Fmark(=E7=9C=9F?= =?UTF-8?q?=E8=A7=86=3D=E5=8D=B1=E9=99=A9=E7=BA=A2=E5=86=85=E7=BC=A9?= =?UTF-8?q?=E6=A1=86)+board=5Fview.set=5Faux=5Foverlays=20=E6=8A=95?= =?UTF-8?q?=E5=BD=B1=E6=B3=A8=E5=85=A5(refresh=20=E6=B6=88=E8=B4=B9;?= =?UTF-8?q?=E9=A2=84=E8=AF=BB=E6=A0=BC=3Dbadge=5Fglyph=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=20REWARD=5FBADGE=20=E5=AD=97=E5=BD=A2=E8=B5=B0=20iden?= =?UTF-8?q?tity=5Fresolver=20=E6=97=A2=E6=9C=89=E5=8F=AA=E8=AF=BB=E9=9D=A2?= =?UTF-8?q?=E9=9B=B6=E6=95=B0=E6=8D=AE=E5=A4=8D=E5=88=B6;=E6=97=97?= =?UTF-8?q?=E6=A0=BC=E8=AE=A9=E4=BD=8D=E4=B8=8D=E6=B3=A8)=20=E2=91=A3?= =?UTF-8?q?=E6=B8=85=E7=A9=BA=E5=8F=8D=E9=A6=88:LevelRunner=20aux=5Fcleare?= =?UTF-8?q?d=20=E5=BF=AB=E7=85=A7(payload=20=E5=B7=B2=E6=9C=89=20T3)?= =?UTF-8?q?=E2=86=92AdventureRunner=20additive=20=E6=89=A9=E7=AD=BE(big=5F?= =?UTF-8?q?reward=5Fpaid.aux=5Fcleared/summary.aux=5Fcleared=5Flast,=5Flas?= =?UTF-8?q?t=5Faux=5Fcleared=20=E6=8D=95=E8=8E=B7)=E2=86=92main=20?= =?UTF-8?q?=E5=A4=A7=E5=A5=96=E5=8A=B1=E5=B1=8F=20toast=E3=80=8C=E4=B8=8A?= =?UTF-8?q?=E5=85=B3=E5=89=A9=E4=BD=99=20N=20=E4=BB=B6=E9=AD=94=E6=B3=95?= =?UTF-8?q?=E9=81=93=E5=85=B7=E5=B7=B2=E9=9A=8F=E5=85=B3=E5=8D=A1=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E6=B8=85=E7=A9=BA=E3=80=8D=20=E9=97=B8:=E5=86=92?= =?UTF-8?q?=E7=83=9F=20577/0+=E4=B8=89=E6=8E=A2=E9=92=88=20PASS;T()=20?= =?UTF-8?q?=E6=8B=BC=E6=8E=A5=E7=97=85=E7=B4=AF=E8=AE=A1=E5=85=AB=E5=A4=84?= =?UTF-8?q?=E5=85=A8=E8=87=AA=E6=84=88(board=5Fview=20=E6=B7=B7=E8=A1=8C?= =?UTF-8?q?=E5=B0=BE=E5=BD=92=E4=B8=80=20LF/adventure=5Frunner=20=5Ffirst?= =?UTF-8?q?=5Fscript=20=E9=87=8D=E5=A4=8D=E5=A3=B0=E6=98=8E=E5=8E=BB?= =?UTF-8?q?=E9=87=8D/=E4=B8=A4=20var=20=E6=8B=BC=E6=8E=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/adventure_runner.gd | 4 + game/presentation/main.gd | 96 +++++++++++++++++++ game/presentation/views/board_cell_unit.gd | 15 ++- game/presentation/views/board_view.gd | 22 +++++ .../views/hero_info_panel_view.gd | 50 +++++++++- 5 files changed, 184 insertions(+), 3 deletions(-) diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 2f267235..1a629109 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -64,6 +64,7 @@ var _last_level_payload := {} # 最近一次关卡结束载荷(失 var _override := [0, 0] # TunePanel 百分比通道 [atk_pct, hp_pct](每关新 runner 都要应用) var _base_override := {"atk": 0, "max_hp": 0, "armor_class": 0} var _first_script := false # 首次脚本模式(关卡节点图生成 v0.4——true = 全关零抽签取池内模板 0;测量夹具恒 false) # 永久强化 flat 注入(元进度实现批——冒险开始参数面 params.hero_override 存入,共享给 LevelRunner;全零 = 白板) +var _last_aux_cleared := 0 # additive(迭代 7 T4——最近一次关卡末清空计数,summary 取数面) func _init(db_ref, rng_ref) -> void: @@ -188,6 +189,7 @@ func _on_level_finished(payload: Dictionary) -> void: if result != "victory": push_error("[冒险结构] 关卡结束结果出域: %s" % result) return + _last_aux_cleared = int(payload.get("aux_cleared", 0)) # additive(迭代 7 T4——末关 summary 取数面) if level_index < levels_total: # 大奖励(非末关通关恰一次):上限提升写加算区 → 血回满 = 新上限 → 钥石入账(顺序即契约) var hp_bonus: int = int(db.adventure.level_clear_hp_bonus) @@ -202,6 +204,7 @@ func _on_level_finished(payload: Dictionary) -> void: "new_max_hp": int(db.hero.max_hp) + int(_base_override["max_hp"]) + int(bonus["max_hp"]), "keystone_gained": int(db.adventure.keystone_per_level), "keystone_total": keystone, + "aux_cleared": int(payload.get("aux_cleared", 0)), # additive 扩签(迭代 7 T4——关卡末清空反馈取数) }) return # 末关通关:钥石照记、不发上限提升(没有下一战可受益)——冒险完成结算 @@ -218,6 +221,7 @@ func _settle(result: String) -> void: "stopped_at": 0 if result == "completed" else level_index, "keystone_total": keystone, "master_seed": master_seed, + "aux_cleared_last": _last_aux_cleared, # additive 扩签(迭代 7 T4——末关清空反馈;非末关走 big_reward 载荷) } # 败退明细(数值验证工具取数面 2026-09-22——tools/balance_verifier.py 止步节点分布数据源; # 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知) diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 32392a7a..f1db2415 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -67,6 +67,7 @@ var shop_overlay: ShopOverlay # 商店覆盖层(GP+商店房实施 var _adventure_gp_view := 0 # GP 冒险钱包呈现侧影子(迭代 6 T7——终局面板 GP 行取数面, # wallet_changed gain/buy 载荷=入账后总额 level_runner.gd:482/539) var _adventure_gp_final := 0 # 收束清零拍暂存的最终合计("clear" 到达时自影子转存) +var _aux_use_selection := 0 # 魔法道具选中句柄(迭代 7 T4——0=未选中;选中期棋盘点击=使用目标拾取) var camp_overlay: CampOverlay # 营地覆盖层(元进度实现批——主菜单营地入口兑付呈现) var tune_panel: TunePanel @@ -115,6 +116,7 @@ func _build_ui() -> void: settlement_fx = battle_ui.settlement_fx log_view.review_requested.connect(_open_review_from_band) board_zone.action_requested.connect(_on_board_action) + info_panel.aux_slot_clicked.connect(_on_aux_slot_clicked) # 迭代 7 T4——两步交互第一步 board_zone.stop_pressed.connect(_on_stop_pressed) board_zone.refresh_confirmed.connect(_on_refresh_confirmed) board_zone.legend_toggled.connect(_on_legend_toggled) @@ -304,6 +306,8 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void: # 大奖励兑付(非末关通关恰一次):复用 _inter_overlay(label + 确认按钮,零新增交互范式); # 文案装配归 Narrator func _on_big_reward(reward: Dictionary) -> void: + if int(reward.get("aux_cleared", 0)) > 0: + board_zone.toast.show_toast("上关剩余 %d 件魔法道具已随关卡结束清空" % int(reward.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈 _inter_label.text = Narrator.big_reward_text(reward) _inter_overlay.visible = true _refresh_presentations() @@ -722,6 +726,91 @@ func _on_shop_leave() -> void: runner.continue_after_node() +# ---- 魔法道具 HUD 接线(迭代 7 T4——库存条+两步交互+叠加层呈现;数据源=runner 级库存只读面)---- + +# 槽点击(第一步):AP 类(target=none)点击即用(无目标);空间类进/出选中态(toggle 取消)。 +func _on_aux_slot_clicked(slot_handle: int) -> void: + if settlement == null or runner == null or runner.aux_inventory == null: + return + if slot_handle == 0: + _aux_use_selection = 0 + _refresh_aux_slots() + return + var rec: Dictionary = {} + for r in _aux_inventory_projection(): + if int(r.get("handle", 0)) == slot_handle: + rec = r + break + if rec.is_empty() or bool(rec.get("used", false)): + return + var arow: Dictionary = db.aux_item_row(String(rec.get("item_id", ""))) + if String(arow.get("target", "none")) == "none": + _attempt_aux_use(slot_handle, Vector2i(-9, -9)) # AP 类:无目标即用 + return + if not bool(rec.get("usable", true)): + board_zone.toast.show_toast("当前无可预读的未开格", "warn") + return + _aux_use_selection = 0 if _aux_use_selection == slot_handle else slot_handle # toggle 取消 + _refresh_aux_slots() + if _aux_use_selection != 0: + board_zone.toast.show_toast("%s:点击目标格使用(9 宫窗口),再点道具取消" % String(rec.get("name", "")), "info") + + +# 使用受理(第二步;core 侧 use_aux 四步编排——拒绝零消耗 toast 可辨认) +func _attempt_aux_use(handle: int, cell: Vector2i) -> void: + if settlement == null: + return + var v: Dictionary = settlement.use_aux(handle, cell) + if not bool(v.ok): + board_zone.toast.show_toast(String(v.get("reason", "无法使用")), "warn") + return + _aux_use_selection = 0 + _refresh_aux_slots() + _refresh_aux_overlays() + var arow: Dictionary = db.aux_item_row(String(v.get("item_id", ""))) + board_zone.toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info") + + +# 库存投影(C 区四槽取数面——字形=名首字;usable=板级零信息预判回调接排雷玩法只读面) +func _aux_inventory_projection() -> Array: + if runner == null or runner.aux_inventory == null: + return [] + var ms = settlement.minesweeper if settlement != null else null + var cb := Callable() + if ms != null: + cb = func(_c: Vector2i) -> bool: + for y in ms.current_board().height: + for x in ms.current_board().width: + if not ms.is_revealed(Vector2i(x, y)): + return true + return false + var out: Array = [] + for rec in runner.aux_inventory.query_inventory(cb): + var arow: Dictionary = db.aux_item_row(String(rec.get("item_id", ""))) + var dn := String(arow.get("display_name", "")) + out.append({ + "handle": int(rec.get("handle", 0)), + "item_id": String(rec.get("item_id", "")), + "glyph": dn.left(1) if dn != "" else "?", + "name": dn, + "used": bool(rec.get("used", false)), + "usable": bool(rec.get("usable", true)), + }) + return out + + +func _refresh_aux_slots() -> void: + info_panel.set_aux_inventory(_aux_inventory_projection(), _aux_use_selection) + + +# 叠加层投影注入(use 成功/三清后——棋盘标记与预读徽章重算) +func _refresh_aux_overlays() -> void: + if runner == null or runner.aux_inventory == null or board_view == null: + return + var ov: Dictionary = runner.aux_inventory.query_overlays() + board_view.set_aux_overlays(ov.get("mine_reveal", []), ov.get("reward_preview", [])) + + func _on_wallet_changed(event: String, gp: int, extra: Dictionary) -> void: # wallet 行族转发(到账/购买/清零——试玩日志验收取数面)+ 常驻余额与在开商店覆盖层重刷 logger.log_wallet(event, gp, extra) @@ -742,9 +831,16 @@ func _on_board_action(cell: Vector2i, kind: String) -> void: # 玩家动作 → 核心写调用的唯一通道(模态/无战斗拒绝在此收口) if _input_locked(): return + # 魔法道具选中态劫持(迭代 7 T4——两步交互第二步):点格=使用目标拾取,不落挖格; + # 只劫持挖掘点击(move/flag 语义不冲突——选中期玩家仍可移动/插旗) + if _aux_use_selection != 0 and kind == "dig": + _attempt_aux_use(_aux_use_selection, cell) + return var outcomes: Array = settlement.minesweeper.settle_batch([{"action": kind, "cell": cell}]) var accepted: bool = not outcomes.is_empty() and bool(outcomes[0].accepted) logger.log_action(kind, cell, accepted) + if runner != null and runner.aux_inventory != null: + _refresh_aux_slots() # 迭代 7 T4——授予发生在 settle_batch 结算段内(同步走完,行尾重查) _handle_outcome_feedback(outcomes[0] if not outcomes.is_empty() else {}) # settle_batch 内部同步走完 结算→战斗结束→关卡推进(事件同步发射)——关卡终局时 settlement 已被 # runner 置空,清图演出判定须先判 settlement(基线既有重入隐患,2026-09-13 加固) diff --git a/game/presentation/views/board_cell_unit.gd b/game/presentation/views/board_cell_unit.gd index 73b94543..5edc1480 100644 --- a/game/presentation/views/board_cell_unit.gd +++ b/game/presentation/views/board_cell_unit.gd @@ -82,7 +82,15 @@ var tex_flag: Texture2D = null # ④ 旗 # 变异角标(迭代 6 T6——程序化,tex_mutation 纹带槽留给变异纹带六型自产批): # "flat_damage" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记) -var mutation_mark := "": +# 叠加层标记(迭代 7 T4——魔法道具板级叠加层的格面呈现;派生信息层零回写): +# "mine_reveal" = 真视格(危险红内缩框——「这格是雷」的确定决策标记,插旗照常计费); +# "reward_preview" 格不复用本字段——预读徽章走 badge_glyph 注入(board_view refresh 注入 REWARD_BADGE 字形) +var aux_mark := "": + set(v): + if aux_mark != v: + aux_mark = v + queue_redraw() +var mutation_mark := ":": set(v): if mutation_mark != v: mutation_mark = v @@ -175,6 +183,11 @@ func _draw() -> void: draw_texture_rect(tex_mutation, rect, false) # ②' 变异角标(迭代 6 T6):左下角危险红三角(危险红=「开此格有代价」语义位,与雷族同源; # 尺寸随格边比例——灰色可辨辅助=红对砖面明度差,禁喧宾) + # ②" 魔法道具叠加层(迭代 7 T4):真视格=危险红内缩框(策略纹理——确定决策输入不喧宾) + if aux_mark == "mine_reveal": + var inset := edge * 0.14 + draw_rect(Rect2(inset, inset, edge - inset * 2.0, edge - inset * 2.0), + Color(UiKit.RED_MINE, 0.9), false, 2.0) if mutation_mark == "flat_damage": var mw := edge * 0.28 var mpts := PackedVector2Array([Vector2(0, edge - mw), Vector2(0, edge), Vector2(mw, edge)]) diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index 976b5b52..72476679 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -98,6 +98,8 @@ var _minesweeper = null # 排雷玩法 只读查询引用(refresh var _identity_cb := Callable() # 地图战斗资源 身份解析回调(组合根注入只读面)——Vector2i → Dictionary/null var _mask_cb := Callable() # 地形变异 数字遮蔽回调(组合根注入只读面)——Vector2i → bool(true = 该格数字不显示) var _vmark_cb := Callable() # 地形变异 角标回调(迭代 6 T6 只读面)——Vector2i → String("flat_damage"=伤害型变异未开格;""=无) +var _aux_reveal := {} # 魔法道具 真视集(迭代 7 T4——Vector2i → true;组合根 set_aux_overlays 注入投影) +var _aux_preview := {} # 魔法道具 预读集(同上——未开格徽章呈现挂点) var _diggable := {} # 可挖域(8 向相邻未开格)——呼吸微光作用集 var _breath_t := 0.0 var _contact_unit: Node2D = null # 接触带承载单元(=英雄格 CellUnit;rebuild 随清场复位) @@ -193,6 +195,16 @@ func set_variant_mask_resolver(cb: Callable) -> void: func set_variant_mark_resolver(cb: Callable) -> void: _vmark_cb = cb +# 魔法道具叠加层投影注入(迭代 7 T4——组合根在 use 成功/三清后调;refresh 消费) +func set_aux_overlays(reveal_cells: Array, preview_cells: Array) -> void: + _aux_reveal.clear() + _aux_preview.clear() + for c in reveal_cells: + _aux_reveal[Vector2i(c)] = true + for c in preview_cells: + _aux_preview[Vector2i(c)] = true + refresh() + # SubViewport 主题/字体显式注入挂点(Phase B 两舞台复用——SubViewport 不自动继承主场景 Control 主题; # 本项目无自定义主题,缺省 = 引擎回退字体,与旧 Button 渲染同源) @@ -722,6 +734,16 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: # 地面投影:北邻墙格在本已开格北缘投渐隐暗带(ITB 同款高度线索——带宽按格边比例常量) if north_in and not _minesweeper.is_revealed(north_n): u.proj_band = u.edge * WALL_PROJECTION_FRAC + # ②( 魔法道具叠加层(迭代 7 T4):真视格=红内缩框;预读格=奖励身份徽章(identity_resolver 既有 + # 只读面取字形——零数据复制,badge 位复用);已开格标记自然退位(refresh 逐拍重算) + if not revealed: + u.aux_mark = "mine_reveal" if _aux_reveal.has(c) else "" + if _aux_preview.has(c) and glyph == "": + var pid = _identity_cb.call(c) if _identity_cb.is_valid() else null + if pid != null and REWARD_BADGE.has(String(pid.get("cell_type", ""))): + glyph = String(REWARD_BADGE[String(pid.get("cell_type", ""))]) + digit_col = COL_GOLD + u.badge_glyph = glyph # ②' 变异角标(迭代 6 T6):未开格带伤害型变异 → 危险红角标(已开=威胁已兑现标记退位); # 回调缺位 = 无标记(程序化角标,tex_mutation 纹带槽留给变异纹带自产批) if not revealed and _vmark_cb.is_valid(): diff --git a/game/presentation/views/hero_info_panel_view.gd b/game/presentation/views/hero_info_panel_view.gd index 9ec21b94..b25159ce 100644 --- a/game/presentation/views/hero_info_panel_view.gd +++ b/game/presentation/views/hero_info_panel_view.gd @@ -14,7 +14,11 @@ const COL_INK := UiKit.INK var _atk_label: Label var _armor_label: Label -var item_slots: HBoxContainer # 魔法道具槽(预留——4 空槽占位;数量/交互形态待魔法道具批定稿) +signal aux_slot_clicked(handle: int) # 迭代 7 T4——槽点击(组合根消费:AP 类即用/空间类进选中态) +var item_slots: HBoxContainer # 魔法道具槽(迭代 7 T4 接线——库存条常读+两步交互第一步) +var _slot_panels: Array = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷) +var _selected_handle := 0 # 选中态(0=无;组合根回写维持单源) +var _slot_handles: Array = [] # 槽位→handle 映射(set_aux_inventory 刷新) var _bonus_section: VBoxContainer # 本关强化(预留——空态隐藏,接线归大奖励/锻造呈现批) var _bonus_list: VBoxContainer @@ -54,8 +58,13 @@ func _init() -> void: slot.custom_minimum_size = Vector2(48, 48) slot.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(4, 4, 4, 4))) - slot.mouse_filter = Control.MOUSE_FILTER_IGNORE + var idx := i + slot.gui_input.connect(func(ev: InputEvent): + if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT: + # 空槽/越界槽点击=取消选中(handle 0);有效槽发其 handle(组合根判 AP 即用/进选中态) + aux_slot_clicked.emit(_slot_handles[idx] if idx < _slot_handles.size() else 0)) item_slots.add_child(slot) + _slot_panels.append(slot) # 本关强化(预留):锻造/大奖励/关卡奖励节点的属性提升——中循环数据因改变战斗数学准入; # 与战斗内状态效果(B 区站桩,随战斗消散)分区,防「战斗结束被清」误读;空态整节隐藏 @@ -110,3 +119,40 @@ func _data_line() -> Label: l.clip_text = true l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS return l + +# ---- 魔法道具库存条(迭代 7 T4——C 区四槽常读+两步交互第一步)---- +# 投影行 = {handle, glyph, name, used, usable}(组合根组装:字形=道具名首字;数据源 runner 级库存只读面)。 +# 空槽=虚位;used=暗态「已用」保留占位(Used 不可逆——受理序展示语义);不可用=置灰(板级零信息); +# selected=金框高亮(两步交互第一步的可见态)。tooltip=全名+维度语义。 +func set_aux_inventory(items: Array, selected_handle: int) -> void: + _selected_handle = selected_handle + _slot_handles.clear() + for i in 4: + var slot: PanelContainer = _slot_panels[i] + for c in slot.get_children(): + c.queue_free() + if i >= items.size(): + _slot_handles.append(0) + slot.tooltip_text = "魔法道具槽(空)" + slot.add_theme_stylebox_override("panel", + UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(4, 4, 4, 4))) + continue + var rec: Dictionary = items[i] + _slot_handles.append(int(rec.get("handle", 0))) + var used := bool(rec.get("used", false)) + var usable := bool(rec.get("usable", true)) + var selected := selected_handle != 0 and int(rec.get("handle", 0)) == selected_handle + var glyph := UiKit.label(String(rec.get("glyph", "?")), 20, true) + glyph.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + glyph.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + glyph.set_anchors_preset(Control.PRESET_FULL_RECT) + glyph.mouse_filter = Control.MOUSE_FILTER_IGNORE + if used: + glyph.add_theme_color_override("font_color", UiKit.STONE_FAINT) + elif not usable: + glyph.add_theme_color_override("font_color", UiKit.STONE) + slot.add_child(glyph) + var border_col: Color = UiKit.GOLD if selected else UiKit.PANEL_BORDER + slot.add_theme_stylebox_override("panel", + UiKit.panel_flat(UiKit.PANEL_DATA, border_col, 0, 1 if not selected else 2, Vector4(4, 4, 4, 4))) + slot.tooltip_text = "%s%s" % [String(rec.get("name", "")), "(已用)" if used else ("(不可用——当前无有效目标)" if not usable else "")]