class_name StaffCommuteService extends RefCounted ## Commutes derive from the saved work plan, Shift and current actor position. ## Runtime owns movement credit, Path projection, elevator rides and events. ## This adapter deliberately owns no additional snapshot payload. const ROUTE_BLOCKED := &"COMMUTE_ROUTE_BLOCKED" const WORKPLACE_MISSING := &"COMMUTE_WORKPLACE_MISSING" const WAITING_SHIFT := &"WAITING_FOR_SHIFT" const MOVING_TO_WORK := &"MOVING_TO_WORK" var _runtime: Variant func configure(runtime: Variant) -> StaffCommuteService: _runtime = runtime _runtime.staff_service.configure_attendance_driver(Callable(self, "can_attend_shift")) return self ## A porter checks in once at the warehouse, then remains dispatchable at the ## end of a delivery. Fixed-post workers return before resuming desk service. func can_attend_shift(staff: StaffRuntime, shift: ShiftInstance, _minute: int) -> bool: if _runtime == null or staff == null or shift == null: return false if _in_transit(staff.staff_id) and (staff.current_task_id.is_empty() or shift.actual_started_at < 0): return false if shift.actual_started_at >= 0 and (staff.role_id == &"porter" or not staff.current_task_id.is_empty()): return true var facility: FacilityInstance = _runtime.facility_service.get_instance(shift.assigned_facility_id) return is_at_facility(staff, facility) ## Physical attendance is separate from dispatch availability. A porter away ## with goods does not provide staff capacity to an empty warehouse. func is_present(staff: StaffRuntime, facility: FacilityInstance) -> bool: if _runtime == null or staff == null or facility == null \ or staff.assigned_facility_id != facility.instance_id \ or staff.state != StaffRuntime.State.AVAILABLE or not staff.can_take_shift(): return false if _in_transit(staff.staff_id): return false if not _runtime.staff_service.requires_attendance(staff.staff_id): return true return is_at_facility(staff, facility) func tick(absolute_minute: int) -> Dictionary: if _runtime == null: return {&"ok": false, &"reason": &"NOT_CONFIGURED"} var moved: Array[StringName] = [] var waiting: Array[StringName] = [] for staff: StaffRuntime in _runtime.staff_service.get_staff_members(): if not _runtime.staff_service.requires_attendance(staff.staff_id) \ or staff.state != StaffRuntime.State.COMMUTING \ or not staff.can_take_shift() or not staff.current_task_id.is_empty(): continue var shift: ShiftInstance = _runtime.staff_service.get_shift(staff.shift_id) if shift == null or (not shift.contains_minute(absolute_minute) and not shift.is_commute_window(absolute_minute)): continue if _in_transit(staff.staff_id): _wait(staff, absolute_minute, &"ELEVATOR_WAIT") waiting.append(staff.staff_id) continue var facility: FacilityInstance = _runtime.facility_service.get_instance(shift.assigned_facility_id) if facility == null: _wait(staff, absolute_minute, WORKPLACE_MISSING) waiting.append(staff.staff_id) continue if is_at_facility(staff, facility): staff.absence_reason = &"" _wait(staff, absolute_minute, WAITING_SHIFT if shift.is_commute_window(absolute_minute) else &"COMMUTE_ARRIVED") waiting.append(staff.staff_id) continue var access := find_facility_access_path(staff.current_cell, facility) if not bool(access.get(&"reachable", false)): _wait(staff, absolute_minute, ROUTE_BLOCKED) waiting.append(staff.staff_id) continue staff.absence_reason = &"" var result: Dictionary = _runtime.advance_staff_commute(staff.staff_id, access[&"cell"], absolute_minute) if bool(result.get(&"moved", false)): moved.append(staff.staff_id) else: waiting.append(staff.staff_id) return {&"ok": true, &"moved_staff_ids": moved, &"waiting_staff_ids": waiting} func get_report() -> Dictionary: var rows: Array[Dictionary] = [] if _runtime == null: return {&"staff": rows} for staff: StaffRuntime in _runtime.staff_service.get_staff_members(): if not _runtime.staff_service.requires_attendance(staff.staff_id): continue var shift: ShiftInstance = _runtime.staff_service.get_shift(staff.shift_id) var facility: FacilityInstance = _runtime.facility_service.get_instance(staff.assigned_facility_id) var reason := staff.absence_reason var target := staff.current_cell if _in_transit(staff.staff_id): reason = &"ELEVATOR_WAIT" if facility != null: var access := find_facility_access_path(staff.current_cell, facility) target = access.get(&"cell", facility.origin) if staff.state == StaffRuntime.State.COMMUTING and reason.is_empty(): reason = WAITING_SHIFT if is_at_facility(staff, facility) else MOVING_TO_WORK rows.append({ &"staff_id": staff.staff_id, &"state": StaffRuntime.State.keys()[staff.state], &"reason": reason, &"facility_id": staff.assigned_facility_id, &"current_cell": staff.current_cell, &"target_cell": target, &"present": is_present(staff, facility), &"late_minutes": shift.late_minutes if shift != null else 0, }) return {&"staff": rows} func find_facility_access_path(start: Vector3i, facility: FacilityInstance) -> Dictionary: if _runtime == null or facility == null: return {&"reachable": false} var candidates: Dictionary = {} var occupied := facility.occupied_cells.duplicate() if occupied.is_empty(): occupied.append(facility.origin) for cell: Vector3i in occupied: for delta: Vector3i in [Vector3i.ZERO, Vector3i.LEFT, Vector3i.RIGHT]: if _runtime.path_service.has_cell(cell + delta): candidates[cell + delta] = true var ordered: Array = candidates.keys() ordered.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: if a.z != b.z: return a.z < b.z if a.y != b.y: return a.y < b.y return a.x < b.x) var best: Array[Vector3i] = [] var best_cost := 9223372036854775807 for cell: Vector3i in ordered: var path: Array[Vector3i] = _runtime.path_service.find_path(start, cell, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"]) var cost: int = _runtime.path_service.calculate_path_cost(path, [&"staff"]) if not path.is_empty() else -1 if cost >= 0 and (best.is_empty() or cost < best_cost): best = path best_cost = cost return {&"reachable": not best.is_empty(), &"path": best, &"cell": best[-1] if not best.is_empty() else facility.origin, &"steps": maxi(0, best.size() - 1)} static func is_at_facility(staff: StaffRuntime, facility: FacilityInstance) -> bool: if staff == null or facility == null: return false var occupied := facility.occupied_cells.duplicate() if occupied.is_empty(): occupied.append(facility.origin) for cell: Vector3i in occupied: if cell.z == staff.current_cell.z and cell.y == staff.current_cell.y \ and absi(cell.x - staff.current_cell.x) <= 1: return true return false func _wait(staff: StaffRuntime, minute: int, reason: StringName) -> void: if reason in [ROUTE_BLOCKED, WORKPLACE_MISSING]: staff.absence_reason = reason _runtime.advance_staff_commute(staff.staff_id, staff.current_cell, minute, reason) func _in_transit(staff_id: StringName) -> bool: return _runtime != null and _runtime.has_method("is_staff_in_transit") \ and bool(_runtime.is_staff_in_transit(staff_id))