M1c: weather, snap building, shelter, beds and regrowth
- Seeded weather (clear/cloudy/rain/storm, snow and blizzards on cold days and above the snowline), lightning, synthesised rain/wind/thunder sound - Rain/snow particles stopped by roofs; wet ground, settled snow and wind via global shader uniforms - Snap-together wood floor, wall, door, roof and gable with structural support, collapse and X to dismantle - Shelter detection: dry under a roof, warmer and Rested inside with a fire; rain burns an uncovered campfire faster - Bed sets the spawn point and lets you sleep through the night - Trees, boulders and ground pickups regrow; timers saved - Grass cleared under floors; shorter HUD hint - New test_weather_building suite (62 checks) and tests/run_all.sh, which also fails on script errors
This commit is contained in:
21
tests/run_all.sh
Executable file
21
tests/run_all.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# Runs every headless test suite. Fails if any suite fails OR prints a script
|
||||
# error (a script that fails to compile can otherwise let a suite "pass").
|
||||
# Usage: tests/run_all.sh (from the repo root; needs `godot` on PATH)
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
status=0
|
||||
for t in test_survival_core test_survival_stats test_save_load test_weather_building test_terrain_edges test_boundary; do
|
||||
extra=""
|
||||
[ "$t" = "test_boundary" ] && extra="--fixed-fps 30"
|
||||
out=$(godot --headless --path . $extra --script "res://tests/$t.gd" 2>&1)
|
||||
code=$?
|
||||
result=$(printf '%s\n' "$out" | grep "RESULT:" | tail -n 1)
|
||||
if [ $code -ne 0 ] || printf '%s\n' "$out" | grep -q "SCRIPT ERROR"; then
|
||||
echo "FAIL $t $result"
|
||||
printf '%s\n' "$out" | grep -E "FAIL|SCRIPT ERROR|at: " | head -n 20
|
||||
status=1
|
||||
else
|
||||
echo "ok $t $result"
|
||||
fi
|
||||
done
|
||||
exit $status
|
||||
@@ -61,6 +61,7 @@ func _body(world: Node3D, id: String) -> void:
|
||||
var terrain: Node3D = world.get_node("Terrain")
|
||||
player.set_physics_process(false) # keep the player put while we fast-forward
|
||||
sv.set_process(false)
|
||||
world.weather.force("clear") # weather has its own test (test_weather_building)
|
||||
|
||||
print("day / night")
|
||||
check(dn.day == 1 and absf(dn.hours() - 8.0) < 0.1, "new world starts on day 1 at 08:00 (%s)" % dn.clock_text())
|
||||
|
||||
382
tests/test_weather_building.gd
Normal file
382
tests/test_weather_building.gd
Normal file
@@ -0,0 +1,382 @@
|
||||
extends SceneTree
|
||||
## M1c: weather, shelter, snap building + support + dismantling, doors, beds
|
||||
## and sleeping, resource regrowth, and saving all of it.
|
||||
## Run: godot --headless --path . --script res://tests/test_weather_building.gd
|
||||
|
||||
var _fails := 0
|
||||
var _finished := false
|
||||
var A: Node
|
||||
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 _spawn_world() -> Node3D:
|
||||
var w: Node3D = load("res://scenes/world.tscn").instantiate()
|
||||
root.add_child(w)
|
||||
for _i in range(5):
|
||||
await physics_frame
|
||||
return w
|
||||
|
||||
func _settle() -> void:
|
||||
for _i in range(3):
|
||||
await physics_frame
|
||||
|
||||
func _run() -> void:
|
||||
A = root.get_node("Actions")
|
||||
S = root.get_node("Saves")
|
||||
DB = root.get_node("ItemDB")
|
||||
S.root_dir = "user://test_worlds"
|
||||
for w in S.list_worlds():
|
||||
S.delete_world(w.id)
|
||||
var id: String = S.create_world("Huts", 1337)
|
||||
S.current_id = id
|
||||
_weather_model()
|
||||
var world: Node3D = await _spawn_world()
|
||||
await _body(world)
|
||||
check(_finished, "test ran to the end (no script errors)")
|
||||
S.delete_world(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)
|
||||
|
||||
## The weather model on its own: deterministic and with sensible odds.
|
||||
func _weather_model() -> void:
|
||||
print("weather model")
|
||||
var dn: Node = load("res://scripts/world/day_night.gd").new()
|
||||
var wa: Node = load("res://scripts/world/weather.gd").new()
|
||||
var wb: Node = load("res://scripts/world/weather.gd").new()
|
||||
wa.setup(1337, dn)
|
||||
wb.setup(1337, dn)
|
||||
var same := true
|
||||
var counts := {}
|
||||
var cold_days := 0
|
||||
var snow_on_warm_day := false
|
||||
var days := 60
|
||||
var t := 0.0
|
||||
while t < days * 1200.0:
|
||||
var s: String = wa.state_at(t)
|
||||
if s != wb.state_at(t):
|
||||
same = false
|
||||
counts[s] = counts.get(s, 0) + 1
|
||||
var day := int(t / 1200.0) + 1
|
||||
if s in ["snow", "blizzard"] and not wa.is_cold_day(day):
|
||||
snow_on_warm_day = true
|
||||
t += 20.0
|
||||
for d in range(1, days + 1):
|
||||
if wa.is_cold_day(d):
|
||||
cold_days += 1
|
||||
var total := float(days * 60)
|
||||
var frac := func(names: Array) -> float:
|
||||
var n := 0
|
||||
for k in names:
|
||||
n += counts.get(k, 0)
|
||||
return n / total
|
||||
check(same, "same seed and time give the same weather")
|
||||
check(frac.call(["clear"]) > 0.3 and frac.call(["clear"]) < 0.5, "clear about 40%% of the time (%.0f%%)" % (frac.call(["clear"]) * 100))
|
||||
var wet: float = frac.call(["rain", "storm", "snow", "blizzard"])
|
||||
check(wet > 0.2 and wet < 0.4, "rain or snow about 30%% of the time (%.0f%%)" % (wet * 100))
|
||||
var hard: float = frac.call(["storm", "blizzard"])
|
||||
check(hard > 0.04 and hard < 0.16, "storms about 10%% of the time (%.0f%%)" % (hard * 100))
|
||||
check(cold_days >= 8 and cold_days <= 24, "about a quarter of days are cold (%d of %d)" % [cold_days, days])
|
||||
check(not snow_on_warm_day, "it only snows in the valley on cold days")
|
||||
check(wa.state_at(0.0) == "clear" and wa.state_at(500.0) == "clear", "a new world starts with clear weather")
|
||||
var other: Node = load("res://scripts/world/weather.gd").new()
|
||||
other.setup(99, dn)
|
||||
var differs := false
|
||||
t = 0.0
|
||||
while t < 20 * 1200.0 and not differs:
|
||||
differs = other.state_at(t) != wa.state_at(t)
|
||||
t += 60.0
|
||||
check(differs, "a different seed gives different weather")
|
||||
check(wa.snows_at(Vector3(0, wa.snowline + 1.0, 0)), "above the snowline it snows")
|
||||
for n in [dn, wa, wb, other]:
|
||||
n.free()
|
||||
|
||||
func _body(world: Node3D) -> void:
|
||||
var player: CharacterBody3D = world.get_node("Player")
|
||||
var sv: Node = player.survival
|
||||
var dn: Node = world.day_night
|
||||
var wx: Node = world.weather
|
||||
var bs: Node = world.build
|
||||
var inv: Inventory = player.inventory
|
||||
var terrain: Node3D = world.get_node("Terrain")
|
||||
var props: Node3D = world.get_node("Props")
|
||||
player.set_physics_process(false)
|
||||
sv.set_process(false)
|
||||
wx.set_process(false)
|
||||
wx.force("clear")
|
||||
|
||||
# A flat-ish spot a little way from the spawn, facing the same way.
|
||||
var fwd := -player.global_basis.z
|
||||
var site: Vector3 = bs.debug_find_site(player.global_position + fwd * 12.0, 3.0)
|
||||
var yaw: float = player.rotation.y
|
||||
|
||||
print("snapping")
|
||||
var floor_def: Dictionary = DB.get_item("wood_floor")
|
||||
var tb: Node = terrain.get_node("TerrainBody")
|
||||
var r0: Dictionary = bs.placement(floor_def, {"position": site, "normal": Vector3.UP, "collider": tb}, yaw)
|
||||
check(r0.ok, "a floor can go on open ground (%s)" % r0.reason)
|
||||
var f0: Node3D = world.spawn_piece(floor_def, r0.xf)
|
||||
await _settle()
|
||||
check(f0.grounded and f0.support == 1.0, "a floor on the ground is fully supported")
|
||||
var right: Vector3 = f0.global_basis.x
|
||||
var aim := f0.global_position + right * 1.6
|
||||
var r1: Dictionary = bs.placement(floor_def, {"position": aim, "normal": Vector3.UP, "collider": f0}, yaw + 0.2)
|
||||
var off: Vector3 = r1.xf.origin - f0.global_position
|
||||
check(r1.ok and r1.snapped and off.distance_to(right * 2.0) < 0.01, "the next floor snaps exactly 2 m along (%s)" % off)
|
||||
check(absf(r1.xf.basis.get_euler().y - f0.global_basis.get_euler().y) < 0.01, "and lines up with the first despite a skewed aim")
|
||||
var wall_def: Dictionary = DB.get_item("wood_wall")
|
||||
var aimw := f0.global_position - f0.global_basis.z * 0.9
|
||||
var rw: Dictionary = bs.placement(wall_def, {"position": aimw, "normal": Vector3.UP, "collider": f0}, yaw)
|
||||
var edge := f0.global_position - f0.global_basis.z * 1.0
|
||||
check(rw.ok and rw.xf.origin.distance_to(edge) < 0.01, "a wall snaps onto the floor's edge")
|
||||
var air := site + Vector3(0, 6, 0)
|
||||
var ra: Dictionary = bs.placement(floor_def, {"position": air, "normal": Vector3.UP, "collider": null}, yaw)
|
||||
check(not ra.ok and ra.reason == "Not enough support", "a floor in mid-air has no support (%s)" % ra.reason)
|
||||
var rblock: Dictionary = bs.placement(floor_def, {"position": f0.global_position, "normal": Vector3.UP, "collider": f0}, yaw)
|
||||
check(not rblock.ok or rblock.xf.origin.distance_to(f0.global_position) > 1.0, "can't place a floor on top of another one")
|
||||
world.remove_piece(f0)
|
||||
await _settle()
|
||||
|
||||
print("hut")
|
||||
var hut: Dictionary = await bs.debug_build_hut(site, yaw)
|
||||
await _settle()
|
||||
check(hut.placed == 20 and hut.failed.is_empty(), "built a 20-piece hut by snapping (%d placed, failed: %s, blocker %s)" % [hut.placed, hut.failed, bs.last_blocker])
|
||||
var weakest := 1.0
|
||||
for p in hut.pieces:
|
||||
weakest = minf(weakest, p.support)
|
||||
check(weakest >= bs.MIN_SUPPORT, "every hut piece is supported (weakest %.2f)" % weakest)
|
||||
var inside: Vector3 = hut.pieces[0].global_position + hut.pieces[0].global_basis * Vector3(1, 0.05, 1)
|
||||
var outside: Vector3 = site + fwd * -6.0
|
||||
outside.y = terrain.sample_height(outside.x, outside.z)
|
||||
check(bs.roofed_at(inside) and bs.enclosure_at(inside) > 0.99 and bs.sheltered_at(inside), "inside the hut is sheltered")
|
||||
check(not bs.roofed_at(outside) and not bs.sheltered_at(outside), "outside is not")
|
||||
|
||||
print("rain and shelter")
|
||||
dn.time = 0.5
|
||||
dn.apply(true)
|
||||
wx.force("rain")
|
||||
player.global_position = outside
|
||||
sv.wet = 0.0
|
||||
sv._shelter_left = 0.0
|
||||
sv.step(0.5)
|
||||
check(sv.wet > 0.0 and "Wet" in sv.statuses, "rain soaks you outside")
|
||||
player.global_position = inside
|
||||
sv.wet = 0.0
|
||||
sv._shelter_left = 0.0
|
||||
sv.step(0.5)
|
||||
check(sv.wet == 0.0 and sv.sheltered and "Sheltered" in sv.statuses, "inside the hut you stay dry")
|
||||
check(world.air_warmth(inside, true) > world.air_warmth(outside, false) + 8.0, "the hut keeps out the weather's chill")
|
||||
var fire_out: Node = world.spawn_piece(DB.get_item("campfire"), Transform3D(Basis(), outside + right * 3.0))
|
||||
var fire_in: Node = world.spawn_piece(DB.get_item("campfire"), Transform3D(Basis(), inside + right * 0.2))
|
||||
check(fire_out.burn_rate() > 2.0 and fire_in.burn_rate() == 1.0, "rain eats an open fire's fuel, not one under a roof (%.1fx)" % fire_out.burn_rate())
|
||||
sv._rest_build = 0.0
|
||||
for _i in range(25):
|
||||
sv.step(1.0)
|
||||
check(sv.rested > 0.0 and "Rested" in sv.statuses, "resting by a fire in the hut makes you Rested")
|
||||
world.remove_piece(fire_out)
|
||||
wx.force("storm")
|
||||
wx.step(30.0)
|
||||
check(wx.lightning_count > 0, "storms bring lightning")
|
||||
wx.force("snow")
|
||||
player.global_position = outside
|
||||
sv.wet = 0.0
|
||||
sv._shelter_left = 0.0
|
||||
sv.step(0.5)
|
||||
check(sv.wet == 0.0, "snow makes you cold, not wet")
|
||||
check(world.air_warmth(outside) < world.air_warmth(inside, true) - 10.0, "snow is colder outside than in the hut")
|
||||
wx.force("clear")
|
||||
|
||||
print("door")
|
||||
var door: Node = null
|
||||
for p in hut.pieces:
|
||||
if p.item_id == "wood_door":
|
||||
door = p
|
||||
player.global_position = door.global_position + door.global_basis.z * 1.5
|
||||
var panel_ray := func() -> bool:
|
||||
var from: Vector3 = door.global_position + door.global_basis.z * 0.6 + Vector3(0, 1.0, 0)
|
||||
var q := PhysicsRayQueryParameters3D.create(from, from - door.global_basis.z * 1.2, 1)
|
||||
return not world.get_world_3d().direct_space_state.intersect_ray(q).is_empty()
|
||||
check(panel_ray.call(), "a closed door blocks the doorway")
|
||||
check(A.toggle_door(player, door) and door.open, "E opens the door")
|
||||
door.set_open(true, false)
|
||||
await _settle()
|
||||
check(not panel_ray.call(), "an open door lets you through")
|
||||
door.set_open(false, false)
|
||||
await _settle()
|
||||
|
||||
print("support and collapse")
|
||||
# A lone wall on a floor, with floors stacked off its top as an overhang:
|
||||
# they reach out a few pieces, then run out of support.
|
||||
var s2: Vector3 = bs.debug_find_site(site + right * 16.0, 6.0) - fwd * 4.0
|
||||
s2.y = terrain.sample_height(s2.x, s2.z)
|
||||
var rg: Dictionary = bs.placement(floor_def, {"position": s2, "normal": Vector3.UP, "collider": tb}, yaw)
|
||||
var g: Node3D = world.spawn_piece(floor_def, rg.xf)
|
||||
await physics_frame
|
||||
var rtw: Dictionary = bs.placement(wall_def, {"position": g.global_position - g.global_basis.z * 0.9, "normal": Vector3.UP, "collider": g}, yaw)
|
||||
check(rg.ok and rtw.ok, "a test wall on its own floor (%s %s)" % [rg.reason, rtw.reason])
|
||||
var tw: Node3D = world.spawn_piece(wall_def, rtw.xf)
|
||||
await physics_frame
|
||||
var out_dir: Vector3 = -tw.global_basis.z
|
||||
var chain: Array = []
|
||||
var top: Vector3 = tw.global_position + Vector3(0, 2, 0)
|
||||
var stopped := -1
|
||||
var stop_reason := ""
|
||||
for i in range(8):
|
||||
var rc: Dictionary = bs.placement(floor_def, {"position": top + out_dir * (1.0 + 2.0 * i), "normal": Vector3.UP, "collider": tw}, yaw)
|
||||
if not rc.ok:
|
||||
stopped = i
|
||||
stop_reason = rc.reason
|
||||
break
|
||||
chain.append(world.spawn_piece(floor_def, rc.xf))
|
||||
await physics_frame
|
||||
check(stopped >= 2 and stopped <= 5 and stop_reason == "Not enough support", "an overhang reaches %d floors before it runs out of support (%s %s)" % [stopped, stop_reason, bs.last_blocker])
|
||||
if chain.size() >= 2:
|
||||
check(chain[-1].support < chain[0].support, "support falls off along the overhang (%.2f -> %.2f)" % [chain[0].support, chain[-1].support])
|
||||
inv.load_data(Inventory.new(inv.size()).to_data())
|
||||
player.global_position = tw.global_position + out_dir * -1.5
|
||||
var n_chain := chain.size()
|
||||
check(A.dismantle(player, tw), "X dismantles a wall")
|
||||
await _settle()
|
||||
var gone := 0
|
||||
for p in chain:
|
||||
if not is_instance_valid(p) or p.get_parent() == null:
|
||||
gone += 1
|
||||
check(gone == n_chain and n_chain > 0, "the overhang it held up collapses (%d of %d)" % [gone, n_chain])
|
||||
check(inv.count("wood_wall") == 1 and inv.count("wood_floor") == n_chain, "dismantled and collapsed pieces come back to you")
|
||||
world.remove_piece(g)
|
||||
|
||||
# Taking one wall out of the hut: the roof is tied in by the other walls,
|
||||
# the ridge and the gables, so nothing falls.
|
||||
var hut_wall: Node3D = hut.pieces[4] # north wall, west half (see debug_build_hut)
|
||||
var hut_floor: Node3D = hut.pieces[0]
|
||||
player.global_position = inside
|
||||
check(hut_wall.item_id == "wood_wall" and A.dismantle(player, hut_wall), "dismantled one hut wall")
|
||||
await _settle()
|
||||
var roofs_left := 0
|
||||
for p in world.get_node("Pieces").get_children():
|
||||
if p.item_id == "wood_roof":
|
||||
roofs_left += 1
|
||||
check(roofs_left == 4, "the roof still stands (%d of 4 roof pieces)" % roofs_left)
|
||||
_hold(player, "wood_wall")
|
||||
var aimb: Vector3 = hut_floor.global_position - hut_floor.global_basis.z * 0.9
|
||||
var rb: Dictionary = bs.placement(wall_def, {"position": aimb, "normal": Vector3.UP, "collider": hut_floor}, yaw)
|
||||
check(rb.ok and A.place(player, 0, rb.xf, world), "and it goes back where it was (%s)" % rb.reason)
|
||||
await _settle()
|
||||
check(bs.sheltered_at(inside), "the hut is whole again")
|
||||
world.remove_piece(fire_in)
|
||||
var chest: Node = world.spawn_piece(DB.get_item("chest"), Transform3D(Basis(Vector3.UP, yaw), inside - right * 1.0))
|
||||
chest.inventory.add("stone", 3)
|
||||
check(not A.dismantle(player, chest), "can't dismantle a chest with things in it")
|
||||
|
||||
print("bed and sleep")
|
||||
inv.add("bed", 1)
|
||||
_hold(player, "bed")
|
||||
var bed_xf := Transform3D(Basis(Vector3.UP, yaw), inside + right * 0.9 - Vector3(0, 0.05, 0))
|
||||
check(world.placement_valid(DB.get_item("bed"), bed_xf), "a bed fits on the hut floor")
|
||||
A.place(player, 0, bed_xf, world)
|
||||
var bed: Node = null
|
||||
for p in world.get_node("Pieces").get_children():
|
||||
if p.item_id == "bed":
|
||||
bed = p
|
||||
player.global_position = bed.global_position + right * 1.0
|
||||
check(A.use_bed(player, bed) and world.has_bed_spawn and bed.is_spawn(), "E on a bed sets your spawn point")
|
||||
dn.time = 14.0 / 24.0
|
||||
check(not A.use_bed(player, bed), "can't sleep in the afternoon")
|
||||
dn.time = 22.0 / 24.0
|
||||
var day0: int = dn.day
|
||||
var hunger0: float = sv.hunger
|
||||
check(A.use_bed(player, bed), "can sleep at 22:00 in a sheltered bed")
|
||||
check(dn.day == day0 + 1 and absf(dn.hours() - 6.0) < 0.05, "woke at 06:00 the next day (%s)" % dn.clock_text())
|
||||
check(sv.rested > 0.0 and sv.hunger < hunger0 and sv.hunger > hunger0 - 15.0, "rested, and a little hungrier (%.0f -> %.0f)" % [hunger0, sv.hunger])
|
||||
sv.hunger = 0.0
|
||||
sv.health = 0.3
|
||||
player.global_position = outside
|
||||
sv.step(2.0)
|
||||
check(player.global_position.distance_to(bed.global_position) < 2.0, "after dying you wake at your bed")
|
||||
|
||||
print("regrowth")
|
||||
var tree: Node = null
|
||||
for uid in props.resource_nodes:
|
||||
var n = props.resource_nodes[uid]
|
||||
if n.kind == "tree" and not n.depleted and n.global_position.distance_to(player.global_position) > 15.0:
|
||||
tree = n
|
||||
break
|
||||
inv.add("stone_axe", 1)
|
||||
_hold(player, "stone_axe")
|
||||
player.global_position = tree.global_position + Vector3(1.5, 0, 0)
|
||||
var hits := 0
|
||||
while not tree.depleted and hits < 20:
|
||||
A.harvest_hit(player, tree, tree.global_position + Vector3(0, 1, 0))
|
||||
sv.stamina = 100.0
|
||||
hits += 1
|
||||
check(tree.depleted and props.regrow.has(tree.uid), "a felled tree starts a regrowth timer (%.0f s)" % props.regrow.get(tree.uid, 0.0))
|
||||
var pk: Node = null
|
||||
for uid in props.pickups:
|
||||
if not props.pickups[uid].taken:
|
||||
pk = props.pickups[uid]
|
||||
break
|
||||
player.global_position = pk.global_position + Vector3(0.5, 0, 0)
|
||||
A.pickup(player, pk)
|
||||
check(pk.taken and props.regrow.has(pk.uid), "a picked-up item starts one too")
|
||||
props.skip_time(1000.0)
|
||||
check(pk.taken and props.regrow.has(pk.uid), "with you standing on the spot, the item waits")
|
||||
player.global_position = outside + Vector3(0, 0, 40)
|
||||
props.skip_time(2500.0)
|
||||
check(not tree.depleted and tree.is_in_group("harvestable") and tree.collision_layer == 1, "the tree grows back after two days")
|
||||
check(not pk.taken and pk.visible, "the item comes back")
|
||||
|
||||
print("save + load")
|
||||
tree.restore(0.0)
|
||||
props.regrow[tree.uid] = 777.0
|
||||
var tree_uid: String = tree.uid
|
||||
door.set_open(true, false)
|
||||
var n_pieces := world.get_node("Pieces").get_child_count()
|
||||
var bed_pos: Vector3 = bed.global_position
|
||||
world.save_now()
|
||||
world.queue_free()
|
||||
await process_frame
|
||||
await process_frame
|
||||
world = await _spawn_world()
|
||||
player = world.get_node("Player")
|
||||
props = world.get_node("Props")
|
||||
await _settle()
|
||||
check(world.get_node("Pieces").get_child_count() == n_pieces, "all %d pieces restored" % n_pieces)
|
||||
var weakest2 := 1.0
|
||||
var door2: Node = null
|
||||
for p in world.get_node("Pieces").get_children():
|
||||
if p.is_in_group("build"):
|
||||
weakest2 = minf(weakest2, p.support)
|
||||
if p.item_id == "wood_door":
|
||||
door2 = p
|
||||
check(weakest2 >= world.build.MIN_SUPPORT, "nothing collapses on load (weakest %.2f)" % weakest2)
|
||||
check(door2 != null and door2.open, "the open door is still open")
|
||||
check(world.has_bed_spawn and world.bed_spawn.distance_to(bed_pos) < 0.05, "spawn point restored")
|
||||
check(absf(float(props.regrow.get(tree_uid, 0.0)) - 777.0) < 2.0, "regrowth timers restored")
|
||||
check(world.build.sheltered_at(inside), "the restored hut still shelters you")
|
||||
# Older saves had no regrowth timers: anything gone starts a fresh one.
|
||||
var state: Dictionary = S.load_state(S.current_id)
|
||||
state.erase("regrow")
|
||||
props.regrow.clear()
|
||||
props.restore_regrow(state.get("regrow", {}))
|
||||
check(props.regrow.has(tree_uid) and props.regrow[tree_uid] == props.REGROW_SECONDS["tree"], "old saves: gone things get a full timer")
|
||||
world.queue_free()
|
||||
_finished = true
|
||||
|
||||
func _hold(player: Node, id: String) -> void:
|
||||
var inv: Inventory = player.inventory
|
||||
for i in range(inv.size()):
|
||||
var s = inv.get_slot(i)
|
||||
if s != null and s.id == id:
|
||||
var tmp = inv.get_slot(0)
|
||||
inv.set_slot(0, s)
|
||||
inv.set_slot(i, tmp)
|
||||
break
|
||||
player.select(0)
|
||||
Reference in New Issue
Block a user