class_name BusinessManagementPanel extends VBoxContainer const LayerSkin := preload("res://scripts/ui/layer_interface_skin.gd") ## Policy drafts are local; accepted commands and read models belong to Runtime. signal status_requested(message: String, success: bool) signal action_requested(action: StringName) const KEY_PREFIX := "ui.business." const COMMAND: StringName = &"set_facility_policy" const KITCHEN_KEY_PREFIX := "ui.business.kitchen." const KITCHEN_COMMAND: StringName = &"set_facility_conditions" const GUEST_POLICY_EXPLANATION := "料金変更は新規受付から。受付済みの料金は変わりません。\n休業すると待機中のお客様は別の施設を探します。開始済みのサービスは完了まで続きます。休業中も給与と施設維持費は発生します。" const SUPPLY_POLICY_EXPLANATION := "休業中は調理と新しい補給を止めます。在庫と運搬中の荷物は保持し、積込済みの荷物は納品先の営業状況に従って配送します。消費期限は経過し、給与と施設維持費も発生します。" const SERVICE_ATTRIBUTE_NAMES := { &"": "未設定", &"appetite": "食欲", &"relaxation": "リラックス欲", &"sexual": "性欲", &"gambling": "ギャンブル欲", } const STATE_NAMES := {&"BROKEN": "故障中", &"NO_ACCESS": "経路なし", &"NO_POWER": "魔力不足", &"NO_STAFF": "職員不足", &"CLOSED": "休業中", &"DEGRADED": "状態悪化", &"OPEN": "稼働中"} const REASONS := { &"FACILITY_NOT_FOUND": "施設が見つかりません。選び直してください。", &"INVALID_POLICY": "営業設定を確認してください。", &"INVALID_PRICE_MODE": "料金を選び直してください。", &"INVALID_PAYLOAD": "営業設定を確認してください。", &"FACILITY_CLOSED": "この施設は利用できません。", &"BANKRUPT": "運営終了後は変更できません。", } var facility_option: OptionButton var price_option: OptionButton var kitchen_option: OptionButton var kitchen_apply_button: Button var kitchen_status_label: Label var business_open_button: CheckButton var quote_label: Label var effect_label: Label var facility_status_label: Label var today_label: Label var explanation_label: Label var apply_button: Button var result_label: Label var selected_facility_id: StringName = &"" var _runtime: Variant var _audio_submitter := Callable() var _style_button: Callable var _report: Dictionary = {} var _drafts: Dictionary = {} var _pending_commands: Dictionary = {} var _kitchen_drafts: Dictionary = {} var _pending_kitchen_commands: Dictionary = {} var _queue_instance_id := 0 var _sequence := 1 var _refreshing := false var _built := false func configure(runtime: Variant, style_button := Callable()) -> void: _runtime = runtime _style_button = style_button func _ready() -> void: name = "BusinessManagementPanel" size_flags_horizontal = Control.SIZE_EXPAND_FILL add_theme_constant_override("separation", 6) _build_interface() _built = true if _runtime != null: if _runtime.has_signal(&"state_updated"): _runtime.connect(&"state_updated", _on_snapshot_published) elif _runtime.has_signal(&"snapshot_published"): _runtime.connect(&"snapshot_published", _on_snapshot_published) if _runtime.has_signal(&"command_completed"): _runtime.connect(&"command_completed", _on_command_completed) refresh() func _build_interface() -> void: add_child(_label("営業 · 料金と受付", "BusinessSummary")) add_child(_label("設定する施設")) facility_option = _option("BusinessFacilityOption") facility_option.item_selected.connect(func(index: int) -> void: selected_facility_id = StringName(facility_option.get_item_metadata(index)) refresh()) add_child(facility_option) facility_status_label = _label("", "BusinessFacilityStatus") add_child(facility_status_label) kitchen_status_label = _label("", "BusinessKitchenStatus") add_child(kitchen_status_label) kitchen_option = _option("BusinessKitchenOption") kitchen_option.item_selected.connect(func(index: int) -> void: if not _refreshing and not _has_pending(selected_facility_id): _kitchen_drafts[selected_facility_id] = StringName(kitchen_option.get_item_metadata(index)) refresh()) add_child(kitchen_option) kitchen_apply_button = _button("関連厨房を設定", "ApplyBusinessKitchenButton", submit_kitchen_link) add_child(kitchen_apply_button) add_child(_label("新規の料金")) price_option = _option("BusinessPriceOption") price_option.item_selected.connect(func(_index: int) -> void: _store_draft()) add_child(price_option) quote_label = _label("", "BusinessPriceQuote") quote_label.add_theme_font_size_override("font_size", 16) add_child(quote_label) effect_label = _label("", "BusinessPriceEffect") add_child(effect_label) business_open_button = CheckButton.new() business_open_button.name = "BusinessOpenButton" business_open_button.text = "新規の受付を開く" for color_name: StringName in [&"font_color", &"font_pressed_color", &"font_hover_color", &"font_hover_pressed_color", &"font_focus_color"]: business_open_button.add_theme_color_override(color_name, Color("55371e")) business_open_button.add_theme_color_override("font_disabled_color", Color("766650")) LayerSkin.register_toggle_text(business_open_button, LayerSkin.active_layer(self)) business_open_button.add_theme_font_size_override("font_size", 13) business_open_button.toggled.connect(func(_enabled: bool) -> void: _store_draft()) add_child(business_open_button) apply_button = _button("営業設定を適用", "ApplyBusinessPolicyButton", submit_policy) add_child(apply_button) explanation_label = _label(GUEST_POLICY_EXPLANATION, "BusinessPolicyExplanation") add_child(explanation_label) add_child(HSeparator.new()) today_label = _label("", "BusinessTodayResults") add_child(today_label) var actions := HBoxContainer.new() actions.add_theme_constant_override("separation", 4) for entry: Array in [[&"staff", "従業員"], [&"product", "商品"], [&"ledger", "帳簿"]]: var button := _button(entry[1], "BusinessAction_%s" % entry[0], func() -> void: action_requested.emit(entry[0])) button.size_flags_horizontal = Control.SIZE_EXPAND_FILL actions.add_child(button) add_child(actions) result_label = _label("", "BusinessResult") add_child(result_label) func refresh() -> void: if not _built: return var queue: Variant = _runtime.get(&"command_queue") if _runtime != null else null if queue != null and queue.get_instance_id() != _queue_instance_id: _queue_instance_id = queue.get_instance_id() _drafts.clear() _kitchen_drafts.clear() kitchen_option.get_popup().hide() facility_option.get_popup().hide() price_option.get_popup().hide() _synchronize_commands(queue) _report = {} if _runtime != null and _runtime.has_method(&"get_business_management_report"): var value: Variant = _runtime.call(&"get_business_management_report") if value is Dictionary: _report = value _refreshing = true _fill_facilities() var row := _facility_row(selected_facility_id) var draft := _selected_draft(row) _fill_kitchens(row) _fill_modes(StringName(draft.get(&"price_mode", &"standard"))) business_open_button.set_pressed_no_signal(bool(draft.get(&"business_open", true))) var pending := _has_pending(selected_facility_id) var can_price := bool(row.get(&"can_price", false)) var is_guest_service := StringName(row.get(&"category", &"")) in [&"guest_service", &"restricted_service"] var is_cleaning := StringName(row.get(&"definition_id", &"")) == &"cleaning_room" var is_supply := StringName(row.get(&"definition_id", &"")) in [&"kitchen", &"warehouse"] business_open_button.text = "営業する" if is_supply else "新規の受付を開く" explanation_label.text = SUPPLY_POLICY_EXPLANATION if is_supply else GUEST_POLICY_EXPLANATION if is_cleaning: business_open_button.text = "清掃業務を有効にする" explanation_label.text = "配置した清掃員が汚れた施設へ向かいます。清掃業務を無効にすると、途中の清掃も中断します。維持費と給与は継続します。" facility_option.disabled = _rows(&"facilities").is_empty() price_option.disabled = row.is_empty() or not can_price or pending or price_option.item_count == 0 business_open_button.disabled = row.is_empty() or pending apply_button.disabled = row.is_empty() or pending or not _is_changed(row, draft) apply_button.text = "設定を受理済み…" if pending else "営業設定を適用" if row.is_empty(): facility_status_label.text = "施設が完成すると営業を設定できます。" quote_label.text = "施設を選んでください。" effect_label.text = "" if int(row.get(&"faith_per_service", 0)) > 0: effect_label.text += "\n礼拝完了ごとに信仰+%d" % int(row[&"faith_per_service"]) today_label.text = "本日の実績はまだありません。" else: facility_status_label.text = "%s · %s\n待ち %d人 / 利用中 %d人\n同時利用上限 %s" % [("営業する設定" if is_supply else "受付を開く設定") if bool(row.get(&"business_open", true)) else ("休業する設定" if is_supply else "新規受付を停止中"), _display_state(row), int(row.get(&"queued_customer_count", 0)), int(row.get(&"active_customer_count", 0)), ("%d人" % int(row[&"customer_capacity"])) if int(row.get(&"customer_capacity", -1)) >= 0 else "未設定"] if is_supply: facility_status_label.text = "%s · %s\n保管・補給の状況は「商品」で確認。" % ["営業する設定" if bool(row.get(&"business_open", true)) else "休業する設定", _display_state(row)] if is_cleaning: facility_status_label.text = "%s · %s\n顧客利用なし・清掃員の業務拠点" % ["清掃業務を有効にする設定" if bool(row.get(&"business_open", true)) else "清掃業務を停止中", _display_state(row)] if int(row.get(&"cleanliness", -1)) >= 0: facility_status_label.text += "\n清潔度 %d / 100" % int(row[&"cleanliness"]) if not String(row.get(&"cleaning_status", "")).is_empty(): facility_status_label.text += "\n" + String(row[&"cleaning_status"]) if can_price: var mode := StringName(draft.get(&"price_mode", &"standard")) var quoted := FacilityPricing.apply_multiplier(FacilityPricing.quote(int(row.get(&"base_price", 0)), {&"price_mode": mode}) * int(row.get(&"donation_multiplier", 1)), int(row.get(&"floor_price_multiplier_bp", 10000))) var unit := String({&"standard_room": "泊", &"premium_room": "泊", &"cafeteria": "食"}.get(StringName(row.get(&"definition_id", &"")), "回")) quote_label.text = "通常料金 · 設定後 %dG / %s\n現在 %dG · 標準 %dG" % [quoted, unit, int(row.get(&"price", 0)), int(row.get(&"base_price", 0))] effect_label.text = _price_effect(mode) if int(row.get(&"donation_multiplier", 1)) == 3: effect_label.text += "\nアメリア配置:寄付金3倍" if int(row.get(&"floor_price_multiplier_bp", 10000)) == 12000: effect_label.text += "\nアンリ配置フロア:利用料金+20%" else: quote_label.text = "通常の利用料金はありません(現在 %dG)。" % int(row.get(&"price", 0)) if is_guest_service else "この施設に利用料金はありません。" effect_label.text = "営業・休業を設定できます。" if is_supply else "受付の開閉だけを設定できます。" if is_cleaning: effect_label.text = "清掃業務の有効・無効を設定できます。" if can_price or is_guest_service: var service_pricing: Dictionary = row.get(&"service_pricing", {}) var attribute: Variant = service_pricing.get(&"service_attribute", null) quote_label.text += "\nサービス属性:%s\n欲求一致加算:未設定" % String(SERVICE_ATTRIBUTE_NAMES.get(attribute, "未設定")) if int(row.get(&"faith_per_service", 0)) > 0: effect_label.text += "\n礼拝完了ごとに信仰+%d" % int(row[&"faith_per_service"]) today_label.text = "本日の記帳 · 00:00〜\n売上 %dG · 直接支出 %dG\n給与の配賦と利益は翌日の帳簿で確認。" % [int(row.get(&"today_revenue", 0)), int(row.get(&"today_expenses", 0))] _refreshing = false func _display_state(row: Dictionary) -> String: var state_name := StringName(row.get(&"state", &"")) var state_text := String(STATE_NAMES.get(state_name, "状態確認中")) var dependency: Dictionary = row.get(&"kitchen_dependency", {}) # Preserve the facility's own fault/staff/access/power/closure reason. # The separate kitchen status label shows the dependency at the same time. if bool(row.get(&"business_open", true)) and state_name in [&"OPEN", &"DEGRADED"] and bool(dependency.get(&"required", false)) and not bool(dependency.get(&"operating", false)): return "関連厨房の稼働待ち" if state_name == &"OPEN" else state_text + " · 関連厨房の稼働待ち" return state_text func _selected_kitchen(row: Dictionary) -> StringName: for payload: Dictionary in _pending_kitchen_commands.values(): if StringName(payload.get(&"facility_id", &"")) == selected_facility_id: return StringName(payload.get(&"conditions", {}).get(&"related_facility_id", &"")) var dependency: Dictionary = row.get(&"kitchen_dependency", {}) return StringName(_kitchen_drafts.get(selected_facility_id, dependency.get(&"related_facility_id", &""))) func _kitchen_placement(id: StringName) -> Dictionary: if _runtime == null: return {} var facilities: Variant = _runtime.get(&"facility_service") if facilities == null or not facilities.has_method(&"get_kitchen_placement_report"): return {} return facilities.call(&"get_kitchen_placement_report", selected_facility_id, id) func _fill_kitchens(row: Dictionary) -> void: var dependency: Dictionary = row.get(&"kitchen_dependency", {}) var required := bool(dependency.get(&"required", false)) kitchen_option.visible = required kitchen_apply_button.visible = required kitchen_status_label.visible = required if not required: return var selected := _selected_kitchen(row) if not kitchen_option.get_popup().visible: kitchen_option.clear() kitchen_option.add_item("未接続(飲食サービスを休止)") kitchen_option.set_item_metadata(0, &"") var selected_index := 0 var found := selected.is_empty() for kitchen: Dictionary in _rows(&"kitchens"): var id := StringName(kitchen.get(&"facility_id", &"")) var placement := _kitchen_placement(id) var label := _facility_name(kitchen) if StringName(placement.get(&"reason", &"")) == &"INVALID_RELATED_KITCHEN_PLACEMENT": label += "(この飲食店の裏に収まりません)" kitchen_option.add_item(label) var index := kitchen_option.item_count - 1 kitchen_option.set_item_metadata(index, id) if id == selected: selected_index = index found = true if not found: kitchen_option.add_item("既存設定 %s(利用できません)" % selected) selected_index = kitchen_option.item_count - 1 kitchen_option.set_item_metadata(selected_index, selected) kitchen_option.set_item_disabled(selected_index, true) kitchen_option.select(selected_index) var pending := _has_pending(selected_facility_id) kitchen_option.disabled = pending kitchen_apply_button.disabled = pending or selected == StringName(dependency.get(&"related_facility_id", &"")) kitchen_apply_button.text = "設定を受理済み…" if pending else "関連厨房を設定" kitchen_status_label.text = "関連厨房:稼働中" if bool(dependency.get(&"operating", false)) else "表層の飲食店と同じ位置範囲・階に収まる裏層の厨房を1つ選んでください。厨房が稼働するまで飲食サービスは休止します。" if StringName(dependency.get(&"reason", &"")) == &"INVALID_RELATED_KITCHEN_PLACEMENT": kitchen_status_label.text = "関連厨房がこの飲食店の裏に収まっていません。同じ位置範囲・階に収まる裏層の厨房を明示的に選んでください。" if StringName(dependency.get(&"reason", &"")) == &"KITCHEN_SUSPENDED": kitchen_status_label.text = "関連厨房が持続休止中です。別の稼働する厨房を明示的に設定できます。" func submit_kitchen_link() -> void: var row := _facility_row(selected_facility_id) var dependency: Dictionary = row.get(&"kitchen_dependency", {}) if row.is_empty() or not bool(dependency.get(&"required", false)) or _has_pending(selected_facility_id): return var selected := _selected_kitchen(row) if selected == StringName(dependency.get(&"related_facility_id", &"")): return if _runtime == null or not _runtime.has_method(&"submit_command"): _set_result("営業サービスへ接続できません。", false) return var payload := {&"facility_id": selected_facility_id, &"conditions": {&"related_facility_id": selected}} var key := StringName("%s%08d" % [KITCHEN_KEY_PREFIX, _sequence]) _sequence += 1 var result: Variant = _submit_ui_command(KITCHEN_COMMAND, payload, &"player", selected_facility_id, 75, -1, key) if result == null or not result.has_method(&"is_success"): _set_result("操作に応答がありません。", false) return var queued := false var queue: Variant = _runtime.get(&"command_queue") if queue != null: for command: Dictionary in queue.create_snapshot().get("pending", []): queued = queued or int(command.get("command_id", 0)) == int(result.command_id) if result.is_success() and queued: _pending_kitchen_commands[int(result.command_id)] = payload.duplicate(true) _set_result("関連厨房の設定を受理しました。次の時間更新で反映します。", true) else: _show_kitchen_result(result, payload) refresh() func _show_kitchen_result(result: Variant, payload: Dictionary) -> void: if not result.is_success(): if StringName(result.details.get(&"reason", &"")) == &"INVALID_RELATED_KITCHEN_PLACEMENT": _set_result("この厨房は飲食店の裏に収まりません。同じ位置範囲・階に収まる裏層の厨房を選び直してください。", false) else: _set_result("関連厨房を設定できません。裏層の実在する厨房を選び直してください。", false) return _kitchen_drafts.erase(StringName(payload.get(&"facility_id", &""))) _set_result("関連厨房の設定を反映しました。", true) func select_facility(id: StringName) -> void: selected_facility_id = id refresh() func get_report() -> Dictionary: return _report.duplicate(true) func submit_policy() -> void: var row := _facility_row(selected_facility_id) if row.is_empty() or _has_pending(selected_facility_id): return var draft := _selected_draft(row) if not _is_changed(row, draft): return if _runtime == null or not _runtime.has_method(&"submit_command"): _set_result("営業サービスへ接続できません。", false) return var patch: Dictionary = {&"business_open": bool(draft.get(&"business_open", true))} if bool(row.get(&"can_price", false)): patch[&"price_mode"] = StringName(draft.get(&"price_mode", &"standard")) var payload := {&"facility_id": selected_facility_id, &"patch": patch} var key := StringName("%s%08d" % [KEY_PREFIX, _sequence]) _sequence += 1 var result: Variant = _submit_ui_command(COMMAND, payload, &"player", selected_facility_id, 75, -1, key) if result == null or not result.has_method(&"is_success"): _set_result("操作に応答がありません。", false) return var queued := false var queue: Variant = _runtime.get(&"command_queue") if queue != null: for command: Dictionary in queue.create_snapshot().get("pending", []): queued = queued or int(command.get("command_id", 0)) == int(result.command_id) if result.is_success() and queued: _pending_commands[int(result.command_id)] = payload.duplicate(true) _set_result("営業設定を受理しました。次の時間更新で反映します。", true) else: _show_result(result, payload) refresh() func _show_result(result: Variant, payload: Dictionary) -> void: if not result.is_success(): var details: Dictionary = result.details if result.details is Dictionary else {} var reason := StringName(details.get(&"reason", &"UNAVAILABLE")) _set_result(String(REASONS.get(reason, "営業設定を反映できませんでした(%s)。" % reason)), false) return var id := StringName(payload.get(&"facility_id", &"")) _drafts.erase(id) var row := _facility_row(id) _set_result("%sの営業設定を反映しました。" % String(row.get(&"display_name", "施設")), true) func _selected_draft(row: Dictionary) -> Dictionary: for payload: Dictionary in _pending_commands.values(): if StringName(payload.get(&"facility_id", &"")) == selected_facility_id: var draft := row.duplicate(true) draft.merge(payload.get(&"patch", {}), true) return draft return _drafts.get(selected_facility_id, row) func _is_changed(row: Dictionary, draft: Dictionary) -> bool: return bool(row.get(&"business_open", true)) != bool(draft.get(&"business_open", true)) or (bool(row.get(&"can_price", false)) and StringName(row.get(&"price_mode", &"standard")) != StringName(draft.get(&"price_mode", &"standard"))) func _store_draft() -> void: if _refreshing or selected_facility_id.is_empty() or _has_pending(selected_facility_id): return var mode := &"standard" if price_option.selected >= 0: mode = StringName(price_option.get_item_metadata(price_option.selected)) _drafts[selected_facility_id] = {&"business_open": business_open_button.button_pressed, &"price_mode": mode} refresh() func _synchronize_commands(queue: Variant) -> void: _pending_commands.clear() _pending_kitchen_commands.clear() for metadata: Dictionary in queue.get_accepted_command_metadata(): var key := String(metadata.get("idempotency_key", "")) if key.begins_with(KITCHEN_KEY_PREFIX) and key.trim_prefix(KITCHEN_KEY_PREFIX).is_valid_int(): _sequence = maxi(_sequence, int(key.trim_prefix(KITCHEN_KEY_PREFIX)) + 1) if key.begins_with(KEY_PREFIX) and key.trim_prefix(KEY_PREFIX).is_valid_int(): _sequence = maxi(_sequence, int(key.trim_prefix(KEY_PREFIX)) + 1) for command: Dictionary in queue.create_snapshot().get("pending", []): if StringName(command.get("command_type", &"")) == COMMAND: _pending_commands[int(command.get("command_id", 0))] = command.get("payload", {}).duplicate(true) elif StringName(command.get("command_type", &"")) == KITCHEN_COMMAND and String(command.get("idempotency_key", "")).begins_with(KITCHEN_KEY_PREFIX): _pending_kitchen_commands[int(command.get("command_id", 0))] = command.get("payload", {}).duplicate(true) func _has_pending(id: StringName) -> bool: for payload: Dictionary in _pending_commands.values() + _pending_kitchen_commands.values(): if StringName(payload.get(&"facility_id", &"")) == id: return true return false func _on_command_completed(result: Variant) -> void: if _pending_kitchen_commands.has(int(result.command_id)): var payload: Dictionary = _pending_kitchen_commands[int(result.command_id)] _pending_kitchen_commands.erase(int(result.command_id)) _show_kitchen_result(result, payload) refresh() return if not _pending_commands.has(int(result.command_id)): return var payload: Dictionary = _pending_commands[int(result.command_id)] _pending_commands.erase(int(result.command_id)) _show_result(result, payload) refresh() func _on_snapshot_published(_snapshot: Dictionary = {}, _digest: String = "") -> void: if is_visible_in_tree(): refresh() func _fill_facilities() -> void: if facility_option.get_popup().visible: return facility_option.clear() var rows := _rows(&"facilities") if rows.is_empty(): facility_option.add_item("完成した施設がありません") facility_option.set_item_metadata(0, &"") selected_facility_id = &"" return var selected_index := 0 for row: Dictionary in rows: facility_option.add_item(_facility_name(row)) var index := facility_option.item_count - 1 var id := StringName(row.get(&"facility_id", &"")) facility_option.set_item_metadata(index, id) if id == selected_facility_id: selected_index = index facility_option.select(selected_index) selected_facility_id = StringName(facility_option.get_item_metadata(selected_index)) func _fill_modes(selected: StringName) -> void: if price_option.get_popup().visible: return price_option.clear() var selected_index := 0 for mode: Dictionary in _rows(&"modes"): price_option.add_item("%s(%d%%)" % [String(mode.get(&"label", "料金")), int(mode.get(&"percent", 100))]) var index := price_option.item_count - 1 price_option.set_item_metadata(index, StringName(mode.get(&"id", &"standard"))) if StringName(mode.get(&"id", &"standard")) == selected: selected_index = index if price_option.item_count > 0: price_option.select(selected_index) func _facility_name(row: Dictionary) -> String: var label := String(row.get(&"display_name", row.get(&"facility_id", "施設"))) var origin: Variant = row.get(&"origin", null) var layer := "裏" if int(row.get(&"layer", 0)) == 1 else "表" if origin is Vector2i or origin is Vector3i: label += " (%d,%d・%s)" % [origin.x, origin.y, layer] elif origin is Dictionary: label += " (%d,%d・%s)" % [int(origin.get(&"x", 0)), int(origin.get(&"y", 0)), layer] return label func _price_effect(mode: StringName) -> String: match mode: &"charitable": return "お財布にやさしい料金。満足度が大きく上がる一方、1回の売上は少なくなります。" &"affordable": return "手頃な料金。満足度が上がり、1回の売上は少し少なくなります。" &"expensive": return "1回の売上が増えますが、満足度は下がります。お客様の懐具合にもご注意を。" &"exorbitant": return "とても高い料金。満足度が大きく下がり、お客様が支払えない場合もあります。" _: return "基準となる料金。価格による満足度の増減はありません。" func _rows(key: StringName) -> Array[Dictionary]: var rows: Array[Dictionary] = [] var value: Variant = _report.get(key, []) if value is Array: for row: Variant in value: if row is Dictionary: rows.append(row) return rows func _facility_row(id: StringName) -> Dictionary: for row: Dictionary in _rows(&"facilities"): if StringName(row.get(&"facility_id", &"")) == id: return row return {} func _set_result(message: String, success: bool) -> void: result_label.text = message LayerSkin.register_status_text(result_label, success, LayerSkin.active_layer(self)) status_requested.emit(message, success) func _label(value: String, node_name := "") -> Label: var label := Label.new() if not node_name.is_empty(): label.name = node_name label.text = value label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART label.size_flags_horizontal = Control.SIZE_EXPAND_FILL label.add_theme_font_size_override("font_size", 13) label.add_theme_color_override("font_color", Color("55371e")) LayerSkin.register_text(label, LayerSkin.active_layer(self)) return label func _button(value: String, node_name: String, callback: Callable) -> Button: var button := Button.new() button.name = node_name button.text = value button.custom_minimum_size.y = 30 button.add_theme_font_size_override("font_size", 13) button.pressed.connect(callback) if _style_button.is_valid(): _style_button.call(button) return button func _option(node_name: String) -> OptionButton: var picker := OptionButton.new() picker.name = node_name picker.fit_to_longest_item = false picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL picker.custom_minimum_size.y = 30 if _style_button.is_valid(): _style_button.call(picker) return picker func configure_audio(submitter: Callable) -> void: _audio_submitter = submitter func _submit_ui_command( command_type: StringName, payload: Dictionary = {}, actor_id: StringName = &"player", target_id: StringName = &"", priority := 0, execute_at := -1, idempotency_key: StringName = &"" ) -> Variant: if _audio_submitter.is_valid(): return _audio_submitter.call(command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key) return _runtime.call(&"submit_command", command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key)