Wolf den search steps into the valley when the rim is steep everywhere
This commit is contained in:
@@ -57,40 +57,53 @@ func _find_site(world: Node3D) -> Vector3:
|
|||||||
var space := world.get_world_3d().direct_space_state
|
var space := world.get_world_3d().direct_space_state
|
||||||
var box := BoxShape3D.new()
|
var box := BoxShape3D.new()
|
||||||
box.size = Vector3(HALF_SIZE * 2.0, 6.0, HALF_SIZE * 2.0)
|
box.size = Vector3(HALF_SIZE * 2.0, 6.0, HALF_SIZE * 2.0)
|
||||||
var ex: Array[RID] = [terrain.get_node("TerrainBody").get_rid(), world.player.get_rid()]
|
# Pass 1 tries only the foot of the rim wall in each direction (the
|
||||||
for step in range(52):
|
# original search, so islands that already had a den keep it in the same
|
||||||
var ang := deg_to_rad(start + step * 7.0)
|
# place). Where the rim is steep all the way round (seed 777, seed 4),
|
||||||
var dir := Vector2(cos(ang), sin(ang))
|
# pass 2 steps further into the valley, a few metres at a time.
|
||||||
# Walk inward from the rim until the den sits inside the walkable
|
for pass_insets in [[0.0], [3.0, 6.0, 9.0, 12.0, 16.0, 20.0, 25.0, 30.0]]:
|
||||||
# area, a little way short of the wall.
|
for step in range(52):
|
||||||
var r: float = terrain.play_radius + 10.0
|
var ang := deg_to_rad(start + step * 7.0)
|
||||||
while r > terrain.play_radius - 40.0:
|
var dir := Vector2(cos(ang), sin(ang))
|
||||||
var x := dir.x * r
|
var r_foot := _rim_foot(dir)
|
||||||
var z := dir.y * r
|
for inset in pass_insets:
|
||||||
if terrain.inside_boundary(x, z, 11.0):
|
var c := _check_site(world, dir * (r_foot - inset))
|
||||||
break
|
if c != Vector3.INF:
|
||||||
r -= 1.0
|
return c
|
||||||
var c := Vector3(dir.x * r, 0, dir.y * r)
|
|
||||||
if terrain.sea_weight(c.x, c.z) > 0.02 or Vector2(c.x, c.z).length() < 50.0:
|
|
||||||
continue
|
|
||||||
var hs: Array[float] = []
|
|
||||||
for d in [Vector2(-1, -1), Vector2(1, -1), Vector2(-1, 1), Vector2(1, 1), Vector2.ZERO]:
|
|
||||||
hs.append(terrain.sample_height(c.x + d.x * 4.0, c.z + d.y * 4.0))
|
|
||||||
if hs.max() - hs.min() > 2.2 or hs.min() < terrain.water_level + 1.0:
|
|
||||||
continue
|
|
||||||
c.y = hs[4]
|
|
||||||
# Trees and boulders in the way are cleared (_clear_area); just avoid
|
|
||||||
# the thickest woods. Counted by position, because their colliders
|
|
||||||
# were only just created and the physics space hasn't seen them yet.
|
|
||||||
var crowd := 0
|
|
||||||
for uid in world.props.resource_nodes:
|
|
||||||
var n: Node3D = world.props.resource_nodes[uid]
|
|
||||||
if Vector2(n.global_position.x - c.x, n.global_position.z - c.z).length() < HALF_SIZE:
|
|
||||||
crowd += 1
|
|
||||||
if crowd <= 5:
|
|
||||||
return c
|
|
||||||
return Vector3.INF
|
return Vector3.INF
|
||||||
|
|
||||||
|
## Walks inward from the rim until the den sits inside the walkable area, a
|
||||||
|
## little way short of the wall. Returns that distance from the centre.
|
||||||
|
func _rim_foot(dir: Vector2) -> float:
|
||||||
|
var r: float = terrain.play_radius + 10.0
|
||||||
|
while r > terrain.play_radius - 40.0:
|
||||||
|
if terrain.inside_boundary(dir.x * r, dir.y * r, 11.0):
|
||||||
|
break
|
||||||
|
r -= 1.0
|
||||||
|
return r
|
||||||
|
|
||||||
|
## The site at `p` (x, z) with its height, or INF if it's no good: near the
|
||||||
|
## sea or the spawn, too sloped, too low, or in thick woods.
|
||||||
|
func _check_site(world: Node3D, p: Vector2) -> Vector3:
|
||||||
|
if terrain.sea_weight(p.x, p.y) > 0.02 or p.length() < 50.0:
|
||||||
|
return Vector3.INF
|
||||||
|
var hs: Array[float] = []
|
||||||
|
for d in [Vector2(-1, -1), Vector2(1, -1), Vector2(-1, 1), Vector2(1, 1), Vector2.ZERO]:
|
||||||
|
hs.append(terrain.sample_height(p.x + d.x * 4.0, p.y + d.y * 4.0))
|
||||||
|
if hs.max() - hs.min() > 2.2 or hs.min() < terrain.water_level + 1.0:
|
||||||
|
return Vector3.INF
|
||||||
|
# Trees and boulders in the way are cleared (_clear_area); just avoid
|
||||||
|
# the thickest woods. Counted by position, because their colliders
|
||||||
|
# were only just created and the physics space hasn't seen them yet.
|
||||||
|
var crowd := 0
|
||||||
|
for uid in world.props.resource_nodes:
|
||||||
|
var n: Node3D = world.props.resource_nodes[uid]
|
||||||
|
if Vector2(n.global_position.x - p.x, n.global_position.z - p.y).length() < HALF_SIZE:
|
||||||
|
crowd += 1
|
||||||
|
if crowd > 5:
|
||||||
|
return Vector3.INF
|
||||||
|
return Vector3(p.x, hs[4], p.y)
|
||||||
|
|
||||||
## Removes the trees, boulders, bushes and pickups the den is built over.
|
## Removes the trees, boulders, bushes and pickups the den is built over.
|
||||||
## Deterministic (the site comes from the seed), and the other scenery keeps
|
## Deterministic (the site comes from the seed), and the other scenery keeps
|
||||||
## its save ids because those come from creation order.
|
## its save ids because those come from creation order.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ if [ ! -f .godot/global_script_class_cache.cfg ]; then
|
|||||||
echo "Importing the project first (fresh copy)..."
|
echo "Importing the project first (fresh copy)..."
|
||||||
godot --headless --path . --import >/dev/null 2>&1
|
godot --headless --path . --import >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
for t in test_survival_core test_survival_stats test_save_load test_weather_building test_creatures_combat test_wolves test_fishing_diet test_net_lobby test_net_world test_terrain_edges test_boundary; do
|
for t in test_survival_core test_survival_stats test_save_load test_weather_building test_creatures_combat test_wolves test_den_sites test_fishing_diet test_net_lobby test_net_world test_terrain_edges test_boundary; do
|
||||||
extra=""
|
extra=""
|
||||||
[ "$t" = "test_boundary" ] && extra="--fixed-fps 30"
|
[ "$t" = "test_boundary" ] && extra="--fixed-fps 30"
|
||||||
# A suite that waits on a world that failed to load would hang forever,
|
# A suite that waits on a world that failed to load would hang forever,
|
||||||
|
|||||||
42
tests/test_den_sites.gd
Normal file
42
tests/test_den_sites.gd
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
extends SceneTree
|
||||||
|
## Every island gets a wolf den: across many seeds the site search finds a
|
||||||
|
## spot away from the spawn and the sea.
|
||||||
|
## Run: godot --headless --path . --script res://tests/test_den_sites.gd
|
||||||
|
|
||||||
|
const SEEDS := [777, 4, 1337, 1, 2, 3, 5, 6, 7, 8, 9, 10, 42, 99, 123, 2024, 4242, 9001, 31337, 65535]
|
||||||
|
|
||||||
|
var _fails := 0
|
||||||
|
|
||||||
|
func check(cond: bool, what: String) -> void:
|
||||||
|
print((" ok " if cond else " FAIL ") + what)
|
||||||
|
if not cond:
|
||||||
|
_fails += 1
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
_run.call_deferred()
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var S: Node = root.get_node("Saves")
|
||||||
|
S.root_dir = "user://test_den_sites"
|
||||||
|
for seed in SEEDS:
|
||||||
|
for w in S.list_worlds():
|
||||||
|
S.delete_world(w.id)
|
||||||
|
S.current_id = S.create_world("Den %d" % seed, seed)
|
||||||
|
var world: Node3D = load("res://scenes/world.tscn").instantiate()
|
||||||
|
root.add_child(world)
|
||||||
|
for _i in range(3):
|
||||||
|
await physics_frame
|
||||||
|
var den: Node3D = world.den
|
||||||
|
var t: Node3D = world.terrain
|
||||||
|
var p: Vector3 = den.position
|
||||||
|
var ok: bool = den.found
|
||||||
|
if ok:
|
||||||
|
ok = Vector2(p.x, p.z).length() >= 50.0 and t.sea_weight(p.x, p.z) <= 0.02
|
||||||
|
check(ok, "seed %d has a den (%.0f m from the centre)" % [seed, Vector2(p.x, p.z).length()])
|
||||||
|
world.queue_free()
|
||||||
|
await physics_frame
|
||||||
|
for w in S.list_worlds():
|
||||||
|
S.delete_world(w.id)
|
||||||
|
DirAccess.remove_absolute(S.root_dir)
|
||||||
|
print("RESULT: %s (%d failures)" % ["PASS" if _fails == 0 else "FAIL", _fails])
|
||||||
|
quit(1 if _fails > 0 else 0)
|
||||||
Reference in New Issue
Block a user