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.
This commit is contained in:
@@ -9,6 +9,8 @@ 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 := {
|
||||
@@ -26,6 +28,9 @@ const REASONS := {
|
||||
}
|
||||
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
|
||||
@@ -37,10 +42,13 @@ 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
|
||||
@@ -78,6 +86,16 @@ func _build_interface() -> void:
|
||||
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())
|
||||
@@ -122,6 +140,8 @@ func refresh() -> void:
|
||||
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)
|
||||
@@ -134,6 +154,7 @@ func refresh() -> void:
|
||||
_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)
|
||||
@@ -159,11 +180,11 @@ func refresh() -> void:
|
||||
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 "新規受付を停止中"), String(STATE_NAMES.get(StringName(row.get(&"state", &"")), "状態確認中")), 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 "未設定"]
|
||||
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 "休業する設定", String(STATE_NAMES.get(StringName(row.get(&"state", &"")), "状態確認中"))]
|
||||
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 "清掃業務を停止中", String(STATE_NAMES.get(StringName(row.get(&"state", &"")), "状態確認中"))]
|
||||
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():
|
||||
@@ -192,6 +213,115 @@ func refresh() -> void:
|
||||
_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()
|
||||
@@ -217,7 +347,7 @@ func submit_policy() -> void:
|
||||
var payload := {&"facility_id": selected_facility_id, &"patch": patch}
|
||||
var key := StringName("%s%08d" % [KEY_PREFIX, _sequence])
|
||||
_sequence += 1
|
||||
var result: Variant = _runtime.call(&"submit_command", COMMAND, payload, &"player", selected_facility_id, 75, -1, key)
|
||||
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
|
||||
@@ -271,23 +401,34 @@ func _store_draft() -> void:
|
||||
|
||||
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():
|
||||
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)]
|
||||
@@ -415,3 +556,17 @@ func _option(node_name: String) -> OptionButton:
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user