feat: replace activity markers with typed sites
This commit is contained in:
@@ -39,12 +39,13 @@ SimulationManager tick
|
||||
- consumes active-world facts through ActiveWorldAdapter;
|
||||
- checks simulation-owned ResourceStateRecord availability;
|
||||
- reserves and returns stable resource target IDs;
|
||||
- returns activity or wander positions without owning presentation.
|
||||
- returns typed activity-site, storage, or wander positions without owning
|
||||
presentation.
|
||||
|
||||
### ActiveWorldAdapter
|
||||
|
||||
- exposes loaded resource interaction positions;
|
||||
- maps current temporary activity markers to action IDs;
|
||||
- exposes loaded storage and activity-site interaction positions;
|
||||
- contains active-world query facts, not persistent mutable authority;
|
||||
- does not choose actions or reserve resources.
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ Do not redo these tasks:
|
||||
- hunger, energy, starvation, death, task scoring, and task duration;
|
||||
- task-change and village-change signals;
|
||||
- visual NPC navigation and arrival reporting;
|
||||
- finite berry/tree ResourceNodes plus food, guard, study, and rest activity
|
||||
markers;
|
||||
- finite berry/tree ResourceNodes plus typed food, guard, study, and rest
|
||||
world sites;
|
||||
- aggregate village UI;
|
||||
- initial tree assets;
|
||||
- a baked navigation region for the current flat test area.
|
||||
@@ -56,21 +56,20 @@ plugin content. They are references, not the game.
|
||||
|
||||
```text
|
||||
Main
|
||||
├── current flat World and NavigationRegion3D
|
||||
├── JajceWorld
|
||||
├── Player
|
||||
├── CameraRig
|
||||
├── SimulationManager
|
||||
├── WorldViewManager
|
||||
├── ActiveNPCs
|
||||
├── ActivityMarkers
|
||||
├── typed world sites inside JajceWorld/WorldObjects
|
||||
├── WorldEnvironment
|
||||
└── UI
|
||||
```
|
||||
|
||||
The player and `WorldViewManager` still reference non-resource activity sites
|
||||
through exported `NodePath` values. `NpcVisual` relies on the navigation map to
|
||||
reach legacy guard/study/rest markers, `ResourceNode` interaction points, and
|
||||
the typed village pantry `StorageNode`.
|
||||
The player still references interactable world sites through exported `NodePath`
|
||||
values. `NpcVisual` relies on the navigation map to reach typed `ActivitySite`,
|
||||
`StorageNode`, and `ResourceNode` interaction points.
|
||||
|
||||
Replacing the ground without respecting those references would break behavior
|
||||
that already works.
|
||||
@@ -95,12 +94,12 @@ Main
|
||||
│ │ ├── ResourceNodes
|
||||
│ │ ├── BerryBush instances
|
||||
│ │ └── Tree instances
|
||||
│ │ └── StorageSites
|
||||
│ │ └── VillagePantry
|
||||
│ ├── LegacyActivityMarkers temporary non-resource activities
|
||||
│ │ ├── GuardZone
|
||||
│ │ ├── StudyZone
|
||||
│ │ └── RestZone
|
||||
│ │ ├── StorageSites
|
||||
│ │ │ └── VillagePantry
|
||||
│ │ └── ActivitySites
|
||||
│ │ ├── GuardPost
|
||||
│ │ ├── StudyDesk
|
||||
│ │ └── RestBench
|
||||
│ ├── DirectionalLight3D
|
||||
│ └── WorldEnvironment
|
||||
├── Player
|
||||
@@ -111,11 +110,10 @@ Main
|
||||
└── UI
|
||||
```
|
||||
|
||||
`JajceWorld` should contain geography, world-object presentations, and temporary
|
||||
legacy interaction locations. The pantry has since moved out of
|
||||
`LegacyActivityMarkers` into `WorldObjects/StorageSites/VillagePantry`; guard,
|
||||
study, and rest remain temporary markers. `JajceWorld` should not own
|
||||
`SimulationManager`, simulated NPC data, player state, or UI.
|
||||
`JajceWorld` should contain geography and world-object presentations. The former
|
||||
legacy interaction locations now live as typed storage and activity sites under
|
||||
`WorldObjects`; `JajceWorld` should not own `SimulationManager`, simulated NPC
|
||||
data, player state, or UI.
|
||||
|
||||
Create a small look-development wrapper that instances the same world:
|
||||
|
||||
@@ -295,7 +293,7 @@ Create `world/jajce/JajceWorld.tscn` with:
|
||||
- placeholder `VillageRoot`;
|
||||
- placeholder `FoliageRoot`;
|
||||
- a navigation region;
|
||||
- resource-node containers plus temporary activity markers;
|
||||
- resource-node, storage-site, and activity-site containers;
|
||||
- lighting and environment.
|
||||
|
||||
Create `JajceLookdev.tscn` that instances `JajceWorld` and adds a temporary
|
||||
@@ -422,8 +420,8 @@ Once the world composition is stable:
|
||||
2. Disable or remove the old flat world only after the new instance is present.
|
||||
3. Place or move bushes and trees under
|
||||
`JajceWorld/WorldObjects/ResourceNodes` while preserving stable IDs.
|
||||
4. Reconnect only remaining non-resource activity markers under
|
||||
`JajceWorld/LegacyActivityMarkers`.
|
||||
4. Reconnect remaining non-resource activities as typed sites under
|
||||
`JajceWorld/WorldObjects`.
|
||||
5. Bake navigation for the new walkable area.
|
||||
6. Test each existing task from multiple spawn positions.
|
||||
7. Verify task arrival still transitions from traveling to working.
|
||||
@@ -436,8 +434,8 @@ Once the world composition is stable:
|
||||
Do not move simulation ownership into `JajceWorld` to make wiring convenient.
|
||||
|
||||
**Status: runtime integration complete.** `main.tscn` now instances the reusable
|
||||
world and points player/simulation adapters at its stable-ID resources and
|
||||
remaining activity markers. The duplicate flat world objects were removed.
|
||||
world and points player/simulation adapters at its stable-ID resources, storage
|
||||
site, and activity sites. The duplicate flat world objects were removed.
|
||||
The runtime regression checks Terrain3D presence, 24 navigation routes, core
|
||||
task execution, starvation/death, player extraction parity, reservations, and
|
||||
NPC visual lifecycle. Terrain sculpting and presentation polish remain
|
||||
@@ -445,7 +443,7 @@ separate work, not blockers for this wiring milestone.
|
||||
|
||||
### Exit condition
|
||||
|
||||
The current three NPCs and player can perform all existing prototype behaviors
|
||||
The current six NPCs and player can perform all existing prototype behaviors
|
||||
inside the new terrain without regression.
|
||||
|
||||
### Phase 5 — Make the simulation readable on video
|
||||
|
||||
@@ -652,9 +652,11 @@ Completed after the architecture gate:
|
||||
|
||||
The practical next sequence is:
|
||||
|
||||
1. Replace the remaining generic activity markers with typed world sites,
|
||||
beginning with the pantry/storage presentation.
|
||||
2. Add a cinematic/debug toggle and repeatable simulation-garden demo reset.
|
||||
1. Add a cinematic/debug toggle and repeatable simulation-garden demo reset.
|
||||
2. Expand resource discovery with many finite `ResourceNode` instances placed
|
||||
in meaningful foliage, animal-camp, berry, and village-stockpile contexts.
|
||||
Score them by reachability, distance, safety, profession, and NPC comfort
|
||||
range rather than reintroducing abstract resource zones.
|
||||
3. Expand persistence only when schedules, player state, or a real save menu
|
||||
creates a concrete requirement.
|
||||
|
||||
|
||||
+30
-34
@@ -180,9 +180,9 @@ The current prototype implements a very early version of observe, autonomous
|
||||
task choice, physical travel, work completion, resource change, and direct
|
||||
player assistance. NPC `gather_food` and `gather_wood` tasks now use finite
|
||||
world `ResourceNode` instances (berry bushes and trees) rather than abstract
|
||||
zone markers. The obsolete farm and forest markers have been removed. Separate
|
||||
temporary activity markers remain for rest, study, and patrol; eating and food
|
||||
transfer now target the typed village pantry. The migration is documented in
|
||||
zone markers. The obsolete farm, forest, and activity markers have been
|
||||
removed. Eating and food transfer target the typed village pantry, while rest,
|
||||
study, and patrol target typed activity sites. The migration is documented in
|
||||
[the ResourceNode plan](RESOURCE_NODE_MIGRATION.md).
|
||||
|
||||
## Current technology
|
||||
@@ -207,7 +207,7 @@ plugin content, not game architecture.
|
||||
|
||||
### World and player
|
||||
|
||||
- `main.tscn` contains a flat approximately 30×30 prototype ground.
|
||||
- `main.tscn` instances the reusable Jajce Terrain3D world.
|
||||
- The player is a `CharacterBody3D` represented by placeholder primitive
|
||||
geometry.
|
||||
- WASD movement is camera-relative.
|
||||
@@ -215,17 +215,19 @@ plugin content, not game architecture.
|
||||
follow/focus behavior.
|
||||
- Pressing `E` near a berry bush or tree extracts its configured yield into the
|
||||
village through the same `ResourceNode` contract used by NPCs.
|
||||
- Guard, study, rest, and food interactions still use temporary activity
|
||||
markers.
|
||||
- Guard, study, rest, and food interactions now use typed world sites.
|
||||
- `Escape` releases captured mouse input.
|
||||
|
||||
### Village simulation
|
||||
|
||||
The simulation currently creates three NPCs:
|
||||
The simulation currently creates six NPCs:
|
||||
|
||||
- Amina
|
||||
- Tarik
|
||||
- Jasmin
|
||||
- Elma
|
||||
- Mirza
|
||||
- Lejla
|
||||
|
||||
Each receives a random profession from:
|
||||
|
||||
@@ -283,39 +285,34 @@ Needs override ordinary work. Otherwise a utility-like score combines:
|
||||
- profession preference;
|
||||
- a small random contribution.
|
||||
|
||||
The visual NPC walks toward the task's marker. Arrival tells the simulation to
|
||||
start work. Work progresses on simulation ticks. The village receives the
|
||||
result only when the task duration completes.
|
||||
The visual NPC walks toward the task's typed target position. Arrival tells the
|
||||
simulation to start work. Work progresses on simulation ticks. The village
|
||||
receives the result only when the task duration completes.
|
||||
|
||||
Starvation can reduce productivity and eventually kill an NPC. Dead visuals
|
||||
stop moving, change appearance, and no longer collide.
|
||||
|
||||
### Current activity markers
|
||||
### Current world targets
|
||||
|
||||
The main scene contains placeholder activity markers for guard duty, study,
|
||||
and rest. The former farm and forest zones and their decorative geometry have
|
||||
been deleted, and food storage now uses a typed pantry site.
|
||||
The former farm, forest, food, guard, study, and rest markers have been deleted.
|
||||
The runtime now uses semantic world targets:
|
||||
|
||||
**Migration status:** NPC `gather_food` and `gather_wood` target `ResourceNode`
|
||||
instances (berry bushes and trees), with no marker fallback. The remaining
|
||||
markers are direct transitional targets for non-resource NPC tasks, while
|
||||
storage actions now route to a typed pantry site:
|
||||
instances (berry bushes and trees), with no marker fallback. Non-resource
|
||||
actions route through typed sites:
|
||||
|
||||
- patrol → guard marker;
|
||||
- study → study marker;
|
||||
- rest → rest marker;
|
||||
- patrol → `ActivitySite` (`guard_post`);
|
||||
- study → `ActivitySite` (`study_desk`);
|
||||
- rest → `ActivitySite` (`rest_bench`);
|
||||
- eat/deposit/withdraw → `StorageNode` (`village_pantry`).
|
||||
|
||||
The player also harvests food and wood directly from nearby `ResourceNode`
|
||||
instances. Extraction returns the actual amount removed, updates the village by
|
||||
that amount, and releases an NPC reservation if the source is depleted.
|
||||
The food bullet above is the historical transition point: active eating,
|
||||
deposit, and withdrawal now resolve to `WorldObjects/StorageSites/VillagePantry`
|
||||
instead of a pantry marker.
|
||||
|
||||
Rest, study, and guard behavior should later migrate to target types that match
|
||||
their actual semantics rather than treating every usable object as a generic
|
||||
marker.
|
||||
Future resource expansion should add many finite `ResourceNode` instances rather
|
||||
than broad resource zones: trees in foliage clusters, berry patches, animal
|
||||
camps, and village stockpiles can be ranked by reachability, distance, safety,
|
||||
profession, and NPC comfort range.
|
||||
|
||||
### Jajce scaffold
|
||||
|
||||
@@ -327,8 +324,7 @@ marker.
|
||||
- fortress, houses, mill, bridge, shader river/waterfall, mist, and smoke;
|
||||
- warm sky, fog, shadows, and wind-reactive foliage;
|
||||
- four ResourceNodes preserving the flat-map stable IDs;
|
||||
- typed village pantry storage plus remaining guard, study, and rest activity
|
||||
markers;
|
||||
- typed village pantry storage and typed guard, study, and rest activity sites;
|
||||
- a temporary baked navigation loop with 24 tested routes;
|
||||
- a look-development scene and camera.
|
||||
|
||||
@@ -506,11 +502,11 @@ NpcVisual navigates through the active world
|
||||
|
||||
These are expected prototype constraints, not necessarily isolated bugs:
|
||||
|
||||
- Action and profession IDs are stable and definition-backed, but activity
|
||||
marker mapping and several action effects remain hard-coded.
|
||||
- Temporary activity markers remain for rest, study, and patrol; NPC and player
|
||||
food/wood gathering use `ResourceNode` instances with no fallback, while food
|
||||
transfer uses the typed pantry `StorageNode`.
|
||||
- Action and profession IDs are stable and definition-backed, but several
|
||||
action effects remain hard-coded.
|
||||
- Temporary activity markers have been removed; NPC and player food/wood
|
||||
gathering use `ResourceNode` instances with no fallback, food transfer uses
|
||||
the typed pantry `StorageNode`, and patrol/study/rest use `ActivitySite`.
|
||||
- Current NPC, village, resource, storage, event, clock, and RNG state serialize
|
||||
through world schema v3. F5/F9 provide one validated local quicksave; a save
|
||||
menu, metadata, and player-transform persistence remain deferred.
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@ sources of truth.
|
||||
visual slice. It must respect the architecture gates in the learning
|
||||
roadmap.
|
||||
4. [`RESOURCE_NODE_MIGRATION.md`](RESOURCE_NODE_MIGRATION.md) is a focused
|
||||
migration plan. Phases 1–5 are complete; Phase 6 tracks replacement of the
|
||||
remaining temporary activity markers.
|
||||
migration plan. Phases 1–6 are complete; follow-up work should expand
|
||||
resource discovery without reintroducing abstract resource zones.
|
||||
5. [`ACTION_SYSTEM_ARCHITECTURE.md`](ACTION_SYSTEM_ARCHITECTURE.md),
|
||||
[`ECONOMIC_EVENTS.md`](ECONOMIC_EVENTS.md),
|
||||
[`FOOD_STORAGE_ARCHITECTURE.md`](FOOD_STORAGE_ARCHITECTURE.md),
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
| **4a** | Wood target selection (NPC) | ✅ Complete |
|
||||
| **4b** | Player parity (extraction contract) | ✅ Complete |
|
||||
| **5** | Jajce world placement and runtime integration | ✅ Complete |
|
||||
| **6** | Replace remaining activity markers | 🔶 Resource zones removed; activity targets pending |
|
||||
| **6** | Replace remaining activity markers | ✅ Storage and activity sites authored |
|
||||
|
||||
### Key divergences from the original plan
|
||||
|
||||
@@ -61,9 +61,9 @@ environment is integrated. It is easier to debug target selection, reservation,
|
||||
depletion, and task completion without terrain and navigation changes happening
|
||||
at the same time.
|
||||
|
||||
The old resource zones have been removed. Temporary activity markers remain
|
||||
only for actions whose real target type does not exist yet. They are not
|
||||
fallbacks and are not part of the target architecture.
|
||||
The old resource zones and temporary activity markers have been removed. Food
|
||||
transfer targets `StorageNode`; rest, study, and patrol target `ActivitySite`.
|
||||
These are not resource fallbacks and are not resource-search areas.
|
||||
|
||||
## Why this is the next systems step
|
||||
|
||||
@@ -522,10 +522,10 @@ Remove each old zone only when its replacement has:
|
||||
- debug visibility;
|
||||
- save representation.
|
||||
|
||||
**Progress:** the `TaskZone` container and obsolete `FarmZone`/`ForestZone`
|
||||
nodes have been deleted from `main.tscn`. The surviving nodes live under
|
||||
`LegacyActivityMarkers` to make their transitional role explicit. Task-to-marker
|
||||
mapping still exists for rest, study, and patrol, so this phase is not complete.
|
||||
**Progress:** the `TaskZone` container, obsolete `FarmZone`/`ForestZone` nodes,
|
||||
and final `LegacyActivityMarkers` container have been deleted from `main.tscn`
|
||||
and `JajceWorld`. Task-to-marker mapping has been replaced by typed site
|
||||
queries.
|
||||
|
||||
The former `FoodZone`/`PantryMarker` is now an authored `StorageNode` at
|
||||
`JajceWorld/WorldObjects/StorageSites/VillagePantry`, backed by
|
||||
@@ -533,9 +533,21 @@ simulation-owned `StorageStateRecord` state and explicit deposit/withdraw/eat
|
||||
transactions. Successful food transfers emit serializable economic events, and
|
||||
an active NPC visibly carries food between source, pantry, and consumption.
|
||||
|
||||
Guard, study, and rest are now authored `ActivitySite` instances at
|
||||
`JajceWorld/WorldObjects/ActivitySites`. The adapter chooses the nearest loaded
|
||||
site that supports the requested action. Capacity is currently presentation
|
||||
metadata; reservation/enforced occupancy can be added when multiple NPCs
|
||||
compete for the same social/workstation site.
|
||||
|
||||
**Exit:** temporary activity markers and task-to-marker mapping are deleted,
|
||||
not merely unused.
|
||||
|
||||
**Status: complete for the marker-removal migration.** Future work should
|
||||
extend target scoring rather than reintroducing zones: resources can be spread
|
||||
as many finite `ResourceNode` instances across foliage, animal camps, berry
|
||||
patches, and village stockpiles, then ranked by distance, reachability, safety,
|
||||
profession, and NPC comfort range.
|
||||
|
||||
## Test scenarios
|
||||
|
||||
At minimum, exercise:
|
||||
|
||||
@@ -12,10 +12,10 @@ same contract now runs against the integrated `JajceWorld` runtime.
|
||||
|
||||
The automated baseline verifies:
|
||||
|
||||
- the scene creates three simulated NPCs;
|
||||
- the scene creates six simulated NPCs;
|
||||
- four stable-ID ResourceNodes register;
|
||||
- three fixed origins can reach the four remaining activity markers and four
|
||||
ResourceNode interaction points (24 route checks);
|
||||
- three fixed origins can reach the typed pantry, three typed activity sites,
|
||||
and four ResourceNode interaction points (24 route checks);
|
||||
- a two-tick working task completes;
|
||||
- starvation still reaches death at its configured threshold.
|
||||
|
||||
@@ -24,6 +24,7 @@ The companion player-parity scenario verifies:
|
||||
- player food extraction conserves the actual remaining amount;
|
||||
- player wood extraction uses the configured yield;
|
||||
- player depletion releases an NPC reservation.
|
||||
- player pantry and guard interactions use typed world sites.
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ height = 1.7
|
||||
|
||||
[node name="JajceWorld" parent="." instance=ExtResource("11_jajce")]
|
||||
|
||||
[node name="Player" type="CharacterBody3D" parent="." unique_id=2022843760 node_paths=PackedStringArray("camera_rig", "simulation_manager", "pantry_storage", "guard_zone", "study_zone")]
|
||||
[node name="Player" type="CharacterBody3D" parent="." unique_id=2022843760 node_paths=PackedStringArray("camera_rig", "simulation_manager", "pantry_storage", "guard_site", "study_site")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
|
||||
script = ExtResource("1_h2yge")
|
||||
camera_rig = NodePath("../CameraRig")
|
||||
simulation_manager = NodePath("../SimulationManager")
|
||||
pantry_storage = NodePath("../JajceWorld/WorldObjects/StorageSites/VillagePantry")
|
||||
guard_zone = NodePath("../JajceWorld/LegacyActivityMarkers/GuardZone")
|
||||
study_zone = NodePath("../JajceWorld/LegacyActivityMarkers/StudyZone")
|
||||
guard_site = NodePath("../JajceWorld/WorldObjects/ActivitySites/GuardPost")
|
||||
study_site = NodePath("../JajceWorld/WorldObjects/ActivitySites/StudyDesk")
|
||||
stomach_capacity_for_food = 10
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player" unique_id=1249497228]
|
||||
@@ -57,11 +57,8 @@ deadzone_forward = 2.0
|
||||
current = true
|
||||
fov = 65.0
|
||||
|
||||
[node name="ActiveWorldAdapter" type="Node" parent="." node_paths=PackedStringArray("guard_marker", "study_marker", "rest_marker", "pantry_storage")]
|
||||
[node name="ActiveWorldAdapter" type="Node" parent="." node_paths=PackedStringArray("pantry_storage")]
|
||||
script = ExtResource("9_adapter")
|
||||
guard_marker = NodePath("../JajceWorld/LegacyActivityMarkers/GuardZone")
|
||||
study_marker = NodePath("../JajceWorld/LegacyActivityMarkers/StudyZone")
|
||||
rest_marker = NodePath("../JajceWorld/LegacyActivityMarkers/RestZone")
|
||||
pantry_storage = NodePath("../JajceWorld/WorldObjects/StorageSites/VillagePantry")
|
||||
|
||||
[node name="SimulationManager" type="Node" parent="." unique_id=1099676814 node_paths=PackedStringArray("active_world_adapter")]
|
||||
|
||||
+11
-4
@@ -8,8 +8,8 @@ extends CharacterBody3D
|
||||
@export var simulation_manager: Node
|
||||
|
||||
@export var pantry_storage: StorageNode
|
||||
@export var guard_zone: Marker3D
|
||||
@export var study_zone: Marker3D
|
||||
@export var guard_site: ActivitySite
|
||||
@export var study_site: ActivitySite
|
||||
|
||||
@export var stomach_capacity_for_food := 5
|
||||
@export var interaction_range := 3.0
|
||||
@@ -59,10 +59,10 @@ func try_interact() -> void:
|
||||
if try_harvest_resource_node():
|
||||
return
|
||||
|
||||
if is_near(guard_zone):
|
||||
if is_near_activity_site(guard_site):
|
||||
simulation_manager.add_safety(3.0)
|
||||
print("Player helped guard the village.")
|
||||
elif is_near(study_zone):
|
||||
elif is_near_activity_site(study_site):
|
||||
simulation_manager.add_knowledge(2.0)
|
||||
print("Player shared knowledge.")
|
||||
elif is_near_storage(pantry_storage):
|
||||
@@ -112,3 +112,10 @@ func is_near_storage(storage_node: StorageNode) -> bool:
|
||||
return false
|
||||
|
||||
return global_position.distance_to(storage_node.get_interaction_position()) <= interaction_range
|
||||
|
||||
|
||||
func is_near_activity_site(activity_site: ActivitySite) -> bool:
|
||||
if activity_site == null:
|
||||
return false
|
||||
|
||||
return global_position.distance_to(activity_site.get_interaction_position()) <= interaction_range
|
||||
|
||||
@@ -432,7 +432,8 @@ func get_pantry() -> StorageStateRecord:
|
||||
|
||||
|
||||
func register_loaded_storage_nodes() -> void:
|
||||
for node in StorageNode.get_all():
|
||||
for candidate in StorageNode.get_all():
|
||||
var node := candidate as StorageNode
|
||||
register_storage_node(node)
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ func resolve(
|
||||
npc, origin, definition, simulation_manager, active_world_adapter
|
||||
)
|
||||
SimulationIds.TARGET_ACTIVITY:
|
||||
return active_world_adapter.get_activity_target(npc.current_task)
|
||||
return active_world_adapter.get_activity_target(npc.current_task, origin)
|
||||
SimulationIds.TARGET_FREE:
|
||||
return {
|
||||
"target_id": "", "position": origin + simulation_manager.get_wander_offset(npc.id)
|
||||
|
||||
@@ -31,6 +31,7 @@ func _run() -> void:
|
||||
not main_scene.has_node("World")
|
||||
and not main_scene.has_node("ResourceNodes")
|
||||
and not main_scene.has_node("ActivityMarkers")
|
||||
and not main_scene.has_node("JajceWorld/LegacyActivityMarkers")
|
||||
),
|
||||
"Runtime should not retain duplicate flat-world objects"
|
||||
)
|
||||
@@ -57,12 +58,10 @@ func _run() -> void:
|
||||
)
|
||||
var pantry := main_scene.get_node("JajceWorld/WorldObjects/StorageSites/VillagePantry") as StorageNode
|
||||
destinations.append(pantry.get_interaction_position())
|
||||
for marker_path in [
|
||||
"JajceWorld/LegacyActivityMarkers/GuardZone",
|
||||
"JajceWorld/LegacyActivityMarkers/StudyZone",
|
||||
"JajceWorld/LegacyActivityMarkers/RestZone"
|
||||
]:
|
||||
destinations.append(main_scene.get_node(marker_path).global_position)
|
||||
var activity_root := main_scene.get_node("JajceWorld/WorldObjects/ActivitySites")
|
||||
_check(activity_root.get_child_count() == 3, "Runtime should expose three typed activity sites")
|
||||
for site in activity_root.get_children():
|
||||
destinations.append((site as ActivitySite).get_interaction_position())
|
||||
|
||||
for resource in resource_root.get_children():
|
||||
destinations.append((resource as ResourceNode).interaction_point.global_position)
|
||||
@@ -96,9 +95,14 @@ func _run() -> void:
|
||||
storage_target.get("target_id", "") == String(SimulationIds.STORAGE_VILLAGE_PANTRY),
|
||||
"Runtime adapter should route storage actions to village_pantry"
|
||||
)
|
||||
var study_target := adapter.get_activity_target(SimulationIds.ACTION_STUDY)
|
||||
_check(
|
||||
study_target.get("target_id", "") == "study_desk",
|
||||
"Runtime adapter should route study to the typed study desk"
|
||||
)
|
||||
|
||||
if failures.is_empty():
|
||||
print("[TEST] Jajce runtime passed: 6 NPCs, 4 resources, typed pantry route")
|
||||
print("[TEST] Jajce runtime passed: 6 NPCs, typed storage and activity sites")
|
||||
quit(0)
|
||||
return
|
||||
|
||||
|
||||
@@ -127,6 +127,19 @@ func _run() -> void:
|
||||
not world.has_node("LegacyActivityMarkers/PantryMarker"),
|
||||
"Pantry should no longer be a legacy activity marker"
|
||||
)
|
||||
_check(
|
||||
not world.has_node("LegacyActivityMarkers"),
|
||||
"JajceWorld should no longer expose legacy activity markers"
|
||||
)
|
||||
var activity_root := world.get_node("WorldObjects/ActivitySites")
|
||||
var activity_ids: Array[String] = []
|
||||
for site in activity_root.get_children():
|
||||
activity_ids.append(String((site as ActivitySite).site_id))
|
||||
activity_ids.sort()
|
||||
_check(
|
||||
activity_ids == ["guard_post", "rest_bench", "study_desk"],
|
||||
"Jajce activity sites should preserve stable semantic IDs"
|
||||
)
|
||||
|
||||
var navigation_region := world.get_node("NavigationRegion3D") as NavigationRegion3D
|
||||
var navigation_map: RID = NavigationServer3D.region_get_map(navigation_region.get_rid())
|
||||
@@ -143,8 +156,8 @@ func _run() -> void:
|
||||
for resource in resources:
|
||||
destinations.append((resource as ResourceNode).interaction_point.global_position)
|
||||
destinations.append(pantry.get_interaction_position())
|
||||
for marker in world.get_node("LegacyActivityMarkers").get_children():
|
||||
destinations.append((marker as Marker3D).global_position)
|
||||
for site in activity_root.get_children():
|
||||
destinations.append((site as ActivitySite).get_interaction_position())
|
||||
|
||||
for origin in origins:
|
||||
for destination in destinations:
|
||||
@@ -187,10 +200,15 @@ func _run() -> void:
|
||||
storage_target_position.distance_to(pantry.get_interaction_position()) < 0.01,
|
||||
"Storage actions should use the pantry StorageNode interaction point"
|
||||
)
|
||||
var rest_target := adapter.get_activity_target(SimulationIds.ACTION_REST)
|
||||
_check(
|
||||
rest_target.get("target_id", "") == "rest_bench",
|
||||
"Rest should target the typed RestBench ActivitySite"
|
||||
)
|
||||
|
||||
if failures.is_empty():
|
||||
print(
|
||||
"[TEST] Jajce scaffold passed: Terrain3D, storage site, shader water, building blockouts"
|
||||
"[TEST] Jajce scaffold passed: Terrain3D, storage/activity sites, shader water"
|
||||
)
|
||||
quit(0)
|
||||
return
|
||||
|
||||
@@ -68,6 +68,14 @@ func _run() -> void:
|
||||
pantry_state.get_amount(SimulationIds.RESOURCE_FOOD) < pantry_food_before,
|
||||
"Player should eat from the typed pantry StorageNode"
|
||||
)
|
||||
var guard_site := main_scene.get_node("JajceWorld/WorldObjects/ActivitySites/GuardPost") as ActivitySite
|
||||
player.global_position = guard_site.get_interaction_position()
|
||||
var safety_before: float = simulation_manager.village.safety
|
||||
player.try_interact()
|
||||
_check(
|
||||
simulation_manager.village.safety > safety_before,
|
||||
"Player should help guard from the typed GuardPost ActivitySite"
|
||||
)
|
||||
|
||||
if failures.is_empty():
|
||||
print("[TEST] ResourceNode player parity passed")
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
class_name ActiveWorldAdapter
|
||||
extends Node
|
||||
|
||||
@export var guard_marker: Marker3D
|
||||
@export var study_marker: Marker3D
|
||||
@export var rest_marker: Marker3D
|
||||
@export var pantry_storage: StorageNode
|
||||
|
||||
|
||||
@@ -18,22 +15,40 @@ func get_resource_candidates(action_id: StringName) -> Array[Dictionary]:
|
||||
return candidates
|
||||
|
||||
|
||||
func get_activity_target(action_id: StringName) -> Dictionary:
|
||||
var marker: Marker3D
|
||||
func get_activity_target(action_id: StringName, origin: Vector3 = Vector3.ZERO) -> Dictionary:
|
||||
match action_id:
|
||||
SimulationIds.ACTION_PATROL:
|
||||
marker = guard_marker
|
||||
return _get_activity_site_target(action_id, origin)
|
||||
SimulationIds.ACTION_STUDY:
|
||||
marker = study_marker
|
||||
return _get_activity_site_target(action_id, origin)
|
||||
SimulationIds.ACTION_REST:
|
||||
marker = rest_marker
|
||||
return _get_activity_site_target(action_id, origin)
|
||||
SimulationIds.ACTION_EAT:
|
||||
return _get_storage_target(pantry_storage)
|
||||
SimulationIds.ACTION_DEPOSIT_FOOD, SimulationIds.ACTION_WITHDRAW_FOOD:
|
||||
return _get_storage_target(pantry_storage)
|
||||
if marker == null:
|
||||
return {}
|
||||
|
||||
|
||||
func _get_activity_site_target(action_id: StringName, origin: Vector3) -> Dictionary:
|
||||
var best_site: ActivitySite
|
||||
var best_distance := INF
|
||||
for candidate in ActivitySite.get_all():
|
||||
var site := candidate as ActivitySite
|
||||
if site == null:
|
||||
continue
|
||||
if not site.supports_action(action_id):
|
||||
continue
|
||||
var distance := origin.distance_squared_to(site.get_interaction_position())
|
||||
if distance < best_distance:
|
||||
best_distance = distance
|
||||
best_site = site
|
||||
if best_site == null:
|
||||
return {}
|
||||
return {"target_id": "", "position": marker.global_position}
|
||||
return {
|
||||
"target_id": String(best_site.site_id),
|
||||
"position": best_site.get_interaction_position()
|
||||
}
|
||||
|
||||
|
||||
func _get_storage_target(storage_node: StorageNode) -> Dictionary:
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
class_name ActivitySite
|
||||
extends Node3D
|
||||
|
||||
static var _all: Array = []
|
||||
|
||||
@export var site_id: StringName
|
||||
@export var action_id: StringName = SimulationIds.ACTION_REST
|
||||
@export var display_name := "Activity Site"
|
||||
@export_range(1, 12, 1) var capacity := 1
|
||||
@export var debug_label_enabled := true
|
||||
|
||||
@onready var interaction_point: Marker3D = $InteractionPoint
|
||||
|
||||
var _last_label_text := ""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if site_id.is_empty():
|
||||
push_error("ActivitySite at %s has empty site_id" % get_path())
|
||||
_update_presentation()
|
||||
return
|
||||
var existing := get_by_id(site_id)
|
||||
if existing != null:
|
||||
push_error(
|
||||
(
|
||||
"Duplicate ActivitySite site_id '%s' at %s; first registered at %s"
|
||||
% [site_id, get_path(), existing.get_path()]
|
||||
)
|
||||
)
|
||||
_update_presentation()
|
||||
return
|
||||
_all.append(self)
|
||||
add_to_group("activity_sites")
|
||||
_update_presentation()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if debug_label_enabled:
|
||||
_update_presentation()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_all.erase(self)
|
||||
|
||||
|
||||
func get_interaction_position() -> Vector3:
|
||||
return interaction_point.global_position if interaction_point != null else global_position
|
||||
|
||||
|
||||
func supports_action(candidate_action_id: StringName) -> bool:
|
||||
return action_id == candidate_action_id
|
||||
|
||||
|
||||
func _update_presentation() -> void:
|
||||
if not has_node("DebugLabel"):
|
||||
return
|
||||
var label := $DebugLabel as Label3D
|
||||
label.visible = debug_label_enabled
|
||||
if not debug_label_enabled:
|
||||
return
|
||||
var text := "%s\n%s\ncap:%d" % [display_name, action_id, capacity]
|
||||
if text == _last_label_text:
|
||||
return
|
||||
_last_label_text = text
|
||||
label.text = text
|
||||
|
||||
|
||||
static func get_by_id(search_id: StringName):
|
||||
for node in _all:
|
||||
if node.site_id == search_id:
|
||||
return node
|
||||
return null
|
||||
|
||||
|
||||
static func get_all() -> Array:
|
||||
return _all.duplicate()
|
||||
+58
-10
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=25 format=3]
|
||||
[gd_scene load_steps=26 format=3]
|
||||
|
||||
[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"]
|
||||
@@ -14,6 +14,7 @@
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/ChimneySmoke.tscn" id="12_smoke"]
|
||||
[ext_resource type="Script" path="res://world/jajce/day_night_cycle.gd" id="13_daynight"]
|
||||
[ext_resource type="PackedScene" path="res://world/storage/StorageNode.tscn" id="14_storage"]
|
||||
[ext_resource type="Script" path="res://world/activity/ActivitySite.gd" id="15_activity"]
|
||||
|
||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"]
|
||||
_shader_parameters = {
|
||||
@@ -251,29 +252,76 @@ yield_per_action = 3.0
|
||||
[node name="VillagePantry" parent="WorldObjects/StorageSites" instance=ExtResource("14_storage")]
|
||||
position = Vector3(0, 0, -8)
|
||||
|
||||
[node name="LegacyActivityMarkers" type="Node3D" parent="."]
|
||||
[node name="ActivitySites" type="Node3D" parent="WorldObjects"]
|
||||
|
||||
[node name="GuardZone" type="Marker3D" parent="LegacyActivityMarkers"]
|
||||
[node name="GuardPost" type="Node3D" parent="WorldObjects/ActivitySites"]
|
||||
position = Vector3(0, 0, 8)
|
||||
script = ExtResource("15_activity")
|
||||
site_id = &"guard_post"
|
||||
action_id = &"patrol"
|
||||
display_name = "Guard Post"
|
||||
capacity = 2
|
||||
|
||||
[node name="GuardTowerVisual" type="MeshInstance3D" parent="LegacyActivityMarkers/GuardZone"]
|
||||
[node name="GuardTowerVisual" type="MeshInstance3D" parent="WorldObjects/ActivitySites/GuardPost"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 0)
|
||||
mesh = SubResource("Mesh_guard_post")
|
||||
|
||||
[node name="StudyZone" type="Marker3D" parent="LegacyActivityMarkers"]
|
||||
position = Vector3(-6, 0, 6)
|
||||
[node name="InteractionPoint" type="Marker3D" parent="WorldObjects/ActivitySites/GuardPost"]
|
||||
|
||||
[node name="StudyDeskVisual" type="MeshInstance3D" parent="LegacyActivityMarkers/StudyZone"]
|
||||
[node name="DebugLabel" type="Label3D" parent="WorldObjects/ActivitySites/GuardPost"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.8, 0)
|
||||
billboard = 1
|
||||
no_depth_test = true
|
||||
font_size = 18
|
||||
outline_modulate = Color(0, 0, 0, 1)
|
||||
outline_size = 4
|
||||
text = "Guard Post"
|
||||
|
||||
[node name="StudyDesk" type="Node3D" parent="WorldObjects/ActivitySites"]
|
||||
position = Vector3(-6, 0, 6)
|
||||
script = ExtResource("15_activity")
|
||||
site_id = &"study_desk"
|
||||
action_id = &"study"
|
||||
display_name = "Study Desk"
|
||||
|
||||
[node name="StudyDeskVisual" type="MeshInstance3D" parent="WorldObjects/ActivitySites/StudyDesk"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.125, 0)
|
||||
mesh = SubResource("Mesh_study_desk")
|
||||
|
||||
[node name="RestZone" type="Marker3D" parent="LegacyActivityMarkers"]
|
||||
position = Vector3(4, 0, 5)
|
||||
[node name="InteractionPoint" type="Marker3D" parent="WorldObjects/ActivitySites/StudyDesk"]
|
||||
|
||||
[node name="RestBenchVisual" type="MeshInstance3D" parent="LegacyActivityMarkers/RestZone"]
|
||||
[node name="DebugLabel" type="Label3D" parent="WorldObjects/ActivitySites/StudyDesk"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.1, 0)
|
||||
billboard = 1
|
||||
no_depth_test = true
|
||||
font_size = 18
|
||||
outline_modulate = Color(0, 0, 0, 1)
|
||||
outline_size = 4
|
||||
text = "Study Desk"
|
||||
|
||||
[node name="RestBench" type="Node3D" parent="WorldObjects/ActivitySites"]
|
||||
position = Vector3(4, 0, 5)
|
||||
script = ExtResource("15_activity")
|
||||
site_id = &"rest_bench"
|
||||
action_id = &"rest"
|
||||
display_name = "Rest Bench"
|
||||
capacity = 2
|
||||
|
||||
[node name="RestBenchVisual" type="MeshInstance3D" parent="WorldObjects/ActivitySites/RestBench"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0)
|
||||
mesh = SubResource("Mesh_rest_bench")
|
||||
|
||||
[node name="InteractionPoint" type="Marker3D" parent="WorldObjects/ActivitySites/RestBench"]
|
||||
|
||||
[node name="DebugLabel" type="Label3D" parent="WorldObjects/ActivitySites/RestBench"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.1, 0)
|
||||
billboard = 1
|
||||
no_depth_test = true
|
||||
font_size = 18
|
||||
outline_modulate = Color(0, 0, 0, 1)
|
||||
outline_size = 4
|
||||
text = "Rest Bench"
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
rotation_degrees = Vector3(-55, -35, 0)
|
||||
light_color = Color(1, 0.87, 0.72, 1)
|
||||
|
||||
@@ -91,15 +91,12 @@ func _update_presentation() -> void:
|
||||
label.text = text
|
||||
|
||||
|
||||
static func get_by_id(search_id: StringName) -> StorageNode:
|
||||
static func get_by_id(search_id: StringName):
|
||||
for node in _all:
|
||||
if node.storage_id == search_id:
|
||||
return node
|
||||
return null
|
||||
|
||||
|
||||
static func get_all() -> Array[StorageNode]:
|
||||
var nodes: Array[StorageNode] = []
|
||||
for node in _all:
|
||||
nodes.append(node as StorageNode)
|
||||
return nodes
|
||||
static func get_all() -> Array:
|
||||
return _all.duplicate()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://cgxnb8qsttpqy
|
||||
Reference in New Issue
Block a user