0.4.0: map (N) that fills in as you explore and a compass (closes #1); Old Tam's seam and Cove tips (voiced); the journey and sea fish in the guide; trailer with the tunnel and the Cove; protocol 16

This commit is contained in:
Paul Bailey
2026-11-06 18:36:20 +00:00
parent 980ee53579
commit 2b7e52cd82
28 changed files with 678 additions and 43 deletions

View File

@@ -9,7 +9,7 @@ if [ ! -f .godot/global_script_class_cache.cfg ]; then
echo "Importing the project first (fresh copy)..."
godot --headless --path . --import >/dev/null 2>&1
fi
for t in test_survival_core test_survival_stats test_save_load test_weather_building test_creatures_combat test_wolves test_alpha test_bear test_thieves test_barrow test_boss test_den_sites test_fishing_diet test_food test_hazards test_iron test_stone test_net_lobby test_net_world test_net_shared test_net_life test_net_boss test_net_rules test_host test_tutorial test_tutorial_full test_hints test_updates test_audio test_items_more test_worldlets test_worldlet_pick test_water_bodies test_cove test_tunnel 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_alpha test_bear test_thieves test_barrow test_boss test_den_sites test_fishing_diet test_food test_hazards test_iron test_stone test_net_lobby test_net_world test_net_shared test_net_life test_net_boss test_net_rules test_host test_tutorial test_tutorial_full test_hints test_updates test_audio test_items_more test_worldlets test_worldlet_pick test_water_bodies test_cove test_tunnel test_map test_terrain_edges test_boundary; do
extra=""
[ "$t" = "test_boundary" ] && extra="--fixed-fps 30"
# A suite that waits on a world that failed to load would hang forever,

View File

@@ -75,7 +75,7 @@ func _for(secs: float) -> void:
func _body() -> void:
print("the tips")
var kinds := ["event", "status", "weather", "has", "night", "stamina_low", "wolf_moon", "hard_winter"]
var kinds := ["event", "status", "weather", "has", "night", "stamina_low", "wolf_moon", "hard_winter", "worldlet", "seam"]
var bad: Array = []
for h in H.hints:
if not String(h.when).get_slice(":", 0) in kinds or String(h.title).is_empty() or String(h.text).is_empty():

139
tests/test_map.gd Normal file
View File

@@ -0,0 +1,139 @@
extends SceneTree
## The map and compass (Gitea issue #1). The map starts blank and fills in
## around you as you go; places are marked once you've seen them (the den,
## the barrow, the stones, the tunnel's seam, the Cove's pier and sea cave,
## your beds and buildings); you're a dot, and the compass says which way
## you're facing. What you've seen is saved with each worldlet.
## Run: godot --headless --path . --script res://tests/test_map.gd
var _fails := 0
var _finished := false
var S: Node
var DB: Node
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:
S = root.get_node("Saves")
DB = root.get_node("ItemDB")
S.root_dir = "user://test_map_worlds"
for w in S.list_worlds():
S.delete_world(w.id)
await _body()
check(_finished, "test ran to the end (no script errors)")
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)
func _load_world() -> Node3D:
var w: Node3D = load("res://scenes/world.tscn").instantiate()
root.add_child(w)
for _i in range(8):
await physics_frame
w.creatures.enabled = false
w.player.set_physics_process(false)
return w
func _count(w: Node3D) -> int:
var n := 0
for b in w.explored:
n += 1 if b != 0 else 0
return n
func _body() -> void:
S.current_id = S.create_world("Mapped", 1337)
var w: Node3D = await _load_world()
var player: Node3D = w.player
print("filling in")
await create_timer(0.7).timeout
var start := _count(w)
var all: int = w.MAP_CELLS * w.MAP_CELLS
check(start > 0 and start < all / 8, "at first, only the ground around you (%d of %d cells)" % [start, all])
check(w.seen(player.global_position.x, player.global_position.z), "(including where you stand)")
check(w.den.found and not w.seen(w.den.global_position.x, w.den.global_position.z), "the den isn't on it yet")
check(not w.map_marks().any(func(m): return m.kind == "den"), "(nor marked)")
player.global_position = w.den.global_position + Vector3(6, 1, 0)
w.explore(player.global_position)
check(w.map_marks().any(func(m): return m.kind == "den" and m.label == "Wolf den"), "go near it and it's marked")
check(_count(w) > start, "and the map's filled in on the way")
w.explore(w.tunnel.seam_at)
w.explore(w.barrow.global_position)
w.explore(w.stones.global_position)
var kinds: Array = w.map_marks().map(func(m): return m.kind)
check("seam" in kinds and "barrow" in kinds and "stones" in kinds, "the cracked seam, the barrow and the stones, once seen (%s)" % [kinds])
var bed: Node3D = w.spawn_piece(DB.get_item("bed"), Transform3D(Basis(), w._spawn + Vector3(4, 0, 2)))
var floor_def: Dictionary = DB.get_item("wood_floor")
if not floor_def.is_empty():
w.spawn_piece(floor_def, Transform3D(Basis(), w._spawn + Vector3(-4, 0, 2)))
w.explore(w._spawn)
kinds = w.map_marks().map(func(m): return m.kind)
check("bed" in kinds, "your bed")
check(floor_def.is_empty() or "build" in kinds, "and your buildings")
var below: Vector3 = w.tunnel.ORIGIN
var before := _count(w)
w.explore(below)
check(_count(w) == before, "(nothing fills in down the tunnel)")
print("the screen")
var MS: Script = load("res://scripts/ui/map_screen.gd")
var img: Image = MS.render(w)
check(img.get_width() == MS.RES, "the map image (%d px)" % img.get_width())
var far := Vector2i(2, 2)
var near: Vector2i = Vector2i(((w._spawn.x + w.terrain.size * 0.5) / w.terrain.size * MS.RES), ((w._spawn.z + w.terrain.size * 0.5) / w.terrain.size * MS.RES))
check(img.get_pixelv(far).is_equal_approx(img.get_pixelv(Vector2i(4, 2))) or img.get_pixelv(far).v < 0.25, "blank where you haven't been")
check(img.get_pixelv(near).v > 0.25, "drawn where you have")
var lake: Vector2 = w.terrain.lake_center
check(MS.ground_colour(w.terrain, lake.x, lake.y).b > MS.ground_colour(w.terrain, lake.x, lake.y).g, "water is blue")
var screen: Control = MS.new()
root.add_child(screen)
screen.setup(w, player)
screen.open()
await process_frame
await process_frame
check(screen.is_open() and player.ui_open and screen._title.text == "The Valley", "N opens it: the Valley")
check(screen._facing.text.begins_with("Facing "), "with which way you face (%s)" % screen._facing.text)
screen.close()
check(not screen.is_open() and not player.ui_open, "and closes it")
screen.queue_free()
print("the compass")
var C: Script = load("res://scripts/ui/compass.gd")
player.rotation = Vector3.ZERO # (facing -Z)
await process_frame
check(absf(C.heading(player)) < 1.0 or absf(C.heading(player) - 360.0) < 1.0, "facing -Z is north (%.0f)" % C.heading(player))
player.rotation = Vector3(0, -PI * 0.5, 0) # (facing +X)
await process_frame
check(absf(C.heading(player) - 90.0) < 1.0, "facing +X is east (%.0f)" % C.heading(player))
print("saved")
var seen_n := _count(w)
w.save_now()
w.queue_free()
for _i in range(3):
await process_frame
w = await _load_world()
check(_count(w) >= seen_n, "what you've seen is still on the map after a reload (%d cells)" % _count(w))
check(w.map_marks().any(func(m): return m.kind == "den"), "(the den too)")
w.queue_free()
for _i in range(3):
await process_frame
print("the Cove")
S.current_id = S.create_world("Cove map", 1337, {"worldlet": "cove"})
w = await _load_world()
await create_timer(0.7).timeout
check(_count(w) < start * 2, "a map of its own: only the ground round the start so far (%d cells)" % _count(w))
w.explore(w.pier.end)
w.explore(w.sea_cave.global_position)
kinds = w.map_marks().map(func(m): return m.kind)
check("pier" in kinds and "cave" in kinds, "the Cove's map: the pier and the sea cave (%s)" % [kinds])
w.queue_free()
await process_frame
_finished = true

1
tests/test_map.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://i5xx2lpb66s7