feat: add paired goat care
This commit is contained in:
@@ -811,15 +811,20 @@ Completed:
|
|||||||
exact mid-route state through schema-v11 restore and presentation
|
exact mid-route state through schema-v11 restore and presentation
|
||||||
unload/reload. The animal feature assets now live together under
|
unload/reload. The animal feature assets now live together under
|
||||||
`world/animals/goat/`, while ignored docs no longer create import sidecars.
|
`world/animals/goat/`, while ignored docs no longer create import sidecars.
|
||||||
|
42. `Jajce Goat Pair 09`: Zora now joins Dunja as a second named, stable animal
|
||||||
|
with an authored private shelter and deterministic access to the shared
|
||||||
|
pasture. Simultaneous hunger selects the nearest available loaded goat,
|
||||||
|
preserves two exact reservations through save/restore, and feeds only the
|
||||||
|
named target. Private shelter ownership remains loaded-world context; no
|
||||||
|
herd system, animal index, or schema bump was introduced.
|
||||||
|
|
||||||
Next:
|
Next:
|
||||||
|
|
||||||
1. Add one second named goat with its own stable state, authored shelter
|
1. Playtest the named pair and record one concrete gameplay or scaling need
|
||||||
context, and deterministic use of the shared pasture. Prove nearest
|
before extending the animal model.
|
||||||
feed-target selection and independent reservations when both animals are
|
|
||||||
hungry.
|
|
||||||
2. Keep breeding, products, herd scheduling, animal spatial indexing, and
|
2. Keep breeding, products, herd scheduling, animal spatial indexing, and
|
||||||
active/abstract simulation LOD deferred until two real animals require them.
|
active/abstract simulation LOD deferred unless that evidence requires one
|
||||||
|
of them; otherwise return to the broader milestone sequence.
|
||||||
|
|
||||||
Do not start with GIS data, a full city, a large asset pack, or more NPC
|
Do not start with GIS data, a full city, a large asset pack, or more NPC
|
||||||
mechanics. The next proof is a beautiful stage for the systems that already
|
mechanics. The next proof is a beautiful stage for the systems that already
|
||||||
|
|||||||
@@ -938,16 +938,28 @@ destination, and every NPC RNG stream. Unloading and re-instantiating the goat
|
|||||||
scene retains that same travel state, arrival is exact, and hunger stops
|
scene retains that same travel state, arrival is exact, and hunger stops
|
||||||
movement before feeding.
|
movement before feeding.
|
||||||
|
|
||||||
The immediate next slice should add one second named goat with its own stable
|
The first multi-animal care slice is complete. Zora adds a second stable record,
|
||||||
record and authored shelter context while sharing the existing pasture.
|
loaded presentation, and private shelter while sharing Dunja's pasture. When
|
||||||
Selection and reservation tests must prove that simultaneous hunger still
|
both goats are hungry, loaded target resolution chooses the nearest available
|
||||||
chooses the nearest real animal and mutates only the exact target. Keep
|
interaction point, two villagers can retain independent exact claims, and
|
||||||
loaded-animal discovery linear and defer herd scheduling, breeding, products,
|
feeding Zora leaves Dunja's hunger, feed history, and reservation untouched.
|
||||||
animal indexing, and active/abstract simulation LOD until two identities
|
Schema-v11 restore preserves both claim pairings and checksum. Loaded routine
|
||||||
demonstrate a concrete need.
|
sites now distinguish private shelter ownership from shared species context
|
||||||
|
without adding that authored-world fact to saved simulation state.
|
||||||
|
|
||||||
|
The immediate next step is a playtest-and-measure gate, not a herd abstraction.
|
||||||
|
Keep loaded-animal discovery linear and use the named pair to identify one
|
||||||
|
concrete gameplay need before adding breeding, products, herd scheduling,
|
||||||
|
animal indexing, or active/abstract animal LOD. If no such need appears, resume
|
||||||
|
the broader milestone sequence instead of adding animal framework by momentum.
|
||||||
|
|
||||||
Recently completed:
|
Recently completed:
|
||||||
|
|
||||||
|
- `Jajce Goat Pair 09`: named goat Zora adds a second independent animal
|
||||||
|
record and private shelter while sharing Dunja's pasture. Stable nearest
|
||||||
|
targeting, simultaneous exact reservations, isolated feed mutation and
|
||||||
|
events, deterministic routine ownership, and active-claim save/restore prove
|
||||||
|
the multi-animal contract without a schema bump or herd system.
|
||||||
- `Jajce Dunja Habitat 08`: the first deterministic animal routine alternates
|
- `Jajce Dunja Habitat 08`: the first deterministic animal routine alternates
|
||||||
shelter and pasture through real loaded navigation, preserves exact
|
shelter and pasture through real loaded navigation, preserves exact
|
||||||
mid-route state across save/restore and presentation unload/reload, stops
|
mid-route state across save/restore and presentation unload/reload, stops
|
||||||
|
|||||||
@@ -999,10 +999,14 @@ World-schema v10 restore reconstructs her persistent state without replaying
|
|||||||
the transient response. Dunja now also alternates between a stable shelter and
|
the transient response. Dunja now also alternates between a stable shelter and
|
||||||
pasture through real loaded navigation. World-schema v11 preserves exact
|
pasture through real loaded navigation. World-schema v11 preserves exact
|
||||||
mid-route position, destination, and schedule through restore or presentation
|
mid-route position, destination, and schedule through restore or presentation
|
||||||
unload/reload without consuming decision RNG. The next bounded animal slice
|
unload/reload without consuming decision RNG. Zora now proves the bounded
|
||||||
should add one second named goat and prove independent nearest-target care
|
multi-animal contract: her independent stable record and private shelter share
|
||||||
before any herd abstraction. Do not yet generalize animal discovery or
|
Dunja's pasture, nearest loaded care targeting supports two simultaneous exact
|
||||||
introduce active/abstract simulation LOD.
|
claims, and feeding one goat cannot mutate the other. World-schema v11
|
||||||
|
preserves both claim pairings without serializing shelter ownership. Do not
|
||||||
|
generalize animal discovery or introduce herd scheduling, products, breeding,
|
||||||
|
or active/abstract animal LOD until playtest or measurement exposes a concrete
|
||||||
|
need.
|
||||||
|
|
||||||
The remaining simulation-garden target still aims for:
|
The remaining simulation-garden target still aims for:
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ both require one stored wood without duplicating that rule.
|
|||||||
farmer profession and declares an exact one-food completion cost. Selection,
|
farmer profession and declares an exact one-food completion cost. Selection,
|
||||||
animal target reservation, and completion remain separate; NPC and player
|
animal target reservation, and completion remain separate; NPC and player
|
||||||
completion both use `AnimalCareSystem`'s same pantry-to-animal transaction.
|
completion both use `AnimalCareSystem`'s same pantry-to-animal transaction.
|
||||||
`SimulationIds` also names Dunja's shelter and pasture anchors. These IDs are
|
`SimulationIds` names Dunja and Zora, their private shelter anchors, and the
|
||||||
saved routine vocabulary; their loaded positions come from
|
shared pasture anchor. These IDs are saved routine vocabulary; their loaded
|
||||||
`AnimalRoutineSite` rather than entering an action definition or resource
|
positions and private/shared ownership come from `AnimalRoutineSite` rather
|
||||||
registry.
|
than entering an action definition or resource registry.
|
||||||
|
|
||||||
## Current profession contract
|
## Current profession contract
|
||||||
|
|
||||||
|
|||||||
@@ -284,13 +284,23 @@ care operation: reserve or select the exact animal, withdraw one real food
|
|||||||
from `village_pantry`, reduce that animal's hunger, and append one exact
|
from `village_pantry`, reduce that animal's hunger, and append one exact
|
||||||
`animal_fed` fact. Failed withdrawal changes neither animal nor history.
|
`animal_fed` fact. Failed withdrawal changes neither animal nor history.
|
||||||
|
|
||||||
The first routine consumer alternates Dunja between stable-ID shelter and
|
The first routine consumers alternate Dunja and Zora between stable-ID private
|
||||||
pasture markers without drawing RNG. `ActiveWorldAdapter` supplies the loaded
|
shelters and one shared pasture without drawing RNG. `AnimalRoutineSite`
|
||||||
marker positions, the selected ID and exact position enter state, and
|
declares optional loaded-world resident ownership: an empty resident ID is
|
||||||
`AnimalNode` follows a real `NavigationServer3D` path while reporting factual
|
shared species context, while a named resident makes a shelter private.
|
||||||
position changes. A mid-route save restores the same position and target;
|
`ActiveWorldAdapter` filters those loaded markers for each stable animal; site
|
||||||
unloading the scene node pauses presentation only, and reloading it resumes
|
ownership is not persistent state and does not require a schema bump. The
|
||||||
from those facts. Hunger cancels travel before care targeting.
|
selected site ID and exact position do enter state, and each `AnimalNode`
|
||||||
|
follows a real `NavigationServer3D` path while reporting factual position
|
||||||
|
changes. A mid-route save restores the same position and target; unloading a
|
||||||
|
scene node pauses presentation only, and reloading it resumes from those facts.
|
||||||
|
Hunger cancels travel before care targeting.
|
||||||
|
|
||||||
|
Multiple animal records remain independently authoritative. A schema-v11 save
|
||||||
|
may contain concurrent feed reservations only when each reserving NPC targets
|
||||||
|
that exact animal in a valid travel or work state. Restoring Dunja and Zora
|
||||||
|
preserves both claim pairings and the checksum; completing one feed releases
|
||||||
|
and mutates only its destination record.
|
||||||
|
|
||||||
The persistent hungry/content presentation derives from authoritative hunger.
|
The persistent hungry/content presentation derives from authoritative hunger.
|
||||||
The short feed response is transient and is reset rather than serialized or
|
The short feed response is transient and is reset rather than serialized or
|
||||||
|
|||||||
@@ -217,9 +217,9 @@ func _select_action_if_idle(npc: SimNPC, previous_state: StringName) -> void:
|
|||||||
if npc.task_state not in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]:
|
if npc.task_state not in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]:
|
||||||
return
|
return
|
||||||
var helper := get_active_opportunity_helper()
|
var helper := get_active_opportunity_helper()
|
||||||
var animal_feed_name := animal_care.get_available_feed_name(npc.id)
|
var animal_feed_available := animal_care.has_available_feed_target(npc.id)
|
||||||
var selection := action_selector.select_action(
|
var selection := action_selector.select_action(
|
||||||
npc, village, clock.time_of_day(), npcs, helper, _population_view, animal_feed_name
|
npc, village, clock.time_of_day(), npcs, helper, _population_view, animal_feed_available
|
||||||
)
|
)
|
||||||
if selection == null:
|
if selection == null:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func select_action(
|
|||||||
all_npcs: Array = [],
|
all_npcs: Array = [],
|
||||||
opportunity_helper: OpportunityHelperResult = null,
|
opportunity_helper: OpportunityHelperResult = null,
|
||||||
population_view: SimulationPopulationView = null,
|
population_view: SimulationPopulationView = null,
|
||||||
animal_feed_name: String = ""
|
animal_feed_available: bool = false
|
||||||
) -> ActionSelectionResult:
|
) -> ActionSelectionResult:
|
||||||
if npc.is_dead:
|
if npc.is_dead:
|
||||||
return null
|
return null
|
||||||
@@ -127,13 +127,13 @@ func select_action(
|
|||||||
return ActionSelectionResult.new(
|
return ActionSelectionResult.new(
|
||||||
SimulationIds.ACTION_REST, -1.0, "Energy is below the rest threshold"
|
SimulationIds.ACTION_REST, -1.0, "Energy is below the rest threshold"
|
||||||
)
|
)
|
||||||
if not animal_feed_name.is_empty():
|
if animal_feed_available:
|
||||||
var feed_definition := SimulationDefinitions.get_action(SimulationIds.ACTION_FEED_ANIMAL)
|
var feed_definition := SimulationDefinitions.get_action(SimulationIds.ACTION_FEED_ANIMAL)
|
||||||
if _get_cost_rejection(feed_definition, village).is_empty():
|
if _get_cost_rejection(feed_definition, village).is_empty():
|
||||||
return ActionSelectionResult.new(
|
return ActionSelectionResult.new(
|
||||||
SimulationIds.ACTION_FEED_ANIMAL,
|
SimulationIds.ACTION_FEED_ANIMAL,
|
||||||
-1.0,
|
-1.0,
|
||||||
"%s is hungry; pantry food is available" % animal_feed_name
|
"A hungry animal is available; pantry food is available"
|
||||||
)
|
)
|
||||||
if village.food <= RELATIONSHIP_AID_PANTRY_THRESHOLD and relationship_system != null:
|
if village.food <= RELATIONSHIP_AID_PANTRY_THRESHOLD and relationship_system != null:
|
||||||
var trusted_starving_npc: SimNPC = relationship_system.get_trusted_starving_subject(
|
var trusted_starving_npc: SimNPC = relationship_system.get_trusted_starving_subject(
|
||||||
|
|||||||
@@ -119,9 +119,9 @@ func can_complete_feed(animal_id: StringName, agent_id: int) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func get_available_feed_name(npc_id: int) -> String:
|
func has_available_feed_target(npc_id: int) -> bool:
|
||||||
if active_world_adapter == null or not active_world_adapter.has_method("get_animal_candidates"):
|
if active_world_adapter == null or not active_world_adapter.has_method("get_animal_candidates"):
|
||||||
return ""
|
return false
|
||||||
var candidates: Array[Dictionary] = active_world_adapter.get_animal_candidates(
|
var candidates: Array[Dictionary] = active_world_adapter.get_animal_candidates(
|
||||||
SimulationIds.ACTION_FEED_ANIMAL
|
SimulationIds.ACTION_FEED_ANIMAL
|
||||||
)
|
)
|
||||||
@@ -132,8 +132,8 @@ func get_available_feed_name(npc_id: int) -> String:
|
|||||||
and animal_state.can_npc_feed()
|
and animal_state.can_npc_feed()
|
||||||
and animal_state.is_available_for(npc_id)
|
and animal_state.is_available_for(npc_id)
|
||||||
):
|
):
|
||||||
return animal_state.get_display_name()
|
return true
|
||||||
return ""
|
return false
|
||||||
|
|
||||||
|
|
||||||
func find_node_for_player(from_position: Vector3, max_distance: float) -> AnimalNode:
|
func find_node_for_player(from_position: Vector3, max_distance: float) -> AnimalNode:
|
||||||
@@ -250,7 +250,7 @@ func _begin_due_routine(animal_state: AnimalStateRecord) -> bool:
|
|||||||
if AnimalNode.get_by_id(animal_state.get_animal_id()) == null:
|
if AnimalNode.get_by_id(animal_state.get_animal_id()) == null:
|
||||||
return false
|
return false
|
||||||
var candidates: Array[Dictionary] = active_world_adapter.get_animal_routine_sites(
|
var candidates: Array[Dictionary] = active_world_adapter.get_animal_routine_sites(
|
||||||
animal_state.get_species_id()
|
animal_state.get_species_id(), animal_state.get_animal_id()
|
||||||
)
|
)
|
||||||
if candidates.is_empty():
|
if candidates.is_empty():
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ const RESOURCE_FOOD := &"food"
|
|||||||
const RESOURCE_WOOD := &"wood"
|
const RESOURCE_WOOD := &"wood"
|
||||||
|
|
||||||
const ANIMAL_DUNJA := &"goat_dunja"
|
const ANIMAL_DUNJA := &"goat_dunja"
|
||||||
|
const ANIMAL_ZORA := &"goat_zora"
|
||||||
const SPECIES_GOAT := &"goat"
|
const SPECIES_GOAT := &"goat"
|
||||||
const ANIMAL_SITE_DUNJA_PASTURE := &"dunja_pasture"
|
const ANIMAL_SITE_DUNJA_PASTURE := &"dunja_pasture"
|
||||||
const ANIMAL_SITE_DUNJA_SHELTER := &"dunja_shelter"
|
const ANIMAL_SITE_DUNJA_SHELTER := &"dunja_shelter"
|
||||||
|
const ANIMAL_SITE_ZORA_SHELTER := &"zora_shelter"
|
||||||
|
|
||||||
const STORAGE_VILLAGE_PANTRY := &"village_pantry"
|
const STORAGE_VILLAGE_PANTRY := &"village_pantry"
|
||||||
const STORAGE_VILLAGE_WOODPILE := &"village_woodpile"
|
const STORAGE_VILLAGE_WOODPILE := &"village_woodpile"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
action_id = &"feed_animal"
|
action_id = &"feed_animal"
|
||||||
display_name = "Feed Dunja"
|
display_name = "Feed animal"
|
||||||
default_duration = 2.0
|
default_duration = 2.0
|
||||||
preferred_profession_id = &"farmer"
|
preferred_profession_id = &"farmer"
|
||||||
target_type = &"animal"
|
target_type = &"animal"
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ func _run() -> void:
|
|||||||
var player := main_scene.get_node("Player") as Node3D
|
var player := main_scene.get_node("Player") as Node3D
|
||||||
var pantry: StorageStateRecord = manager.get_pantry()
|
var pantry: StorageStateRecord = manager.get_pantry()
|
||||||
var state: AnimalStateRecord = manager.animal_care.get_state(SimulationIds.ANIMAL_DUNJA)
|
var state: AnimalStateRecord = manager.animal_care.get_state(SimulationIds.ANIMAL_DUNJA)
|
||||||
_check(goat != null, "Jajce should load one named goat presentation")
|
_check(goat != null, "Jajce should load Dunja's named presentation")
|
||||||
_check(
|
_check(
|
||||||
AnimalNode.get_all().size() == 1 and ResourceNode.get_all().size() == 18,
|
AnimalNode.get_all().size() == 2 and ResourceNode.get_all().size() == 18,
|
||||||
"The goat should be one AnimalNode without changing the finite-resource count"
|
"Named goats should remain AnimalNodes without changing the finite-resource count"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
not goat.is_in_group("resource_nodes") and ResourceNode.get_by_id(goat.animal_id) == null,
|
not goat.is_in_group("resource_nodes") and ResourceNode.get_by_id(goat.animal_id) == null,
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var main_scene: Node = load("res://main.tscn").instantiate()
|
||||||
|
root.add_child(main_scene)
|
||||||
|
await process_frame
|
||||||
|
var manager: Node = main_scene.get_node("SimulationManager")
|
||||||
|
manager.set_process(false)
|
||||||
|
for _frame in 3:
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
var dunja_node := AnimalNode.get_by_id(SimulationIds.ANIMAL_DUNJA)
|
||||||
|
var zora_node := AnimalNode.get_by_id(SimulationIds.ANIMAL_ZORA)
|
||||||
|
var dunja_state: AnimalStateRecord = manager.animal_care.get_state(SimulationIds.ANIMAL_DUNJA)
|
||||||
|
var zora_state: AnimalStateRecord = manager.animal_care.get_state(SimulationIds.ANIMAL_ZORA)
|
||||||
|
var shared_pasture := AnimalRoutineSite.get_by_id(SimulationIds.ANIMAL_SITE_DUNJA_PASTURE)
|
||||||
|
var dunja_shelter := AnimalRoutineSite.get_by_id(SimulationIds.ANIMAL_SITE_DUNJA_SHELTER)
|
||||||
|
var zora_shelter := AnimalRoutineSite.get_by_id(SimulationIds.ANIMAL_SITE_ZORA_SHELTER)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
AnimalNode.get_all().size() == 2
|
||||||
|
and manager.animal_care.states.size() == 2
|
||||||
|
and dunja_node != null
|
||||||
|
and zora_node != null
|
||||||
|
and dunja_state != null
|
||||||
|
and zora_state != null
|
||||||
|
and dunja_node.state == dunja_state
|
||||||
|
and zora_node.state == zora_state
|
||||||
|
),
|
||||||
|
"The real main scene should bind exactly two stable named-goat records",
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
shared_pasture != null and dunja_shelter != null and zora_shelter != null,
|
||||||
|
"The pair proof needs one shared pasture and one authored shelter per goat",
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
dunja_node == null
|
||||||
|
or zora_node == null
|
||||||
|
or dunja_state == null
|
||||||
|
or zora_state == null
|
||||||
|
or shared_pasture == null
|
||||||
|
or dunja_shelter == null
|
||||||
|
or zora_shelter == null
|
||||||
|
):
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
for npc in manager.npcs:
|
||||||
|
_park_npc(npc)
|
||||||
|
|
||||||
|
var adapter := manager.active_world_adapter as ActiveWorldAdapter
|
||||||
|
var dunja_sites: Array[Dictionary] = adapter.get_animal_routine_sites(
|
||||||
|
SimulationIds.SPECIES_GOAT, SimulationIds.ANIMAL_DUNJA
|
||||||
|
)
|
||||||
|
var zora_sites: Array[Dictionary] = adapter.get_animal_routine_sites(
|
||||||
|
SimulationIds.SPECIES_GOAT, SimulationIds.ANIMAL_ZORA
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
_site_ids(dunja_sites)
|
||||||
|
== [
|
||||||
|
SimulationIds.ANIMAL_SITE_DUNJA_PASTURE,
|
||||||
|
SimulationIds.ANIMAL_SITE_DUNJA_SHELTER,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
_site_ids(zora_sites)
|
||||||
|
== [
|
||||||
|
SimulationIds.ANIMAL_SITE_DUNJA_PASTURE,
|
||||||
|
SimulationIds.ANIMAL_SITE_ZORA_SHELTER,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"Routine discovery should expose the shared pasture plus only each goat's own shelter",
|
||||||
|
)
|
||||||
|
|
||||||
|
zora_state.set_hunger(30.0)
|
||||||
|
_check(
|
||||||
|
zora_state.schedule_routine_at(manager.tick_count),
|
||||||
|
"Zora's routine should be schedulable immediately from her authored shelter",
|
||||||
|
)
|
||||||
|
manager.simulate_tick()
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
zora_state.has_active_travel_target()
|
||||||
|
and zora_state.get_routine_site_id() == SimulationIds.ANIMAL_SITE_ZORA_SHELTER
|
||||||
|
and (zora_state.get_travel_target_site_id() == SimulationIds.ANIMAL_SITE_DUNJA_PASTURE)
|
||||||
|
and (zora_state.get_travel_target_position().is_equal_approx(
|
||||||
|
shared_pasture.get_routine_position()
|
||||||
|
))
|
||||||
|
),
|
||||||
|
"Zora should deterministically choose the shared pasture from her own shelter",
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
zora_state.complete_travel(shared_pasture.get_routine_position(), manager.tick_count),
|
||||||
|
"The routine proof should be able to complete Zora's pasture leg directly",
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
zora_state.schedule_routine_at(manager.tick_count),
|
||||||
|
"Zora's return leg should be schedulable without waiting for long navigation",
|
||||||
|
)
|
||||||
|
manager.simulate_tick()
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
zora_state.has_active_travel_target()
|
||||||
|
and (zora_state.get_routine_site_id() == SimulationIds.ANIMAL_SITE_DUNJA_PASTURE)
|
||||||
|
and (zora_state.get_travel_target_site_id() == SimulationIds.ANIMAL_SITE_ZORA_SHELTER)
|
||||||
|
and (zora_state.get_travel_target_position().is_equal_approx(
|
||||||
|
zora_shelter.get_routine_position()
|
||||||
|
))
|
||||||
|
),
|
||||||
|
"Zora should return to her own shelter rather than entering Dunja's shelter",
|
||||||
|
)
|
||||||
|
zora_state.cancel_travel(manager.tick_count + AnimalCareSystem.ROUTINE_RETRY_TICKS)
|
||||||
|
|
||||||
|
dunja_state.set_hunger(72.0)
|
||||||
|
zora_state.set_hunger(72.0)
|
||||||
|
var zora_caretaker: SimNPC = manager.npcs[0]
|
||||||
|
var dunja_caretaker: SimNPC = manager.npcs[1]
|
||||||
|
_prepare_feed_task(manager, zora_caretaker)
|
||||||
|
_prepare_feed_task(manager, dunja_caretaker)
|
||||||
|
var shared_origin := zora_node.get_interaction_position()
|
||||||
|
zora_caretaker.position = shared_origin
|
||||||
|
dunja_caretaker.position = shared_origin
|
||||||
|
_check(
|
||||||
|
manager.resolve_npc_target(zora_caretaker.id, shared_origin),
|
||||||
|
"The first caretaker should resolve a real hungry animal target",
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
zora_caretaker.target_id == SimulationIds.ANIMAL_ZORA
|
||||||
|
and zora_state.get_reserved_by() == zora_caretaker.id
|
||||||
|
and dunja_state.get_reserved_by() == -1
|
||||||
|
),
|
||||||
|
"The first caretaker should claim the nearest hungry goat, Zora, by exact ID",
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
manager.resolve_npc_target(dunja_caretaker.id, shared_origin),
|
||||||
|
"The second caretaker should resolve the remaining available hungry goat",
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
dunja_caretaker.target_id == SimulationIds.ANIMAL_DUNJA
|
||||||
|
and dunja_state.get_reserved_by() == dunja_caretaker.id
|
||||||
|
and zora_state.get_reserved_by() == zora_caretaker.id
|
||||||
|
),
|
||||||
|
"Simultaneous hunger should retain two independent exact animal reservations",
|
||||||
|
)
|
||||||
|
|
||||||
|
var claimed_json: String = manager.serialize_state()
|
||||||
|
var claimed_checksum: String = manager.get_state_checksum()
|
||||||
|
var zora_caretaker_id := zora_caretaker.id
|
||||||
|
var dunja_caretaker_id := dunja_caretaker.id
|
||||||
|
_check(
|
||||||
|
manager.restore_state_from_json(claimed_json),
|
||||||
|
"A save containing both exact feed claims should pass the complete schema",
|
||||||
|
)
|
||||||
|
dunja_state = manager.animal_care.get_state(SimulationIds.ANIMAL_DUNJA)
|
||||||
|
zora_state = manager.animal_care.get_state(SimulationIds.ANIMAL_ZORA)
|
||||||
|
zora_caretaker = _find_npc(manager.npcs, zora_caretaker_id)
|
||||||
|
dunja_caretaker = _find_npc(manager.npcs, dunja_caretaker_id)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
manager.get_state_checksum() == claimed_checksum
|
||||||
|
and zora_node.state == zora_state
|
||||||
|
and dunja_node.state == dunja_state
|
||||||
|
and zora_caretaker != null
|
||||||
|
and dunja_caretaker != null
|
||||||
|
and zora_caretaker.target_id == SimulationIds.ANIMAL_ZORA
|
||||||
|
and dunja_caretaker.target_id == SimulationIds.ANIMAL_DUNJA
|
||||||
|
and zora_state.get_reserved_by() == zora_caretaker_id
|
||||||
|
and dunja_state.get_reserved_by() == dunja_caretaker_id
|
||||||
|
),
|
||||||
|
"Restore should retain both claims, bindings, target IDs, and the exact checksum",
|
||||||
|
)
|
||||||
|
if zora_caretaker == null or dunja_caretaker == null:
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
var pantry: StorageStateRecord = manager.get_pantry()
|
||||||
|
var pantry_food_before := pantry.get_amount(SimulationIds.RESOURCE_FOOD)
|
||||||
|
var zora_hunger_before := zora_state.get_hunger()
|
||||||
|
var zora_fed_tick_before := zora_state.get_last_fed_tick()
|
||||||
|
var dunja_hunger_before := dunja_state.get_hunger()
|
||||||
|
var dunja_fed_tick_before := dunja_state.get_last_fed_tick()
|
||||||
|
var dunja_reservation_before := dunja_state.get_reserved_by()
|
||||||
|
_check(
|
||||||
|
manager.feed_animal(SimulationIds.ANIMAL_ZORA, zora_caretaker_id),
|
||||||
|
"The restored exact Zora claim should complete one real feed operation",
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
is_equal_approx(
|
||||||
|
zora_state.get_hunger(), zora_hunger_before - AnimalStateRecord.HUNGER_RELIEF
|
||||||
|
)
|
||||||
|
and zora_state.get_last_fed_tick() == manager.tick_count
|
||||||
|
and zora_state.get_last_fed_tick() != zora_fed_tick_before
|
||||||
|
and zora_state.get_reserved_by() == -1
|
||||||
|
and is_equal_approx(dunja_state.get_hunger(), dunja_hunger_before)
|
||||||
|
and dunja_state.get_last_fed_tick() == dunja_fed_tick_before
|
||||||
|
and dunja_state.get_reserved_by() == dunja_reservation_before
|
||||||
|
),
|
||||||
|
"Feeding Zora should mutate only Zora's hunger, feed tick, and reservation",
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), pantry_food_before - 1.0),
|
||||||
|
"Feeding exactly one goat should consume exactly one pantry food",
|
||||||
|
)
|
||||||
|
var feed_event := _latest_feed_event(manager)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
feed_event != null
|
||||||
|
and int(feed_event.data["actor_id"]) == zora_caretaker_id
|
||||||
|
and StringName(feed_event.data["source_id"]) == SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||||
|
and StringName(feed_event.data["destination_id"]) == SimulationIds.ANIMAL_ZORA
|
||||||
|
and StringName(feed_event.data["item_id"]) == SimulationIds.RESOURCE_FOOD
|
||||||
|
and is_equal_approx(float(feed_event.data["amount"]), 1.0)
|
||||||
|
and feed_event.get_world_position().is_equal_approx(zora_state.get_position())
|
||||||
|
),
|
||||||
|
"The feed event should record the exact caretaker, pantry, Zora destination, and amount",
|
||||||
|
)
|
||||||
|
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _park_npc(npc: SimNPC) -> void:
|
||||||
|
npc.set_task(SimulationIds.ACTION_WANDER, 1000.0)
|
||||||
|
npc.start_working()
|
||||||
|
|
||||||
|
|
||||||
|
func _prepare_feed_task(manager: Node, npc: SimNPC) -> void:
|
||||||
|
manager.release_npc_reservation(npc.id)
|
||||||
|
npc.target_id = &""
|
||||||
|
npc.has_travel_target = false
|
||||||
|
npc.set_task(SimulationIds.ACTION_FEED_ANIMAL)
|
||||||
|
|
||||||
|
|
||||||
|
func _site_ids(candidates: Array[Dictionary]) -> Array[StringName]:
|
||||||
|
var ids: Array[StringName] = []
|
||||||
|
for candidate in candidates:
|
||||||
|
ids.append(StringName(candidate["site_id"]))
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
func _find_npc(npcs: Array[SimNPC], npc_id: int) -> SimNPC:
|
||||||
|
for npc in npcs:
|
||||||
|
if npc.id == npc_id:
|
||||||
|
return npc
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _latest_feed_event(manager: Node) -> EconomicEventRecord:
|
||||||
|
for index in range(manager.economic_events.size() - 1, -1, -1):
|
||||||
|
var event := manager.economic_events[index] as EconomicEventRecord
|
||||||
|
if StringName(event.data["event_type"]) == SimulationIds.EVENT_ANIMAL_FED:
|
||||||
|
return event
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures.is_empty():
|
||||||
|
print("[TEST] Two-goat vertical slice passed")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
for failure in failures:
|
||||||
|
push_error("[TEST] " + failure)
|
||||||
|
quit(1)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://drh502csu7pj6
|
||||||
@@ -397,29 +397,39 @@ func _run() -> void:
|
|||||||
_check(outskirt_context_count >= 4, "Jajce discovery should include outskirts/river contexts")
|
_check(outskirt_context_count >= 4, "Jajce discovery should include outskirts/river contexts")
|
||||||
_check(farming_context_count >= 1, "Jajce food discovery should include farming contexts")
|
_check(farming_context_count >= 1, "Jajce food discovery should include farming contexts")
|
||||||
var animal_root := world.get_node("WorldObjects/Animals")
|
var animal_root := world.get_node("WorldObjects/Animals")
|
||||||
var goat := animal_root.get_node("Dunja") as AnimalNode
|
var dunja := animal_root.get_node("Dunja") as AnimalNode
|
||||||
|
var zora := animal_root.get_node("Zora") as AnimalNode
|
||||||
_check(
|
_check(
|
||||||
animal_root.get_child_count() == 1 and goat != null,
|
animal_root.get_child_count() == 2 and dunja != null and zora != null,
|
||||||
"Jajce should contain one bounded loaded-animal presentation"
|
"Jajce should contain the bounded pair of loaded-animal presentations"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
(
|
(
|
||||||
goat.animal_id == SimulationIds.ANIMAL_DUNJA
|
dunja.animal_id == SimulationIds.ANIMAL_DUNJA
|
||||||
and goat.species_id == SimulationIds.SPECIES_GOAT
|
and dunja.species_id == SimulationIds.SPECIES_GOAT
|
||||||
and goat.display_name == "Dunja"
|
and dunja.display_name == "Dunja"
|
||||||
|
and zora.animal_id == SimulationIds.ANIMAL_ZORA
|
||||||
|
and zora.species_id == SimulationIds.SPECIES_GOAT
|
||||||
|
and zora.display_name == "Zora"
|
||||||
),
|
),
|
||||||
"The authored goat should preserve its stable identity and readable name"
|
"The authored goats should preserve unique stable identities and readable names"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
not goat.is_in_group("resource_nodes") and ResourceNode.get_by_id(goat.animal_id) == null,
|
(
|
||||||
"The identity-backed goat should remain separate from finite resources"
|
not dunja.is_in_group("resource_nodes")
|
||||||
|
and not zora.is_in_group("resource_nodes")
|
||||||
|
and ResourceNode.get_by_id(dunja.animal_id) == null
|
||||||
|
and ResourceNode.get_by_id(zora.animal_id) == null
|
||||||
|
),
|
||||||
|
"The identity-backed goats should remain separate from finite resources"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
goat.is_in_group("grass_interactors"),
|
dunja.is_in_group("grass_interactors") and zora.is_in_group("grass_interactors"),
|
||||||
"Dunja should bend camera-local grass through the shared presentation group"
|
"Both goats should bend camera-local grass through the shared presentation group"
|
||||||
)
|
)
|
||||||
var habitat_root := world.get_node("WorldObjects/AnimalHabitats")
|
var habitat_root := world.get_node("WorldObjects/AnimalHabitats")
|
||||||
var habitat := habitat_root.get_node("DunjaHabitat")
|
var dunja_habitat := habitat_root.get_node("DunjaHabitat")
|
||||||
|
var zora_shelter_context := habitat_root.get_node("ZoraShelter")
|
||||||
var routine_sites: Array[AnimalRoutineSite] = []
|
var routine_sites: Array[AnimalRoutineSite] = []
|
||||||
for site in AnimalRoutineSite.get_all():
|
for site in AnimalRoutineSite.get_all():
|
||||||
routine_sites.append(site)
|
routine_sites.append(site)
|
||||||
@@ -428,16 +438,25 @@ func _run() -> void:
|
|||||||
return String(first.site_id) < String(second.site_id)
|
return String(first.site_id) < String(second.site_id)
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
habitat_root.get_child_count() == 1 and habitat != null and routine_sites.size() == 2,
|
(
|
||||||
"Jajce should keep Dunja's two routine anchors inside one self-contained habitat scene"
|
habitat_root.get_child_count() == 2
|
||||||
|
and dunja_habitat != null
|
||||||
|
and zora_shelter_context != null
|
||||||
|
and routine_sites.size() == 3
|
||||||
|
),
|
||||||
|
"Jajce should author one shared pasture and a private shelter context for each goat"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
(
|
(
|
||||||
routine_sites.size() == 2
|
routine_sites.size() == 3
|
||||||
and routine_sites[0].site_id == SimulationIds.ANIMAL_SITE_DUNJA_PASTURE
|
and routine_sites[0].site_id == SimulationIds.ANIMAL_SITE_DUNJA_PASTURE
|
||||||
and routine_sites[1].site_id == SimulationIds.ANIMAL_SITE_DUNJA_SHELTER
|
and routine_sites[1].site_id == SimulationIds.ANIMAL_SITE_DUNJA_SHELTER
|
||||||
|
and routine_sites[2].site_id == SimulationIds.ANIMAL_SITE_ZORA_SHELTER
|
||||||
|
and routine_sites[0].resident_animal_id.is_empty()
|
||||||
|
and routine_sites[1].resident_animal_id == SimulationIds.ANIMAL_DUNJA
|
||||||
|
and routine_sites[2].resident_animal_id == SimulationIds.ANIMAL_ZORA
|
||||||
),
|
),
|
||||||
"Dunja's pasture and shelter should expose deterministic stable IDs"
|
"Routine sites should expose stable IDs with shared pasture and exact shelter ownership"
|
||||||
)
|
)
|
||||||
_check(world.has_node("WorldObjects/StorageSites"), "JajceWorld should expose StorageSites")
|
_check(world.has_node("WorldObjects/StorageSites"), "JajceWorld should expose StorageSites")
|
||||||
var pantry := world.get_node("WorldObjects/StorageSites/VillagePantry") as StorageNode
|
var pantry := world.get_node("WorldObjects/StorageSites/VillagePantry") as StorageNode
|
||||||
@@ -491,7 +510,8 @@ func _run() -> void:
|
|||||||
var destinations: Array[Vector3] = []
|
var destinations: Array[Vector3] = []
|
||||||
for resource in resources:
|
for resource in resources:
|
||||||
destinations.append((resource as ResourceNode).interaction_point.global_position)
|
destinations.append((resource as ResourceNode).interaction_point.global_position)
|
||||||
destinations.append(goat.get_interaction_position())
|
destinations.append(dunja.get_interaction_position())
|
||||||
|
destinations.append(zora.get_interaction_position())
|
||||||
for site in routine_sites:
|
for site in routine_sites:
|
||||||
destinations.append(site.get_routine_position())
|
destinations.append(site.get_routine_position())
|
||||||
destinations.append(pantry.get_interaction_position())
|
destinations.append(pantry.get_interaction_position())
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ func get_animal_candidates(action_id: StringName) -> Array[Dictionary]:
|
|||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
func get_animal_routine_sites(species_id: StringName) -> Array[Dictionary]:
|
func get_animal_routine_sites(species_id: StringName, animal_id: StringName) -> Array[Dictionary]:
|
||||||
var candidates: Array[Dictionary] = []
|
var candidates: Array[Dictionary] = []
|
||||||
for site in AnimalRoutineSite.get_all():
|
for site in AnimalRoutineSite.get_all():
|
||||||
if not site.supports_species(species_id):
|
if not site.supports_animal(animal_id, species_id):
|
||||||
continue
|
continue
|
||||||
(
|
(
|
||||||
candidates
|
candidates
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ static var _all: Array[AnimalRoutineSite] = []
|
|||||||
@export var site_id: StringName
|
@export var site_id: StringName
|
||||||
@export var display_name := "Animal site"
|
@export var display_name := "Animal site"
|
||||||
@export var species_id: StringName = SimulationIds.SPECIES_GOAT
|
@export var species_id: StringName = SimulationIds.SPECIES_GOAT
|
||||||
|
@export var resident_animal_id: StringName
|
||||||
@export var debug_label_enabled := false
|
@export var debug_label_enabled := false
|
||||||
|
|
||||||
|
|
||||||
@@ -48,6 +49,13 @@ func supports_species(candidate_species_id: StringName) -> bool:
|
|||||||
return candidate_species_id == species_id
|
return candidate_species_id == species_id
|
||||||
|
|
||||||
|
|
||||||
|
func supports_animal(candidate_animal_id: StringName, candidate_species_id: StringName) -> bool:
|
||||||
|
return (
|
||||||
|
supports_species(candidate_species_id)
|
||||||
|
and (resident_animal_id.is_empty() or resident_animal_id == candidate_animal_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func get_routine_position() -> Vector3:
|
func get_routine_position() -> Vector3:
|
||||||
return global_position
|
return global_position
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ script = ExtResource("1_site")
|
|||||||
site_id = &"dunja_shelter"
|
site_id = &"dunja_shelter"
|
||||||
display_name = "Warm shelter"
|
display_name = "Warm shelter"
|
||||||
species_id = &"goat"
|
species_id = &"goat"
|
||||||
|
resident_animal_id = &"goat_dunja"
|
||||||
|
|
||||||
[node name="DebugLabel" type="Label3D" parent="ShelterSite"]
|
[node name="DebugLabel" type="Label3D" parent="ShelterSite"]
|
||||||
position = Vector3(0, 2.45, 0)
|
position = Vector3(0, 2.45, 0)
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
[gd_scene load_steps=13 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://world/animals/animal_routine_site.gd" id="1_site"]
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_timber"]
|
||||||
|
albedo_color = Color(0.39, 0.22, 0.11, 1)
|
||||||
|
roughness = 0.96
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_roof"]
|
||||||
|
albedo_color = Color(0.48, 0.3, 0.2, 1)
|
||||||
|
roughness = 0.98
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_bowl"]
|
||||||
|
albedo_color = Color(0.58, 0.31, 0.19, 1)
|
||||||
|
roughness = 0.82
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_straw"]
|
||||||
|
albedo_color = Color(0.72, 0.54, 0.27, 1)
|
||||||
|
roughness = 0.98
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_glow"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(1, 0.65, 0.28, 0.9)
|
||||||
|
emission_enabled = true
|
||||||
|
emission = Color(1, 0.38, 0.1, 1)
|
||||||
|
emission_energy_multiplier = 0.42
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_post"]
|
||||||
|
material = SubResource("Material_timber")
|
||||||
|
size = Vector3(0.18, 2.05, 0.18)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_beam"]
|
||||||
|
material = SubResource("Material_timber")
|
||||||
|
size = Vector3(2.75, 0.16, 0.18)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_roof"]
|
||||||
|
material = SubResource("Material_roof")
|
||||||
|
size = Vector3(1.65, 0.13, 2.55)
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id="Mesh_bowl"]
|
||||||
|
material = SubResource("Material_bowl")
|
||||||
|
top_radius = 0.34
|
||||||
|
bottom_radius = 0.27
|
||||||
|
height = 0.13
|
||||||
|
radial_segments = 12
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_straw"]
|
||||||
|
material = SubResource("Material_straw")
|
||||||
|
size = Vector3(1.65, 0.09, 1.15)
|
||||||
|
|
||||||
|
[sub_resource type="SphereMesh" id="Mesh_glow"]
|
||||||
|
material = SubResource("Material_glow")
|
||||||
|
radius = 0.08
|
||||||
|
height = 0.13
|
||||||
|
radial_segments = 8
|
||||||
|
rings = 4
|
||||||
|
|
||||||
|
[node name="ZoraShelter" type="Node3D"]
|
||||||
|
|
||||||
|
[node name="Shelter" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="PostFrontLeft" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(-1.18, 1.02, 2.2)
|
||||||
|
mesh = SubResource("Mesh_post")
|
||||||
|
|
||||||
|
[node name="PostFrontRight" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(1.18, 1.02, 2.2)
|
||||||
|
mesh = SubResource("Mesh_post")
|
||||||
|
|
||||||
|
[node name="PostBackLeft" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(-1.18, 1.02, 0.25)
|
||||||
|
mesh = SubResource("Mesh_post")
|
||||||
|
|
||||||
|
[node name="PostBackRight" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(1.18, 1.02, 0.25)
|
||||||
|
mesh = SubResource("Mesh_post")
|
||||||
|
|
||||||
|
[node name="FrontBeam" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(0, 2.02, 2.2)
|
||||||
|
mesh = SubResource("Mesh_beam")
|
||||||
|
|
||||||
|
[node name="BackBeam" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(0, 2.02, 0.25)
|
||||||
|
mesh = SubResource("Mesh_beam")
|
||||||
|
|
||||||
|
[node name="RoofLeft" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(-0.72, 2.31, 1.22)
|
||||||
|
rotation_degrees = Vector3(0, 0, -19)
|
||||||
|
mesh = SubResource("Mesh_roof")
|
||||||
|
|
||||||
|
[node name="RoofRight" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(0.72, 2.31, 1.22)
|
||||||
|
rotation_degrees = Vector3(0, 0, 19)
|
||||||
|
mesh = SubResource("Mesh_roof")
|
||||||
|
|
||||||
|
[node name="StrawBed" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(0, 0.05, 1.15)
|
||||||
|
mesh = SubResource("Mesh_straw")
|
||||||
|
|
||||||
|
[node name="FeedBowl" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(-0.72, 0.08, 2.52)
|
||||||
|
mesh = SubResource("Mesh_bowl")
|
||||||
|
|
||||||
|
[node name="Lantern" type="MeshInstance3D" parent="Shelter"]
|
||||||
|
position = Vector3(0, 1.87, 2.1)
|
||||||
|
mesh = SubResource("Mesh_glow")
|
||||||
|
|
||||||
|
[node name="ShelterSite" type="Marker3D" parent="."]
|
||||||
|
position = Vector3(0, 0, 1.5)
|
||||||
|
script = ExtResource("1_site")
|
||||||
|
site_id = &"zora_shelter"
|
||||||
|
display_name = "Zora's shelter"
|
||||||
|
species_id = &"goat"
|
||||||
|
resident_animal_id = &"goat_zora"
|
||||||
|
|
||||||
|
[node name="DebugLabel" type="Label3D" parent="ShelterSite"]
|
||||||
|
position = Vector3(0, 2.45, 0)
|
||||||
|
billboard = 1
|
||||||
|
no_depth_test = true
|
||||||
|
font_size = 20
|
||||||
|
outline_size = 5
|
||||||
|
modulate = Color(1, 0.72, 0.5, 1)
|
||||||
|
pixel_size = 0.006
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=61 format=3]
|
[gd_scene load_steps=62 format=3]
|
||||||
|
|
||||||
[ext_resource type="Terrain3DAssets" path="res://terrain/jajce/assets.tres" id="1_assets"]
|
[ext_resource type="Terrain3DAssets" path="res://terrain/jajce/assets.tres" id="1_assets"]
|
||||||
[ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"]
|
[ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"]
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
[ext_resource type="PackedScene" path="res://world/jajce/ForestEdgeResourceCluster.tscn" id="27_forest_cluster"]
|
[ext_resource type="PackedScene" path="res://world/jajce/ForestEdgeResourceCluster.tscn" id="27_forest_cluster"]
|
||||||
[ext_resource type="PackedScene" path="res://world/animals/goat/cozy_goat.tscn" id="28_goat"]
|
[ext_resource type="PackedScene" path="res://world/animals/goat/cozy_goat.tscn" id="28_goat"]
|
||||||
[ext_resource type="PackedScene" path="res://world/animals/goat/dunja_habitat.tscn" id="29_habitat"]
|
[ext_resource type="PackedScene" path="res://world/animals/goat/dunja_habitat.tscn" id="29_habitat"]
|
||||||
|
[ext_resource type="PackedScene" path="res://world/animals/goat/zora_shelter.tscn" id="30_zora_shelter"]
|
||||||
|
|
||||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"]
|
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"]
|
||||||
_shader_parameters = {
|
_shader_parameters = {
|
||||||
@@ -466,11 +467,24 @@ phase = 4.4
|
|||||||
position = Vector3(0, 0, -15.5)
|
position = Vector3(0, 0, -15.5)
|
||||||
rotation_degrees = Vector3(0, 150, 0)
|
rotation_degrees = Vector3(0, 150, 0)
|
||||||
|
|
||||||
|
[node name="Zora" parent="WorldObjects/Animals" instance=ExtResource("28_goat")]
|
||||||
|
position = Vector3(-8, 0, -15.5)
|
||||||
|
rotation_degrees = Vector3(0, 210, 0)
|
||||||
|
scale = Vector3(0.92, 0.92, 0.92)
|
||||||
|
animal_id = &"goat_zora"
|
||||||
|
display_name = "Zora"
|
||||||
|
initial_hunger = 42.0
|
||||||
|
initial_routine_site_id = &"zora_shelter"
|
||||||
|
initial_next_routine_tick = 12
|
||||||
|
|
||||||
[node name="AnimalHabitats" type="Node3D" parent="WorldObjects"]
|
[node name="AnimalHabitats" type="Node3D" parent="WorldObjects"]
|
||||||
|
|
||||||
[node name="DunjaHabitat" parent="WorldObjects/AnimalHabitats" instance=ExtResource("29_habitat")]
|
[node name="DunjaHabitat" parent="WorldObjects/AnimalHabitats" instance=ExtResource("29_habitat")]
|
||||||
position = Vector3(0, 0, -17)
|
position = Vector3(0, 0, -17)
|
||||||
|
|
||||||
|
[node name="ZoraShelter" parent="WorldObjects/AnimalHabitats" instance=ExtResource("30_zora_shelter")]
|
||||||
|
position = Vector3(-8, 0, -17)
|
||||||
|
|
||||||
[node name="ResourceNodes" type="Node3D" parent="WorldObjects"]
|
[node name="ResourceNodes" type="Node3D" parent="WorldObjects"]
|
||||||
|
|
||||||
[node name="BerryBush_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
[node name="BerryBush_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
|||||||
Reference in New Issue
Block a user