From 556d0fdc26c206ee0e76d788615724b565eae849 Mon Sep 17 00:00:00 2001 From: Rijad Zuzo Date: Sat, 4 Jul 2026 21:06:57 +0200 Subject: [PATCH] feat: add Jajce world scaffold --- docs/baselines/FLAT_MAP_BASELINE.md | 46 ++++++ terrain/jajce/assets.tres | 3 + terrain/jajce/data/.gitkeep | 1 + terrain/jajce/data/terrain3d-01-01.res | Bin 0 -> 5845 bytes terrain/jajce/data/terrain3d-01_00.res | Bin 0 -> 5843 bytes terrain/jajce/data/terrain3d_00-01.res | Bin 0 -> 5847 bytes terrain/jajce/data/terrain3d_00_00.res | Bin 0 -> 5847 bytes tests/flat_map_baseline_test.gd | 90 +++++++++++ tests/flat_map_baseline_test.gd.uid | 1 + tests/jajce_world_scaffold_test.gd | 118 ++++++++++++++ tests/jajce_world_scaffold_test.gd.uid | 1 + tools/generate_jajce_terrain_seed.gd | 41 +++++ tools/generate_jajce_terrain_seed.gd.uid | 1 + world/jajce/JajceLookdev.tscn | 15 ++ world/jajce/JajceWorld.tscn | 198 +++++++++++++++++++++++ world/jajce/beauty_camera.gd | 17 ++ world/jajce/beauty_camera.gd.uid | 1 + world/jajce/jajce_world.gd | 11 ++ world/jajce/jajce_world.gd.uid | 1 + 19 files changed, 545 insertions(+) create mode 100644 docs/baselines/FLAT_MAP_BASELINE.md create mode 100644 terrain/jajce/assets.tres create mode 100644 terrain/jajce/data/.gitkeep create mode 100644 terrain/jajce/data/terrain3d-01-01.res create mode 100644 terrain/jajce/data/terrain3d-01_00.res create mode 100644 terrain/jajce/data/terrain3d_00-01.res create mode 100644 terrain/jajce/data/terrain3d_00_00.res create mode 100644 tests/flat_map_baseline_test.gd create mode 100644 tests/flat_map_baseline_test.gd.uid create mode 100644 tests/jajce_world_scaffold_test.gd create mode 100644 tests/jajce_world_scaffold_test.gd.uid create mode 100644 tools/generate_jajce_terrain_seed.gd create mode 100644 tools/generate_jajce_terrain_seed.gd.uid create mode 100644 world/jajce/JajceLookdev.tscn create mode 100644 world/jajce/JajceWorld.tscn create mode 100644 world/jajce/beauty_camera.gd create mode 100644 world/jajce/beauty_camera.gd.uid create mode 100644 world/jajce/jajce_world.gd create mode 100644 world/jajce/jajce_world.gd.uid diff --git a/docs/baselines/FLAT_MAP_BASELINE.md b/docs/baselines/FLAT_MAP_BASELINE.md new file mode 100644 index 0000000..0116912 --- /dev/null +++ b/docs/baselines/FLAT_MAP_BASELINE.md @@ -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. diff --git a/terrain/jajce/assets.tres b/terrain/jajce/assets.tres new file mode 100644 index 0000000..13d134b --- /dev/null +++ b/terrain/jajce/assets.tres @@ -0,0 +1,3 @@ +[gd_resource type="Terrain3DAssets" format=3] + +[resource] diff --git a/terrain/jajce/data/.gitkeep b/terrain/jajce/data/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/terrain/jajce/data/.gitkeep @@ -0,0 +1 @@ + diff --git a/terrain/jajce/data/terrain3d-01-01.res b/terrain/jajce/data/terrain3d-01-01.res new file mode 100644 index 0000000000000000000000000000000000000000..6794324c12ed72ca4a156013af53c394261ebcc6 GIT binary patch literal 5845 zcmWFvc6MfB00IF9hV)2YhCoIJ24Ns~q~d&_-$h3-ZXCgWr&YY?0K-&ctNz~v27X&E zhO_{4!-Aup$v(xQr3+U9!C~h8H*@(nnFqd({q$5VUMS74!~4)Y)n2*QImhjFihk#3 zM02Sys59s=ygq8GT75Z)%`#u~ANT(!4D|=znR*%uh+P+Qn)^feUt|1(_8RT9CCg^s zPdI5h?dm%2UXEUPpwPwlL>fCi4~7dC&gl%?obcng zi1d1=b}pBd4pUBi(zv|jS?BrU$gU@h3OWi6Oq_maW#{YiCj|N2WxEktz3moLrP|9y zQ-swnn8!7DS4wR;acJwA>Rt_w7k3%5oFp?WMYb(;-zvX)>Z(+m@W+kvx{H10?>M&m z@>vNn=c?VuB{&$r?hZ*(E}H$h`24eXCO_oZHb4W^7hG~NGW`C|C^jm@z%UvT4E&>M zVl*oNn=^wiE9f#aOgh3RHo-TU!SV661xw6sod{&u-MvVMt3F%v@X77d+iqog|K)$C zab}*~n#h=yAIuAa-H!CvY=~NZI{wcA{ThA-mbc3apoEnLg7q$n~P-jmeFx zSeFU$YOzfCwJtSWtAQ0-5XLevD1d^2k>Th4a+gs-{?Py#O$?(M0oV*3%?M~2VJ|bo z6BbZe$;<|9tb67rrl&FhyTwvK4r_U4N=XJgkd={|nVwOSmS2>cSQ6y!VG6wJ*m z$W1ILW=u&eNo0@*3WS8RGW`Glzn-7r>WqkIrA`7D_Ce9=(*eBe8xn)r$zaXq+@8qN+$jja^~M;1%rSC+5nh-s1*PJ literal 0 HcmV?d00001 diff --git a/terrain/jajce/data/terrain3d-01_00.res b/terrain/jajce/data/terrain3d-01_00.res new file mode 100644 index 0000000000000000000000000000000000000000..d164256b24a0b3b49f3e409a617c83211caaa60a GIT binary patch literal 5843 zcmWFvc6MfB00IF9hV)2Y27g8d24Ns~q~d&_-$h3-ZXCgWr&YY?0K-&ctNz~v27X;G zhO`T2`UTmSBvuMCI0S@-hF)cs*1dIW)*Ig~<(^Y!*Gzm+!je!kU$1wbLT6w9%3V^y z*}*Cd>I^yzuaBy#R$mHYGrXtuN3`ygto?y^TRn^g#jguFE&U-_*J%HszeYQ2(Xv_l zADl3qc4b}Pv1Zf7Zx`u=oU^wK;@$LDHQmQ!c?sL<;BAchr5D>dSfmzZT|MgjIASYb zdgheNM?NR~YYBa3v@ZLDC-bG|i?(i0T{gJsCGzYK@e7$fPi11K<-v5t$~Bz!@56o7-$Fxgsh->*4|@ zhLvw`ofp=tE`PcSsaOVT`j1goj|!qyUZVjr8X(}FG^h|8O$?)nVKgxeiHwlWA%%)n|AILZtfab^ffEhUpM literal 0 HcmV?d00001 diff --git a/terrain/jajce/data/terrain3d_00-01.res b/terrain/jajce/data/terrain3d_00-01.res new file mode 100644 index 0000000000000000000000000000000000000000..2bd1ee3e95e94d672a0a6922281d661299d83f96 GIT binary patch literal 5847 zcmWFvc6MfB00IF9hV)2YhG0ep24Ns~q~d&_-$h3-ZXCgWr&YY?0K-&ctNz~v27X^I zhO`Ujh6P7GlYNRqOBb#Jg2RXJE!ung-QLb^zCx*g2&-K%k8AF(l-hFQ(AG26y&4=Z?lNRKNoH7zY+LBQRetr$|9g?J6H2ZV$`DgD;e#o(HfCVYI>|$j2{hd*4REU9LG$er8 zY&1=bW`)6+6?B;yCLQ4uo8X(w;Q093f+c3RP6RUS?p~zBRiCYS_~iEKZMQPL|MEZ6 zI5W>~O=Qf<59S5IZb$lSHbgBy9slQmehohZ%U#(8jfH1J1=dToOrPl)?lLOKKN=vTiD5J&02`vC838RL>}6(n z!U8HQnc0BNbz-kgW(hM4PW(Y|wDoV`EGj<6|P0!5FV*pjOEY%`;_N+h#7efOBsFIZcVkQPo tuAtx`XC8)X1q-bMJ$E~t&zOk!v?%|PbPR1+$;5v`&itFKU=VOf8vsVtt;qlY literal 0 HcmV?d00001 diff --git a/terrain/jajce/data/terrain3d_00_00.res b/terrain/jajce/data/terrain3d_00_00.res new file mode 100644 index 0000000000000000000000000000000000000000..61b12bca55ec21d9f93523416fe75a09734e0f1e GIT binary patch literal 5847 zcmWFvc6MfB00IF9hV)2YhG0ep24Ns~q~d&_-$h3-ZXCgWr&YY?0K-&ctNz~v27X^I zhO`UjMhT&lk0~ZFFbDx<7#J>``uhJ~?#X*4x79AGye;~vtgvrKkKOVe%-+AmD(-xZ z?F~%|R%K9U&}HE9Gs|oizAa|B;LZ>IKU0|hFxtOZwAUwO-ermFkNkhI*EQOIV$AWH z82!@RqH0~H>dj+*E!z)!x=k!J{&#Y7(Z-YUoBUd*T`K>uFV|>0Pnxh`;R02KCA};k zC4%_QZ0en|D&q7f?Gw5F%WL!wbtW_w+TGAv%60EWCJ(Q^_EIhPgPo6zrgj-9H|qGF z$TZmV`RMVY$A>>}c%B%LntORg+S#o-ml7W_aOfyBFmd{wm7TB4pAh78m+eMq^|o6~ zm1-{+O%YbRU>?`pT`9HY#G$Qcs(Up!UfgBKa+1uj6xp`WeXIQHsjE_L!XG!v>n`@0 zzvI~M%V#CToU3*pm*8Ohx;rFExoGz1;`7hmnf#Dr+W-qvaM{Jk@cTQX*r*T#!)QnV zv)O2x7|jZUF)QdYGfX-)7x%kdjI8r zrg3JT-I~all^@Itg58ev*KCMdemefo0sR_&29~?B3mOa0h6=2gYMDOMGsyL#<&DXW zt5}x_@oKS5__Z!IT&sZ<8vC&f3<{uNU}X4tzuaY1kbg8lMiawmMgTTMM>7IiM%c^D z@Pq|aRx+~zo8+FkiRq~fz>cvLki%M@nNpI$4rFDdW~OJ9q~#aoCYA)bJ2_f#00nb1 z3vv?+iWyT9OA;C6fdV0+tPKDE|F7p~xVqyr_s3<9HD@lb*HO7DHn-VUaz#{v*2M)* z3@hK>IxnnOUH)_vQn3uw^dFdX+5T2z#nnP==0l$xHIpT_{IXj!U7^6XiG3@(NS22dp{0mMuU uoLoV{LC!o3)e06`2YT*yIG-^Q?`cu~Bk360u#$=Ygq-;|S-~LSkTw9Px36UY literal 0 HcmV?d00001 diff --git a/tests/flat_map_baseline_test.gd b/tests/flat_map_baseline_test.gd new file mode 100644 index 0000000..82ab8fb --- /dev/null +++ b/tests/flat_map_baseline_test.gd @@ -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) diff --git a/tests/flat_map_baseline_test.gd.uid b/tests/flat_map_baseline_test.gd.uid new file mode 100644 index 0000000..5d296c8 --- /dev/null +++ b/tests/flat_map_baseline_test.gd.uid @@ -0,0 +1 @@ +uid://cpach52mwjq67 diff --git a/tests/jajce_world_scaffold_test.gd b/tests/jajce_world_scaffold_test.gd new file mode 100644 index 0000000..bf4ebeb --- /dev/null +++ b/tests/jajce_world_scaffold_test.gd @@ -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) diff --git a/tests/jajce_world_scaffold_test.gd.uid b/tests/jajce_world_scaffold_test.gd.uid new file mode 100644 index 0000000..9b8cd71 --- /dev/null +++ b/tests/jajce_world_scaffold_test.gd.uid @@ -0,0 +1 @@ +uid://bhujwfm70p60v diff --git a/tools/generate_jajce_terrain_seed.gd b/tools/generate_jajce_terrain_seed.gd new file mode 100644 index 0000000..4418857 --- /dev/null +++ b/tools/generate_jajce_terrain_seed.gd @@ -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) diff --git a/tools/generate_jajce_terrain_seed.gd.uid b/tools/generate_jajce_terrain_seed.gd.uid new file mode 100644 index 0000000..09cb8c7 --- /dev/null +++ b/tools/generate_jajce_terrain_seed.gd.uid @@ -0,0 +1 @@ +uid://cbpngrlbf6fia diff --git a/world/jajce/JajceLookdev.tscn b/world/jajce/JajceLookdev.tscn new file mode 100644 index 0000000..cd86b1c --- /dev/null +++ b/world/jajce/JajceLookdev.tscn @@ -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) diff --git a/world/jajce/JajceWorld.tscn b/world/jajce/JajceWorld.tscn new file mode 100644 index 0000000..ca29d34 --- /dev/null +++ b/world/jajce/JajceWorld.tscn @@ -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") diff --git a/world/jajce/beauty_camera.gd b/world/jajce/beauty_camera.gd new file mode 100644 index 0000000..ff998f2 --- /dev/null +++ b/world/jajce/beauty_camera.gd @@ -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) diff --git a/world/jajce/beauty_camera.gd.uid b/world/jajce/beauty_camera.gd.uid new file mode 100644 index 0000000..022fa8c --- /dev/null +++ b/world/jajce/beauty_camera.gd.uid @@ -0,0 +1 @@ +uid://dqe7gnj5dxho7 diff --git a/world/jajce/jajce_world.gd b/world/jajce/jajce_world.gd new file mode 100644 index 0000000..80ae15d --- /dev/null +++ b/world/jajce/jajce_world.gd @@ -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) diff --git a/world/jajce/jajce_world.gd.uid b/world/jajce/jajce_world.gd.uid new file mode 100644 index 0000000..80864ca --- /dev/null +++ b/world/jajce/jajce_world.gd.uid @@ -0,0 +1 @@ +uid://b2kolejde4fxs