221 lines
5.4 KiB
GDScript
221 lines
5.4 KiB
GDScript
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)
|