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:
@@ -3,6 +3,8 @@ extends VBoxContainer
|
||||
|
||||
const LayerSkin := preload("res://scripts/ui/layer_interface_skin.gd")
|
||||
|
||||
signal status_requested(message: String, success: bool)
|
||||
|
||||
## Projection only: authoritative close and ledger entries own all amounts.
|
||||
var _initial_layer := 0
|
||||
var _runtime: Variant
|
||||
@@ -13,6 +15,18 @@ var _closed_label: Label
|
||||
var _facility_label: Label
|
||||
var _selected_day := -1
|
||||
var _picker_days: Array[int] = []
|
||||
var loan_option: OptionButton
|
||||
var loan_amount: LineEdit
|
||||
var repay_button: Button
|
||||
var repay_all_button: Button
|
||||
var _loan_box: VBoxContainer
|
||||
var _loan_hint: Label
|
||||
var _loan_result: Label
|
||||
var _selected_loan: StringName = &""
|
||||
var _loan_pending_command := 0
|
||||
var _loan_queue_instance := 0
|
||||
var _audio_submitter := Callable()
|
||||
|
||||
|
||||
|
||||
func setup(runtime: Variant, clock: Variant, style_button := Callable(), layer := 0) -> void:
|
||||
@@ -27,6 +41,7 @@ func setup(runtime: Variant, clock: Variant, style_button := Callable(), layer :
|
||||
LayerSkin.register_text(heading, _initial_layer)
|
||||
add_child(heading)
|
||||
_cash_label = _label()
|
||||
_build_repayment_controls(style_button)
|
||||
_day_picker = OptionButton.new()
|
||||
_day_picker.name = "LedgerDayPicker"
|
||||
if style_button.is_valid(): style_button.call(_day_picker)
|
||||
@@ -36,6 +51,8 @@ func setup(runtime: Variant, clock: Variant, style_button := Callable(), layer :
|
||||
add_child(_day_picker)
|
||||
_closed_label = _label()
|
||||
_facility_label = _label()
|
||||
if _runtime != null and _runtime.has_signal(&"command_completed"):
|
||||
_runtime.connect(&"command_completed", _on_repayment_completed)
|
||||
refresh()
|
||||
|
||||
|
||||
@@ -97,6 +114,7 @@ func refresh() -> void:
|
||||
return
|
||||
if _day_picker.get_popup().visible:
|
||||
return
|
||||
_refresh_repayment_controls()
|
||||
var report := get_report()
|
||||
_cash_label.text = "第%d営業日 · 手持ち %dG\n本日の入金 +%dG / 出金 −%dG\nうち建築 %dG / 給与支払 %dG / 維持費支払 %dG\n未払い維持費 %dG\n資金増減 %+dG(営業利益は下の決算)" % [report[&"business_day"], report[&"cash"], report[&"cash_in"], report[&"cash_out"], report[&"construction_cash_out"], report[&"wage_cash_out"], report[&"upkeep_cash_out"], report[&"upkeep_arrears"], report[&"cash_net"]]
|
||||
var summary: Dictionary = report[&"selected_close"]
|
||||
@@ -124,7 +142,11 @@ func refresh() -> void:
|
||||
var other := 0
|
||||
for key: StringName in [&"maintenance", &"utilities", &"taxes_and_interest", &"fines"]:
|
||||
other += int(totals.get(key, 0))
|
||||
_closed_label.text = "売上 %dG / 返金 %dG\n運営費(仕入れ・契約金等) %dG\n給与 %dG / その他 %dG\n営業利益 %+dG\n前日比 %+dG / 未払給与 %dG\n帳簿照合:%s" % [totals.get(&"revenue", 0), totals.get(&"refunds", 0), totals.get(&"cost_of_goods", 0), totals.get(&"wages", 0), other, summary.get(&"net_profit", 0), summary.get(&"previous_day_delta", 0), summary.get(&"arrears_total", 0), "一致" if bool(summary.get(&"balanced", false)) else "差額あり"]
|
||||
_closed_label.text = "売上 %dG / 返金 %dG\n運営費(仕入れ・契約金等) %dG\n給与 %dG / その他 %dG\n日次利益 %+dG\n前日比 %+dG / 未払給与 %dG\n帳簿照合:%s" % [totals.get(&"revenue", 0), totals.get(&"refunds", 0), totals.get(&"cost_of_goods", 0), totals.get(&"wages", 0), other, summary.get(&"net_profit", 0), summary.get(&"previous_day_delta", 0), summary.get(&"arrears_total", 0), "一致" if bool(summary.get(&"balanced", false)) else "差額あり"]
|
||||
if totals.has(&"financing_principal_inflows") or totals.has(&"financing_principal_outflows"):
|
||||
_closed_label.text += "\n借入元本の入金 %dG(売上外) / 元本返済 %dG(決算経費)" % [int(totals.get(&"financing_principal_inflows", 0)), int(totals.get(&"financing_principal_outflows", 0))]
|
||||
if _runtime.operations_coordinator.is_legacy_principal_profit_history(int(summary[&"day"])):
|
||||
_closed_label.text += "\n旧版の確定履歴:元本返済を利益から除外した計算を保持"
|
||||
var legacy: bool = _runtime.operations_coordinator.is_legacy_financial_history(int(summary[&"day"]))
|
||||
_closed_label.text += "\n%s %d回%s" % ["旧版の連続未払い" if legacy else "連続赤字", int(summary.get(&"consecutive_insolvency_count", 0)), "" if legacy else "(3回でゲーム終了)"]
|
||||
var close_minute := int(summary[&"closed_at"]) % 1440
|
||||
@@ -160,3 +182,154 @@ func _label() -> Label:
|
||||
label.add_theme_font_size_override("font_size", 13)
|
||||
add_child(label)
|
||||
return label
|
||||
|
||||
|
||||
func configure_audio(submitter: Callable) -> void:
|
||||
_audio_submitter = submitter
|
||||
|
||||
|
||||
func _build_repayment_controls(style_button: Callable) -> void:
|
||||
_loan_hint = _label()
|
||||
_loan_hint.name = "LoanRepaymentHint"
|
||||
_loan_box = VBoxContainer.new()
|
||||
_loan_box.name = "LoanRepaymentControls"
|
||||
_loan_box.add_theme_constant_override("separation", 6)
|
||||
add_child(_loan_box)
|
||||
loan_option = OptionButton.new()
|
||||
loan_option.name = "LoanRepaymentOption"
|
||||
if style_button.is_valid(): style_button.call(loan_option)
|
||||
_loan_box.add_child(loan_option)
|
||||
loan_option.item_selected.connect(func(index: int) -> void:
|
||||
_selected_loan = StringName(loan_option.get_item_metadata(index))
|
||||
loan_amount.text = ""
|
||||
_refresh_repayment_controls())
|
||||
loan_amount = LineEdit.new()
|
||||
loan_amount.name = "LoanRepaymentAmount"
|
||||
loan_amount.placeholder_text = "返済する元本(整数G)"
|
||||
loan_amount.tooltip_text = "残元本と手持ちを超えない整数額を入力"
|
||||
_loan_box.add_child(loan_amount)
|
||||
loan_amount.text_changed.connect(func(_text: String) -> void: _refresh_repayment_controls())
|
||||
loan_amount.text_submitted.connect(func(_text: String) -> void: submit_repayment(false))
|
||||
repay_button = Button.new()
|
||||
repay_button.name = "LoanRepaymentButton"
|
||||
repay_button.text = "指定額を返済"
|
||||
if style_button.is_valid(): style_button.call(repay_button)
|
||||
_loan_box.add_child(repay_button)
|
||||
repay_button.pressed.connect(submit_repayment.bind(false))
|
||||
repay_all_button = Button.new()
|
||||
repay_all_button.name = "LoanRepayAllButton"
|
||||
repay_all_button.text = "元本を完済"
|
||||
if style_button.is_valid(): style_button.call(repay_all_button)
|
||||
_loan_box.add_child(repay_all_button)
|
||||
repay_all_button.pressed.connect(submit_repayment.bind(true))
|
||||
_loan_result = _label()
|
||||
_loan_result.name = "LoanRepaymentResult"
|
||||
|
||||
|
||||
func _loan_report() -> Dictionary:
|
||||
if _runtime == null or not _runtime.has_method(&"get_loan_principal_report"): return {}
|
||||
return _runtime.call(&"get_loan_principal_report")
|
||||
|
||||
|
||||
func _loan_row(report: Dictionary) -> Dictionary:
|
||||
for row: Dictionary in report.get(&"loans", []):
|
||||
if StringName(row[&"loan_id"]) == _selected_loan: return row
|
||||
return {}
|
||||
|
||||
|
||||
func _refresh_repayment_controls() -> void:
|
||||
if loan_option == null: return
|
||||
var queue: Variant = _runtime.get(&"command_queue") if _runtime != null else null
|
||||
var instance: int = queue.get_instance_id() if queue != null else 0
|
||||
if instance != _loan_queue_instance:
|
||||
_loan_queue_instance = instance
|
||||
_loan_pending_command = 0
|
||||
_loan_result.text = ""
|
||||
loan_amount.text = ""
|
||||
var report := _loan_report()
|
||||
var loans: Array = report.get(&"loans", [])
|
||||
_loan_box.visible = not loans.is_empty()
|
||||
if not loan_option.get_popup().visible:
|
||||
loan_option.clear()
|
||||
var selection := -1
|
||||
for index: int in loans.size():
|
||||
var row: Dictionary = loans[index]
|
||||
var remaining := int(row[&"outstanding_principal"])
|
||||
loan_option.add_item("借入 %d · %s" % [index + 1, "完済" if remaining == 0 else "残元本 %dG" % remaining])
|
||||
loan_option.set_item_metadata(index, row[&"loan_id"])
|
||||
if StringName(row[&"loan_id"]) == _selected_loan: selection = index
|
||||
if selection < 0 and not loans.is_empty():
|
||||
selection = 0
|
||||
_selected_loan = StringName(loans[0][&"loan_id"])
|
||||
if selection >= 0: loan_option.select(selection)
|
||||
var row := _loan_row(report)
|
||||
var remaining := int(row.get(&"outstanding_principal", 0))
|
||||
var cash := int(report.get(&"cash", 0))
|
||||
var waiting := not (row.get(&"pending_command_ids", []) as Array).is_empty()
|
||||
var allowed := bool(report.get(&"available", false)) and bool(report.get(&"ledger_valid", false)) and not bool(report.get(&"terminal", false)) and remaining > 0 and not waiting
|
||||
var text := loan_amount.text.strip_edges()
|
||||
var amount := int(text) if text.is_valid_int() else 0
|
||||
repay_button.disabled = not allowed or amount <= 0 or amount > remaining or amount > cash
|
||||
repay_all_button.disabled = not allowed or remaining > cash
|
||||
loan_amount.editable = allowed
|
||||
if not bool(report.get(&"available", false)) or not bool(report.get(&"ledger_valid", false)):
|
||||
_loan_hint.text = "返済の帳簿を確認できません。"
|
||||
elif bool(report.get(&"terminal", false)):
|
||||
_loan_hint.text = "運営は終了しました。借入の記録を確認できます。"
|
||||
elif loans.is_empty():
|
||||
_loan_hint.text = "借入はありません。"
|
||||
elif waiting:
|
||||
_loan_hint.text = "返済を受理済みです。次の時間更新で実行し、手持ちと残元本を再確認します。"
|
||||
elif remaining == 0:
|
||||
_loan_hint.text = "この借入は完済しています。"
|
||||
else:
|
||||
_loan_hint.text = "手持ち %dGから元本を返済します。残元本 %dG。返済額は日次経費に含まれます。" % [cash, remaining]
|
||||
if remaining > cash: _loan_hint.text += "\n完済には手持ちが不足しています。"
|
||||
if not text.is_empty() and repay_button.disabled: _loan_hint.text += "\n残元本と手持ちを超えない正の整数額を入力してください。"
|
||||
|
||||
|
||||
func submit_repayment(full: bool = false) -> void:
|
||||
var report := _loan_report()
|
||||
var row := _loan_row(report)
|
||||
var remaining := int(row.get(&"outstanding_principal", 0))
|
||||
var text := loan_amount.text.strip_edges()
|
||||
var amount := remaining if full else (int(text) if text.is_valid_int() else 0)
|
||||
if not bool(report.get(&"ledger_valid", false)) or bool(report.get(&"terminal", false)) or row.is_empty() or not (row.get(&"pending_command_ids", []) as Array).is_empty() or amount <= 0 or amount > remaining or amount > int(report.get(&"cash", 0)):
|
||||
_set_repayment_result("返済できません。手持ち・残元本・返済待ちを確認してください。", false)
|
||||
_refresh_repayment_controls()
|
||||
return
|
||||
var key := StringName("ui.loan.repay.%d" % int(report[&"next_command_id"]))
|
||||
var payload := {&"loan_id": _selected_loan, &"amount": amount}
|
||||
var result: Variant
|
||||
if _audio_submitter.is_valid():
|
||||
result = _audio_submitter.call(&"repay_loan_principal", payload, &"player", _selected_loan, 0, -1, key)
|
||||
else:
|
||||
result = _runtime.call(&"submit_command", &"repay_loan_principal", payload, &"player", _selected_loan, 0, -1, key)
|
||||
var after := _loan_row(_loan_report())
|
||||
if result.is_success() and (after.get(&"pending_command_ids", []) as Array).has(int(result.command_id)):
|
||||
_loan_pending_command = int(result.command_id)
|
||||
_set_repayment_result("返済を受理しました。実行時に手持ちと残元本を確認します。", true)
|
||||
else:
|
||||
_show_repayment_result(result)
|
||||
_refresh_repayment_controls()
|
||||
|
||||
|
||||
func _show_repayment_result(result: Variant) -> void:
|
||||
if result.is_success():
|
||||
_set_repayment_result("元本 %dGを返済しました。残元本 %dG。" % [int(result.details.get(&"paid_principal", 0)), int(result.details.get(&"outstanding_principal", 0))], true)
|
||||
loan_amount.text = ""
|
||||
else:
|
||||
_set_repayment_result("返済を実行できませんでした。手持ちと残元本を確認してください。", false)
|
||||
|
||||
|
||||
func _on_repayment_completed(result: Variant) -> void:
|
||||
if int(result.command_id) != _loan_pending_command: return
|
||||
_loan_pending_command = 0
|
||||
_show_repayment_result(result)
|
||||
_refresh_repayment_controls()
|
||||
|
||||
|
||||
func _set_repayment_result(message: String, success: bool) -> void:
|
||||
_loan_result.text = message
|
||||
LayerSkin.register_status_text(_loan_result, success, LayerSkin.active_layer(self))
|
||||
status_requested.emit(message, success)
|
||||
|
||||
Reference in New Issue
Block a user