feat: give the player embodied hunger, energy, and starvation death

This commit is contained in:
2026-08-08 23:38:50 +02:00
parent 21b2eb06da
commit 9678526c5a
11 changed files with 488 additions and 65 deletions
+35 -1
View File
@@ -37,7 +37,7 @@ func _physics_process(delta: float) -> void:
var direction := (right * input.x + forward * -input.y).normalized() var direction := (right * input.x + forward * -input.y).normalized()
var target_velocity := direction * move_speed var target_velocity := direction * move_speed * _needs_speed_factor()
velocity.x = move_toward(velocity.x, target_velocity.x, acceleration * delta) velocity.x = move_toward(velocity.x, target_velocity.x, acceleration * delta)
velocity.z = move_toward(velocity.z, target_velocity.z, acceleration * delta) velocity.z = move_toward(velocity.z, target_velocity.z, acceleration * delta)
@@ -373,11 +373,45 @@ func _execute_pantry_interaction(context: PlayerInteractionResult) -> void:
var food_before := _get_pantry_food() var food_before := _get_pantry_food()
simulation_manager.eat_food(stomach_capacity_for_food) simulation_manager.eat_food(stomach_capacity_for_food)
var consumed := food_before - _get_pantry_food() var consumed := food_before - _get_pantry_food()
var player_hunger := _get_player_hunger()
if consumed > 0.0 and player_hunger > 0.0:
simulation_manager.player_eat(consumed)
interaction_feedback.emit( interaction_feedback.emit(
"Ate from the pantry", "%.0f village food consumed." % consumed, consumed > 0.0 "Ate from the pantry", "%.0f village food consumed." % consumed, consumed > 0.0
) )
func _get_player_hunger() -> float:
if simulation_manager == null or not simulation_manager.has_method("get_player_state"):
return 0.0
var state: PlayerStateRecord = simulation_manager.get_player_state()
return state.get_hunger() if state != null else 0.0
func is_starving() -> bool:
if simulation_manager == null or not simulation_manager.has_method("get_player_state"):
return false
var state: PlayerStateRecord = simulation_manager.get_player_state()
return state.is_starving() if state != null else false
func _needs_speed_factor() -> float:
if simulation_manager == null or not simulation_manager.has_method("get_player_state"):
return 1.0
var state: PlayerStateRecord = simulation_manager.get_player_state()
if state == null or state.is_dead():
return 0.0
var factor := 1.0
if state.is_starving():
factor *= 0.5
var energy := state.get_energy()
if energy < 20.0:
factor *= 0.6
elif energy < 45.0:
factor *= 0.8
return factor
func _get_animal_feed_cost() -> float: func _get_animal_feed_cost() -> float:
var definition := SimulationDefinitions.get_action(SimulationIds.ACTION_FEED_ANIMAL) var definition := SimulationDefinitions.get_action(SimulationIds.ACTION_FEED_ANIMAL)
return definition.completion_cost_amount if definition != null else 1.0 return definition.completion_cost_amount if definition != null else 1.0
+42 -57
View File
@@ -29,6 +29,8 @@ signal player_quest_completed(quest: PlayerQuestRecord)
signal player_quest_expired(quest: PlayerQuestRecord) signal player_quest_expired(quest: PlayerQuestRecord)
signal player_standing_changed(standing: PlayerStandingRecord) signal player_standing_changed(standing: PlayerStandingRecord)
signal player_tier_advanced(tier: int, tier_name: String) signal player_tier_advanced(tier: int, tier_name: String)
signal player_needs_changed(state: PlayerStateRecord)
signal player_died(state: PlayerStateRecord)
var village := SimVillage.new() var village := SimVillage.new()
@@ -52,6 +54,7 @@ var relationship_system := RelationshipSystemScript.new()
var event_knowledge_system := EventKnowledgeSystemScript.new() var event_knowledge_system := EventKnowledgeSystemScript.new()
var opportunity_system := VillageOpportunitySystem.new() var opportunity_system := VillageOpportunitySystem.new()
var player_quest_system := PlayerQuestSystem.new() var player_quest_system := PlayerQuestSystem.new()
var player_needs := PlayerNeedsSystem.new()
var _last_player_tier := PlayerStandingRecord.TIER_STRANGER var _last_player_tier := PlayerStandingRecord.TIER_STRANGER
var storage_states: Dictionary: var storage_states: Dictionary:
get: get:
@@ -176,6 +179,7 @@ func simulate_tick() -> void:
print("--- Tick ", tick_count, " ---") print("--- Tick ", tick_count, " ---")
animal_care.advance(tick_count) animal_care.advance(tick_count)
_consider_animal_care_quests() _consider_animal_care_quests()
_advance_player_needs()
var village_was_changed := false var village_was_changed := false
_population_view.rebuild(npcs) _population_view.rebuild(npcs)
for npc in npcs: for npc in npcs:
@@ -591,17 +595,13 @@ func _finish_quest_resolution(resolution: Dictionary) -> void:
var quest: PlayerQuestRecord = resolution["quest"] var quest: PlayerQuestRecord = resolution["quest"]
if resolution["completed"]: if resolution["completed"]:
player_quest_completed.emit(quest) player_quest_completed.emit(quest)
_emit_standing_change_and_tier()
else:
player_quest_expired.emit(quest)
func _emit_standing_change_and_tier() -> void:
var standing := player_quest_system.standing var standing := player_quest_system.standing
player_standing_changed.emit(standing) player_standing_changed.emit(standing)
if standing.get_tier() > _last_player_tier: if standing.get_tier() > _last_player_tier:
_last_player_tier = standing.get_tier() _last_player_tier = standing.get_tier()
player_tier_advanced.emit(standing.get_tier(), standing.get_tier_name()) player_tier_advanced.emit(standing.get_tier(), standing.get_tier_name())
else:
player_quest_expired.emit(quest)
func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bool: func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bool:
@@ -631,31 +631,8 @@ func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bo
func _can_communicate_at_shared_activity(speaker: SimNPC, listener: SimNPC) -> bool: func _can_communicate_at_shared_activity(speaker: SimNPC, listener: SimNPC) -> bool:
if speaker == null or listener == null or speaker.id == listener.id: return EventKnowledgeSystem.can_communicate_at_shared_activity(
return false speaker, listener, storage_states, KNOWLEDGE_COMMUNICATION_RADIUS
if speaker.is_dead or listener.is_dead:
return false
if (
speaker.task_state != SimNPC.TASK_STATE_WORKING
or listener.task_state != SimNPC.TASK_STATE_WORKING
):
return false
if speaker.target_id.is_empty() or speaker.target_id != listener.target_id:
return false
if storage_states.has(speaker.target_id):
return false
var speaker_action := SimulationDefinitions.get_action(speaker.current_task)
var listener_action := SimulationDefinitions.get_action(listener.current_task)
if (
speaker_action == null
or listener_action == null
or speaker_action.target_type != SimulationIds.TARGET_ACTIVITY
or listener_action.target_type != SimulationIds.TARGET_ACTIVITY
):
return false
return (
speaker.position.distance_squared_to(listener.position)
<= KNOWLEDGE_COMMUNICATION_RADIUS * KNOWLEDGE_COMMUNICATION_RADIUS
) )
@@ -713,33 +690,9 @@ func _maintain_event_knowledge(expire_by_age: bool) -> void:
func _get_lasting_knowledge_records() -> Array[KnownEventStateRecord]: func _get_lasting_knowledge_records() -> Array[KnownEventStateRecord]:
var lasting: Array[KnownEventStateRecord] = [] return event_knowledge_system.collect_lasting_records(
var seen := {} relationship_system.get_all_sorted(), opportunity_system.get_open_opportunity()
for relationship in relationship_system.get_all_sorted():
var event_id := relationship.get_last_trust_cause_event_id()
if event_id == RelationshipStateRecord.NO_CAUSE_EVENT:
continue
var record := event_knowledge_system.get_record(relationship.get_observer_id(), event_id)
if record == null:
continue
var key := "%d:%d" % [record.get_knower_id(), record.get_event_id()]
if seen.has(key):
continue
seen[key] = true
lasting.append(record)
var open_opportunity: OpportunityStateRecord = opportunity_system.get_open_opportunity()
if open_opportunity != null:
var opportunity_record := event_knowledge_system.get_record(
open_opportunity.get_interested_npc_id(), open_opportunity.get_trigger_event_id()
) )
if opportunity_record != null:
var opportunity_key := (
"%d:%d" % [opportunity_record.get_knower_id(), opportunity_record.get_event_id()]
)
if not seen.has(opportunity_key):
seen[opportunity_key] = true
lasting.append(opportunity_record)
return lasting
func _get_lasting_event_ids(npc_id: int) -> Array[int]: func _get_lasting_event_ids(npc_id: int) -> Array[int]:
@@ -788,6 +741,36 @@ func get_active_player_quest() -> PlayerQuestRecord:
return player_quest_system.get_active_quest() return player_quest_system.get_active_quest()
func _advance_player_needs() -> void:
if not player_needs.advance(village):
player_needs_changed.emit(player_needs.state)
return
player_died.emit(player_needs.state)
player_needs.apply_death_consequence(player_quest_system.standing)
_last_player_tier = player_quest_system.standing.get_tier()
player_standing_changed.emit(player_quest_system.standing)
func respawn_player() -> bool:
var was_dead := player_needs.state.is_dead()
player_needs.respawn()
if was_dead and not player_needs.state.is_dead():
player_needs_changed.emit(player_needs.state)
return true
return false
func get_player_state() -> PlayerStateRecord:
return player_needs.state
func player_eat(amount: float) -> float:
var eaten := player_needs.eat(amount)
if eaten > 0.0:
player_needs_changed.emit(player_needs.state)
return eaten
func _consider_animal_care_quests() -> void: func _consider_animal_care_quests() -> void:
var pantry: StorageStateRecord = get_pantry() var pantry: StorageStateRecord = get_pantry()
for animal_state in animal_care.get_all_states(): for animal_state in animal_care.get_all_states():
@@ -1141,6 +1124,7 @@ func create_state_record() -> SimulationStateRecord:
record.player_standing = player_quest_system.standing record.player_standing = player_quest_system.standing
for quest in player_quest_system.get_all_sorted(): for quest in player_quest_system.get_all_sorted():
record.player_quests.append(quest) record.player_quests.append(quest)
record.player_state = player_needs.state
return record return record
@@ -1182,6 +1166,7 @@ func restore_state(record: SimulationStateRecord) -> bool:
record.player_quests, int(record.simulation.get("next_quest_id", 0)), record.player_standing record.player_quests, int(record.simulation.get("next_quest_id", 0)), record.player_standing
) )
_last_player_tier = player_quest_system.standing.get_tier() _last_player_tier = player_quest_system.standing.get_tier()
player_needs.restore(record.player_state)
_maintain_event_knowledge(tick_count % get_knowledge_review_interval() == 0) _maintain_event_knowledge(tick_count % get_knowledge_review_interval() == 0)
wander_random_sources.clear() wander_random_sources.clear()
var wander_streams: Array = record.simulation["wander_random_streams"] var wander_streams: Array = record.simulation["wander_random_streams"]
@@ -204,6 +204,65 @@ func restore(records: Array[KnownEventStateRecord]) -> void:
known_events[_key(record.get_knower_id(), record.get_event_id())] = record known_events[_key(record.get_knower_id(), record.get_event_id())] = record
static func can_communicate_at_shared_activity(
speaker: SimNPC, listener: SimNPC, storage_states: Dictionary, radius: float
) -> bool:
if speaker == null or listener == null or speaker.id == listener.id:
return false
if speaker.is_dead or listener.is_dead:
return false
if (
speaker.task_state != SimNPC.TASK_STATE_WORKING
or listener.task_state != SimNPC.TASK_STATE_WORKING
):
return false
if speaker.target_id.is_empty() or speaker.target_id != listener.target_id:
return false
if storage_states.has(speaker.target_id):
return false
var speaker_action := SimulationDefinitions.get_action(speaker.current_task)
var listener_action := SimulationDefinitions.get_action(listener.current_task)
if (
speaker_action == null
or listener_action == null
or speaker_action.target_type != SimulationIds.TARGET_ACTIVITY
or listener_action.target_type != SimulationIds.TARGET_ACTIVITY
):
return false
return speaker.position.distance_squared_to(listener.position) <= radius * radius
func collect_lasting_records(
relationships: Array[RelationshipStateRecord], open_opportunity: OpportunityStateRecord
) -> Array[KnownEventStateRecord]:
var lasting: Array[KnownEventStateRecord] = []
var seen := {}
for relationship in relationships:
var event_id := relationship.get_last_trust_cause_event_id()
if event_id == RelationshipStateRecord.NO_CAUSE_EVENT:
continue
var record := get_record(relationship.get_observer_id(), event_id)
if record == null:
continue
var key := "%d:%d" % [record.get_knower_id(), record.get_event_id()]
if seen.has(key):
continue
seen[key] = true
lasting.append(record)
if open_opportunity != null:
var opportunity_record := get_record(
open_opportunity.get_interested_npc_id(), open_opportunity.get_trigger_event_id()
)
if opportunity_record != null:
var opportunity_key := (
"%d:%d" % [opportunity_record.get_knower_id(), opportunity_record.get_event_id()]
)
if not seen.has(opportunity_key):
seen[opportunity_key] = true
lasting.append(opportunity_record)
return lasting
func _remember( func _remember(
knower_id: int, knower_id: int,
event_id: int, event_id: int,
+33
View File
@@ -0,0 +1,33 @@
class_name PlayerNeedsSystem
extends RefCounted
var state := PlayerStateRecord.create()
func advance(village: SimVillage) -> bool:
if state.is_dead():
return false
var hunger_rate := 0.125 * village.food_modifier
if state.advance(hunger_rate, 0.5):
state.die()
return true
return false
func eat(amount: float) -> float:
var eaten := state.eat(amount)
return eaten
func respawn() -> void:
if state.is_dead():
state.respawn()
func apply_death_consequence(standing: PlayerStandingRecord) -> void:
var reduced := standing.get_standing() * PlayerStateRecord.DEATH_STANDING_PENALTY
standing.data["standing"] = reduced
func restore(record: PlayerStateRecord) -> void:
state = record if record != null else PlayerStateRecord.create()
@@ -0,0 +1 @@
uid://brf3a7drbfs7
+152
View File
@@ -0,0 +1,152 @@
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
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,
}
)
)
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
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,
}
)
)
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 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)
@@ -0,0 +1 @@
uid://v86fryuks1u3
+17 -2
View File
@@ -2,7 +2,7 @@ class_name SimulationStateRecord
extends RefCounted extends RefCounted
const SCHEMA_NAME := "the_steward.simulation" const SCHEMA_NAME := "the_steward.simulation"
const SCHEMA_VERSION := 12 const SCHEMA_VERSION := 13
const LEGACY_SCHEMA_VERSION := 1 const LEGACY_SCHEMA_VERSION := 1
const EVENT_LEGACY_SCHEMA_VERSION := 2 const EVENT_LEGACY_SCHEMA_VERSION := 2
const RELATIONSHIP_LEGACY_SCHEMA_VERSION := 3 const RELATIONSHIP_LEGACY_SCHEMA_VERSION := 3
@@ -14,6 +14,7 @@ const ANIMAL_LEGACY_SCHEMA_VERSION := 9
const ROUTINE_LEGACY_SCHEMA_VERSION := 10 const ROUTINE_LEGACY_SCHEMA_VERSION := 10
const PREVIOUS_SCHEMA_VERSION := 7 const PREVIOUS_SCHEMA_VERSION := 7
const PLAYER_LEGACY_SCHEMA_VERSION := 11 const PLAYER_LEGACY_SCHEMA_VERSION := 11
const PLAYER_STATE_LEGACY_SCHEMA_VERSION := 12
var simulation: Dictionary var simulation: Dictionary
var village: VillageStateRecord var village: VillageStateRecord
@@ -27,6 +28,7 @@ var event_knowledge: Array[KnownEventStateRecord] = []
var opportunities: Array[OpportunityStateRecord] = [] var opportunities: Array[OpportunityStateRecord] = []
var player_standing: PlayerStandingRecord var player_standing: PlayerStandingRecord
var player_quests: Array[PlayerQuestRecord] = [] var player_quests: Array[PlayerQuestRecord] = []
var player_state: PlayerStateRecord
func to_dictionary() -> Dictionary: func to_dictionary() -> Dictionary:
@@ -73,7 +75,8 @@ func to_dictionary() -> Dictionary:
"event_knowledge": knowledge_data, "event_knowledge": knowledge_data,
"opportunities": opportunity_data, "opportunities": opportunity_data,
"player_standing": player_standing.to_dictionary(), "player_standing": player_standing.to_dictionary(),
"player_quests": player_quest_data "player_quests": player_quest_data,
"player_state": player_state.to_dictionary()
} }
@@ -106,6 +109,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
ANIMAL_LEGACY_SCHEMA_VERSION, ANIMAL_LEGACY_SCHEMA_VERSION,
ROUTINE_LEGACY_SCHEMA_VERSION, ROUTINE_LEGACY_SCHEMA_VERSION,
PLAYER_LEGACY_SCHEMA_VERSION, PLAYER_LEGACY_SCHEMA_VERSION,
PLAYER_STATE_LEGACY_SCHEMA_VERSION,
] ]
): ):
record_data = _migrate_legacy(record_data, version) record_data = _migrate_legacy(record_data, version)
@@ -127,6 +131,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
"opportunities", "opportunities",
"player_standing", "player_standing",
"player_quests", "player_quests",
"player_state",
] ]
) )
): ):
@@ -530,6 +535,14 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
highest_quest_id = maxi(highest_quest_id, quest_id) highest_quest_id = maxi(highest_quest_id, quest_id)
record.player_quests.append(quest_record) record.player_quests.append(quest_record)
var player_state_data = record_data["player_state"]
if not player_state_data is Dictionary:
return null
var player_state_record := PlayerStateRecord.from_dictionary(player_state_data)
if player_state_record == null:
return null
record.player_state = player_state_record
return record return record
@@ -850,6 +863,8 @@ static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary
var quest_simulation_data: Dictionary = migrated.get("simulation", {}) var quest_simulation_data: Dictionary = migrated.get("simulation", {})
quest_simulation_data["next_quest_id"] = 0 quest_simulation_data["next_quest_id"] = 0
migrated["simulation"] = quest_simulation_data migrated["simulation"] = quest_simulation_data
if version <= PLAYER_STATE_LEGACY_SCHEMA_VERSION:
migrated["player_state"] = PlayerStateRecord.create().to_dictionary()
return migrated return migrated
+127
View File
@@ -0,0 +1,127 @@
extends SceneTree
var failures: Array[String] = []
var died_events := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var manager := _create_manager()
manager.player_died.connect(_on_player_died)
var state: PlayerStateRecord = manager.get_player_state()
_check(
(
state != null
and is_equal_approx(state.get_hunger(), 20.0)
and is_equal_approx(state.get_energy(), 90.0)
and not state.is_dead()
),
"A fresh player should start with embodied needs and no death state",
)
for _tick in 3:
manager.simulate_tick()
_check(
state.get_hunger() > 20.0 and state.get_energy() < 90.0,
"Simulation ticks should advance the player's hunger and drain energy",
)
state.data["hunger"] = 80.0
var hunger_before_eat: float = state.get_hunger()
var eaten: float = manager.player_eat(10.0)
_check(
(
is_equal_approx(eaten, 10.0)
and state.get_hunger() < hunger_before_eat
and state.get_energy() > 80.0
),
"Eating should reduce the player's own hunger and restore energy",
)
var initial_standing: float = manager.get_player_standing().get_standing()
manager.get_player_standing().grant_standing(20.0, 1)
var standing_after_help: float = manager.get_player_standing().get_standing()
state.data["hunger"] = PlayerStateRecord.STACK_THRESHOLD
state.data["starvation_ticks"] = (int(state.data["starvation_death_threshold"]) - 1)
manager.simulate_tick()
_check(
(
died_events == 1
and state.is_dead()
and is_equal_approx(
manager.get_player_standing().get_standing(),
standing_after_help * PlayerStateRecord.DEATH_STANDING_PENALTY
)
),
"Starvation death should emit a death event and cut standing as a consequence",
)
var saved_json: String = manager.serialize_state()
var restored := _create_manager(902)
_check(
restored.restore_state_from_json(saved_json),
"The dead player state should restore through the current schema"
)
_check(
(
restored.get_state_checksum() == manager.get_state_checksum()
and restored.get_player_state().is_dead()
and restored.get_player_state().get_deaths() == 1
),
"Restore should preserve the embodied death state and checksum",
)
_check(manager.respawn_player(), "A dead player should be able to respawn")
_check(
(
not state.is_dead()
and is_equal_approx(state.get_hunger(), PlayerStateRecord.RESET_HUNGER)
and is_equal_approx(state.get_energy(), PlayerStateRecord.RESET_ENERGY)
),
"Respawn should reset needs to the documented baseline",
)
manager.free()
restored.free()
_finish()
func _create_manager(seed_value: int = 901) -> Node:
var manager: Node = load("res://simulation/SimulationManager.gd").new()
manager.simulation_seed = seed_value
manager.debug_logs = false
var home_positions: Array[Vector3] = [
Vector3(0.0, 0.0, 0.0),
Vector3(2.0, 0.0, 0.0),
Vector3(10.0, 0.0, 0.0),
Vector3(12.0, 0.0, 0.0),
Vector3(30.0, 0.0, 30.0),
Vector3(40.0, 0.0, 40.0),
]
manager.home_positions = home_positions
root.add_child(manager)
manager.set_process(false)
return manager
func _on_player_died(_state: PlayerStateRecord) -> void:
died_events += 1
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
func _finish() -> void:
if failures.is_empty():
print("[TEST] Embodied player needs passed")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)
+1
View File
@@ -0,0 +1 @@
uid://jeax46jjayqj
+16 -1
View File
@@ -87,6 +87,7 @@ func _on_village_changed(village: SimVillage) -> void:
wood_rate = " (%.0f/d)" % rates["wood_per_day"] wood_rate = " (%.0f/d)" % rates["wood_per_day"]
var opportunity_text := _build_active_opportunity_display() var opportunity_text := _build_active_opportunity_display()
var standing_text := _build_standing_display() var standing_text := _build_standing_display()
var player_needs_text := _build_player_needs_display()
village_stats_label.text = ( village_stats_label.text = (
""" """
Village [%s] Village [%s]
@@ -100,7 +101,7 @@ func _on_village_changed(village: SimVillage) -> void:
Food Mod: %.2f Food Mod: %.2f
Safety Mod: %.2f Safety Mod: %.2f
Knowledge Mod: %.2f Knowledge Mod: %.2f
%s%s%s %s%s%s%s
""" """
% [ % [
speed_text, speed_text,
@@ -114,6 +115,7 @@ func _on_village_changed(village: SimVillage) -> void:
village.food_modifier, village.food_modifier,
village.safety_modifier, village.safety_modifier,
village.knowledge_modifier, village.knowledge_modifier,
player_needs_text,
standing_text, standing_text,
opportunity_text, opportunity_text,
event_text, event_text,
@@ -121,6 +123,19 @@ func _on_village_changed(village: SimVillage) -> void:
) )
func _build_player_needs_display() -> String:
if not simulation_manager.has_method("get_player_state"):
return ""
var state: PlayerStateRecord = simulation_manager.get_player_state()
if state == null:
return ""
var status := "dead" if state.is_dead() else ("starving" if state.is_starving() else "ok")
return (
"\n\nYou\nHunger: %.0f Energy: %.0f (%s)"
% [state.get_hunger(), state.get_energy(), status]
)
func _build_standing_display() -> String: func _build_standing_display() -> String:
if not simulation_manager.has_method("get_player_standing"): if not simulation_manager.has_method("get_player_standing"):
return "" return ""