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

447 lines
25 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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", 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", false)
var acknowledge := _risk_command_button(overlay, "RiskCommitmentAcknowledge", Rect2(0.40, 0.83, 0.20, 0.15), _tr("确认", "ACK", data), "✓" if acknowledged else "!", "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), "➜", "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 := _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.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, glyph: String, intent_id: String, primary := false, disabled := false) -> Button:
var button := _risk_hit_button(parent, node_name, area, intent_id, {}, text_value, disabled)
button.add_theme_stylebox_override("hover", _flat_style(Color(0.95, 0.82, 0.35, 0.12), Color("#f3d991"), 2, 6))
button.add_theme_stylebox_override("pressed", _flat_style(Color(0.28, 0.53, 0.38, 0.24), Color("#f3d991"), 2, 6))
button.add_theme_stylebox_override("disabled", _flat_style(Color(0.12, 0.16, 0.18, 0.22), Color(0.48, 0.50, 0.48, 0.34), 1, 6))
var icon := _risk_label(button, "CommandGlyph", Rect2(0.06, 0.18, 0.22, 0.64), glyph, 15 if primary else 12, Color("#f3d991") if not disabled else Color("#89939a"), HORIZONTAL_ALIGNMENT_CENTER)
icon.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.65))
icon.add_theme_constant_override("shadow_offset_x", 1)
icon.add_theme_constant_override("shadow_offset_y", 1)
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
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
label.visible = false
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:
button.disabled = disabled
button.mouse_default_cursor_shape = Control.CURSOR_FORBIDDEN if disabled else Control.CURSOR_POINTING_HAND
var label := button.get_node_or_null("ButtonLabel") as Label
var icon := button.get_node_or_null("CommandGlyph") as Label
if label != null:
label.add_theme_color_override("font_color", Color("#89939a") if disabled else Color("#fff4d2"))
if icon != null:
icon.add_theme_color_override("font_color", Color("#89939a") if disabled else Color("#f3d991"))
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