Files
TheTree_mock/scripts/ui/business_management_panel.gd
T
Takeshi b84bade045 Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
2026-10-11 15:58:00 +09:00

573 lines
28 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 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)