T2 tutorial: all five chapters with Old Tam, scripted dusk and demo wolf, rabbits for the hunt, title-screen Tutorial entry, pause-menu skip/leave, goodbye card; full bot playthrough test
This commit is contained in:
@@ -14,10 +14,18 @@ extends Node
|
||||
## {"felled": "tree" | "rock"} chopped down / broken
|
||||
## {"placed": item_id}, {"ate": item_id or "any"}, {"killed": kind},
|
||||
## {"butchered": kind}, {"caught": "any"}, {"slept": true}
|
||||
## {"continue": true} the player pressed Continue
|
||||
## {"continue": true} the player pressed Enter ("Continue")
|
||||
## {"shelter": true} standing under a roof with walls round you
|
||||
## {"bed_spawn": true} you've set your spawn at a bed
|
||||
## {"demo_wolf": true} the tutorial's wolf has been and gone
|
||||
## {"all": [goal, goal, ...]} every one of them
|
||||
## Markers: {"nearest": "pickup:<id>" | "tree" | "rock"}, optionally
|
||||
## {"any": [goal, goal, ...]} one of them
|
||||
## Markers: {"nearest": "pickup:<id>" | "tree" | "rock" | "bush" | "lake" |
|
||||
## "creature:<kind>" | "carcass" | "piece:<item id>"}, optionally
|
||||
## {"unless_have": {id: n}} to hide it once you've got enough.
|
||||
## Set-up, when a step comes up: {"time": hours} (the clock jumps on),
|
||||
## {"spawn": {"kind", "count", "distance"}} (calm animals round you),
|
||||
## {"demo_wolf": true} (a wolf that circles and leaves: tutorial_wolf.gd).
|
||||
##
|
||||
## Single-player only (the tutorial is your own world). The panel and the
|
||||
## in-world marker are ui/guide.gd. Progress is saved with the world.
|
||||
@@ -90,10 +98,12 @@ func _load_data() -> void:
|
||||
steps = d.get("steps", [])
|
||||
chapters = d.get("chapters", [])
|
||||
guide_name = String(d.get("guide", {}).get("name", ""))
|
||||
finish = d.get("finish", {})
|
||||
|
||||
## Who's talking you through it (data/tutorial.json "guide": Old Tam, the
|
||||
## island's hermit).
|
||||
var guide_name := ""
|
||||
var finish: Dictionary = {} ## the card at the end: title and text
|
||||
|
||||
# --- running it -----------------------------------------------------------------------
|
||||
|
||||
@@ -129,10 +139,44 @@ func _enter() -> void:
|
||||
if index >= steps.size():
|
||||
finished.emit()
|
||||
return
|
||||
_setup(steps[index].get("setup", {}))
|
||||
step_started.emit(index, steps[index])
|
||||
_marker_t = 0.0
|
||||
_check() # (done early? it ticks straight away)
|
||||
|
||||
const DemoWolf = preload("res://scripts/creatures/tutorial_wolf.gd")
|
||||
var demo_wolf: Node3D = null
|
||||
|
||||
## A step's set-up: the clock, some animals, the demo wolf.
|
||||
func _setup(su: Dictionary) -> void:
|
||||
if su.is_empty() or world == null or not is_instance_valid(world):
|
||||
return
|
||||
if su.has("time"):
|
||||
var want: float = float(su.time) / 24.0
|
||||
if world.day_night.time < want:
|
||||
world.day_night.time = want
|
||||
world.day_night.apply(true)
|
||||
if su.has("spawn"):
|
||||
var sp: Dictionary = su.spawn
|
||||
var p: Vector3 = world.player.global_position
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = int(p.x * 13.0 + p.z * 7.0)
|
||||
for i in range(int(sp.get("count", 1))):
|
||||
for _try in range(12):
|
||||
var a := rng.randf() * TAU
|
||||
var at := p + Vector3(cos(a), 0, sin(a)) * float(sp.get("distance", 12.0))
|
||||
if world.terrain.inside_boundary(at.x, at.z, 4.0) and world.terrain.sample_height(at.x, at.z) > world.terrain.water_level + 0.3:
|
||||
world.creatures.spawn(String(sp.kind), at)
|
||||
break
|
||||
if su.get("demo_wolf", false):
|
||||
if demo_wolf != null and is_instance_valid(demo_wolf):
|
||||
demo_wolf.queue_free()
|
||||
demo_wolf = DemoWolf.new()
|
||||
demo_wolf.setup("wolf", world, world.player.global_position + Vector3(20, 0, 0), 4242)
|
||||
world.add_child(demo_wolf)
|
||||
demo_wolf.begin(world.player)
|
||||
demo_wolf.gone.connect(func(): _count("demo_wolf:true", 1))
|
||||
|
||||
## The player read a "Continue" step.
|
||||
func continue_step() -> void:
|
||||
_count("continue:true", 1)
|
||||
@@ -168,15 +212,24 @@ func _advance() -> void:
|
||||
|
||||
## [[label, have, need], ...] for each part of `goal`.
|
||||
func parts(goal: Dictionary) -> Array:
|
||||
if goal.has("any"):
|
||||
var names := []
|
||||
var met := false
|
||||
for g in goal.any:
|
||||
for pp in parts(g):
|
||||
names.append(String(pp[0]))
|
||||
met = met or _met(g)
|
||||
return [[" or ".join(PackedStringArray(names)), 1.0 if met else 0.0, 1.0]]
|
||||
if goal.has("all"):
|
||||
var out := []
|
||||
for g in goal.all:
|
||||
out.append_array(parts(g))
|
||||
return out
|
||||
var need := float(goal.get("count", 1))
|
||||
for k in ["gained", "crafted", "felled", "placed", "ate", "killed", "butchered", "caught", "slept", "continue"]:
|
||||
for k in ["gained", "crafted", "felled", "placed", "ate", "killed", "butchered", "caught", "slept", "continue",
|
||||
"shelter", "bed_spawn", "demo_wolf"]:
|
||||
if goal.has(k):
|
||||
var key := "%s:%s" % [k, String(goal[k]).to_lower() if goal[k] is bool else String(goal[k])]
|
||||
var key := "%s:%s" % [k, str(goal[k]).to_lower() if goal[k] is bool else str(goal[k])] # (true -> "true")
|
||||
return [[_label(k, goal), float(counters.get(key, 0.0)), need]]
|
||||
if goal.has("walk"):
|
||||
return [["Walk", floorf(float(counters.get("walk", 0.0))), float(goal.walk)]]
|
||||
@@ -191,7 +244,13 @@ func _label(kind: String, goal: Dictionary) -> String:
|
||||
var r: Dictionary = ItemDB.recipes.get(String(goal.crafted), {})
|
||||
return ItemDB.item_name(String(r.get("output", goal.crafted)))
|
||||
"felled": return "Tree" if goal.felled == "tree" else "Boulder"
|
||||
"continue": return ""
|
||||
"continue", "shelter", "bed_spawn", "demo_wolf": return ""
|
||||
"placed": return ItemDB.item_name(String(goal.placed)) + " placed"
|
||||
"ate": return "Eaten"
|
||||
"killed": return String(goal.killed).capitalize()
|
||||
"butchered": return "Butchered"
|
||||
"caught": return "A fish"
|
||||
"slept": return "Slept"
|
||||
return kind.capitalize()
|
||||
|
||||
static func _plural(name: String, n: int) -> String:
|
||||
@@ -233,6 +292,17 @@ func _process(delta: float) -> void:
|
||||
if _marker_t <= 0.0:
|
||||
_marker_t = 0.5
|
||||
_marker = find_marker()
|
||||
poll()
|
||||
|
||||
## Goals that are states, not events: checked twice a second.
|
||||
func poll() -> void:
|
||||
if not active or world == null or not is_instance_valid(world):
|
||||
return
|
||||
var pl: Node = world.player
|
||||
if pl.survival.sheltered and float(counters.get("shelter:true", 0.0)) < 1.0:
|
||||
_count("shelter:true", 1)
|
||||
if world.has_bed_spawn and float(counters.get("bed_spawn:true", 0.0)) < 1.0:
|
||||
_count("bed_spawn:true", 1)
|
||||
|
||||
## Called by tests (no frames): moves the walk counter on by `metres`.
|
||||
func walked(metres: float) -> void:
|
||||
@@ -265,6 +335,31 @@ func find_marker() -> Vector3:
|
||||
for n in world.props.resource_nodes.values():
|
||||
if is_instance_valid(n) and n.kind == what and not n.depleted:
|
||||
cands.append(n)
|
||||
elif what == "bush":
|
||||
for b in world.props.forage.values():
|
||||
if is_instance_valid(b) and b.ripe():
|
||||
cands.append(b)
|
||||
elif what.begins_with("creature:"):
|
||||
for c in world.creatures.alive(what.substr(9)):
|
||||
cands.append(c)
|
||||
elif what == "carcass":
|
||||
for c in world.get_tree().get_nodes_in_group("carcass"):
|
||||
cands.append(c)
|
||||
elif what.begins_with("piece:"):
|
||||
for pc in world.all_pieces():
|
||||
if pc.item_id == what.substr(6):
|
||||
cands.append(pc)
|
||||
elif what == "lake":
|
||||
# The lake's shore, on your side of it.
|
||||
var c: Vector2 = world.terrain.lake_center
|
||||
var dir := Vector2(from.x - c.x, from.z - c.y)
|
||||
dir = dir.normalized() if dir.length() > 0.1 else Vector2(1, 0)
|
||||
var r: float = world.terrain.lake_radius * 0.6
|
||||
while r < world.terrain.lake_radius * 2.0 and world.terrain.sample_height(c.x + dir.x * r, c.y + dir.y * r) < world.terrain.water_level + 0.3:
|
||||
r += 0.5
|
||||
var sx: float = c.x + dir.x * r
|
||||
var sz: float = c.y + dir.y * r
|
||||
return Vector3(sx, world.terrain.sample_height(sx, sz), sz)
|
||||
for c in cands:
|
||||
var d: float = (c as Node3D).global_position.distance_to(from)
|
||||
if d < best_d:
|
||||
|
||||
Reference in New Issue
Block a user