class_name ABGRiskCommitmentPageView extends "res://presentation/page_view.gd" ## UI-only RSK-002 Risk Commitment View. ## ## The authored page uses a normalized 16:9 canvas. The View owns the canvas, ## static master, semantic overlays, focus and explicit intents; all values are ## supplied by the controller snapshot. const PageChrome = preload("res://ui/shared/page_chrome.gd") const UITokens = preload("res://foundation/ui_tokens.gd") const InitialCompanyArt = preload("res://entry/initial_company_art.gd") const RISK_COMMITMENT_STATIC_MASTER = preload("res://assets/entry/rsk_002_risk_commitment/risk-commitment-static-master.png") var design_canvas: Control func _notification(what: int) -> void: if what == NOTIFICATION_RESIZED: _layout_design_canvas() func _render_view(data: Dictionary) -> void: clear_view_children() design_canvas = null var matte := ColorRect.new() matte.name = "RiskCommitmentMatte" matte.color = Color("#16252d") matte.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) matte.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(matte) design_canvas = Control.new() design_canvas.name = "RiskCommitmentMasterCanvas" design_canvas.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(design_canvas) _layout_design_canvas() var master := TextureRect.new() master.name = "RiskCommitmentStaticMaster" master.texture = RISK_COMMITMENT_STATIC_MASTER master.expand_mode = TextureRect.EXPAND_IGNORE_SIZE master.stretch_mode = TextureRect.STRETCH_SCALE master.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS master.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) master.mouse_filter = Control.MOUSE_FILTER_IGNORE design_canvas.add_child(master) if not bool(data.get("ok", false)): _render_invalid(data) return _build_overlay(data) func _layout_design_canvas() -> void: if not is_instance_valid(design_canvas): return var available := size if available.x <= 0.0 or available.y <= 0.0: return var canvas_size := Vector2( available.y * 16.0 / 9.0, available.y ) if available.x / available.y >= 16.0 / 9.0 else Vector2( available.x, available.x * 9.0 / 16.0 ) design_canvas.position = (available - canvas_size) * 0.5 design_canvas.size = canvas_size func _build_overlay(data: Dictionary) -> void: var overlay := Control.new() overlay.name = "RiskCommitmentMasterOverlay" overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) overlay.mouse_filter = Control.MOUSE_FILTER_PASS design_canvas.add_child(overlay) var view_contract: Dictionary = Dictionary(data.get("contract", {})) var view_risk: Dictionary = Dictionary(data.get("risk", {})) var option: Dictionary = Dictionary(view_risk.get("option", {})) var view_party: Dictionary = Dictionary(data.get("party", {})) var view_dependency: Dictionary = Dictionary(data.get("dependency", {})) var dependency: Dictionary = Dictionary(view_dependency.get("facts", {})) var confirmation: Dictionary = Dictionary(data.get("confirmation", {})) var commands: Dictionary = Dictionary(data.get("commands", {})) var facts_data: Array = Array(Dictionary(data.get("consequences", {})).get("facts", [])) var party_recruits: Array = Array(view_party.get("ordered_recruits", [])) var acknowledged := bool(confirmation.get("acknowledged", false)) var party_ids: Array = Array(view_party.get("front_to_rear_ids", [])) var ready := bool(commands.get("begin_enabled", false)) _risk_label(overlay, "RiskCommitmentPageTitle", Rect2(0.08, 0.018, 0.27, 0.052), _tr("风险承诺", "RISK COMMITMENT", data), 12, Color("#fff1c2")) _risk_label(overlay, "RiskCommitmentHeaderRule", Rect2(0.08, 0.068, 0.30, 0.032), _tr("在进入战斗前核对一条已选路线。", "VERIFY ONE SELECTED ROUTE BEFORE BATTLE.", data), 7, Color("#e8d39d")) var settings_button := _risk_command_button(overlay, "RiskCommitmentSettings", Rect2(0.905, 0.015, 0.085, 0.078), "", "settings", "settings", false) settings_button.tooltip_text = _tr("打开设置", "Open settings", data) var risk_glyphs := ["◆", "▲", "✦"] var risk_index := clampi(int(option.get("risk", 0)), 0, 2) _risk_marker(overlay, "RiskIdentityMarker", Rect2(0.085, 0.105, 0.055, 0.072), risk_glyphs[risk_index], [Color("#3d7449"), Color("#a07816"), Color("#a74337")][risk_index]) var identity := _risk_label(overlay, "RiskSelectedIdentity", Rect2(0.16, 0.105, 0.20, 0.048), _risk_title(option, data), 9, Color("#2d241a"), HORIZONTAL_ALIGNMENT_RIGHT) identity.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS var source := _risk_label(overlay, "RiskSelectedSource", Rect2(0.16, 0.152, 0.20, 0.034), _tr("来源 %s", "SOURCE %s", data) % String(option.get("risk_option_id", "")).substr(0, 8), 5, Color("#6e5634"), HORIZONTAL_ALIGNMENT_RIGHT) source.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS var contract_id := String(view_contract.get("contract_id", "CTR-R1-01")) _risk_label(overlay, "RiskSelectedRegion", Rect2(0.10, 0.53, 0.16, 0.04), _tr("区域 %s", "REGION %s", data) % contract_id, 6, Color("#3d3124"), HORIZONTAL_ALIGNMENT_CENTER) var slot_labels: Array = [] for raw_fact in facts_data: if raw_fact is Dictionary: var fact: Dictionary = Dictionary(raw_fact) slot_labels.append([ String(fact.get("label", "")), String(fact.get("value", fact.get("text", ""))), String(fact.get("glyph", "•")), fact.get("color", Color("#3d7449")), ]) for index in range(slot_labels.size()): var y := 0.115 + float(index) * 0.073 _risk_marker(overlay, "RiskSlotMarker%d" % index, Rect2(0.265, y + 0.004, 0.026, 0.038), String(slot_labels[index][2]), slot_labels[index][3]) var slot_label := _risk_label(overlay, "RiskSlotLabel%d" % index, Rect2(0.296, y, 0.095, 0.020), String(slot_labels[index][0]), 5, Color("#766043"), HORIZONTAL_ALIGNMENT_CENTER) slot_label.visible = false slot_label.set_meta("semantic_node_retained", true) var slot_value := _risk_label(overlay, "RiskSlotValue%d" % index, Rect2(0.292, y + 0.006, 0.101, 0.050), String(slot_labels[index][1]), 7, Color("#31271c"), HORIZONTAL_ALIGNMENT_CENTER) slot_value.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS var detail := _risk_label(overlay, "RiskCommitmentDetail", Rect2(0.08, 0.575, 0.31, 0.045), _risk_detail_copy(option, data), 6, Color("#5c4930")) detail.visible = false detail.set_meta("semantic_node_retained", true) var recovery_rule := _risk_label(overlay, "RiskRecoveryRule", Rect2(0.08, 0.615, 0.31, 0.03), _tr("击倒成员仅离开本场;下阶段以同一身份归队。", "A knocked-out member leaves this battle only and returns next stage with the same identity.", data), 6, Color("#3d7449")) recovery_rule.visible = false recovery_rule.set_meta("semantic_node_retained", true) var selected_hit := _risk_hit_button(overlay, "RiskSelectedRiskDetails", Rect2(0.055, 0.055, 0.36, 0.585), "review_risk_details", {}, _tr("查看已选风险事实", "Review selected risk facts", data)) selected_hit.set_meta("hit_area_id", "selected_risk_details") var consequence_hit := _risk_hit_button(overlay, "RiskConsequenceBand", Rect2(0.075, 0.65, 0.85, 0.145), "review_consequences", {}, _tr("查看六条后果事实", "Review six consequence facts", data)) consequence_hit.set_meta("hit_area_id", "consequence_band") var fact_root := Control.new() fact_root.name = "RiskCommitmentFacts" fact_root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) fact_root.mouse_filter = Control.MOUSE_FILTER_IGNORE overlay.add_child(fact_root) for index in range(slot_labels.size()): var fact := Control.new() fact.name = "RiskConsequenceFact%d" % index _risk_place(fact, Rect2(0.085 + float(index) * 0.139, 0.668, 0.128, 0.12)) fact_root.add_child(fact) _risk_marker(fact, "FactMarker", Rect2(0.01, 0.08, 0.16, 0.32), String(slot_labels[index][2]), slot_labels[index][3]) _risk_label(fact, "FactLabel", Rect2(0.20, 0.03, 0.78, 0.32), String(slot_labels[index][0]), 7, Color("#766043"), HORIZONTAL_ALIGNMENT_CENTER) _risk_label(fact, "FactValue", Rect2(0.18, 0.37, 0.80, 0.57), String(slot_labels[index][1]), 7, Color("#31271c"), HORIZONTAL_ALIGNMENT_CENTER) var consequence_summary := _risk_label(overlay, "RiskConsequenceSummary", Rect2(0.09, 0.785, 0.82, 0.025), _tr("六条事实均来自当前风险投影;没有隐藏胜率或自动推荐。", "Six facts come from the current risk projection; no hidden win rate or recommendation.", data), 6, Color("#3e3428"), HORIZONTAL_ALIGNMENT_CENTER) consequence_summary.set_meta("semantic_node_retained", true) var party_root := Control.new() party_root.name = "RiskFrozenMembers" _risk_place(party_root, Rect2(0.47, 0.235, 0.49, 0.40)) party_root.mouse_filter = Control.MOUSE_FILTER_IGNORE overlay.add_child(party_root) _risk_label(overlay, "RiskPartySnapshotTitle", Rect2(0.48, 0.075, 0.25, 0.04), _tr("冻结队伍 · 四人", "FROZEN PARTY · FOUR", data), 9, Color("#2d241a")) var snapshot_id := String(view_party.get("snapshot_id", view_party.get("snapshot_hash", ""))).substr(0, 10) _risk_label(overlay, "RiskPartySnapshotVersion", Rect2(0.80, 0.055, 0.145, 0.04), _tr("快照 %s", "SNAP %s", data) % snapshot_id, 7, Color("#463725"), HORIZONTAL_ALIGNMENT_RIGHT) for visual_index in range(party_ids.size() - 1, -1, -1): var ordered: Dictionary = Dictionary(party_recruits[visual_index]) if visual_index < party_recruits.size() else {} var position_index := int(ordered.get("position", visual_index)) var display_index := party_ids.size() - 1 - visual_index var recruit_id := String(party_ids[visual_index]) var recruit: Dictionary = Dictionary(ordered.get("recruit", {})).duplicate(true) var member := Control.new() member.name = "RiskFrozenMember%d" % display_index _risk_place(member, Rect2(float(display_index) * 0.245, 0.0, 0.225, 1.0)) member.mouse_filter = Control.MOUSE_FILTER_IGNORE member.set_meta("recruit_id", recruit_id) member.set_meta("position_index", position_index) party_root.add_child(member) var actor := TextureRect.new() actor.name = "RecruitPortrait" actor.texture = InitialCompanyArt.texture_for(recruit) actor.expand_mode = TextureRect.EXPAND_IGNORE_SIZE actor.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED actor.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS _risk_place(actor, Rect2(0.02, 0.02, 0.96, 0.72)) actor.mouse_filter = Control.MOUSE_FILTER_IGNORE member.add_child(actor) var relation: Dictionary = Dictionary(ordered.get("relation", {})).duplicate(true) var exposure := _risk_exposure_text(String(relation.get("exposure_id", "rear")), data) var marker := _risk_marker(member, "PartyFactMarker", Rect2(0.28, 0.10, 0.16, 0.14), "●", Color("#3d7449")) marker.set_meta("reach_id", String(relation.get("reach_id", "limited"))) _risk_label(member, "PartyPosition", Rect2(0.02, 0.70, 0.96, 0.12), _tr("位置 %d · %s", "POS %d · %s", data) % [position_index, exposure], 7, Color("#3a3025"), HORIZONTAL_ALIGNMENT_CENTER) _risk_label(member, "PartyIdentity", Rect2(0.0, 0.81, 1.0, 0.19), _tr("%s\n%s", "%s\n%s", data) % [_recruit_name(recruit, data), _recruit_profession_name(recruit, data)], 7, Color("#2e271e"), HORIZONTAL_ALIGNMENT_CENTER) var coverage: Dictionary = Dictionary(view_party.get("coverage", {})) _risk_label(overlay, "RiskPartyCoverage", Rect2(0.49, 0.505, 0.44, 0.035), _tr("覆盖 · 保护 %d 远程 %d 续航 %d 控制 %d", "COVERAGE · GUARD %d REACH %d SUSTAIN %d CONTROL %d", data) % [int(coverage.get("protection", 0)), int(coverage.get("long_reach", 0)), int(coverage.get("sustain", 0)), int(coverage.get("control", 0))], 6, Color("#3e3428"), HORIZONTAL_ALIGNMENT_CENTER) var cancel := _risk_command_button(overlay, "RiskCommitmentCancel", Rect2(0.03, 0.866, 0.19, 0.075), _tr("返回风险板", "RISK BOARD", data), "back", "back", false) var acknowledge := _risk_command_button(overlay, "RiskCommitmentAcknowledge", Rect2(0.40, 0.866, 0.20, 0.075), _tr("确认", "ACK", data), "confirm", "acknowledge", true, not bool(commands.get("acknowledge_enabled", false))) acknowledge.set_meta("confirmed", acknowledged) var begin := _risk_command_button(overlay, "RiskBeginBattle", Rect2(0.78, 0.866, 0.19, 0.075), _tr("开始战斗", "BEGIN BATTLE", data), "risk", "begin_battle", true, not ready) begin.tooltip_text = _tr("确认后创建一次承诺回执并进入战斗开场。", "After acknowledgement, create one commitment receipt and enter Battle Opening.", data) if not begin.disabled else _risk_block_reason(dependency, acknowledged, data) _set_button_disabled(begin, not ready) var unavailable := Button.new() unavailable.name = "RiskBeginBattleUnavailable" unavailable.visible = false unavailable.disabled = true unavailable.mouse_filter = Control.MOUSE_FILTER_IGNORE unavailable.set_meta("semantic_alias", "RiskBeginBattle") overlay.add_child(unavailable) var summary := _risk_label(overlay, "RiskCommitmentSummary", Rect2(0.36, 0.855, 0.28, 0.028), _tr("一次回执", "ONE RECEIPT", data), 5, Color("#ead8ad"), HORIZONTAL_ALIGNMENT_CENTER) summary.mouse_filter = Control.MOUSE_FILTER_IGNORE var notice_text := String(data.get("notice", "")) if notice_text.is_empty(): notice_text = _tr("先确认后果;确认前不改变金币或队伍。", "Acknowledge first; Coin and Party stay unchanged.", data) if not acknowledged else _tr("已确认:下一步进入战斗开场。", "Acknowledged: next step opens Battle Opening.", data) if not bool(dependency.get("ok", false)): notice_text = _risk_block_reason(dependency, acknowledged, data) var reason_surface := UIFactory.notice_surface("RiskBeginBattleReasonSurface", "success" if ready else "warning") _risk_place(reason_surface, Rect2(0.34, 0.925, 0.32, 0.038)) reason_surface.mouse_filter = Control.MOUSE_FILTER_IGNORE reason_surface.z_index = 20 overlay.add_child(reason_surface) var reason := _risk_label(overlay, "RiskBeginBattleReason", Rect2(0.34, 0.925, 0.32, 0.038), notice_text, 6, Color("#f0ca75") if not ready else Color("#9ed7aa"), HORIZONTAL_ALIGNMENT_CENTER) reason.z_index = 21 reason.mouse_filter = Control.MOUSE_FILTER_IGNORE var focus_nodes: Array[Control] = [settings_button, selected_hit, consequence_hit, acknowledge, begin, cancel] for index in range(focus_nodes.size()): focus_nodes[index].focus_next = focus_nodes[index].get_path_to(focus_nodes[(index + 1) % focus_nodes.size()]) focus_nodes[index].focus_previous = focus_nodes[index].get_path_to(focus_nodes[(index - 1 + focus_nodes.size()) % focus_nodes.size()]) settings_button.call_deferred("grab_focus") func _risk_place(control: Control, area: Rect2) -> void: control.anchor_left = area.position.x control.anchor_top = area.position.y control.anchor_right = area.end.x control.anchor_bottom = area.end.y control.offset_left = 0.0 control.offset_top = 0.0 control.offset_right = 0.0 control.offset_bottom = 0.0 func _risk_label(parent: Control, node_name: String, area: Rect2, value: String, font_size: int, color: Color, align := HORIZONTAL_ALIGNMENT_LEFT) -> Label: var label := make_label(value, _risk_text_size(font_size), color) label.name = node_name label.horizontal_alignment = align label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART label.text_overrun_behavior = TextServer.OVERRUN_NO_TRIMMING label.mouse_filter = Control.MOUSE_FILTER_IGNORE _risk_place(label, area) parent.add_child(label) return label func _risk_text_size(design_size: int) -> int: var canvas_height := design_canvas.size.y if is_instance_valid(design_canvas) else size.y var canvas_scale := clampf(canvas_height / 360.0, 1.0, 2.0) var text_scale := float(presentation.get("text_scale", 1.0)) return mini(17, maxi(7, int(round(float(design_size) * lerpf(1.0, canvas_scale, 0.35) * text_scale)))) func _risk_hit_button(parent: Control, node_name: String, area: Rect2, intent_id: String, payload: Dictionary, tooltip: String, disabled := false) -> Button: var button := Button.new() button.name = node_name button.flat = true button.focus_mode = Control.FOCUS_ALL button.tooltip_text = tooltip button.disabled = disabled button.mouse_default_cursor_shape = Control.CURSOR_FORBIDDEN if disabled else Control.CURSOR_POINTING_HAND for style_name in ["normal", "disabled"]: button.add_theme_stylebox_override(style_name, StyleBoxEmpty.new()) button.add_theme_stylebox_override("hover", UITokens.hit_area_style("hover")) button.add_theme_stylebox_override("pressed", UITokens.hit_area_style("pressed")) button.add_theme_stylebox_override("focus", UITokens.hit_area_style("focus")) _risk_place(button, area) button.pressed.connect(func(): emit_intent(intent_id, payload), CONNECT_DEFERRED) parent.add_child(button) return button func _risk_command_button(parent: Control, node_name: String, area: Rect2, text_value: String, icon_kind: String, intent_id: String, primary := false, disabled := false) -> Button: var button := _risk_hit_button(parent, node_name, area, intent_id, {}, text_value, disabled) var variant := "primary" if primary else "icon" if text_value.is_empty() else "utility" if intent_id == "back" else "secondary" UIFactory.apply_flat_command(button, variant, icon_kind, disabled, false, "content") var icon := UIFactory.attach_icon(button, icon_kind, "CommandGlyph", "dark", Rect2(0.05, 0.16, 0.22, 0.68)) if text_value.is_empty(): icon.anchor_left = 0.12 icon.anchor_top = 0.08 icon.anchor_right = 0.88 icon.anchor_bottom = 0.92 var command_colors: Dictionary = UITokens.command_colors(variant, disabled) var label := _risk_label(button, "ButtonLabel", Rect2(0.27, 0.16, 0.68, 0.68), text_value, 10 if primary else 8, command_colors.text, HORIZONTAL_ALIGNMENT_CENTER) label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS label.visible = not text_value.is_empty() button.set_meta("risk_commitment_primary", primary) return button func _risk_marker(parent: Control, node_name: String, area: Rect2, glyph: String, color: Color, text_value := "") -> Control: var marker := Control.new() marker.name = node_name marker.mouse_filter = Control.MOUSE_FILTER_IGNORE _risk_place(marker, area) var seal := Panel.new() seal.name = "MarkerSeal" seal.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) seal.mouse_filter = Control.MOUSE_FILTER_IGNORE seal.add_theme_stylebox_override("panel", UITokens.seal_style(color)) marker.add_child(seal) _risk_label(marker, "MarkerGlyph", Rect2(0.0, 0.0, 1.0, 1.0), glyph, 8, Color("#fff7dc"), HORIZONTAL_ALIGNMENT_CENTER) if not text_value.is_empty(): var text := _risk_label(marker, "MarkerText", Rect2(1.05, 0.0, 3.8, 1.0), text_value, 7, Color("#3a3025")) text.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT parent.add_child(marker) return marker func _set_button_disabled(button: Button, disabled: bool) -> void: set_command_state(button, disabled) var label := button.get_node_or_null("ButtonLabel") as Label var icon := button.get_node_or_null("CommandGlyph") as ABGUIIcon if label != null: label.add_theme_color_override("font_color", Color("#89939a") if disabled else Color("#fff4d2")) if icon != null: icon.set_state(disabled, false) func _risk_block_reason(dependency: Dictionary, acknowledged: bool, data: Dictionary) -> String: if not acknowledged: return _tr("请先确认已读六条后果。", "Acknowledge the six consequences first.", data) match String(dependency.get("code", "")): "READINESS_REQUIRED": return _tr("准备快照不可用,请返回准备检查。", "The readiness snapshot is unavailable; return to Readiness Review.", data) "PARTY_SNAPSHOT_INVALID": return _tr("队伍快照无效,请返回准备检查。", "The Party snapshot is invalid; return to Readiness Review.", data) "RISK_PROJECTION_STALE": return _tr("风险投影已失效,请返回风险板。", "The risk projection is stale; return to the Risk Board.", data) "INSUFFICIENT_COIN": return _tr("金币不足:需要 %d,当前 %d。", "Insufficient Coin: need %d, have %d.", data) % [int(dependency.get("cost", 0)), int(dependency.get("coin", 0))] return _tr("依赖检查未完成。", "Dependency checks are not complete.", data) func _risk_title(option: Dictionary, data: Dictionary) -> String: match String(option.get("tier_id", "steady")): "pressured": return _tr("受压路线", "PRESSURED ROUTE", data) "elite": return _tr("精英路线", "ELITE ROUTE", data) return _tr("稳健路线", "STEADY ROUTE", data) func _risk_detail_copy(option: Dictionary, data: Dictionary) -> String: var threat: Dictionary = Dictionary(option.get("known_threat", {})) return _tr( "已知威胁:%d 名敌人 · %d 波 · 0 号为首要目标\n未解析信息:%s\n入场成本:%d 金币\n胜利回报:%d 金币 · %s\n恢复暴露:%s", "Known threat: %d enemies · %d waves · position 0 is primary\nUncertainty: %s\nEntry cost: %d Coin\nVictory reward: %d Coin · %s\nRecovery exposure: %s", data ) % [ int(threat.get("enemy_count", 0)), int(threat.get("wave_count", 0)), _uncertainty_name(String(option.get("uncertainty_id", "")), data), int(option.get("entry_cost", 0)), int(option.get("reward_coin", 0)), _reward_class_name(String(option.get("reward_class_id", "")), data), _recovery_name(String(option.get("recovery_exposure_id", "")), data), ] func _uncertainty_name(value: String, data: Dictionary) -> String: match value: "one_intent_unresolved": return _tr("一项敌方意图未解析", "one enemy intent unresolved", data) "elite_response_unresolved": return _tr("精英反应时机未解析", "elite response timing unresolved", data) return _tr("全部公开", "fully disclosed", data) func _reward_class_name(value: String, data: Dictionary) -> String: match value: "enhanced_choice": return _tr("强化选择", "enhanced choice", data) "elite_cache": return _tr("精英宝箱", "elite cache", data) return _tr("标准补给", "standard supplies", data) func _risk_uncertainty_short(value: String, data: Dictionary) -> String: match value: "one_intent_unresolved": return _tr("1项未解析", "1 INTENT OPEN", data) "elite_response_unresolved": return _tr("精英时机未解析", "ELITE TIMING OPEN", data) return _tr("全公开", "CLEAR", data) func _risk_reward_short(value: String, data: Dictionary) -> String: match value: "enhanced_choice": return _tr("强化选择", "ENHANCED", data) "elite_cache": return _tr("精英宝箱", "ELITE CACHE", data) return _tr("标准补给", "STANDARD", data) func _recovery_name(value: String, data: Dictionary) -> String: match value: "moderate": return _tr("中", "moderate", data) "high": return _tr("高", "high", data) return _tr("低", "low", data) func _risk_exposure_text(value: String, data: Dictionary) -> String: match value: "highest": return _tr("最前", "FRONT", data) "high": return _tr("前列", "HIGH", data) "guarded": return _tr("受护", "GUARDED", data) return _tr("后列", "REAR", data) func _recruit_name(recruit: Dictionary, data: Dictionary) -> String: return _localized(recruit.get("rendered_name_snapshot", {}), page_locale(data), recruit.get("recruit_id", "Unknown")) func _recruit_profession_name(recruit: Dictionary, data: Dictionary) -> String: return _localized(recruit.get("profession_name", {}), page_locale(data), recruit.get("profession_id", "Unknown Profession")) func _localized(raw_value, locale: String, fallback) -> String: if raw_value is Dictionary: var values: Dictionary = Dictionary(raw_value) return String(values.get(locale, values.get("en", fallback))) var raw_text := String(raw_value) return raw_text if not raw_text.is_empty() else String(fallback) func _tr(zh: String, en: String, data: Dictionary = view_data) -> String: return zh if page_locale(data) == "zh_CN" else en func _render_invalid(data: Dictionary) -> void: var overlay := Control.new() overlay.name = "RiskCommitmentInvalidOverlay" overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE design_canvas.add_child(overlay) var title := _risk_label(overlay, "RiskCommitmentInvalidTitle", Rect2(0.20, 0.36, 0.60, 0.10), _tr("风险承诺不可用", "RISK COMMITMENT UNAVAILABLE", data), 14, Color("#fff1c2"), HORIZONTAL_ALIGNMENT_CENTER) title.set_meta("semantic_node_retained", true) var detail := _risk_label(overlay, "RiskCommitmentInvalidDetail", Rect2(0.20, 0.48, 0.60, 0.12), String(data.get("notice", "")), 8, Color("#ffd4d4"), HORIZONTAL_ALIGNMENT_CENTER) detail.set_meta("semantic_node_retained", true) detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART