221 lines
6.7 KiB
GDScript
221 lines
6.7 KiB
GDScript
class_name ConstructionJob
|
|
extends RefCounted
|
|
|
|
enum State {
|
|
PLANNED,
|
|
BUILDING,
|
|
COMPLETED,
|
|
CANCELLED,
|
|
BLOCKED,
|
|
}
|
|
|
|
const MAX_PROGRESS := 10_000
|
|
const NORMAL_WORK_RATE := 10_000
|
|
|
|
var job_id: StringName = &""
|
|
var definition_id: StringName = &""
|
|
var origin := Vector3i.ZERO
|
|
var rotation := 0
|
|
var layer := 0
|
|
var reserved_cells: Array[Vector3i] = []
|
|
var progress := 0
|
|
var state: State = State.PLANNED
|
|
var state_reason := ""
|
|
var assigned_builder_ids: Array[StringName] = []
|
|
var construction_cost: Dictionary = {}
|
|
var build_minutes := 60
|
|
var work_units_done := 0
|
|
var created_at := 0
|
|
var started_at := -1
|
|
var completed_at := -1
|
|
var cancelled_at := -1
|
|
var facility_instance_id: StringName = &""
|
|
|
|
|
|
func configure(
|
|
p_job_id: StringName,
|
|
p_definition_id: StringName,
|
|
p_origin: Vector3i,
|
|
p_rotation: int,
|
|
p_layer: int,
|
|
p_reserved_cells: Array[Vector3i],
|
|
p_construction_cost: Dictionary,
|
|
p_build_minutes: int,
|
|
p_created_at: int
|
|
) -> ConstructionJob:
|
|
job_id = p_job_id
|
|
definition_id = p_definition_id
|
|
origin = p_origin
|
|
rotation = _normalize_rotation(p_rotation)
|
|
layer = p_layer
|
|
reserved_cells.assign(p_reserved_cells)
|
|
construction_cost = _normalize_cost(p_construction_cost)
|
|
build_minutes = maxi(p_build_minutes, 1)
|
|
created_at = maxi(p_created_at, 0)
|
|
return self
|
|
|
|
|
|
func advance_one_minute(work_rate_basis_points: int = NORMAL_WORK_RATE, absolute_minute: int = -1) -> bool:
|
|
if state == State.COMPLETED or state == State.CANCELLED:
|
|
return false
|
|
if progress >= MAX_PROGRESS:
|
|
return true
|
|
if work_rate_basis_points <= 0:
|
|
mark_blocked("No effective construction work was supplied.")
|
|
return false
|
|
if started_at < 0:
|
|
started_at = absolute_minute
|
|
state = State.BUILDING
|
|
state_reason = ""
|
|
work_units_done += work_rate_basis_points
|
|
progress = mini(MAX_PROGRESS, work_units_done / build_minutes)
|
|
return progress >= MAX_PROGRESS
|
|
|
|
|
|
func mark_blocked(reason: String) -> void:
|
|
if state == State.COMPLETED or state == State.CANCELLED:
|
|
return
|
|
state = State.BLOCKED
|
|
state_reason = reason
|
|
|
|
|
|
func mark_completed(instance_id: StringName, absolute_minute: int = -1) -> void:
|
|
progress = MAX_PROGRESS
|
|
state = State.COMPLETED
|
|
state_reason = ""
|
|
facility_instance_id = instance_id
|
|
completed_at = absolute_minute
|
|
|
|
|
|
func mark_cancelled(absolute_minute: int = -1) -> void:
|
|
state = State.CANCELLED
|
|
state_reason = ""
|
|
cancelled_at = absolute_minute
|
|
|
|
|
|
func is_terminal() -> bool:
|
|
return state == State.COMPLETED or state == State.CANCELLED
|
|
|
|
|
|
func refundable_cost() -> Dictionary:
|
|
var refund: Dictionary = {}
|
|
var remaining_basis_points := maxi(MAX_PROGRESS - progress, 0)
|
|
for resource_name: Variant in construction_cost:
|
|
var amount := int(construction_cost[resource_name])
|
|
refund[StringName(resource_name)] = amount * remaining_basis_points / MAX_PROGRESS
|
|
return refund
|
|
|
|
|
|
func create_snapshot() -> Dictionary:
|
|
return {
|
|
&"job_id": String(job_id),
|
|
&"definition_id": String(definition_id),
|
|
&"origin": [origin.x, origin.y, origin.z],
|
|
&"rotation": rotation,
|
|
&"layer": layer,
|
|
&"reserved_cells": _serialize_cells(reserved_cells),
|
|
&"progress": progress,
|
|
&"state": State.keys()[state],
|
|
&"state_reason": state_reason,
|
|
&"assigned_builder_ids": _string_names_to_strings(assigned_builder_ids),
|
|
&"construction_cost": construction_cost.duplicate(true),
|
|
&"build_minutes": build_minutes,
|
|
&"work_units_done": work_units_done,
|
|
&"created_at": created_at,
|
|
&"started_at": started_at,
|
|
&"completed_at": completed_at,
|
|
&"cancelled_at": cancelled_at,
|
|
&"facility_instance_id": String(facility_instance_id),
|
|
}
|
|
|
|
|
|
static func from_snapshot(snapshot: Dictionary) -> ConstructionJob:
|
|
if not _is_valid_snapshot(snapshot):
|
|
return null
|
|
var job := ConstructionJob.new()
|
|
job.job_id = StringName(snapshot.get(&"job_id", ""))
|
|
job.definition_id = StringName(snapshot.get(&"definition_id", ""))
|
|
job.origin = _parse_cell(snapshot.get(&"origin", []))
|
|
job.rotation = _normalize_rotation(int(snapshot.get(&"rotation", 0)))
|
|
job.layer = int(snapshot.get(&"layer", job.origin.z))
|
|
job.reserved_cells = _parse_cells(snapshot.get(&"reserved_cells", []))
|
|
job.progress = clampi(int(snapshot.get(&"progress", 0)), 0, MAX_PROGRESS)
|
|
var state_index := State.keys().find(String(snapshot.get(&"state", "PLANNED")))
|
|
if state_index < 0:
|
|
return null
|
|
job.state = state_index as State
|
|
job.state_reason = String(snapshot.get(&"state_reason", ""))
|
|
var saved_builders: Variant = snapshot.get(&"assigned_builder_ids", [])
|
|
if saved_builders is Array:
|
|
for builder_id: Variant in saved_builders:
|
|
job.assigned_builder_ids.append(StringName(builder_id))
|
|
job.construction_cost = _normalize_cost(snapshot.get(&"construction_cost", {}))
|
|
job.build_minutes = maxi(int(snapshot.get(&"build_minutes", 60)), 1)
|
|
job.work_units_done = maxi(int(snapshot.get(&"work_units_done", 0)), 0)
|
|
job.created_at = maxi(int(snapshot.get(&"created_at", 0)), 0)
|
|
job.started_at = int(snapshot.get(&"started_at", -1))
|
|
job.completed_at = int(snapshot.get(&"completed_at", -1))
|
|
job.cancelled_at = int(snapshot.get(&"cancelled_at", -1))
|
|
job.facility_instance_id = StringName(snapshot.get(&"facility_instance_id", ""))
|
|
return job
|
|
|
|
|
|
static func _is_valid_snapshot(snapshot: Dictionary) -> bool:
|
|
if String(snapshot.get(&"job_id", "")).is_empty():
|
|
return false
|
|
if String(snapshot.get(&"definition_id", "")).is_empty():
|
|
return false
|
|
if not snapshot.get(&"origin", null) is Array:
|
|
return false
|
|
if not snapshot.get(&"reserved_cells", null) is Array:
|
|
return false
|
|
if not snapshot.get(&"construction_cost", null) is Dictionary:
|
|
return false
|
|
return true
|
|
|
|
|
|
static func _normalize_cost(source: Variant) -> Dictionary:
|
|
var normalized: Dictionary = {}
|
|
if not source is Dictionary:
|
|
return normalized
|
|
for resource_name: Variant in source:
|
|
normalized[StringName(resource_name)] = maxi(int(source[resource_name]), 0)
|
|
return normalized
|
|
|
|
|
|
static func _normalize_rotation(value: int) -> int:
|
|
var normalized := value % 360
|
|
if normalized < 0:
|
|
normalized += 360
|
|
return (normalized / 90) * 90
|
|
|
|
|
|
static func _parse_cell(source: Variant) -> Vector3i:
|
|
if not source is Array or source.size() != 3:
|
|
return Vector3i.ZERO
|
|
return Vector3i(int(source[0]), int(source[1]), int(source[2]))
|
|
|
|
|
|
static func _parse_cells(source: Variant) -> Array[Vector3i]:
|
|
var cells: Array[Vector3i] = []
|
|
if not source is Array:
|
|
return cells
|
|
for value: Variant in source:
|
|
if value is Array and value.size() == 3:
|
|
cells.append(_parse_cell(value))
|
|
return cells
|
|
|
|
|
|
static func _serialize_cells(source: Array[Vector3i]) -> Array[Array]:
|
|
var serialized: Array[Array] = []
|
|
for cell: Vector3i in source:
|
|
serialized.append([cell.x, cell.y, cell.z])
|
|
return serialized
|
|
|
|
|
|
static func _string_names_to_strings(source: Array[StringName]) -> Array[String]:
|
|
var serialized: Array[String] = []
|
|
for value: StringName in source:
|
|
serialized.append(String(value))
|
|
return serialized
|