Preserve approved pixel tower prototype as development baseline
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://csul0q7j7m1a3
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
uid://cmjm3lvgmc06d
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
uid://b6sa4hwanps1l
|
||||
@@ -0,0 +1,116 @@
|
||||
class_name PlacementReport
|
||||
extends RefCounted
|
||||
|
||||
const ERROR_INVALID_DEFINITION := &"INVALID_DEFINITION"
|
||||
const ERROR_MISSING_DEPENDENCY := &"MISSING_DEPENDENCY"
|
||||
const ERROR_OUT_OF_BOUNDS := &"OUT_OF_BOUNDS"
|
||||
const ERROR_ZONE_MISMATCH := &"ZONE_MISMATCH"
|
||||
const ERROR_LAYER_MISMATCH := &"LAYER_MISMATCH"
|
||||
const ERROR_OVERLAP := &"OVERLAP"
|
||||
const ERROR_NOT_EXCAVATED := &"NOT_EXCAVATED"
|
||||
const ERROR_UNSUPPORTED := &"UNSUPPORTED"
|
||||
const ERROR_INSUFFICIENT_RESOURCES := &"INSUFFICIENT_RESOURCES"
|
||||
|
||||
var can_build := false
|
||||
var error_codes: Array[StringName] = []
|
||||
var reasons: Array[Dictionary] = []
|
||||
|
||||
var cells: Array[Vector3i] = []
|
||||
var out_of_bounds_cells: Array[Vector3i] = []
|
||||
var zone_mismatch_cells: Array[Vector3i] = []
|
||||
var layer_mismatch_cells: Array[Vector3i] = []
|
||||
var occupied_cells: Array[Vector3i] = []
|
||||
var unexcavated_cells: Array[Vector3i] = []
|
||||
var unsupported_cells: Array[Vector3i] = []
|
||||
var access_points: Array[Vector3i] = []
|
||||
var mana_connection := false
|
||||
|
||||
var total_cost: Dictionary = {}
|
||||
var missing_resources: Dictionary = {}
|
||||
var build_time := 0
|
||||
var predicted_environment_delta: Dictionary = {}
|
||||
var predicted_operating_cost: Dictionary = {}
|
||||
var predicted_heat := 0
|
||||
|
||||
|
||||
func add_error(
|
||||
code: StringName,
|
||||
reason: String,
|
||||
affected_cells: Array = [],
|
||||
details: Dictionary = {}
|
||||
) -> void:
|
||||
if not error_codes.has(code):
|
||||
error_codes.append(code)
|
||||
var normalized_cells: Array[Vector3i] = []
|
||||
for cell: Variant in affected_cells:
|
||||
if cell is Vector3i:
|
||||
normalized_cells.append(cell)
|
||||
reasons.append({
|
||||
&"code": code,
|
||||
&"message": reason,
|
||||
&"cells": normalized_cells,
|
||||
&"details": details.duplicate(true),
|
||||
})
|
||||
can_build = false
|
||||
|
||||
|
||||
func finalize() -> PlacementReport:
|
||||
can_build = error_codes.is_empty()
|
||||
return self
|
||||
|
||||
|
||||
func has_error(code: StringName) -> bool:
|
||||
return error_codes.has(code)
|
||||
|
||||
|
||||
func reason_for(code: StringName) -> Dictionary:
|
||||
for reason: Dictionary in reasons:
|
||||
if StringName(reason.get(&"code", &"")) == code:
|
||||
return reason.duplicate(true)
|
||||
return {}
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
var serialized_reasons: Array[Dictionary] = []
|
||||
for reason: Dictionary in reasons:
|
||||
var serialized := reason.duplicate(true)
|
||||
serialized[&"code"] = String(reason.get(&"code", &""))
|
||||
serialized[&"cells"] = _serialize_cells(reason.get(&"cells", []))
|
||||
serialized_reasons.append(serialized)
|
||||
return {
|
||||
&"can_build": can_build,
|
||||
&"error_codes": _string_names_to_strings(error_codes),
|
||||
&"reasons": serialized_reasons,
|
||||
&"cells": _serialize_cells(cells),
|
||||
&"out_of_bounds_cells": _serialize_cells(out_of_bounds_cells),
|
||||
&"zone_mismatch_cells": _serialize_cells(zone_mismatch_cells),
|
||||
&"layer_mismatch_cells": _serialize_cells(layer_mismatch_cells),
|
||||
&"occupied_cells": _serialize_cells(occupied_cells),
|
||||
&"unexcavated_cells": _serialize_cells(unexcavated_cells),
|
||||
&"unsupported_cells": _serialize_cells(unsupported_cells),
|
||||
&"access_points": _serialize_cells(access_points),
|
||||
&"mana_connection": mana_connection,
|
||||
&"total_cost": total_cost.duplicate(true),
|
||||
&"missing_resources": missing_resources.duplicate(true),
|
||||
&"build_time": build_time,
|
||||
&"predicted_environment_delta": predicted_environment_delta.duplicate(true),
|
||||
&"predicted_operating_cost": predicted_operating_cost.duplicate(true),
|
||||
&"predicted_heat": predicted_heat,
|
||||
}
|
||||
|
||||
|
||||
static func _serialize_cells(source: Variant) -> Array[Array]:
|
||||
var serialized: Array[Array] = []
|
||||
if not source is Array:
|
||||
return serialized
|
||||
for cell: Variant in source:
|
||||
if cell is Vector3i:
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://bqgwlfqero41j
|
||||
Reference in New Issue
Block a user