Files
aetherbound-guild/runtime/entry/views/risk_commitment_page_view.gd
T

449 lines
25 KiB
GDScript

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 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.90, 0.0, 0.10, 0.14), "", "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.83, 0.19, 0.15), _tr("返回风险板", "RISK BOARD", data), "back", "back", false)
var acknowledge := _risk_command_button(overlay, "RiskCommitmentAcknowledge", Rect2(0.40, 0.83, 0.20, 0.15), _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.83, 0.19, 0.15), _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", _flat_style(Color(0.95, 0.82, 0.35, 0.08), Color(0.95, 0.82, 0.35, 0.45), 1, 5))
button.add_theme_stylebox_override("pressed", _flat_style(Color(0.28, 0.53, 0.38, 0.16), Color(0.95, 0.82, 0.35, 0.62), 2, 5))
button.add_theme_stylebox_override("focus", _flat_style(Color(0.22, 0.45, 0.57, 0.13), Color(0.55, 0.84, 0.94, 0.92), 2, 5))
_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 label := _risk_label(button, "ButtonLabel", Rect2(0.27, 0.16, 0.68, 0.68), text_value, 10 if primary else 8, Color("#fff4d2") if not disabled else Color("#89939a"), HORIZONTAL_ALIGNMENT_CENTER)
label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
label.visible = not text_value.is_empty()
UIFactory.apply_flat_command(button, variant, icon_kind, disabled, false, "content")
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
var style := StyleBoxFlat.new()
style.bg_color = color
style.border_color = Color("#f5e8c4")
style.set_border_width_all(1)
style.set_corner_radius_all(10)
seal.add_theme_stylebox_override("panel", style)
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 _flat_style(background: Color, border: Color, width: int, radius: int) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = background
style.border_color = border
style.set_border_width_all(width)
style.set_corner_radius_all(radius)
return style
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