Files
gamedev-the-steward/simulation/state/AnimalStateRecord.gd
T
2026-07-26 22:26:00 +02:00

224 lines
5.6 KiB
GDScript

class_name AnimalStateRecord
extends RefCounted
signal changed(state: AnimalStateRecord)
signal fed(state: AnimalStateRecord, actor_id: int)
const SCHEMA_VERSION := 1
const HUNGER_PER_TICK := 0.125
const FEED_THRESHOLD := 65.0
const HUNGER_RELIEF := 55.0
const NEVER_FED_TICK := -1
var data: Dictionary
func _init(record_data: Dictionary = {}) -> void:
data = record_data.duplicate(true)
static func create_from_node(node: AnimalNode) -> AnimalStateRecord:
return (
AnimalStateRecord
. new(
{
"schema_version": SCHEMA_VERSION,
"animal_id": String(node.animal_id),
"display_name": node.display_name,
"species_id": String(node.species_id),
"position":
[node.global_position.x, node.global_position.y, node.global_position.z],
"hunger": node.initial_hunger,
"last_fed_tick": NEVER_FED_TICK,
"reserved_by": -1,
"enabled": node.initial_enabled,
"can_npcs_feed": node.can_npcs_feed,
"can_player_feed": node.can_player_feed,
}
)
)
static func from_dictionary(record_data: Dictionary) -> AnimalStateRecord:
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
return null
if not (
record_data
. has_all(
[
"animal_id",
"display_name",
"species_id",
"position",
"hunger",
"last_fed_tick",
"reserved_by",
"enabled",
"can_npcs_feed",
"can_player_feed",
]
)
):
return null
var animal_id := String(record_data["animal_id"])
var display_name := String(record_data["display_name"])
var species_id := String(record_data["species_id"])
var saved_position = record_data["position"]
var hunger := float(record_data["hunger"])
var last_fed_tick := int(record_data["last_fed_tick"])
var reserved_by := int(record_data["reserved_by"])
if animal_id.is_empty() or display_name.is_empty() or species_id.is_empty():
return null
if StringName(species_id) != SimulationIds.SPECIES_GOAT:
return null
if not saved_position is Array or saved_position.size() != 3:
return null
for component in saved_position:
if not is_finite(float(component)):
return null
if not is_finite(hunger) or hunger < 0.0 or hunger > 100.0:
return null
if last_fed_tick < NEVER_FED_TICK or reserved_by < -1:
return null
if reserved_by >= 0 and hunger < FEED_THRESHOLD:
return null
var normalized := record_data.duplicate(true)
normalized["schema_version"] = SCHEMA_VERSION
normalized["animal_id"] = animal_id
normalized["display_name"] = display_name
normalized["species_id"] = species_id
normalized["position"] = [
float(saved_position[0]), float(saved_position[1]), float(saved_position[2])
]
normalized["hunger"] = hunger
normalized["last_fed_tick"] = last_fed_tick
normalized["reserved_by"] = reserved_by
normalized["enabled"] = bool(record_data["enabled"])
normalized["can_npcs_feed"] = bool(record_data["can_npcs_feed"])
normalized["can_player_feed"] = bool(record_data["can_player_feed"])
return AnimalStateRecord.new(normalized)
func apply_definition(node: AnimalNode) -> bool:
if node == null or node.animal_id != get_animal_id():
return false
return (
node.display_name == get_display_name()
and node.species_id == get_species_id()
and node.can_npcs_feed == can_npc_feed()
and node.can_player_feed == can_player_feed_animal()
)
func get_animal_id() -> StringName:
return StringName(data["animal_id"])
func get_display_name() -> String:
return String(data["display_name"])
func get_species_id() -> StringName:
return StringName(data["species_id"])
func get_position() -> Vector3:
var saved_position: Array = data["position"]
return Vector3(float(saved_position[0]), float(saved_position[1]), float(saved_position[2]))
func get_hunger() -> float:
return float(data["hunger"])
func get_last_fed_tick() -> int:
return int(data["last_fed_tick"])
func get_reserved_by() -> int:
return int(data["reserved_by"])
func is_enabled() -> bool:
return bool(data["enabled"])
func can_npc_feed() -> bool:
return bool(data["can_npcs_feed"])
func can_player_feed_animal() -> bool:
return bool(data["can_player_feed"])
func needs_feed() -> bool:
return is_enabled() and get_hunger() >= FEED_THRESHOLD
func is_available_for(agent_id: int) -> bool:
return needs_feed() and (get_reserved_by() == -1 or get_reserved_by() == agent_id)
func reserve(agent_id: int) -> bool:
if agent_id < 0 or 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 advance_tick() -> void:
if not is_enabled():
return
var next_hunger := minf(get_hunger() + HUNGER_PER_TICK, 100.0)
if is_equal_approx(next_hunger, get_hunger()):
return
data["hunger"] = next_hunger
changed.emit(self)
func feed(actor_id: int, current_tick: int) -> bool:
if not needs_feed() or current_tick < 0:
return false
data["hunger"] = maxf(get_hunger() - HUNGER_RELIEF, 0.0)
data["last_fed_tick"] = current_tick
data["reserved_by"] = -1
changed.emit(self)
fed.emit(self, actor_id)
return true
func set_position(value: Vector3) -> void:
if not value.is_finite() or value.is_equal_approx(get_position()):
return
data["position"] = [value.x, value.y, value.z]
changed.emit(self)
func set_hunger(value: float) -> void:
if not is_finite(value):
return
data["hunger"] = clampf(value, 0.0, 100.0)
if not needs_feed():
data["reserved_by"] = -1
changed.emit(self)
func set_enabled(value: bool) -> void:
data["enabled"] = value
if not value:
data["reserved_by"] = -1
changed.emit(self)
func to_dictionary() -> Dictionary:
return data.duplicate(true)