feat: add Jajce world scaffold
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
# Flat-map baseline
|
||||||
|
|
||||||
|
- **Captured:** 2026-07-04
|
||||||
|
- **Scene:** `res://main.tscn`
|
||||||
|
- **Automated check:** `res://tests/flat_map_baseline_test.gd`
|
||||||
|
- **Companion resource test:** `res://tests/resource_node_player_parity_test.gd`
|
||||||
|
|
||||||
|
## Contract
|
||||||
|
|
||||||
|
The flat prototype remains the regression reference while `JajceWorld` is
|
||||||
|
developed separately.
|
||||||
|
|
||||||
|
The automated baseline verifies:
|
||||||
|
|
||||||
|
- the scene creates three 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);
|
||||||
|
- a two-tick working task completes;
|
||||||
|
- starvation still reaches death at its configured threshold.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& "C:\Program Files (x86)\Godot\Godot_v4.5.1-stable_win64_console.exe" `
|
||||||
|
--headless --path . --script res://tests/flat_map_baseline_test.gd
|
||||||
|
|
||||||
|
& "C:\Program Files (x86)\Godot\Godot_v4.5.1-stable_win64_console.exe" `
|
||||||
|
--headless --path . --script res://tests/resource_node_player_parity_test.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
Godot 4.5.1 is the locally available validation runtime. The project currently
|
||||||
|
declares Godot 4.7 features, so final editor validation should also be performed
|
||||||
|
with the project version when available.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This baseline protects behavior, not visual composition. Terrain, lighting,
|
||||||
|
camera framing, and placeholder geometry may change. A Jajce migration is a
|
||||||
|
regression if it breaks these contracts without an explicit replacement test.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[gd_resource type="Terrain3DAssets" format=3]
|
||||||
|
|
||||||
|
[resource]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,90 @@
|
|||||||
|
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
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
var simulation_manager: Node = main_scene.get_node("SimulationManager")
|
||||||
|
simulation_manager.set_process(false)
|
||||||
|
|
||||||
|
_check(simulation_manager.npcs.size() == 3, "Baseline should create three NPCs")
|
||||||
|
_check(main_scene.get_node("ResourceNodes").get_child_count() == 4, "Baseline should contain four ResourceNodes")
|
||||||
|
|
||||||
|
var navigation_map: RID = main_scene.get_world_3d().navigation_map
|
||||||
|
await _wait_for_navigation_map(navigation_map)
|
||||||
|
NavigationServer3D.map_force_update(navigation_map)
|
||||||
|
|
||||||
|
var fixed_origins := [
|
||||||
|
Vector3(-6.0, 0.0, -4.0),
|
||||||
|
Vector3(0.0, 0.0, 0.0),
|
||||||
|
Vector3(6.0, 0.0, 4.0)
|
||||||
|
]
|
||||||
|
var destinations: Array[Vector3] = []
|
||||||
|
for marker_path in [
|
||||||
|
"TaskZone/FoodZone",
|
||||||
|
"TaskZone/GuardZone",
|
||||||
|
"TaskZone/StudyZone",
|
||||||
|
"TaskZone/RestZone"
|
||||||
|
]:
|
||||||
|
destinations.append(main_scene.get_node(marker_path).global_position)
|
||||||
|
|
||||||
|
for resource in main_scene.get_node("ResourceNodes").get_children():
|
||||||
|
destinations.append((resource as ResourceNode).interaction_point.global_position)
|
||||||
|
|
||||||
|
for origin in fixed_origins:
|
||||||
|
for destination in destinations:
|
||||||
|
var path := NavigationServer3D.map_get_path(
|
||||||
|
navigation_map,
|
||||||
|
origin,
|
||||||
|
destination,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
not path.is_empty(),
|
||||||
|
"Navigation path should exist from %s to %s" % [origin, destination]
|
||||||
|
)
|
||||||
|
|
||||||
|
var village := SimVillage.new()
|
||||||
|
var worker := SimNPC.new(100, "BaselineWorker", "scholar", 5.0, 5.0)
|
||||||
|
worker.set_task("study", 2.0)
|
||||||
|
worker.start_working()
|
||||||
|
worker.simulate_tick(village)
|
||||||
|
worker.simulate_tick(village)
|
||||||
|
_check(worker.task_complete, "A two-tick working task should complete")
|
||||||
|
|
||||||
|
var starving := SimNPC.new(101, "BaselineStarving", "wanderer", 5.0, 5.0)
|
||||||
|
starving.hunger = 100.0
|
||||||
|
starving.starvation_death_threshold = 1
|
||||||
|
starving.simulate_tick(village)
|
||||||
|
_check(starving.is_dead, "Starvation threshold should still kill an NPC")
|
||||||
|
|
||||||
|
if failures.is_empty():
|
||||||
|
print("[TEST] Flat-map baseline passed: 3 NPCs, 4 resources, 24 navigation routes")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
|
||||||
|
for failure in failures:
|
||||||
|
push_error("[TEST] " + failure)
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
func _wait_for_navigation_map(navigation_map: RID) -> void:
|
||||||
|
for attempt in 30:
|
||||||
|
if (
|
||||||
|
NavigationServer3D.map_get_iteration_id(navigation_map) > 0
|
||||||
|
and not NavigationServer3D.map_get_regions(navigation_map).is_empty()
|
||||||
|
):
|
||||||
|
await physics_frame
|
||||||
|
return
|
||||||
|
await physics_frame
|
||||||
|
_check(false, "Navigation map did not synchronize within 30 physics frames")
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cpach52mwjq67
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var camera := Camera3D.new()
|
||||||
|
camera.current = true
|
||||||
|
root.add_child(camera)
|
||||||
|
|
||||||
|
var world: Node3D = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
||||||
|
root.add_child(world)
|
||||||
|
await process_frame
|
||||||
|
for frame in 10:
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
var terrain = world.get_node("TerrainRoot/Terrain3D")
|
||||||
|
_check(terrain is Terrain3D, "JajceWorld should own a Terrain3D node")
|
||||||
|
_check(
|
||||||
|
terrain.data_directory == "res://terrain/jajce/data",
|
||||||
|
"Terrain3D should use dedicated Jajce data"
|
||||||
|
)
|
||||||
|
_check(world.has_node("WaterRoot"), "JajceWorld should expose WaterRoot")
|
||||||
|
_check(world.has_node("VillageRoot"), "JajceWorld should expose VillageRoot")
|
||||||
|
_check(world.has_node("FoliageRoot"), "JajceWorld should expose FoliageRoot")
|
||||||
|
_check(
|
||||||
|
world.has_node("GreyboxLandmarks/FortressBlockout"),
|
||||||
|
"Greybox should include the fortress landmark"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
world.has_node("WaterRoot/WaterfallGreybox"),
|
||||||
|
"Greybox should include the waterfall drop"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
world.has_node("VillageRoot/MillBlockout")
|
||||||
|
and world.has_node("VillageRoot/BridgeBlockout"),
|
||||||
|
"Greybox should include the mill and bridge"
|
||||||
|
)
|
||||||
|
var lookdev: Node3D = load("res://world/jajce/JajceLookdev.tscn").instantiate()
|
||||||
|
_check(lookdev.has_node("JajceWorld"), "Lookdev should instance JajceWorld")
|
||||||
|
_check(lookdev.has_node("BeautyCamera"), "Lookdev should provide a beauty camera")
|
||||||
|
lookdev.free()
|
||||||
|
|
||||||
|
var resources := world.get_node("WorldObjects/ResourceNodes").get_children()
|
||||||
|
var ids: Array[String] = []
|
||||||
|
for resource in resources:
|
||||||
|
ids.append(String((resource as ResourceNode).node_id))
|
||||||
|
ids.sort()
|
||||||
|
_check(
|
||||||
|
ids == ["berry_bush_01", "berry_bush_02", "tree_01", "tree_02"],
|
||||||
|
"Jajce resources should preserve all stable IDs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var navigation_region := world.get_node("NavigationRegion3D") as NavigationRegion3D
|
||||||
|
var navigation_map: RID = NavigationServer3D.region_get_map(navigation_region.get_rid())
|
||||||
|
_check(navigation_map.is_valid(), "Jajce navigation region should be assigned to a map")
|
||||||
|
_check(
|
||||||
|
navigation_region.navigation_mesh.get_polygon_count() > 0,
|
||||||
|
"Jajce navigation mesh should contain baked polygons"
|
||||||
|
)
|
||||||
|
await _wait_for_navigation_map(navigation_map)
|
||||||
|
NavigationServer3D.map_force_update(navigation_map)
|
||||||
|
|
||||||
|
var origins := [
|
||||||
|
Vector3(-6, 0, -4),
|
||||||
|
Vector3(0, 0, 0),
|
||||||
|
Vector3(6, 0, 4)
|
||||||
|
]
|
||||||
|
var destinations: Array[Vector3] = []
|
||||||
|
for resource in resources:
|
||||||
|
destinations.append((resource as ResourceNode).interaction_point.global_position)
|
||||||
|
for marker in world.get_node("LegacyActivityMarkers").get_children():
|
||||||
|
destinations.append((marker as Marker3D).global_position)
|
||||||
|
|
||||||
|
for origin in origins:
|
||||||
|
for destination in destinations:
|
||||||
|
var path := NavigationServer3D.map_get_path(
|
||||||
|
navigation_map,
|
||||||
|
origin,
|
||||||
|
destination,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
not path.is_empty(),
|
||||||
|
"Jajce navigation path should exist from %s to %s" % [origin, destination]
|
||||||
|
)
|
||||||
|
|
||||||
|
var selected := ResourceNode.find_available(&"gather_food", 777, Vector3.ZERO)
|
||||||
|
_check(selected != null, "Target discovery should find Jajce resources without a parent path")
|
||||||
|
if selected != null:
|
||||||
|
_check(selected.reserve(777), "Selected Jajce resource should be reservable")
|
||||||
|
selected.release(777)
|
||||||
|
|
||||||
|
if failures.is_empty():
|
||||||
|
print("[TEST] Jajce scaffold passed: Terrain3D, stable IDs, 24 navigation routes")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
|
||||||
|
for failure in failures:
|
||||||
|
push_error("[TEST] " + failure)
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
func _wait_for_navigation_map(navigation_map: RID) -> void:
|
||||||
|
for attempt in 30:
|
||||||
|
if (
|
||||||
|
NavigationServer3D.map_get_iteration_id(navigation_map) > 0
|
||||||
|
and not NavigationServer3D.map_get_regions(navigation_map).is_empty()
|
||||||
|
):
|
||||||
|
await physics_frame
|
||||||
|
return
|
||||||
|
await physics_frame
|
||||||
|
_check(false, "Jajce navigation map did not synchronize within 30 physics frames")
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bhujwfm70p60v
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const REGION_SIZE := 256
|
||||||
|
const TERRAIN_SIZE := 512
|
||||||
|
const DATA_DIRECTORY := "res://terrain/jajce/data"
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var camera := Camera3D.new()
|
||||||
|
camera.current = true
|
||||||
|
root.add_child(camera)
|
||||||
|
|
||||||
|
var terrain := Terrain3D.new()
|
||||||
|
terrain.region_size = REGION_SIZE
|
||||||
|
root.add_child(terrain)
|
||||||
|
terrain.set_camera(camera)
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
var height_map := Image.create_empty(
|
||||||
|
TERRAIN_SIZE,
|
||||||
|
TERRAIN_SIZE,
|
||||||
|
false,
|
||||||
|
Image.FORMAT_RF
|
||||||
|
)
|
||||||
|
height_map.fill(Color(0.0, 0.0, 0.0, 1.0))
|
||||||
|
|
||||||
|
var images: Array[Image]
|
||||||
|
images.resize(Terrain3DRegion.TYPE_MAX)
|
||||||
|
images[Terrain3DRegion.TYPE_HEIGHT] = height_map
|
||||||
|
|
||||||
|
terrain.data.import_images(
|
||||||
|
images,
|
||||||
|
Vector3(-TERRAIN_SIZE / 2.0, 0.0, -TERRAIN_SIZE / 2.0),
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
)
|
||||||
|
terrain.data.save_directory(DATA_DIRECTORY)
|
||||||
|
print("[TOOL] Generated flat Jajce Terrain3D seed data in ", DATA_DIRECTORY)
|
||||||
|
quit(0)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cbpngrlbf6fia
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[gd_scene load_steps=3 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://world/jajce/JajceWorld.tscn" id="1_world"]
|
||||||
|
[ext_resource type="Script" path="res://world/jajce/beauty_camera.gd" id="2_camera"]
|
||||||
|
|
||||||
|
[node name="JajceLookdev" type="Node3D"]
|
||||||
|
|
||||||
|
[node name="JajceWorld" parent="." instance=ExtResource("1_world")]
|
||||||
|
|
||||||
|
[node name="BeautyCamera" type="Camera3D" parent="."]
|
||||||
|
position = Vector3(44, 34, 44)
|
||||||
|
current = true
|
||||||
|
fov = 55.0
|
||||||
|
script = ExtResource("2_camera")
|
||||||
|
focus_point = Vector3(-4, 2, 0)
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
[gd_scene load_steps=21 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"]
|
||||||
|
[ext_resource type="Script" path="res://world/jajce/jajce_world.gd" id="3_world"]
|
||||||
|
|
||||||
|
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"]
|
||||||
|
world_background = 0
|
||||||
|
auto_shader = true
|
||||||
|
|
||||||
|
[sub_resource type="NavigationMesh" id="NavigationMesh_greybox"]
|
||||||
|
vertices = PackedVector3Array(-14.5, 0.5, 0, -1, 0.5, 0, -1, 0.5, -0.75, 0, 0.5, -1, 0, 0.5, -14.5, -14.5, 0.5, -14.5, 0.75, 0.5, -1, 0.75, 0.75, -0.5, 14.5, 0.5, -0.5, 14.5, 0.5, -14.5, 0.75, 0.75, 0, 0, 1, 0, 0, 0.75, 0.75, -0.5, 0.75, 0.75, -0.5, 0.5, 14.5, 14.5, 0.5, 14.5, -1, 0.5, 0.75, -14.5, 0.5, 14.5)
|
||||||
|
polygons = [PackedInt32Array(2, 1, 0), PackedInt32Array(3, 2, 4), PackedInt32Array(4, 2, 5), PackedInt32Array(5, 2, 0), PackedInt32Array(6, 3, 4), PackedInt32Array(8, 7, 6), PackedInt32Array(8, 6, 9), PackedInt32Array(9, 6, 4), PackedInt32Array(12, 11, 10), PackedInt32Array(10, 7, 8), PackedInt32Array(14, 13, 12), PackedInt32Array(10, 8, 12), PackedInt32Array(12, 8, 14), PackedInt32Array(14, 8, 15), PackedInt32Array(0, 1, 16), PackedInt32Array(16, 13, 14), PackedInt32Array(14, 17, 16), PackedInt32Array(16, 17, 0)]
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_ground"]
|
||||||
|
albedo_color = Color(0.24, 0.48, 0.25, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_ridge"]
|
||||||
|
albedo_color = Color(0.34, 0.31, 0.27, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_terrace"]
|
||||||
|
albedo_color = Color(0.42, 0.5, 0.29, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_water"]
|
||||||
|
albedo_color = Color(0.16, 0.43, 0.62, 0.78)
|
||||||
|
transparency = 1
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_building"]
|
||||||
|
albedo_color = Color(0.62, 0.48, 0.32, 1)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_ground"]
|
||||||
|
material = SubResource("Material_ground")
|
||||||
|
size = Vector3(64, 0.2, 64)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="Shape_ground"]
|
||||||
|
size = Vector3(64, 0.2, 64)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_ridge"]
|
||||||
|
material = SubResource("Material_ridge")
|
||||||
|
size = Vector3(12, 10, 28)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_terrace"]
|
||||||
|
material = SubResource("Material_terrace")
|
||||||
|
size = Vector3(15, 1.5, 8)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_river"]
|
||||||
|
material = SubResource("Material_water")
|
||||||
|
size = Vector3(5, 0.08, 56)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_house"]
|
||||||
|
material = SubResource("Material_building")
|
||||||
|
size = Vector3(4, 4, 5)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_fortress"]
|
||||||
|
material = SubResource("Material_building")
|
||||||
|
size = Vector3(8, 5, 10)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_bridge"]
|
||||||
|
material = SubResource("Material_building")
|
||||||
|
size = Vector3(8, 0.5, 3)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_waterfall"]
|
||||||
|
material = SubResource("Material_water")
|
||||||
|
size = Vector3(5, 10, 0.5)
|
||||||
|
|
||||||
|
[sub_resource type="Environment" id="Environment_greybox"]
|
||||||
|
background_mode = 1
|
||||||
|
background_color = Color(0.55, 0.72, 0.82, 1)
|
||||||
|
ambient_light_source = 2
|
||||||
|
ambient_light_color = Color(1, 0.9, 0.78, 1)
|
||||||
|
ambient_light_energy = 0.55
|
||||||
|
|
||||||
|
[node name="JajceWorld" type="Node3D"]
|
||||||
|
script = ExtResource("3_world")
|
||||||
|
|
||||||
|
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
|
||||||
|
navigation_mesh = SubResource("NavigationMesh_greybox")
|
||||||
|
|
||||||
|
[node name="TerrainRoot" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="Terrain3D" type="Terrain3D" parent="TerrainRoot"]
|
||||||
|
data_directory = "res://terrain/jajce/data"
|
||||||
|
material = SubResource("Terrain3DMaterial_jajce")
|
||||||
|
assets = ExtResource("1_assets")
|
||||||
|
collision_mask = 3
|
||||||
|
collision_mode = 0
|
||||||
|
top_level = true
|
||||||
|
|
||||||
|
[node name="GreyboxGround" type="StaticBody3D" parent="NavigationRegion3D"]
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="NavigationRegion3D/GreyboxGround"]
|
||||||
|
mesh = SubResource("Mesh_ground")
|
||||||
|
|
||||||
|
[node name="Collision" type="CollisionShape3D" parent="NavigationRegion3D/GreyboxGround"]
|
||||||
|
shape = SubResource("Shape_ground")
|
||||||
|
|
||||||
|
[node name="GreyboxLandmarks" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="FortressRidge" type="MeshInstance3D" parent="GreyboxLandmarks"]
|
||||||
|
position = Vector3(-25, 5, 9)
|
||||||
|
mesh = SubResource("Mesh_ridge")
|
||||||
|
|
||||||
|
[node name="FortressBlockout" type="MeshInstance3D" parent="GreyboxLandmarks"]
|
||||||
|
position = Vector3(-25, 12.5, 9)
|
||||||
|
mesh = SubResource("Mesh_fortress")
|
||||||
|
|
||||||
|
[node name="UpperTerrace" type="MeshInstance3D" parent="GreyboxLandmarks"]
|
||||||
|
position = Vector3(-14, 0.75, 12)
|
||||||
|
mesh = SubResource("Mesh_terrace")
|
||||||
|
|
||||||
|
[node name="LowerTerrace" type="MeshInstance3D" parent="GreyboxLandmarks"]
|
||||||
|
position = Vector3(-11, 0.75, 2)
|
||||||
|
mesh = SubResource("Mesh_terrace")
|
||||||
|
|
||||||
|
[node name="WaterRoot" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="RiverGreybox" type="MeshInstance3D" parent="WaterRoot"]
|
||||||
|
position = Vector3(25, 0.08, 0)
|
||||||
|
mesh = SubResource("Mesh_river")
|
||||||
|
|
||||||
|
[node name="WaterfallGreybox" type="MeshInstance3D" parent="WaterRoot"]
|
||||||
|
position = Vector3(25, 5, -24)
|
||||||
|
mesh = SubResource("Mesh_waterfall")
|
||||||
|
|
||||||
|
[node name="VillageRoot" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="House_01" type="MeshInstance3D" parent="VillageRoot"]
|
||||||
|
position = Vector3(-15, 2, 7)
|
||||||
|
mesh = SubResource("Mesh_house")
|
||||||
|
|
||||||
|
[node name="House_02" type="MeshInstance3D" parent="VillageRoot"]
|
||||||
|
position = Vector3(-8, 2, 2)
|
||||||
|
mesh = SubResource("Mesh_house")
|
||||||
|
|
||||||
|
[node name="House_03" type="MeshInstance3D" parent="VillageRoot"]
|
||||||
|
position = Vector3(-15, 2, -4)
|
||||||
|
mesh = SubResource("Mesh_house")
|
||||||
|
|
||||||
|
[node name="MillBlockout" type="MeshInstance3D" parent="VillageRoot"]
|
||||||
|
position = Vector3(19, 2, 7)
|
||||||
|
mesh = SubResource("Mesh_house")
|
||||||
|
|
||||||
|
[node name="BridgeBlockout" type="MeshInstance3D" parent="VillageRoot"]
|
||||||
|
position = Vector3(25, 0.5, 0)
|
||||||
|
mesh = SubResource("Mesh_bridge")
|
||||||
|
|
||||||
|
[node name="FoliageRoot" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="WorldObjects" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="ResourceNodes" type="Node3D" parent="WorldObjects"]
|
||||||
|
|
||||||
|
[node name="BerryBush_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
position = Vector3(-6, 0, -8)
|
||||||
|
node_id = &"berry_bush_01"
|
||||||
|
|
||||||
|
[node name="BerryBush_02" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
position = Vector3(-10, 0, -4)
|
||||||
|
node_id = &"berry_bush_02"
|
||||||
|
amount_remaining = 8.0
|
||||||
|
|
||||||
|
[node name="Tree_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
position = Vector3(6, 0, -8)
|
||||||
|
node_id = &"tree_01"
|
||||||
|
action_id = &"gather_wood"
|
||||||
|
resource_id = &"wood"
|
||||||
|
amount_remaining = 15.0
|
||||||
|
yield_per_action = 3.0
|
||||||
|
|
||||||
|
[node name="Tree_02" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
position = Vector3(10, 0, -4)
|
||||||
|
node_id = &"tree_02"
|
||||||
|
action_id = &"gather_wood"
|
||||||
|
resource_id = &"wood"
|
||||||
|
amount_remaining = 12.0
|
||||||
|
yield_per_action = 3.0
|
||||||
|
|
||||||
|
[node name="LegacyActivityMarkers" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="FoodZone" type="Marker3D" parent="LegacyActivityMarkers"]
|
||||||
|
position = Vector3(0, 0, -8)
|
||||||
|
|
||||||
|
[node name="GuardZone" type="Marker3D" parent="LegacyActivityMarkers"]
|
||||||
|
position = Vector3(0, 0, 8)
|
||||||
|
|
||||||
|
[node name="StudyZone" type="Marker3D" parent="LegacyActivityMarkers"]
|
||||||
|
position = Vector3(-6, 0, 6)
|
||||||
|
|
||||||
|
[node name="RestZone" type="Marker3D" parent="LegacyActivityMarkers"]
|
||||||
|
position = Vector3(4, 0, 5)
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||||
|
rotation_degrees = Vector3(-55, -35, 0)
|
||||||
|
light_color = Color(1, 0.87, 0.72, 1)
|
||||||
|
light_energy = 1.25
|
||||||
|
|
||||||
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||||
|
environment = SubResource("Environment_greybox")
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
extends Camera3D
|
||||||
|
|
||||||
|
@export var focus_point := Vector3(0.0, 2.0, 0.0)
|
||||||
|
@export var orbit_speed := 0.025
|
||||||
|
@export var animate_orbit := true
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
look_at(focus_point, Vector3.UP)
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if not animate_orbit:
|
||||||
|
return
|
||||||
|
|
||||||
|
var offset := global_position - focus_point
|
||||||
|
offset = offset.rotated(Vector3.UP, orbit_speed * delta)
|
||||||
|
global_position = focus_point + offset
|
||||||
|
look_at(focus_point, Vector3.UP)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dqe7gnj5dxho7
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
@onready var terrain: Terrain3D = $TerrainRoot/Terrain3D
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
call_deferred("_bind_active_camera")
|
||||||
|
|
||||||
|
func _bind_active_camera() -> void:
|
||||||
|
var active_camera := get_viewport().get_camera_3d()
|
||||||
|
if active_camera != null:
|
||||||
|
terrain.set_camera(active_camera)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b2kolejde4fxs
|
||||||
Reference in New Issue
Block a user