Files
TheTree_mock/scripts/construction/construction_job.gd

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