Files
TheTree_mock/scripts/staff/staff_task.gd
Takeshi 5dde0c5ec2 Record original-alignment implementation and traceability checkpoint
Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
2026-09-30 23:52:24 +09:00

367 lines
12 KiB
GDScript

class_name StaffTask
extends RefCounted
enum State {
OPEN,
RESERVED,
MOVING,
WORKING,
COMPLETED,
BLOCKED,
CANCELLED,
FAILED,
}
const MAX_PRIORITY := 100
const MAX_INTERRUPT_LEVEL := 3
var task_id: StringName = &""
var task_type: StringName = &""
var priority := 0
var created_at := 0
var deadline_at := -1
var rescue_facility_id: StringName = &""
var target_entity_id: StringName = &""
var target_cell := Vector3i.ZERO
var required_role_tags: Array[StringName] = []
var required_skill_min: Dictionary = {}
var required_items: Dictionary = {}
var estimated_minutes := 1
var interrupt_level := 3
var state: State = State.OPEN
var assigned_staff_id: StringName = &""
var retry_count := 0
var task_tags: Array[StringName] = []
var reservation_ids: Array[StringName] = []
var progress_minutes := 0
var dangerous := false
var emergency := false
var preserve_progress_on_interrupt := true
var state_reason: StringName = &""
var state_changed_at := 0
func configure(
p_task_id: StringName,
p_task_type: StringName,
p_priority: int,
p_created_at: int,
options: Dictionary = {}
) -> StaffTask:
task_id = p_task_id
task_type = p_task_type
priority = clampi(p_priority, 0, MAX_PRIORITY)
created_at = maxi(p_created_at, 0)
deadline_at = int(_value(options, &"deadline_at", -1))
rescue_facility_id = StringName(_value(options, &"rescue_facility_id", &""))
target_entity_id = StringName(_value(options, &"target_entity_id", &""))
target_cell = _vector3i(_value(options, &"target_cell", Vector3i.ZERO))
required_role_tags = _sorted_unique_names(_value(options, &"required_role_tags", []))
required_skill_min = _normalize_positive_dictionary(_value(options, &"required_skill_min", {}), 999)
required_items = _normalize_positive_dictionary(_value(options, &"required_items", {}))
estimated_minutes = maxi(int(_value(options, &"estimated_minutes", 1)), 1)
interrupt_level = clampi(int(_value(options, &"interrupt_level", 3)), 0, MAX_INTERRUPT_LEVEL)
state = _state_from_value(_value(options, &"state", State.OPEN), State.OPEN)
assigned_staff_id = StringName(_value(options, &"assigned_staff_id", &""))
retry_count = maxi(int(_value(options, &"retry_count", 0)), 0)
task_tags = _sorted_unique_names(_value(options, &"task_tags", []))
reservation_ids = _sorted_unique_names(_value(options, &"reservation_ids", []))
progress_minutes = clampi(
int(_value(options, &"progress_minutes", 0)),
0,
estimated_minutes
)
dangerous = bool(_value(options, &"dangerous", false))
emergency = bool(_value(options, &"emergency", priority >= 90))
preserve_progress_on_interrupt = bool(_value(options, &"preserve_progress_on_interrupt", true))
state_reason = StringName(_value(options, &"state_reason", &""))
state_changed_at = maxi(int(_value(options, &"state_changed_at", created_at)), 0)
return self
func is_valid() -> bool:
if task_id.is_empty() or task_type.is_empty():
return false
if priority < 0 or priority > MAX_PRIORITY:
return false
if estimated_minutes <= 0 or progress_minutes < 0 or progress_minutes > estimated_minutes:
return false
if interrupt_level < 0 or interrupt_level > MAX_INTERRUPT_LEVEL:
return false
if state in [State.RESERVED, State.MOVING, State.WORKING] and assigned_staff_id.is_empty():
return false
if state in [State.OPEN, State.BLOCKED, State.COMPLETED, State.CANCELLED, State.FAILED] \
and not assigned_staff_id.is_empty():
return false
if state == State.OPEN and not reservation_ids.is_empty():
return false
return true
func is_terminal() -> bool:
return state in [State.COMPLETED, State.CANCELLED, State.FAILED]
func is_dispatchable() -> bool:
return state == State.OPEN and assigned_staff_id.is_empty()
func reserve(
staff_id: StringName,
p_reservation_ids: Array[StringName],
absolute_minute: int
) -> bool:
if not is_dispatchable() or staff_id.is_empty():
return false
if not required_items.is_empty() and p_reservation_ids.is_empty():
return false
assigned_staff_id = staff_id
reservation_ids.assign(p_reservation_ids)
state = State.RESERVED
state_reason = &""
state_changed_at = maxi(absolute_minute, 0)
return true
func begin_moving(absolute_minute: int) -> bool:
if state != State.RESERVED:
return false
state = State.MOVING
state_changed_at = maxi(absolute_minute, 0)
return true
func begin_working(absolute_minute: int) -> bool:
if state not in [State.RESERVED, State.MOVING]:
return false
state = State.WORKING
state_changed_at = maxi(absolute_minute, 0)
return true
func advance_work(minutes: int, absolute_minute: int) -> bool:
if state != State.WORKING or minutes <= 0:
return false
progress_minutes = mini(progress_minutes + minutes, estimated_minutes)
state_changed_at = maxi(absolute_minute, 0)
return progress_minutes >= estimated_minutes
func complete(absolute_minute: int) -> bool:
if state != State.WORKING or progress_minutes < estimated_minutes:
return false
assigned_staff_id = &""
reservation_ids.clear()
state = State.COMPLETED
state_changed_at = maxi(absolute_minute, 0)
return true
func block(reason: StringName, absolute_minute: int) -> bool:
if is_terminal():
return false
retry_count += 1
assigned_staff_id = &""
reservation_ids.clear()
state = State.BLOCKED
state_reason = reason
state_changed_at = maxi(absolute_minute, 0)
return true
func reopen(reason: StringName, absolute_minute: int, preserve_progress := true) -> bool:
if is_terminal():
return false
assigned_staff_id = &""
reservation_ids.clear()
if not preserve_progress:
progress_minutes = 0
state = State.OPEN
state_reason = reason
state_changed_at = maxi(absolute_minute, 0)
return true
func cancel(reason: StringName, absolute_minute: int) -> bool:
if is_terminal():
return false
assigned_staff_id = &""
reservation_ids.clear()
state = State.CANCELLED
state_reason = reason
state_changed_at = maxi(absolute_minute, 0)
return true
func fail(reason: StringName, absolute_minute: int) -> bool:
if is_terminal():
return false
assigned_staff_id = &""
reservation_ids.clear()
state = State.FAILED
state_reason = reason
state_changed_at = maxi(absolute_minute, 0)
return true
func can_be_interrupted_by(candidate: StaffTask) -> bool:
if candidate == null or candidate.task_id == task_id:
return false
match interrupt_level:
0:
return false
1:
return candidate.emergency
2:
return candidate.priority >= priority
_:
return true
func create_snapshot() -> Dictionary:
return {
&"task_id": String(task_id),
&"task_type": String(task_type),
&"priority": priority,
&"created_at": created_at,
&"deadline_at": deadline_at,
&"rescue_facility_id": String(rescue_facility_id),
&"target_entity_id": String(target_entity_id),
&"target_cell": [target_cell.x, target_cell.y, target_cell.z],
&"required_role_tags": _names_to_strings(required_role_tags),
&"required_skill_min": _serialize_dictionary(required_skill_min),
&"required_items": _serialize_dictionary(required_items),
&"estimated_minutes": estimated_minutes,
&"interrupt_level": interrupt_level,
&"state": State.keys()[state],
&"assigned_staff_id": String(assigned_staff_id),
&"retry_count": retry_count,
&"task_tags": _names_to_strings(task_tags),
&"reservation_ids": _names_to_strings(reservation_ids),
&"progress_minutes": progress_minutes,
&"dangerous": dangerous,
&"emergency": emergency,
&"preserve_progress_on_interrupt": preserve_progress_on_interrupt,
&"state_reason": String(state_reason),
&"state_changed_at": state_changed_at,
}
static func from_snapshot(snapshot: Dictionary) -> StaffTask:
if not _snapshot_shape_is_valid(snapshot):
return null
var state_index := State.keys().find(String(snapshot.get(&"state", "")))
if state_index < 0:
return null
var task := StaffTask.new().configure(
StringName(snapshot.get(&"task_id", "")),
StringName(snapshot.get(&"task_type", "")),
int(snapshot.get(&"priority", 0)),
int(snapshot.get(&"created_at", 0)),
{
&"deadline_at": snapshot.get(&"deadline_at", -1),
&"rescue_facility_id": snapshot.get(&"rescue_facility_id", ""),
&"target_entity_id": snapshot.get(&"target_entity_id", ""),
&"target_cell": snapshot.get(&"target_cell", []),
&"required_role_tags": snapshot.get(&"required_role_tags", []),
&"required_skill_min": snapshot.get(&"required_skill_min", {}),
&"required_items": snapshot.get(&"required_items", {}),
&"estimated_minutes": snapshot.get(&"estimated_minutes", 1),
&"interrupt_level": snapshot.get(&"interrupt_level", 3),
&"state": state_index,
&"assigned_staff_id": snapshot.get(&"assigned_staff_id", ""),
&"retry_count": snapshot.get(&"retry_count", 0),
&"task_tags": snapshot.get(&"task_tags", []),
&"reservation_ids": snapshot.get(&"reservation_ids", []),
&"progress_minutes": snapshot.get(&"progress_minutes", 0),
&"dangerous": snapshot.get(&"dangerous", false),
&"emergency": snapshot.get(&"emergency", false),
&"preserve_progress_on_interrupt": snapshot.get(&"preserve_progress_on_interrupt", true),
&"state_reason": snapshot.get(&"state_reason", ""),
&"state_changed_at": snapshot.get(&"state_changed_at", 0),
}
)
return task if task.is_valid() else null
static func _snapshot_shape_is_valid(snapshot: Dictionary) -> bool:
return (
(snapshot.get(&"rescue_facility_id", "") is String or snapshot.get(&"rescue_facility_id", "") is StringName)
and not String(snapshot.get(&"task_id", "")).is_empty()
and not String(snapshot.get(&"task_type", "")).is_empty()
and snapshot.get(&"target_cell", null) is Array
and snapshot.get(&"required_role_tags", null) is Array
and snapshot.get(&"required_skill_min", null) is Dictionary
and snapshot.get(&"required_items", null) is Dictionary
and snapshot.get(&"task_tags", null) is Array
and snapshot.get(&"reservation_ids", null) is Array
and int(snapshot.get(&"priority", -1)) >= 0
and int(snapshot.get(&"priority", -1)) <= MAX_PRIORITY
and int(snapshot.get(&"estimated_minutes", 0)) > 0
and int(snapshot.get(&"interrupt_level", -1)) in range(0, MAX_INTERRUPT_LEVEL + 1)
)
static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant:
return source.get(key, source.get(String(key), fallback))
static func _state_from_value(value: Variant, fallback: State) -> State:
if value is int:
var numeric := int(value)
if numeric >= 0 and numeric < State.size():
return numeric as State
var index := State.keys().find(String(value))
return index as State if index >= 0 else fallback
static func _vector3i(value: Variant) -> Vector3i:
if value is Vector3i:
return value
if value is Array and value.size() == 3:
return Vector3i(int(value[0]), int(value[1]), int(value[2]))
return Vector3i.ZERO
static func _normalize_positive_dictionary(source: Variant, maximum := 2_147_483_647) -> Dictionary:
var result: Dictionary = {}
if not source is Dictionary:
return result
var keys: Array = source.keys()
keys.sort_custom(func(a: Variant, b: Variant) -> bool: return String(a) < String(b))
for raw_id: Variant in keys:
var item_id := StringName(raw_id)
var value := int(source[raw_id])
if not item_id.is_empty() and value > 0:
result[item_id] = mini(value, maximum)
return result
static func _serialize_dictionary(source: Dictionary) -> Dictionary:
var result: Dictionary = {}
var keys: Array = source.keys()
keys.sort_custom(func(a: Variant, b: Variant) -> bool: return String(a) < String(b))
for raw_id: Variant in keys:
result[String(raw_id)] = int(source[raw_id])
return result
static func _sorted_unique_names(source: Variant) -> Array[StringName]:
var result: Array[StringName] = []
if not source is Array:
return result
for value: Variant in source:
var name := StringName(value)
if not name.is_empty() and not result.has(name):
result.append(name)
result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b))
return result
static func _names_to_strings(source: Array[StringName]) -> Array[String]:
var result: Array[String] = []
for value: StringName in source:
result.append(String(value))
return result