Files
gamedev-the-steward/simulation/state/ResourceStateRecord.gd
T

290 lines
8.3 KiB
GDScript

class_name ResourceStateRecord
extends RefCounted
signal changed(state: ResourceStateRecord)
signal depleted(state: ResourceStateRecord)
const SCHEMA_VERSION := 4
const LEGACY_SCHEMA_VERSION := 1
const DISCOVERY_LEGACY_SCHEMA_VERSION := 2
const REGROW_LEGACY_SCHEMA_VERSION := 3
var data: Dictionary
func _init(record_data: Dictionary = {}) -> void:
data = record_data.duplicate(true)
static func create_from_node(node: ResourceNode) -> ResourceStateRecord:
return ResourceStateRecord.new(
{
"schema_version": SCHEMA_VERSION,
"node_id": String(node.node_id),
"action_id": String(node.action_id),
"resource_id": String(node.resource_id),
"amount_remaining": node.initial_amount,
"yield_per_action": node.yield_per_action,
"max_amount": node.max_amount if node.max_amount > 0.0 else node.initial_amount,
"regrow_rate": node.regrow_rate,
"reserved_by": -1,
"enabled": node.initial_enabled,
"can_npcs_use": node.can_npcs_use,
"can_player_use": node.can_player_use,
"safety_risk": node.safety_risk,
"comfort_distance": node.comfort_distance,
"discovery_priority": node.discovery_priority
}
)
static func from_dictionary(record_data: Dictionary) -> ResourceStateRecord:
var version := int(record_data.get("schema_version", -1))
if version == LEGACY_SCHEMA_VERSION:
record_data = _migrate_v1(record_data)
elif version == DISCOVERY_LEGACY_SCHEMA_VERSION:
record_data = _migrate_v2(record_data)
elif version == REGROW_LEGACY_SCHEMA_VERSION:
record_data = _migrate_v3(record_data)
elif version != SCHEMA_VERSION:
return null
if not record_data.has_all(
[
"node_id",
"action_id",
"resource_id",
"amount_remaining",
"yield_per_action",
"max_amount",
"regrow_rate",
"reserved_by",
"enabled",
"can_npcs_use",
"can_player_use",
"safety_risk",
"comfort_distance",
"discovery_priority"
]
):
return null
if String(record_data["node_id"]).is_empty():
return null
var action_id := StringName(record_data["action_id"])
if not action_id.is_empty() and SimulationDefinitions.get_action(action_id) == null:
return null
var normalized := record_data.duplicate(true)
normalized["schema_version"] = SCHEMA_VERSION
normalized["node_id"] = String(record_data["node_id"])
normalized["action_id"] = String(record_data["action_id"])
normalized["resource_id"] = String(record_data["resource_id"])
normalized["amount_remaining"] = float(record_data["amount_remaining"])
normalized["yield_per_action"] = float(record_data["yield_per_action"])
normalized["max_amount"] = float(record_data["max_amount"])
normalized["regrow_rate"] = float(record_data["regrow_rate"])
normalized["reserved_by"] = int(record_data["reserved_by"])
normalized["enabled"] = bool(record_data["enabled"])
normalized["can_npcs_use"] = bool(record_data["can_npcs_use"])
normalized["can_player_use"] = bool(record_data["can_player_use"])
normalized["safety_risk"] = float(record_data["safety_risk"])
normalized["comfort_distance"] = float(record_data["comfort_distance"])
normalized["discovery_priority"] = float(record_data["discovery_priority"])
if (
not is_finite(normalized["max_amount"])
or normalized["max_amount"] < 0.0
or not is_finite(normalized["regrow_rate"])
or normalized["regrow_rate"] < 0.0
):
return null
return ResourceStateRecord.new(normalized)
static func _migrate_v1(legacy_data: Dictionary) -> Dictionary:
if not legacy_data.has_all(["node_id", "amount_remaining", "reserved_by", "enabled"]):
return {}
var migrated := legacy_data.duplicate(true)
migrated["schema_version"] = SCHEMA_VERSION
migrated["action_id"] = ""
migrated["resource_id"] = ""
migrated["yield_per_action"] = 0.0
migrated["can_npcs_use"] = false
migrated["can_player_use"] = false
return _migrate_v2(migrated)
static func _migrate_v2(legacy_data: Dictionary) -> Dictionary:
var migrated := legacy_data.duplicate(true)
migrated["schema_version"] = SCHEMA_VERSION
migrated["safety_risk"] = clampf(float(migrated.get("safety_risk", 0.0)), 0.0, 1.0)
migrated["comfort_distance"] = maxf(float(migrated.get("comfort_distance", 18.0)), 0.1)
migrated["discovery_priority"] = float(migrated.get("discovery_priority", 0.0))
return _migrate_v3(migrated)
static func _migrate_v3(legacy_data: Dictionary) -> Dictionary:
var migrated := legacy_data.duplicate(true)
migrated["schema_version"] = SCHEMA_VERSION
var remaining := float(migrated.get("amount_remaining", 0.0))
migrated["max_amount"] = maxf(float(migrated.get("max_amount", remaining)), 0.0)
migrated["regrow_rate"] = maxf(float(migrated.get("regrow_rate", 0.0)), 0.0)
return migrated
func apply_definition(node: ResourceNode) -> bool:
if node == null or node.node_id != get_node_id():
return false
var has_legacy_definition := get_action_id().is_empty()
if has_legacy_definition:
data["action_id"] = String(node.action_id)
data["resource_id"] = String(node.resource_id)
data["yield_per_action"] = node.yield_per_action
data["max_amount"] = node.max_amount if node.max_amount > 0.0 else node.initial_amount
data["regrow_rate"] = node.regrow_rate
data["can_npcs_use"] = node.can_npcs_use
data["can_player_use"] = node.can_player_use
data["safety_risk"] = node.safety_risk
data["comfort_distance"] = node.comfort_distance
data["discovery_priority"] = node.discovery_priority
data["schema_version"] = SCHEMA_VERSION
return true
return (
get_action_id() == node.action_id
and get_resource_id() == node.resource_id
and is_equal_approx(get_yield_per_action(), node.yield_per_action)
and can_npc_use() == node.can_npcs_use
and can_player_use_resource() == node.can_player_use
)
func get_node_id() -> StringName:
return StringName(data["node_id"])
func get_action_id() -> StringName:
return StringName(data["action_id"])
func get_resource_id() -> StringName:
return StringName(data["resource_id"])
func get_amount_remaining() -> float:
return float(data["amount_remaining"])
func get_yield_per_action() -> float:
return float(data["yield_per_action"])
func get_max_amount() -> float:
return maxf(float(data["max_amount"]), 0.0)
func get_regrow_rate() -> float:
return maxf(float(data["regrow_rate"]), 0.0)
func regrow() -> float:
var rate := get_regrow_rate()
if rate <= 0.0 or is_equal_approx(get_amount_remaining(), get_max_amount()):
return 0.0
var previous := get_amount_remaining()
var next := minf(previous + rate, get_max_amount())
data["amount_remaining"] = next
if is_depleted():
data["reserved_by"] = -1
changed.emit(self)
return next - previous
func get_reserved_by() -> int:
return int(data["reserved_by"])
func is_enabled() -> bool:
return bool(data["enabled"])
func can_npc_use() -> bool:
return bool(data["can_npcs_use"])
func can_player_use_resource() -> bool:
return bool(data["can_player_use"])
func get_safety_risk() -> float:
return clampf(float(data["safety_risk"]), 0.0, 1.0)
func get_comfort_distance() -> float:
return maxf(float(data["comfort_distance"]), 0.1)
func get_discovery_priority() -> float:
return float(data["discovery_priority"])
func is_depleted() -> bool:
return get_amount_remaining() <= 0.0
func can_extract() -> bool:
return is_enabled() and not is_depleted()
func is_available_for(agent_id: int) -> bool:
return can_extract() and (get_reserved_by() == -1 or get_reserved_by() == agent_id)
func reserve(agent_id: int) -> bool:
if not is_available_for(agent_id):
return false
data["reserved_by"] = agent_id
changed.emit(self)
return true
func release(agent_id: int) -> void:
if get_reserved_by() != agent_id:
return
data["reserved_by"] = -1
changed.emit(self)
func extract(requested_amount: float = -1.0) -> float:
if not can_extract():
return 0.0
var amount := requested_amount
if amount < 0.0:
amount = get_yield_per_action()
var extracted := minf(maxf(amount, 0.0), get_amount_remaining())
data["amount_remaining"] = get_amount_remaining() - extracted
if is_depleted():
data["reserved_by"] = -1
changed.emit(self)
if is_depleted():
depleted.emit(self)
return extracted
func set_amount_remaining(amount: float) -> void:
var was_depleted := is_depleted()
data["amount_remaining"] = maxf(amount, 0.0)
if is_depleted():
data["reserved_by"] = -1
changed.emit(self)
if not was_depleted and is_depleted():
depleted.emit(self)
func set_enabled(value: bool) -> void:
data["enabled"] = value
changed.emit(self)
func to_dictionary() -> Dictionary:
return data.duplicate(true)