## VERTICAL SLICE - NOT FOR PRODUCTION ## 状态效果槽行:效果图标+层数角标+剩余点+悬停 tooltip;空态整行隐藏不占位。 ## 极性映射=战斗结算效果语义(非 magnitude 符号);效果投影装配归 HudPresenter,本类纯被动零信号。 class_name StatusSlotRow extends HBoxContainer const MAX_SLOTS := 4 # 纹理映射:effect_id→定稿纹理,全局恒定禁换图(图标键契约 §4.6 的 # 文件侧延伸——文件键 = status_.png,命名规范圣经 §8.3「对象键 = 数据表主键」)。 # 状态表九行全映射(attack_up/ac_up/weaken/alert/enrage/off_guard/sapped/empower/fortify) # ——HUD框架 图标键契约"一键一枚专属纹样,查不到的键立即断言 + 占位 + 诊断" const EFFECT_ICON := { "attack_up": "res://assets/ui/icons/status_attack_up.png", "ac_up": "res://assets/ui/icons/status_ac_up.png", "weaken": "res://assets/ui/icons/status_weaken.png", "alert": "res://assets/ui/icons/status_alert.png", "enrage": "res://assets/ui/icons/status_enrage.png", "off_guard": "res://assets/ui/icons/status_off_guard.png", "sapped": "res://assets/ui/icons/status_sapped.png", "empower": "res://assets/ui/icons/status_empower.png", "fortify": "res://assets/ui/icons/status_fortify.png", } # 极性词(tooltip 第三通道;运行时不再按极性染色/画框——绿↑红↓极性语言已内化于定稿图标本体) const EFFECT_POLARITY := { "attack_up": "益", "ac_up": "益", "weaken": "益", "alert": "害", "enrage": "害", "off_guard": "害", "sapped": "害", "empower": "害", "fortify": "害", } const EFFECT_SEMANTIC := { "attack_up": "主角攻击加成(hero_offense 池加算)", "ac_up": "护甲等级 +1/层(掷点折扣判定阈值——AC 数值轴,受击低掷按档位打折)", "weaken": "敌方输出削弱(enemy_output 池乘算)", "alert": "敌方护甲等级 +1/层(blast 型负向——我方普通攻击掷点更易落入折扣档)", "enrage": "敌方输出加成(enemy_output 池加算——attack 型负向)", "off_guard": "主角承伤加深(hero_defense 池加算——defense 型负向)", "sapped": "主角输出降低(hero_offense 池加算——weaken 型负向)", "empower": "敌方输出 ×2(enemy_output 池加算——蓄力技施放;持续 1 个有效结算)", "fortify": "敌方防御 −50(enemy_defense 池——硬化技施放,主角攻击 ×0.5;持续 1 个有效结算)", } const SOURCE_NAMES := {"safe_cell": "安全格", "seal_hit": "封印命中", "mine_hit": "踩雷", "clear_reward": "清图奖励"} # 未知图标键的占位(HUD框架:占位也要可见,禁静默空图标——debug 断言先行,release 回退槽内 ? 文本) const ICON_PLACEHOLDER := "?" var _tip: PanelContainer var _tip_label: Label var _slots: Array[PanelContainer] = [] # 槽引用数组持有——tooltip _tip 悬停后入子节点表,按位序 get_child 会误取(BUG-0001) var _cleared_feedback := false var compact := false # B 区站桩迷你档:16px 图标+层数角标、无 meta 行——tooltip 义务随组件迁入站桩 func _init() -> void: add_theme_constant_override("separation", 6) _tip = PanelContainer.new() _tip.z_index = 10 _tip.mouse_filter = Control.MOUSE_FILTER_IGNORE _tip.visible = false var sb := UiKit.panel_flat(UiKit.TIP_BG, UiKit.GOLD,UiKit.RADIUS_OBJECT, 1, Vector4(6, 3, 6, 3)) _tip.add_theme_stylebox_override("panel", sb) _tip_label = UiKit.label("",UiKit.FS_HINT, false, UiKit.WARM) _tip_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _tip_label.custom_minimum_size = Vector2(210, 0) _tip.add_child(_tip_label) # effects = 增益状态 query_active 投影数组(含空态);空槽隐藏,非空按插入序填充(最多 4 槽可见——超出截断) func set_effects(effects: Array) -> void: _cleared_feedback = false _sync_slots(effects.size()) for i in _slots.size(): _fill_slot(_slots[i], effects[i]) _slots[i].visible = true # battle_over 反馈:逐槽化光收回 + 微灰化(下次 set_effects 恢复常态; # 灰化即清场反馈) func set_cleared_feedback() -> void: _cleared_feedback = true for slot in _slots: slot.visible = true slot.modulate = Color(0.7, 0.7, 0.7, 0.5) func _sync_slots(n: int) -> void: # 移除旧槽(槽生命周期由 _slots 数组唯一管理——子节点表含 tooltip 节点,禁按位序索引;BUG-0001) for slot in _slots: remove_child(slot) slot.queue_free() _slots.clear() var count := mini(maxi(n, 0), MAX_SLOTS) for i in count: var slot := _make_slot() _slots.append(slot) add_child(slot) func _make_slot() -> PanelContainer: var slot := PanelContainer.new() if compact: # 槽底不透明(图标显示不清晰——半透明底叠 B 区米色面板吃掉白图标对比度; # 改深墨棕实底 = 白色纹样图标对比承载,槽界同时可辨) slot.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.SLOT_BACKDROP, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(2, 1, 2, 1))) else: slot.custom_minimum_size = Vector2(50, 46) slot.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER)) slot.mouse_entered.connect(_on_slot_hover.bind(slot)) slot.mouse_exited.connect(_hide_tip) var col := VBoxContainer.new() col.alignment = BoxContainer.ALIGNMENT_CENTER if compact: col.add_theme_constant_override("separation", 0) slot.add_child(col) # 定稿纹理图标(32px 源 → 常规档 32px=1×;mini(站桩)档 16px=0.5× # 共用 §8.2 例外登记——线性过滤匹配抗锯齿矢量风源图) var icon := TextureRect.new() icon.custom_minimum_size = Vector2(32, 32) if not compact else Vector2(16, 16) icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR icon.mouse_filter = Control.MOUSE_FILTER_IGNORE col.add_child(icon) var meta := UiKit.label("",UiKit.FS_CAPTION, false) meta.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER col.add_child(meta) return slot func _fill_slot(slot: PanelContainer, eff: Dictionary) -> void: slot.modulate = Color(1, 1, 1) slot.set_meta("eff", eff) var col := slot.get_child(0) as VBoxContainer var icon := col.get_child(0) as TextureRect var tex := _icon_tex(String(eff.effect_id)) icon.visible = tex != null icon.texture = tex var layers := int(eff.get("layers", 1)) if compact: # 迷你档:层数角标挂图标下方(×N)——槽随内容自适应,无 meta 行/剩余点(悬停 tooltip 承载全部明细) var tag := col.get_child(1) as Label tag.text = "×%d" % layers if layers > 1 else "" tag.add_theme_font_size_override("font_size", UiKit.FS_CAPTION) if tex == null: tag.text = (ICON_PLACEHOLDER + tag.text).strip_edges() return var meta := col.get_child(1) as Label # 层数角标 + 剩余结算次数点(● 未耗余量)——叠层数字呈现逻辑保持不动 var dots := "" var left: int = int(eff.get("remaining_settlements", 0)) for i in maxi(left, 1): dots += "●" if i < left else "○" meta.text = "×%d %s" % [layers, dots] if tex == null: meta.text = ICON_PLACEHOLDER + " " + meta.text # release 回退占位(debug 已断言) func _on_slot_hover(slot: Control) -> void: # tooltip:来源 + 语义 + 剩余(≥13px;悬停只读反馈通道——无点击无写) if not slot.has_meta("eff"): return var eff: Dictionary[Variant, Variant] = slot.get_meta("eff") var eid := String(eff.effect_id) var src: String = String(SOURCE_NAMES.get(String(eff.get("source_class", "")), String(eff.get("source_class", "")))) var semantic: String = String(EFFECT_SEMANTIC.get(eid, String(eff.get("display_name", "")))) _tip_label.text = "%s〔%s〕\n%s\n剩余结算 %d 次\n来源:%s(第 %d 轮)" % [ String(eff.get("display_name", "")), String(EFFECT_POLARITY.get(eid, ICON_PLACEHOLDER)), semantic, int(eff.get("remaining_settlements", 0)), src, int(eff.get("round_index", 0))] if not _tip.is_inside_tree(): add_child(_tip) _tip.reset_size() # 防出屏定位(缺陷①家族回归——边缘格 tooltip 硬编码上偏移会被视口裁切): # 默认悬浮槽位上方,越上缘翻转到槽位下方,再钳制在视口内;top_level 脱离容器布局管理 _tip.top_level = true var slot_rect := slot.get_global_rect() var desired := slot_rect.position + Vector2(0.0, -_tip.size.y - 6.0) var vp := get_viewport_rect().size desired.x = clampf(desired.x, 4.0, maxf(4.0, vp.x - _tip.size.x - 4.0)) if desired.y < 4.0: desired.y = slot_rect.end.y + 6.0 desired.y = clampf(desired.y, 4.0, maxf(4.0, vp.y - _tip.size.y - 4.0)) _tip.global_position = desired _tip.visible = true # 图标键解析(HUD框架:查不到的键按错误处理——立即断言 + 占位 + 诊断,禁止静默显示空图标) # 装载走 UiKit.load_tex 守卫(呈现层素材 preload 红线=0—— # 素材缺位树上 preload 直接断言炸脚本;路径串表 + exists 守卫,缺位 null 走占位回退) func _icon_tex(effect_id: String) -> Texture2D: if EFFECT_ICON.has(effect_id): return UiKit.load_tex(EFFECT_ICON[effect_id]) push_error("[状态槽] 未知图标键(映射表外键——立即断言): %s" % effect_id) assert(false, "未知图标键: " + effect_id) return null func _hide_tip() -> void: _tip.visible = false