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:
@@ -43,6 +43,8 @@ The tutorial island is calm: no wolf pack (just one demonstration wolf), no bear
|
||||
- `1` to `8` or the mouse wheel pick a hotbar slot.
|
||||
- `K` and `M` move the hotbar up and down your bag's rows, so `1` to `8` pick from the row above or below. The hotbar shows which row you're on.
|
||||
- `Q` drops what you're holding at your feet; `Shift` and `Q` drops just one.
|
||||
- `N` opens the map. It starts blank and fills in as you explore, marking the places you've found, your camp and your bed. You're the white dot.
|
||||
- The compass across the top of the screen shows which way you're facing.
|
||||
- `Tab` or `I` opens your inventory and crafting.
|
||||
- In the inventory: `Left click` picks up or puts down a stack, `Right click` splits a stack or puts down one, `Shift` + click moves a stack to or from a chest, `Shift` + click on **Craft** makes up to five.
|
||||
- `Esc` closes the inventory, or opens the pause menu.
|
||||
@@ -284,7 +286,7 @@ Food on the ground that nobody has been within 10 m of for 20 seconds draws a th
|
||||
|
||||
### Fishing
|
||||
|
||||
Hold a fishing rod and `Left click` to cast into the lake. The float needs water at least half a metre deep. The sea has no fish. Fish swimming near the surface, and the odd one jumping, show you where to cast.
|
||||
Hold a fishing rod and `Left click` to cast into the lake. The float needs water at least half a metre deep. The sea below the Valley's cliffs is out of reach, but the Cove has sea fishing (see [The journey](#journey)). Fish swimming near the surface, and the odd one jumping, show you where to cast.
|
||||
|
||||
- A bite comes after 5 to 14 seconds, sooner at dawn and dusk and a little sooner in rain.
|
||||
- **Bait:** with rotten food anywhere in your bag, bites come 40% sooner, and each bite uses one.
|
||||
@@ -326,7 +328,29 @@ A ring of standing stones at the foot of the rim has an altar with three hollows
|
||||
- At two thirds health it calls three spirit wolves out of the mist.
|
||||
- At one third it calls two more, moves faster and stamps the ground if you stay close.
|
||||
|
||||
It doesn't run and ignores fire. If dawn comes, or everyone leaves or dies, it fades away and the key pieces stay in the altar for another night. Beat it and it drops its antler and a trophy you can place; the key pieces are used up. At the forge, the antler, an iron bar and some wood make the **antler pickaxe**: faster than iron, longer-lasting and lighter to swing.
|
||||
It doesn't run and ignores fire. If dawn comes, or everyone leaves or dies, it fades away and the key pieces stay in the altar for another night. Beat it and it drops its antler and a trophy you can place; the key pieces are used up. At the forge, the antler, an iron bar and some wood make the **antler pickaxe**: faster than iron, longer-lasting and lighter to swing. It's also the only pick that can open the way out of the Valley.
|
||||
|
||||
## The journey {#journey}
|
||||
|
||||
The Valley is only the start. Beyond it lie other lands, each reached a different way, and each with its own ground, weather and creatures. The first is the **Cove**, down at the foot of the sea cliffs. For now the journey is single-player; on a server everyone stays in the Valley.
|
||||
|
||||
### The cliff tunnel
|
||||
|
||||
- **The seam:** on the clifftop, near the edge, is an outcrop of cracked rock. Only the **antler pickaxe** bites into it (anything less is "too hard"); about 10 blows open it. `E` on the opening goes down.
|
||||
- **Digging down:** the tunnel goes down inside the cliff in six sloping stretches, each turning back under the last like a stair, about 24 m in all. Each stretch is a rock face of about 15 blows, and knocks loose some stone.
|
||||
- **Props:** before you can dig the next stretch, prop the one you've just opened: `E` at its mouth sets a timber prop for 2 wood. Unpropped, the next face won't take a blow ("It's loose"). Nothing caves in. That's 10 wood all the way down.
|
||||
- **Light:** no daylight reaches the tunnel, so it's pitch black. Set **candles** and **torch stands** on its floor as you go; they stay, so the way is lit when you come back. It's dry and out of the weather down there.
|
||||
- **Out:** `E` on the passage at the top climbs back out onto the clifftop.
|
||||
- **The bottom:** the last stretch breaks into a sea cave. Walk out of its mouth and you're in the Cove. Your bag, what you're wearing, your health and food, and the day all come with you.
|
||||
|
||||
### The Cove
|
||||
|
||||
Low hills run down to a sheltered sandy bay, with mountains behind and a small pond inland. Wade out from the beach and it gets deeper slowly; swim too far and a strong current carries you back to the shore.
|
||||
|
||||
- **The pier** runs out over the bay. Fish off its end for **mackerel**, the usual catch. **Cod** are bigger but only bite where the water's at least 2.2 m deep, so cast well out. Both bite best at dawn and dusk, and grill at a campfire, salt or smoke like lake fish.
|
||||
- **The sea cave** at the end of the beach is the way back: `E` goes into it and up the same tunnel, arriving at the bottom.
|
||||
|
||||
Each land keeps its own state. What you build in the Valley waits for you while you're away, and time stands still in a land you're not in.
|
||||
|
||||
## Multiplayer {#multiplayer}
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@
|
||||
<li>Wolves move in, and every seventh night the moon turns red.</li>
|
||||
<li>Weather that matters: storms, blizzards and hard winters.</li>
|
||||
<li>A bear, a barrow, and the Old Stag at the standing stones.</li>
|
||||
<li>Dig down through the sea cliffs to the Cove, and fish off its pier.</li>
|
||||
<li>A map that fills in as you explore, and a compass.</li>
|
||||
<li>Play alone, or host a server for friends on your own machine.</li>
|
||||
<li>A guided tutorial with Old Tam the hermit.</li>
|
||||
</ul>
|
||||
|
||||
@@ -165,14 +165,18 @@ def ref_sections(data):
|
||||
total = sum(f["weight"] for f in w["fish"])
|
||||
total_dd = sum(f.get("dawn_dusk", f["weight"]) for f in w["fish"])
|
||||
for f in w["fish"]:
|
||||
depth = f.get("min_depth", 0) or w.get("min_depth", 0)
|
||||
rows.append(["<b>%s</b>" % esc(name(f["id"])), esc(water.title()),
|
||||
"%g m" % depth,
|
||||
"%d%%" % round(100 * f["weight"] / total),
|
||||
"%d%%" % round(100 * f.get("dawn_dusk", f["weight"]) / total_dd)])
|
||||
m = fish.get("modifiers", {})
|
||||
out.append(("fish", "Fish", "<p>What bites where. Bites come %g to %g s after the float settles, sooner at dawn "
|
||||
"(05:00-08:30) and dusk (17:00-20:30) and in rain.</p>"
|
||||
% tuple(fish["waters"]["lake"]["bite_wait"])
|
||||
+ table(["Fish", "Water", "Usually", "At dawn and dusk"], rows)))
|
||||
waits = ", ".join("%g to %g s in the %s" % (w["bite_wait"][0], w["bite_wait"][1], water)
|
||||
for water, w in fish.get("waters", {}).items())
|
||||
out.append(("fish", "Fish", "<p>What bites where. Bites come %s after the float settles, sooner at dawn "
|
||||
"(05:00-08:30) and dusk (17:00-20:30) and in rain. A fish that needs deeper water only bites "
|
||||
"where the float is at least that deep (the share shown is where it can bite).</p>"
|
||||
% waits
|
||||
+ table(["Fish", "Water", "Deep enough", "Usually", "At dawn and dusk"], rows)))
|
||||
|
||||
# Server settings.
|
||||
desc = {
|
||||
@@ -216,7 +220,7 @@ PAGE = """<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Emberwild guide: everything in the game</title>
|
||||
<meta name="description" content="How to play Emberwild: controls, survival, food, building, weather, wildlife, hunting, fishing, iron, the Old Stag and multiplayer, with every item, recipe and creature.">
|
||||
<meta name="description" content="How to play Emberwild: controls, survival, food, building, weather, wildlife, hunting, fishing, iron, the Old Stag, the journey to the Cove and multiplayer, with every item, recipe and creature.">
|
||||
<style>
|
||||
:root { --bg: #15110d; --panel: #221b14; --line: #3a2e22; --text: #f1e8da; --dim: #b8a892;
|
||||
--ember: #e8833a; --ember-hi: #f6a35c; --mark: #5a3a14; }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
extends SceneTree
|
||||
## Temporary: a ~32 s test trailer, recorded with Movie Maker:
|
||||
## The trailer (~46 s), recorded with Movie Maker (tools/trailer/make_trailer.sh):
|
||||
## godot --path . --fixed-fps 30 --write-movie out.avi --script tools/trailer/trailer.gd -- --no-creatures
|
||||
## Six shots: dawn flyover + title, morning meadow, a hut at dusk, the wolf
|
||||
## moon, a blizzard, and an end card.
|
||||
## Eight shots: dawn flyover + title, morning meadow, a hut at dusk, the wolf
|
||||
## moon, a blizzard, down the cliff tunnel, the Cove's pier, and an end card
|
||||
## over the bay (with the version from project.godot).
|
||||
|
||||
const FPS := 30.0
|
||||
const UITheme = preload("res://scripts/ui/theme.gd")
|
||||
@@ -32,6 +33,37 @@ func _run() -> void:
|
||||
H.path = "user://trailer_hints.json" # (never your real tips)
|
||||
H.load_prefs()
|
||||
H.enabled = false
|
||||
# (Its own save folder, so the Cove can be loaded as a world of its own.)
|
||||
var S: Node = root.get_node("Saves")
|
||||
S.root_dir = "user://trailer_worlds"
|
||||
_overlay()
|
||||
await _enter_world("")
|
||||
await _stage()
|
||||
await _shot_dawn()
|
||||
await _shot_meadow()
|
||||
await _shot_hut()
|
||||
await _shot_wolves()
|
||||
await _shot_blizzard()
|
||||
await _shot_tunnel()
|
||||
await _enter_world("cove")
|
||||
await _shot_cove()
|
||||
await _shot_end()
|
||||
for w in S.list_worlds():
|
||||
S.delete_world(w.id)
|
||||
DirAccess.remove_absolute(S.root_dir)
|
||||
DirAccess.remove_absolute(H.path)
|
||||
quit(0)
|
||||
|
||||
## Loads the world to film: the Valley with no save (""), or a fresh world
|
||||
## in `worldlet`. Hides the HUD and takes the camera.
|
||||
func _enter_world(worldlet: String) -> void:
|
||||
var S: Node = root.get_node("Saves")
|
||||
if world != null:
|
||||
black.modulate.a = 1.0
|
||||
world.queue_free()
|
||||
for _i in range(3):
|
||||
await process_frame
|
||||
S.current_id = "" if worldlet.is_empty() else S.create_world("Trailer", 1337, {"worldlet": worldlet})
|
||||
world = load("res://scenes/world.tscn").instantiate()
|
||||
root.add_child(world)
|
||||
for _i in range(8):
|
||||
@@ -53,16 +85,6 @@ func _run() -> void:
|
||||
cam.fov = 62
|
||||
world.add_child(cam)
|
||||
cam.current = true
|
||||
_overlay()
|
||||
await _stage()
|
||||
await _shot_dawn()
|
||||
await _shot_meadow()
|
||||
await _shot_hut()
|
||||
await _shot_wolves()
|
||||
await _shot_blizzard()
|
||||
await _shot_end()
|
||||
DirAccess.remove_absolute(H.path)
|
||||
quit(0)
|
||||
|
||||
# --- set dressing --------------------------------------------------------------------
|
||||
|
||||
@@ -261,14 +283,65 @@ func _shot_blizzard() -> void:
|
||||
if world.weather_fx != null:
|
||||
world.weather_fx.visible = false # (no leftover snow on the end card)
|
||||
|
||||
## Down the cliff tunnel (J1): dug through and propped, lit by torch stands
|
||||
## you've set, the camera walking down the first stretch in the dark.
|
||||
func _shot_tunnel() -> void:
|
||||
var t: Node3D = world.tunnel
|
||||
if t == null:
|
||||
return
|
||||
t.opened = true
|
||||
t.dug = t.STRETCHES
|
||||
for k in range(1, t.STRETCHES + 1):
|
||||
t.props[k] = true
|
||||
t._rebuild()
|
||||
var ts: Dictionary = ItemDBRef().get_item("torch_stand")
|
||||
var candle: Dictionary = ItemDBRef().get_item("tallow_candle")
|
||||
var land1: Vector3 = t.ORIGIN + Vector3(t._landing_x(1), t._floor_y(1), t._lane_z(1))
|
||||
world.spawn_piece(ts, Transform3D(Basis(), t._mouth(1) + Vector3(-0.5, 0, 0.9)))
|
||||
world.spawn_piece(ts, Transform3D(Basis(), land1 + Vector3(0.6, 0, -0.8)))
|
||||
world.spawn_piece(candle, Transform3D(Basis(), t._mouth(1) + Vector3(3.0, -2.0, -0.95)))
|
||||
world.spawn_piece(candle, Transform3D(Basis(), t._mouth(2) + Vector3(-0.4, 0, 0.95)))
|
||||
for _i in range(5):
|
||||
await physics_frame
|
||||
var from: Vector3 = t.top + Vector3(-0.6, 1.65, 0)
|
||||
var to: Vector3 = land1 + Vector3(-1.4, 1.65, 0)
|
||||
dn.underground = 1.0
|
||||
await _shot(5.5, func(_t: float, k: float):
|
||||
dn.underground = 1.0
|
||||
_clock(13.0)
|
||||
var e := k * k * (3.0 - 2.0 * k)
|
||||
var pos := from.lerp(to, e)
|
||||
cam.global_position = pos
|
||||
cam.look_at(pos + Vector3(3.0, -1.4 + e * 0.9, 0.0) + Vector3(0, 0, lerpf(0.0, 1.2, e)))
|
||||
_park_player(pos), "Dig through the cliffs.")
|
||||
dn.underground = 0.0
|
||||
|
||||
## The Cove (W3): morning over the bay, along the pier.
|
||||
func _shot_cove() -> void:
|
||||
var pier: Node3D = world.pier
|
||||
if pier == null:
|
||||
return
|
||||
var side: Vector3 = pier.dir.cross(Vector3.UP)
|
||||
var from: Vector3 = pier.end + pier.dir * 18.0 + side * 14.0 + Vector3(0, 2.5, 0)
|
||||
var to: Vector3 = pier.end + pier.dir * 6.0 + side * 7.0 + Vector3(0, 1.6, 0)
|
||||
await _shot(6.0, func(_t: float, k: float):
|
||||
_clock(8.2 + k * 0.3)
|
||||
var e := k * k * (3.0 - 2.0 * k)
|
||||
cam.global_position = from.lerp(to, e)
|
||||
cam.look_at(pier.start.lerp(pier.end, 0.35) + Vector3(0, 1.0, 0))
|
||||
_park_player(Vector3(pier.start.x, pier.start.y + 1.0, pier.start.z)), "Find what lies beyond.")
|
||||
|
||||
func _shot_end() -> void:
|
||||
big.text = "EMBERWILD"
|
||||
sub.text = "Self-hosted co-op survival · 0.1.0"
|
||||
var b := Basis(Vector3.UP, yaw)
|
||||
sub.text = "Self-hosted co-op survival · %s" % String(ProjectSettings.get_setting("application/config/version", ""))
|
||||
# Rising over the Cove's bay, looking back at the land.
|
||||
var pier: Node3D = world.pier
|
||||
var c: Vector3 = Vector3.ZERO if pier == null else pier.start
|
||||
var out: Vector3 = Vector3(0, 0, 1) if pier == null else pier.dir
|
||||
await _shot(6.0, func(t: float, k: float):
|
||||
_clock(17.6 + k * 0.4)
|
||||
cam.global_position = _g(hut_c + b * Vector3(10, 0, 9), 0.0) + Vector3(0, 6.0 + k * 10.0, 0) + b * Vector3(0, 0, k * 10.0)
|
||||
cam.look_at(hut_c + Vector3(0, 1.0, 0))
|
||||
_park_player(cam.global_position)
|
||||
cam.global_position = c + out * (40.0 + k * 14.0) + Vector3(0, 8.0 + k * 14.0, 0)
|
||||
cam.look_at(c - out * 30.0 + Vector3(0, 4.0, 0))
|
||||
_park_player(c + Vector3(0, 1.0, 0))
|
||||
big.modulate.a = _env(t, 0.8, 5.6, 0.8, 0.6)
|
||||
sub.modulate.a = _env(t, 1.6, 5.6, 0.8, 0.6), "", 0.7)
|
||||
|
||||
Reference in New Issue
Block a user