153 lines
7.8 KiB
GDScript
153 lines
7.8 KiB
GDScript
class_name InnOpeningGuide
|
|
extends PanelContainer
|
|
|
|
## Read-only guidance. Every action goes back through the normal dashboard.
|
|
signal action_requested(action: StringName)
|
|
|
|
var _runtime: Variant
|
|
var _clock: Variant
|
|
var _heading: Button
|
|
var _body: VBoxContainer
|
|
var _detail: Label
|
|
var _action: Button
|
|
var _progress: Dictionary = {}
|
|
var _sale_economy_id := 0
|
|
var _first_sale_minute := -1
|
|
|
|
|
|
func setup(runtime: Variant, clock: Variant) -> void:
|
|
_runtime = runtime
|
|
_clock = clock
|
|
name = "InnOpeningGuide"
|
|
custom_minimum_size.x = 290
|
|
var skin := StyleBoxFlat.new()
|
|
skin.bg_color = Color("30261be8")
|
|
skin.border_color = Color("b58a45")
|
|
skin.set_border_width_all(1)
|
|
skin.set_content_margin_all(8)
|
|
add_theme_stylebox_override("panel", skin)
|
|
var column := VBoxContainer.new()
|
|
column.add_theme_constant_override("separation", 5)
|
|
add_child(column)
|
|
_heading = Button.new()
|
|
_heading.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
|
_heading.add_theme_font_size_override("font_size", 14)
|
|
_heading.pressed.connect(func() -> void: _body.visible = not _body.visible)
|
|
column.add_child(_heading)
|
|
_body = VBoxContainer.new()
|
|
column.add_child(_body)
|
|
_detail = Label.new()
|
|
_detail.custom_minimum_size.x = 270
|
|
_detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
_detail.add_theme_font_size_override("font_size", 13)
|
|
_detail.add_theme_color_override("font_color", Color("fff0cf"))
|
|
_body.add_child(_detail)
|
|
_action = Button.new()
|
|
_action.add_theme_font_size_override("font_size", 13)
|
|
_action.pressed.connect(func() -> void: action_requested.emit(StringName(_progress.get(&"action", &""))))
|
|
_body.add_child(_action)
|
|
refresh()
|
|
|
|
|
|
func refresh() -> void:
|
|
if _runtime == null or _heading == null:
|
|
return
|
|
_progress = _read_progress()
|
|
_heading.text = "開業の手引き · %s" % _progress[&"title"]
|
|
_detail.text = _progress[&"detail"]
|
|
_action.text = _progress[&"button"]
|
|
_action.visible = not String(_progress[&"action"]).is_empty()
|
|
# A collapsed guide must shrink as soon as its body is hidden.
|
|
reset_size()
|
|
|
|
|
|
func get_progress() -> Dictionary:
|
|
return _read_progress().duplicate(true) if _runtime != null else {}
|
|
|
|
|
|
func _read_progress() -> Dictionary:
|
|
var first_sale := _first_lodging_sale()
|
|
if first_sale >= 0 and _runtime.operations_coordinator != null:
|
|
for summary: Dictionary in _runtime.operations_coordinator.get_day_summaries():
|
|
if int(summary.get(&"closed_at", -1)) > first_sale:
|
|
return _step(&"complete", "初日の営業を達成", "宿泊売上と翌朝の決算を確認できました。帳簿を見ながら、客室と従業員を少しずつ増やしましょう。", &"ledger", "決算を見る")
|
|
var reception: FacilityInstance
|
|
var rooms: Array[FacilityInstance] = []
|
|
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if facility.definition_id == &"reception":
|
|
if reception == null or _reception_score(facility) > _reception_score(reception):
|
|
reception = facility
|
|
elif facility.definition_id == &"standard_room":
|
|
rooms.append(facility)
|
|
if reception == null:
|
|
return _build_step(&"reception", &"reception", "1/6 受付をつくる", "表の地表階に受付を配置します。完成した受付の上に客室を増築できます。", &"build_reception")
|
|
if rooms.is_empty():
|
|
return _build_step(&"room", &"standard_room", "2/6 客室をつくる", "受付が支えになる位置に一般客室を配置します。", &"build_room")
|
|
var accessible_room := false
|
|
for room: FacilityInstance in rooms:
|
|
accessible_room = accessible_room or room.access_available
|
|
if not reception.access_available or not accessible_room:
|
|
return _step(&"access", "3/6 道をつなぐ", "客室の出入口まで客用通路をつなぎます。施設を選択すると、通行不可など営業できない理由を確認できます。", &"build_corridor", "客用通路を選ぶ")
|
|
if not _has_reception_assignment(reception):
|
|
return _step(&"staff", "4/6 受付係を配属", "従業員から受付係を雇い、雇用済み一覧でこの受付と勤務帯を選び「配属・勤務帯を変更」を押します。", &"staff", "従業員を開く")
|
|
if first_sale < 0:
|
|
return _step(&"sale", "5/6 最初の宿泊売上", "時間を進めて、お客さまの到着と宿泊を見守りましょう。売上は現在のサービスが完了すると記帳されます。", &"resume" if _clock.paused else &"ledger", "時間を進める" if _clock.paused else "帳簿を見る")
|
|
return _step(&"close", "6/6 翌朝の決算へ", "最初の宿泊売上が入りました。06:00に営業日が締まり、給与・利益・未払いを帳簿で確認できます。", &"resume" if _clock.paused else &"ledger", "時間を進める" if _clock.paused else "帳簿を見る")
|
|
|
|
|
|
func _reception_score(facility: FacilityInstance) -> int:
|
|
return (2 if facility.access_available else 0) + (1 if _has_reception_assignment(facility) else 0)
|
|
|
|
|
|
func _has_reception_assignment(reception: FacilityInstance) -> bool:
|
|
var assigned := not reception.active_staff_ids.is_empty()
|
|
# A repeating plan remains valid off-duty. Historical or cancelled shifts
|
|
# cannot conceal a deliberate unassignment made before the first sale.
|
|
for staff: StaffRuntime in _runtime.staff_service.get_staff_members():
|
|
var plan: Dictionary = _runtime.staff_service.get_work_assignment(staff.staff_id)
|
|
if not plan.is_empty():
|
|
assigned = assigned or StringName(plan.get(&"facility_id", &"")) == reception.instance_id
|
|
continue
|
|
for shift: ShiftInstance in _runtime.staff_service.get_shifts():
|
|
if shift.staff_id == staff.staff_id and not shift.cancelled and shift.end_at > _clock.absolute_minute and shift.assigned_facility_id == reception.instance_id:
|
|
assigned = true
|
|
return assigned
|
|
|
|
|
|
func _first_lodging_sale() -> int:
|
|
# Once found, an append-only ledger cannot revoke historical first revenue.
|
|
# Restore/reset replaces EconomyService, which invalidates this visual cache.
|
|
var economy_id: int = _runtime.economy_service.get_instance_id()
|
|
if economy_id != _sale_economy_id:
|
|
_sale_economy_id = economy_id
|
|
_first_sale_minute = -1
|
|
if _first_sale_minute >= 0:
|
|
return _first_sale_minute
|
|
var room_ids: Dictionary = {}
|
|
for job: ConstructionJob in _runtime.construction_service.get_jobs():
|
|
if job.definition_id == &"standard_room" and not job.facility_instance_id.is_empty():
|
|
room_ids[job.facility_instance_id] = true
|
|
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if facility.definition_id == &"standard_room":
|
|
room_ids[facility.instance_id] = true
|
|
if room_ids.is_empty():
|
|
return -1
|
|
for entry: LedgerEntry in _runtime.economy_service.get_entries():
|
|
if entry.category == &"SERVICE_SALE" and room_ids.has(entry.source_entity_id):
|
|
_first_sale_minute = entry.absolute_minute if _first_sale_minute < 0 else mini(_first_sale_minute, entry.absolute_minute)
|
|
return _first_sale_minute
|
|
|
|
|
|
func _build_step(stage: StringName, definition_id: StringName, title: String, detail: String, action: StringName) -> Dictionary:
|
|
for job: ConstructionJob in _runtime.construction_service.get_jobs():
|
|
if job.definition_id == definition_id and job.state not in [ConstructionJob.State.COMPLETED, ConstructionJob.State.CANCELLED]:
|
|
return _step(stage, title, "工事中 %d%%。時間を進めると完成します。" % (job.progress / 100), &"resume" if _clock.paused else &"", "時間を進める")
|
|
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(definition_id)
|
|
if definition != null:
|
|
detail += "\n費用 %dG / 資材 %d" % [int(definition.construction_cost.get(&"gold", 0)), int(definition.construction_cost.get(&"materials", 0))]
|
|
return _step(stage, title, detail, action, "受付を選ぶ" if definition_id == &"reception" else "一般客室を選ぶ")
|
|
|
|
|
|
func _step(stage: StringName, title: String, detail: String, action: StringName, button: String) -> Dictionary:
|
|
return {&"stage": stage, &"title": title, &"detail": detail, &"action": action, &"button": button}
|