class_name PlayerStateRecord extends RefCounted const SCHEMA_VERSION := 1 const HUNGER_DEATH_THRESHOLD := 100.0 const STACK_THRESHOLD := 90.0 const DEATH_STANDING_PENALTY := 0.5 const RESET_HUNGER := 20.0 const RESET_ENERGY := 70.0 const DEFAULT_CARRY_CAPACITY := 4.0 var data: Dictionary func _init(record_data: Dictionary = {}) -> void: data = record_data.duplicate(true) static func create() -> PlayerStateRecord: return ( PlayerStateRecord . new( { "schema_version": SCHEMA_VERSION, "hunger": 20.0, "energy": 90.0, "is_starving": false, "starvation_ticks": 0, "starvation_death_threshold": 600, "is_dead": false, "deaths": 0, "inventory": {}, "carry_capacity": DEFAULT_CARRY_CAPACITY, } ) ) static func from_dictionary(record_data: Dictionary) -> PlayerStateRecord: if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION: return null if not ( record_data . has_all( [ "hunger", "energy", "is_starving", "starvation_ticks", "starvation_death_threshold", "is_dead", "deaths", ] ) ): return null var hunger := float(record_data["hunger"]) var energy := float(record_data["energy"]) var starvation_ticks := int(record_data["starvation_ticks"]) var death_threshold := int(record_data["starvation_death_threshold"]) var deaths := int(record_data["deaths"]) if ( not is_finite(hunger) or hunger < 0.0 or hunger > HUNGER_DEATH_THRESHOLD or not is_finite(energy) or energy < 0.0 or energy > 100.0 or starvation_ticks < 0 or death_threshold < 1 or deaths < 0 ): return null var inventory := {} var carry_capacity := DEFAULT_CARRY_CAPACITY if record_data.has("inventory"): if not record_data["inventory"] is Dictionary: return null var normalized := {} for raw_item_id in record_data["inventory"]: var item_id := String(raw_item_id) var amount := float(record_data["inventory"][raw_item_id]) if ( item_id.is_empty() or normalized.has(item_id) or not is_finite(amount) or amount < 0.0 ): return null normalized[item_id] = amount inventory = normalized if record_data.has("carry_capacity"): carry_capacity = float(record_data["carry_capacity"]) if not is_finite(carry_capacity) or carry_capacity < 0.0: return null return ( PlayerStateRecord . new( { "schema_version": SCHEMA_VERSION, "hunger": hunger, "energy": energy, "is_starving": bool(record_data["is_starving"]), "starvation_ticks": starvation_ticks, "starvation_death_threshold": death_threshold, "is_dead": bool(record_data["is_dead"]), "deaths": deaths, "inventory": inventory, "carry_capacity": carry_capacity, } ) ) func get_hunger() -> float: return float(data["hunger"]) func get_energy() -> float: return float(data["energy"]) func is_starving() -> bool: return bool(data["is_starving"]) func is_dead() -> bool: return bool(data["is_dead"]) func get_starvation_ticks() -> int: return int(data["starvation_ticks"]) func get_deaths() -> int: return int(data["deaths"]) func get_inventory_amount(item_id: StringName) -> float: return float(data["inventory"].get(String(item_id), 0.0)) func get_carry_capacity() -> float: return float(data["carry_capacity"]) func get_carried_total() -> float: var total := 0.0 for amount in data["inventory"].values(): total += float(amount) return total func get_available_carry() -> float: return maxf(get_carry_capacity() - get_carried_total(), 0.0) func add_inventory(item_id: StringName, amount: float) -> float: if item_id.is_empty() or not is_finite(amount) or amount <= 0.0 or is_dead(): return 0.0 var accepted := minf(amount, get_available_carry()) if accepted <= 0.0: return 0.0 data["inventory"][String(item_id)] = get_inventory_amount(item_id) + accepted return accepted func remove_inventory(item_id: StringName, requested_amount: float) -> float: if item_id.is_empty() or not is_finite(requested_amount) or requested_amount <= 0.0: return 0.0 var removed := minf(requested_amount, get_inventory_amount(item_id)) if removed <= 0.0: return 0.0 data["inventory"][String(item_id)] = get_inventory_amount(item_id) - removed if is_equal_approx(get_inventory_amount(item_id), 0.0): data["inventory"].erase(String(item_id)) return removed func eat(amount: float) -> float: if amount <= 0.0 or is_dead(): return 0.0 var previous := get_hunger() data["hunger"] = clampf(previous - amount, 0.0, HUNGER_DEATH_THRESHOLD) var consumed := previous - get_hunger() if consumed > 0.0: data["energy"] = minf(get_energy() + consumed * 0.5, 100.0) return consumed func advance(hunger_rate: float, energy_rate: float) -> bool: if is_dead(): return false data["hunger"] = minf(get_hunger() + maxf(hunger_rate, 0.0), HUNGER_DEATH_THRESHOLD) data["energy"] = clampf(get_energy() - maxf(energy_rate, 0.0), 0.0, 100.0) data["is_starving"] = get_hunger() >= STACK_THRESHOLD if is_starving(): data["starvation_ticks"] = int(data["starvation_ticks"]) + 1 if get_starvation_ticks() >= int(data["starvation_death_threshold"]): return true else: data["starvation_ticks"] = 0 return false func die() -> void: data["is_dead"] = true data["deaths"] = int(data["deaths"]) + 1 func respawn() -> void: data["hunger"] = RESET_HUNGER data["energy"] = RESET_ENERGY data["is_starving"] = false data["starvation_ticks"] = 0 data["is_dead"] = false func to_dictionary() -> Dictionary: return data.duplicate(true)