class_name P99RuntimeController extends "res://p9_7/runtime_controller.gd" const P99Content = preload("res://content/p9_9_content.gd") const P99ProfileState = preload("res://p9_9/profile_state.gd") const P99CharterService = preload("res://p9_9/charter_service.gd") const P99RunService = preload("res://p9_9/run_service.gd") const P99RunState = preload("res://p9_9/run_state.gd") const P99BattleService = preload("res://p9_9/battle_service.gd") const P99SaveService = preload("res://p9_9/save_service.gd") var charter = P99CharterService.new() var focused_charter_id := "" var focused_draft_id := "" var focused_hire_id := "" var focused_route_id := "" var input_guard_until_frame := -1 func _bootstrap_market() -> void: if entry_snapshot.is_empty(): last_code = "entry_snapshot_required" _queue_render() return market = null var restored := false if String(entry_snapshot.get("state_kind", "")) == "p99_run": market = P99RunService.new() restored = market.restore_snapshot(entry_snapshot) if restored: charter.state.restore_snapshot(market.state.profile) elif bool(entry_snapshot.get("p99_activated", false)): restored = charter.state.restore_snapshot(entry_snapshot) if restored and not charter.state.selected_charter_claim_id.is_empty(): market = P99RunService.new(charter.state.snapshot()) else: charter = P99CharterService.new(entry_snapshot) restored = charter.state.invariants_hold() if not restored: market = null last_code = "p99_snapshot_invalid" _queue_render() return _install_run_content() battle = P99BattleService.new(_selected_claim_or_default()) saves = P99SaveService.new() pending_line_ids.clear() if market != null: pending_line_ids.assign(market.state.deployed_recruit_ids) last_code = "p99_charter_ready" if charter.state.selected_charter_claim_id.is_empty() else "p99_run_ready" battle_active = false battle_result_visible = false focused_charter_id = "" focused_draft_id = "" focused_hire_id = "" focused_route_id = "" _save_stable() _queue_render() func _install_run_content() -> void: content = P99Content.run_snapshot(_selected_claim_or_default()) var recruits: Dictionary = Dictionary(content.draft_recruits).duplicate(true) recruits.merge(content.market_hires, true) content["recruits"] = recruits func tap_charter_packet(claim_id: String) -> void: if not _consume_interaction(): return if not charter.state.selected_charter_claim_id.is_empty(): return if focused_charter_id != claim_id: focused_charter_id = claim_id last_code = "charter_preview" _queue_render() return var result: Dictionary = charter.claim(_operation("charter", claim_id), claim_id, charter.state.revision) last_code = String(result.code) if result.ok: market = P99RunService.new(charter.state.snapshot()) _install_run_content() battle = P99BattleService.new(charter.state.selected_charter_claim_id) focused_charter_id = "" _save_stable() _queue_render() func tap_draft_actor(recruit_id: String) -> void: if not _consume_interaction(): return if market == null or market.state.run_created: return if focused_draft_id != recruit_id: focused_draft_id = recruit_id last_code = "draft_inspected" _queue_render() return var selected: bool = recruit_id not in market.state.selected_provisional_ids var result: Dictionary = market.set_provisional(_operation("draft", recruit_id), recruit_id, selected, market.state.revision) last_code = String(result.code) if result.ok: _save_stable() _queue_render() func confirm_run_creation() -> void: if not _consume_interaction(): return if market == null or market.state.run_created: return var result: Dictionary = market.confirm_run(_operation("run", "confirm"), market.state.revision) last_code = String(result.code) if result.ok: focused_draft_id = "" pending_line_ids.clear() _save_stable() _queue_render() func tap_hire_actor(recruit_id: String) -> void: if not _consume_interaction(): return if not _market_ready() or market.state.current_market != 1: return if focused_hire_id != recruit_id: focused_hire_id = recruit_id last_code = "hire_inspected" _queue_render() return var result: Dictionary = market.hire(_operation("hire", recruit_id), recruit_id) last_code = String(result.code) if result.ok: focused_hire_id = "" pending_line_ids.assign(market.state.deployed_recruit_ids) _save_stable() _queue_render() func tap_route(route_id: String) -> void: if not _consume_interaction(): return if not _market_ready(): return if focused_route_id != route_id: focused_route_id = route_id last_code = "route_preview" _queue_render() return var result: Dictionary = market.commit_route(_operation("route", route_id), route_id) last_code = String(result.code) if not result.ok: _queue_render() return _save_stable() battle = P99BattleService.new(market.state.selected_charter_claim_id) start_committed_battle() func recover_company() -> void: if not _consume_interaction(): return if not _market_ready(): return var result: Dictionary = market.recover_defeat(_operation("recover", "company")) last_code = String(result.code) if result.ok: _save_stable() _queue_render() func settle_and_return() -> void: if not _consume_interaction(): return if not battle_active or not battle.state.terminal: return var result: Dictionary = market.settle_battle(_operation("settle", battle.state.route_id), battle.state.snapshot()) last_code = String(result.code) if not result.ok: _queue_render() return battle_active = false battle_result_visible = false focused_route_id = "" pending_line_ids.assign(market.state.deployed_recruit_ids) _save_stable() _queue_render() func _market_ready() -> bool: return market != null and market.state.run_created and not battle_active and market.state.phase == P99RunState.Phase.MARKET func _consume_interaction() -> bool: var frame := int(Engine.get_process_frames()) if frame <= input_guard_until_frame: return false input_guard_until_frame = frame + 2 return true func _save_stable() -> bool: if not persistence_enabled: return false if market != null: return bool(saves.save_snapshot(market.state.snapshot()).ok) return bool(saves.save_snapshot(charter.state.snapshot()).ok) func _operation(kind: String, target: String) -> String: operation_index += 1 var revision: int = market.state.revision if market != null else charter.state.revision return "p99-ui-%s-%d-%d-%s" % [kind, revision, operation_index, target] func _render_current() -> void: if display_root == null: return _clear_display() if size.y > size.x: _render_rotate_notice() elif charter.state.selected_charter_claim_id.is_empty(): _render_charter_table() elif market == null: _render_missing_entry() elif battle_active: _render_battle() elif not market.state.run_created: _render_run_draft() else: _render_market() func _render_charter_table() -> void: _add_background(HALL_BACKGROUND) _add_p99_header(_tr("公会宪章 · 风晶边境", "GUILD CHARTER · WINDGLASS VERGE"), _tr("选择未来机会或威胁情报,不提供永久战斗数值", "Choose future options or threat intel. No permanent combat power.")) var seal_width := (size.x - 36.0) / 8.0 for index in range(8): var seal_id := "RG-%02d" % (index + 1) var eligible := index == 0 var seal := _outlined_label("◆\n%s" % seal_id, 9, GOLD_LIGHT if eligible else Color(0.48, 0.58, 0.66)) seal.name = "P99RegionSeal_%s" % seal_id seal.position = Vector2(18 + seal_width * index, 52) seal.size = Vector2(seal_width, 48) seal.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER display_root.add_child(seal) for index in range(2): var claim_id: String = ["CH-RG01-CATALOG-01", "CH-RG01-THREAT-01"][index] var row: Dictionary = P99Content.CHARTER_CLAIMS[claim_id] var center_x := size.x * (0.31 if index == 0 else 0.69) var width := minf(270.0, size.x * 0.32) var packet := ColorRect.new() packet.name = "P99CharterPacket_%s" % claim_id packet.position = Vector2(center_x - width * 0.5, size.y * 0.31) packet.size = Vector2(width, size.y * 0.41) packet.color = Color(0.12, 0.19, 0.24, 0.94) if focused_charter_id != claim_id else Color(0.25, 0.22, 0.12, 0.96) packet.mouse_filter = Control.MOUSE_FILTER_IGNORE display_root.add_child(packet) var seal_mark := _outlined_label("◇" if index == 0 else "◎", 34, CYAN if index == 0 else VIOLET) seal_mark.position = Vector2(center_x - 32, size.y * 0.32) seal_mark.size = Vector2(64, 58) seal_mark.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER display_root.add_child(seal_mark) var text := _outlined_label("%s · %d %s\n%s" % [_tr(String(row.name_zh), String(row.name_en)), int(row.renown_cost), _tr("声望", "RENOWN"), _tr(String(row.effect_zh), String(row.effect_en))], 11, WHITE) text.position = Vector2(center_x - width * 0.45, size.y * 0.46) text.size = Vector2(width * 0.90, size.y * 0.20) text.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART display_root.add_child(text) var target := _actor_target("P99CharterPacketTarget_%s" % claim_id, packet.position, packet.size, tap_charter_packet.bind(claim_id)) display_root.add_child(target) var preview := _tr("点击一份契约查看;再次点击签署。当前声望 14。", "Tap a packet to inspect; tap again to sign. Current Renown: 14.") if not focused_charter_id.is_empty(): var focused: Dictionary = P99Content.CHARTER_CLAIMS[focused_charter_id] preview = _tr("签署后剩余 %d 声望。只改变:%s" % [14 - int(focused.renown_cost), String(focused.effect_zh)], "Renown after signing: %d. Changes only: %s" % [14 - int(focused.renown_cost), String(focused.effect_en)]) _add_context_ribbon(preview) func _render_run_draft() -> void: _add_background(HALL_BACKGROUND) _add_p99_header(_tr("第二契约 · 召集新队伍", "CONTRACT 2 · MUSTER A NEW COMPANY"), _tr("从四名站立候选中选择两名;可以在确认前更换", "Choose two of four standing Recruits. Revise before confirming.")) var actor_size := clampf(size.y * 0.30, 88.0, 122.0) var first_center := actor_size * 0.5 + 12.0 var last_center := size.x - actor_size * 0.5 - 12.0 var spacing := (last_center - first_center) / 3.0 for index in range(market.state.draft_offer_ids.size()): var recruit_id: String = market.state.draft_offer_ids[index] var row: Dictionary = content.draft_recruits[recruit_id] var profession: Dictionary = content.professions[row.profession_id] var selected: bool = recruit_id in market.state.selected_provisional_ids var x := first_center + spacing * index - actor_size * 0.5 var y := size.y * 0.27 _add_ground_ring(Vector2(x + actor_size * 0.5, y + actor_size - 4), actor_size * 0.72, GOLD if selected else CYAN, "P99DraftRing_%s" % recruit_id) var actor := _add_actor(String(profession.visual_rig), Vector2(x, y), actor_size, "P99DraftActor_%s" % recruit_id, true) actor.self_modulate = _p99_profession_color(String(row.profession_id)) var target := _actor_target("P99DraftTarget_%s" % recruit_id, Vector2(x - 4, y - 4), Vector2(actor_size + 8, actor_size + 22), tap_draft_actor.bind(recruit_id)) display_root.add_child(target) var name := _outlined_label("%s\n%s" % [_tr(String(row.name_zh), String(row.name_en)), _tr(String(profession.name_zh), String(profession.name_en))], 9, GOLD_LIGHT if selected else WHITE) name.position = Vector2(x - 24, y + actor_size - 2) name.size = Vector2(actor_size + 48, 50) name.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER display_root.add_child(name) var selected_count: int = market.state.selected_provisional_ids.size() var context := _tr("已选 %d/2。点击角色查看,再次点击加入或移出。" % selected_count, "SELECTED %d/2. Tap to inspect, tap again to add or remove." % selected_count) if not focused_draft_id.is_empty(): var focused: Dictionary = content.draft_recruits[focused_draft_id] context = "%s\n%s / %s" % [_tr(String(focused.name_zh), String(focused.name_en)), _tr(String(focused.benefit_zh), String(focused.benefit_en)), _tr(String(focused.cost_zh), String(focused.cost_en))] _add_context_ribbon(context) if selected_count == 2: var confirm := _action_button("P99ConfirmRunButton", _tr("确认两人 · 进入集市", "CONFIRM TWO · ENTER MARKET"), confirm_run_creation) confirm.position = Vector2(size.x - 246, size.y - 62) confirm.size = Vector2(228, 50) display_root.add_child(confirm) func _render_market() -> void: _add_background(WINDGLASS_BACKGROUND) if market.state.continuation_locked: _render_market_four() return _add_p99_market_hud() _render_p99_company_line() _render_p99_hires_or_catalog() _render_p99_route_gates() _render_p99_market_context() func _render_battle() -> void: super._render_battle() for node_name in ["P97PauseButton", "P97SpeedButton"]: var control := find_child(node_name, true, false) as Control if control != null: control.position.y = 4 control.size.y = 48 func _add_p99_market_hud() -> void: var header := ColorRect.new() header.name = "P99MarketHud" header.position = Vector2.ZERO header.size = Vector2(size.x, 52) header.color = Color(0.015, 0.035, 0.075, 0.88) header.mouse_filter = Control.MOUSE_FILTER_IGNORE display_root.add_child(header) var title := _outlined_label(_tr("第二契约 · 集市 %d" % market.state.current_market, "CONTRACT 2 · MARKET %d" % market.state.current_market), 16, GOLD_LIGHT) title.position = Vector2(16, 4) title.size = Vector2(size.x * 0.42, 42) display_root.add_child(title) var values := _outlined_label(_tr("◆%d 队伍 %d/4 战线 %d/%d 已胜 %d/3" % [market.state.coin, market.state.company.size(), market.state.deployed_recruit_ids.size(), market.state.line_capacity, market.state.cleared_rounds], "◆%d COMPANY %d/4 LINE %d/%d CLEARS %d/3" % [market.state.coin, market.state.company.size(), market.state.deployed_recruit_ids.size(), market.state.line_capacity, market.state.cleared_rounds]), 12, WHITE) values.position = Vector2(size.x * 0.42, 4) values.size = Vector2(size.x * 0.56, 42) values.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT values.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS display_root.add_child(values) func _render_p99_company_line() -> void: var actor_size := clampf(size.y * 0.22, 72.0, 96.0) var y := size.y - actor_size - 74.0 var lane_width := size.x * 0.43 var spacing := minf(actor_size * 0.82, (lane_width - 32.0) / maxf(1.0, float(market.state.company.size()))) for index in range(market.state.company.size()): var recruit_id: String = market.state.company[index] var x := 18.0 + spacing * index _add_ground_ring(Vector2(x + actor_size * 0.5, y + actor_size - 2), actor_size * 0.70, GOLD, "P99CompanyRing_%s" % recruit_id) var actor := _add_actor(_recruit_rig(recruit_id), Vector2(x, y), actor_size, "P99CompanyActor_%s" % recruit_id, true) actor.self_modulate = _recruit_color(recruit_id) var hp := _outlined_label("%d HP" % int(market.state.member_condition[recruit_id]), 8, WHITE) hp.position = Vector2(x, y + actor_size - 4) hp.size = Vector2(actor_size, 24) hp.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER display_root.add_child(hp) func _render_p99_hires_or_catalog() -> void: var left := size.x * 0.43 var width := size.x - 226.0 - left if market.state.current_market == 1: var offers: Array = market.state.available_recruit_offer_ids var slot := width / maxf(1.0, float(offers.size())) for index in range(offers.size()): var recruit_id := String(offers[index]) var row: Dictionary = content.market_hires[recruit_id] var profession: Dictionary = content.professions[row.profession_id] var actor_size := clampf(slot * 0.72, 44.0, 70.0) var x := left + slot * index + (slot - actor_size) * 0.5 var y := 76.0 var actor := _add_actor(String(profession.visual_rig), Vector2(x, y), actor_size, "P99HireActor_%s" % recruit_id, true) actor.self_modulate = _p99_profession_color(String(row.profession_id)) var target := _actor_target("P99HireTarget_%s" % recruit_id, Vector2(x - 4, y - 4), Vector2(actor_size + 8, actor_size + 48), tap_hire_actor.bind(recruit_id)) display_root.add_child(target) var label := _outlined_label("%s\n◆%d" % [_tr(String(profession.name_zh), String(profession.name_en)), int(row.list_price)], 8, GOLD_LIGHT if focused_hire_id == recruit_id else WHITE) label.position = Vector2(left + slot * index, y + actor_size - 2) label.size = Vector2(slot, 42) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER display_root.add_child(label) else: var equipment_ids: Array = market.state.available_equipment_offer_ids for index in range(equipment_ids.size()): var catalog_id := String(equipment_ids[index]) var x := left + 26.0 + float(index) * minf(106.0, width * 0.42) var icon := TextureRect.new() icon.name = "P99CatalogItem_%s" % catalog_id icon.texture = _equipment_texture(catalog_id) icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon.position = Vector2(x, 88) icon.size = Vector2(58, 68) icon.self_modulate = _equipment_color(catalog_id) icon.mouse_filter = Control.MOUSE_FILTER_IGNORE display_root.add_child(icon) _add_ground_ring(Vector2(x + 29, 156), 68, GOLD, "P99CatalogPedestal_%s" % catalog_id) var name := _outlined_label(_equipment_name(catalog_id), 8, WHITE) name.position = Vector2(x - 20, 158) name.size = Vector2(98, 30) name.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER display_root.add_child(name) func _render_p99_route_gates() -> void: var route_ids: Array = market.state.available_battle_offer_ids var lane_x := size.x - 218.0 var gap := 6.0 var width := (202.0 - gap * 2.0) / 3.0 for index in range(route_ids.size()): var route_id := String(route_ids[index]) var route: Dictionary = content.routes[route_id] var x := lane_x + float(index) * (width + gap) var gate := ColorRect.new() gate.name = "P99RouteGate_%s" % route_id gate.position = Vector2(x, 74) gate.size = Vector2(width, 112) gate.color = _risk_color(int(route.risk), focused_route_id == route_id) gate.mouse_filter = Control.MOUSE_FILTER_IGNORE display_root.add_child(gate) var label := _outlined_label("%s\n+%d" % [[_tr("稳", "S"), _tr("险", "P"), _tr("精", "E")][int(route.risk)], int(route.reward)], 11, WHITE) label.position = gate.position + Vector2(0, 16) label.size = Vector2(width, 76) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER display_root.add_child(label) var target := _actor_target("P99RouteTarget_%s" % route_id, gate.position - Vector2(2, 2), gate.size + Vector2(4, 4), tap_route.bind(route_id)) display_root.add_child(target) func _render_p99_market_context() -> void: var text := _tr("先查看候选并雇佣至 4 人,系统会自动列阵。", "Inspect and hire to four; the Company forms automatically.") if market.state.company.size() < 4 else _tr("点击路线查看风险、敌人和奖励;再次点击出发。", "Tap a route to inspect risk, enemies and reward; tap again to depart.") if market.state.recovery_required: text = _tr("队伍战败。支付 ◆6 全员恢复,再选择一条路线。", "Company defeated. Pay ◆6 to recover, then choose a route.") elif not focused_hire_id.is_empty(): var row: Dictionary = content.market_hires[focused_hire_id] var profession: Dictionary = content.professions[row.profession_id] text = "%s · %s · ◆%d\n%s / %s" % [_tr(String(row.name_zh), String(row.name_en)), _tr(String(profession.name_zh), String(profession.name_en)), int(row.list_price), _tr(String(profession.get("strength_zh", "自动职责")), String(profession.get("strength_en", "Automatic role"))), _tr(String(profession.get("limit_zh", "需要队伍配合")), String(profession.get("limit_en", "Needs team support")))] elif not focused_route_id.is_empty(): var preview: Dictionary = market.route_preview(focused_route_id) text = "%s · %s · +%d\n%s" % [_tr(String(content.routes[focused_route_id].name_zh), String(content.routes[focused_route_id].name_en)), _tr(["稳妥", "压力", "精英"][int(preview.risk)], ["STEADY", "PRESSURED", "ELITE"][int(preview.risk)]), int(preview.reward), _tr(String(preview.pressure_zh), String(preview.pressure_en))] if preview.threat_intel_revealed: text += _tr(" · 到场 %d / 目标 %s / 阵风 %d" % [int(preview.intel.arrival_tick), String(preview.intel.selected_target), int(preview.intel.gust_cadence)], " · ARRIVAL %d / TARGET %s / GUST %d" % [int(preview.intel.arrival_tick), String(preview.intel.selected_target), int(preview.intel.gust_cadence)]) _add_context_ribbon(text) if market.state.recovery_required: var recover := _action_button("P99RecoveryButton", _tr("恢复队伍 ◆6", "RECOVER COMPANY ◆6"), recover_company) recover.position = Vector2(size.x - 226, size.y - 62) recover.size = Vector2(208, 50) display_root.add_child(recover) func _render_market_four() -> void: _add_background(WINDGLASS_BACKGROUND) _render_p99_company_line() var glow := ColorRect.new() glow.name = "P99MarketFourGlow" glow.position = Vector2(size.x * 0.50, 0) glow.size = Vector2(size.x * 0.50, size.y) glow.color = Color(0.25, 0.76, 0.84, 0.18) glow.mouse_filter = Control.MOUSE_FILTER_IGNORE display_root.add_child(glow) var title := _outlined_label(_tr("第二契约 · 前三轮完成", "CONTRACT 2 · FIRST THREE ROUNDS COMPLETE"), 23, GOLD_LIGHT) title.name = "P99MarketFourTitle" title.position = Vector2(size.x * 0.48, size.y * 0.22) title.size = Vector2(size.x * 0.50, 74) title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART display_root.add_child(title) var body := _outlined_label(_tr("集市 4 已保存\n战线容量 5 · 队伍 4 · 路线与生命保留\n\n下一目标:接近 Gale Loom(第 4-12 轮尚未开放)", "MARKET 4 SAVED\nLINE CAPACITY 5 · COMPANY 4 · ROUTES AND HP PRESERVED\n\nNEXT: APPROACH THE GALE LOOM (ROUNDS 4-12 NOT YET OPEN)"), 13, WHITE) body.name = "P99MarketFourBody" body.position = Vector2(size.x * 0.51, size.y * 0.43) body.size = Vector2(size.x * 0.45, 132) body.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART display_root.add_child(body) func _add_p99_header(title_text: String, subtitle_text: String) -> void: var band := ColorRect.new() band.name = "P99Header" band.position = Vector2.ZERO band.size = Vector2(size.x, 52) band.color = Color(0.015, 0.035, 0.075, 0.88) band.mouse_filter = Control.MOUSE_FILTER_IGNORE display_root.add_child(band) var compact := size.x < 800.0 or text_scale > 1.0 var title_fraction := 0.58 if compact else 0.48 var subtitle_fraction := 1.0 - title_fraction var title := _outlined_label(title_text, 13 if compact else 15, GOLD_LIGHT) title.name = "P99HeaderTitle" title.position = Vector2(16, 3) title.size = Vector2(size.x * title_fraction - 24, 44) title.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS display_root.add_child(title) var subtitle := _outlined_label(subtitle_text, 9 if compact else 10, WHITE) subtitle.name = "P99HeaderSubtitle" subtitle.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART subtitle.clip_text = true subtitle.position = Vector2(size.x * title_fraction, 3) subtitle.size = Vector2(size.x * subtitle_fraction - 16, 44) subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT display_root.add_child(subtitle) func _add_context_ribbon(value: String) -> void: var ribbon := ColorRect.new() ribbon.name = "P99ContextRibbon" ribbon.position = Vector2(0, size.y - 70) ribbon.size = Vector2(size.x, 70) ribbon.color = Color(0.015, 0.035, 0.075, 0.94) ribbon.mouse_filter = Control.MOUSE_FILTER_IGNORE display_root.add_child(ribbon) var detail := _outlined_label(value, 10, WHITE) detail.name = "P99ContextText" detail.position = Vector2(16, size.y - 66) detail.size = Vector2(size.x - 280, 58) detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART display_root.add_child(detail) func _selected_claim_or_default() -> String: return charter.state.selected_charter_claim_id if not charter.state.selected_charter_claim_id.is_empty() else "CH-RG01-CATALOG-01" func _p99_profession_color(profession_id: String) -> Color: return {"PF-B01": GOLD_LIGHT, "PF-B02": CORAL, "PF-B03": JADE, "PF-B04": CYAN, "PF-B05": Color("#78bfff"), "PF-B06": VIOLET, "PF-B07": Color("#8fe0b6"), "PF-B09": Color("#78d9d0")}.get(profession_id, WHITE) func _equipment_color(catalog_id: String) -> Color: if catalog_id == "EQ-041": return Color("#7dd6c3") return super._equipment_color(catalog_id) func _battle_event_word(event_id: String) -> String: var p99_words := { "cross_line_hinder": _tr("跨线迟滞", "CROSS HINDER"), "majority_band_delay": _tr("多数延迟", "BAND DELAY"), "changed_target_pursuit": _tr("换目标追击", "RETARGET"), } return String(p99_words[event_id]) if p99_words.has(event_id) else super._battle_event_word(event_id)