5 Commits

17 changed files with 407 additions and 23 deletions

View File

@@ -16,7 +16,18 @@ addon SDK with guard rails.
## Status ## Status
See `docs/ROADMAP.md` for milestones and `docs/ARCHITECTURE.md` for the design. See `docs/ROADMAP.md` for milestones and `docs/ARCHITECTURE.md` for the design.
Current milestone: **M0 — playable single-player world skeleton** (scaffolding on disk; needs Godot 4 installed to open & run). **Emberwild 0.3.0** is released (download page and player guide:
https://emberwild-95k.pages.dev, builds on the Gitea releases). Built so far:
- M0-M1: the island, survival, crafting, building, weather, wildlife, hunting, fishing.
- M2: self-hosted multiplayer (Host Game, dedicated servers, rules, chat, admin).
- P1-P5 (`docs/PROGRESSION_DESIGN.md`): iron, stone building, the wolf moon and
alpha, the bear, crows and foxes, the barrow, hazards, the Old Stag. P6: a use
for every material, lights to place, dropping items, bag rows on the hotbar.
- T1, T2, T4 (`docs/TUTORIAL_DESIGN.md`): the tutorial with Old Tam (spoken),
and his tips in normal worlds.
- M4 so far: packaged builds, the update check, music, ambience, sound effects
and creature voices (`assets/audio/CREDITS.md`), the trailer and help pages.
Next: the worldlets and the journey between them (`docs/WORLDLETS_DESIGN.md`).
## Requirements ## Requirements
- **Godot 4.3+** (standard build; no C# needed — this project uses GDScript). - **Godot 4.3+** (standard build; no C# needed — this project uses GDScript).

View File

@@ -964,7 +964,7 @@
"name": "Bear Fat", "name": "Bear Fat",
"category": "resource", "category": "resource",
"stack": 20, "stack": 20,
"description": "Thick white fat. Burns long on a fire, and makes pemmican.", "description": "Thick white fat. Burns long on a fire, and makes pemmican and tallow candles.",
"mesh": "bear_fat", "mesh": "bear_fat",
"fuel": 180 "fuel": 180
}, },
@@ -1216,6 +1216,61 @@
"plants": 28 "plants": 28
} }
} }
},
{
"id": "tallow_candle",
"name": "Tallow Candle",
"category": "placeable",
"stack": 20,
"description": "Bear fat and a fibre wick. Set it on a floor or a step and it lights a small room, steadily, for good.",
"mesh": "tallow_candle",
"place": {
"piece": "lamp",
"footprint": [
0.2,
0.2
]
},
"lamp": {
"range": 7.0,
"energy": 1.2,
"height": 0.2,
"flicker": 0.08,
"flame": 0.022,
"color": [
1.0,
0.82,
0.6
]
}
},
{
"id": "torch_stand",
"name": "Torch Stand",
"category": "placeable",
"stack": 5,
"description": "A torch on a wooden stand. Lights a whole room or a doorway, and never goes out.",
"mesh": "torch_stand",
"place": {
"piece": "lamp",
"footprint": [
0.5,
0.5
]
},
"lamp": {
"range": 11.0,
"energy": 1.6,
"height": 1.45,
"flicker": 0.22,
"flame": 0.06,
"color": [
1.0,
0.74,
0.5
],
"shadows": true
}
} }
] ]
} }

View File

@@ -658,6 +658,29 @@
"berries": 3, "berries": 3,
"bear_fat": 1 "bear_fat": 1
} }
},
{
"id": "tallow_candle",
"output": "tallow_candle",
"count": 4,
"station": "",
"category": "furniture",
"inputs": {
"bear_fat": 1,
"fiber": 1
}
},
{
"id": "torch_stand",
"output": "torch_stand",
"count": 1,
"station": "",
"category": "furniture",
"inputs": {
"wood": 2,
"stick": 2,
"fiber": 2
}
} }
] ]
} }

View File

@@ -1,6 +1,26 @@
# Progression and challenges design # Progression and challenges design
Status: **P1-P5 built** (iron, stone, adversaries, hazards, the Old Stag). Status: **P1-P6 built** (iron, stone, adversaries, hazards, the Old Stag,
and uses for every material).
*As built in P6 (0.3.0, every material earns its place):*
- Clay jar (8 clay, fired at a campfire): 6 slots, food inside ages at a
third of the rate (`Inventory.keep_mult`, which moves the "rot" stamps).
- Bedroll (3 hide, 6 feathers, 4 fibre): a bed (group "bed", like the bed
everywhere a spawn bed is looked for) that needs no shelter, only no rain
or snow on it.
- Wolf-pelt rug (3 wolf pelts): a rug, so Cozy like the fur rug.
- Fang necklace (2 fangs, 1 alpha pelt): a "charm" carried anywhere in the
bag; hits whose cause names a wolf do 70% (`Actions.charm_mult`).
- Bear cloak (2 bear hide, 2 hide): +40 warmth, the warmest.
- Pemmican (2 cooked meat, 3 berries, 1 bear fat at a campfire, makes 3):
meat and greens, keeps 30 days.
- Rotten food is fishing bait: carried, bite waits x0.6, a bite uses one.
- Lights to place (after 0.3.0): tallow candle (bear fat and fibre, makes 4,
7 m) and torch stand (11 m, shadows), always lit (`lamp.gd`).
- `Q` drops what you hold (shift: one), at your feet, online too; `K`/`M`
move the hotbar up and down the bag's five rows (any slot can be held).
- `tests/test_items_more.gd` checks every material has a use.
*As built in P1:* *As built in P1:*
- Iron nuggets (70, in the scree under the rim), clay (60, on the lake - Iron nuggets (70, in the scree under the rim), clay (60, on the lake

View File

@@ -85,8 +85,18 @@ phases M2.0-M2.7).
## M4 — Content & polish · ongoing ## M4 — Content & polish · ongoing
- More biomes, creatures, items, building pieces - More biomes, creatures, items, building pieces
- PvP / raiding rules, building damage - PvP / raiding rules, building damage
- Better UI, settings, controls - Better UI, settings, controls. *Done so far:* volume sliders, tips and
- Audio, music, weather update settings in the pause menu; `Q` drops items; `K`/`M` put any bag row
on the 1-8 keys (0.3.0).
- Audio, music, weather. *Done (0.2.0-0.2.1):* mood music (calm, night,
storm, danger, boss) that crossfades with what's happening, ambience (CC0
birdsong, crickets, owls, fire), sound effects for every action (Kenney
CC0 plus synthesised), creature voices, Old Tam's spoken lines (Kokoro).
Sources and licences: `assets/audio/CREDITS.md`; tools in `tools/audio/`
and `tools/voice/`.
- Help: the player guide on the download site (`tools/site/`), written
sections plus item, recipe, creature, fish and server-setting tables made
from the game's own data each build. The trailer (`tools/trailer/`).
- Packaged builds for Win/Mac/Linux (+ Mac signing/notarisation). - Packaged builds for Win/Mac/Linux (+ Mac signing/notarisation).
*First cut done:* `tools/export.sh` makes a universal macOS .app (ad-hoc *First cut done:* `tools/export.sh` makes a universal macOS .app (ad-hoc
signed, not notarised) and a Windows x86_64 .exe + .pck (unsigned), zipped signed, not notarised) and a Windows x86_64 .exe + .pck (unsigned), zipped

View File

@@ -164,3 +164,146 @@ its edge or animals that spawn in the sea.
wolves sooner, or keep the same grace period everywhere? wolves sooner, or keep the same grace period everywhere?
4. **Which second worldlet first:** Cove (sea fishing, the beach you 4. **Which second worldlet first:** Cove (sea fishing, the beach you
mentioned) is my suggestion. mentioned) is my suggestion.
## 10. The journey: tunnelling out (proposal, Nov 2026)
A goal for the game, from Paul: once you have a good enough pick, you tunnel
through the mountain rim into the next, slightly harder worldlet, and now and
then go back to earlier ones for what only they have. This is option B
(linked worldlets) from §2, reached by digging rather than a path.
### The shape of it
- **A chain of worldlets**, each a little harder: Valley (today's island) →
Cove (sea, salt, mackerel) → Highlands (cold, deep snow, bigger packs) →
Marsh (bog iron, sickness, mists) → the Ember, the volcanic heart the game
is named for: the end of the journey.
- **The goal**, something to tell players on day 1: Old Tam talks of the
land beyond the mountains, where the fire in the earth still burns. Reach
the Ember and light the old beacon there. (Whether there's an ending or
it's open-ended is a question below.)
- **Gates:** each tunnel needs the best tool from the worldlet you're in.
The antler pickaxe (from the Old Stag) opens the Valley's tunnel; the
Highlands need a pick made from something found in the Cove, and so on.
Each worldlet has its own boss or trial, like the Old Stag.
### A different crossing each time (Paul, Nov 2026)
Players have to work out how to get from each worldlet to the next, and
it's a different way every time: tunnelling, climbing, burrowing, sailing.
Each crossing is that worldlet's puzzle, and what it needs comes from what
you've learned and gathered there:
| From → to | Crossing | Needs (a first idea) |
|---|---|---|
| Valley → Cove | **Tunnel** through the rim at a seam | the antler pickaxe, timber props, light |
| Cove → Highlands | **Sail** out of the bay and round the headland | a boat (planks, nails, a hide sail, rope), calm weather, food that keeps |
| Highlands → Marsh | **Climb** a frozen cliff | climbing picks and rope, warmth to last the climb (bear cloak, pemmican) |
| Marsh → Ember | **Burrow** under the old lava crust | a digging tool from bog iron, and a way through the heat and gas |
- **The hint, not the answer:** each worldlet shows its way out without
explaining it: a seam in the rock with old pick marks, a wrecked boat on
the beach, rope frayed on a cliff ledge. Old Tam (and his tips) nudge,
and the guide can keep it vague.
- **Coming back** uses the same crossing in reverse (the tunnel stays dug,
the boat stays moored, the rope stays fixed), so supply runs get easier
once you've made the way.
- **New systems this needs, one per crossing:** digging into a rim seam
(tunnel and burrow share most of it), a boat you build and steer on the
water, and climbing a marked face. Each is a phase of its own (J1 tunnel;
later the sail, the climb, the burrow).
### Tunnelling (the first crossing)
- **Where:** each worldlet's rim has one or two **seams**: cracked, darker
rock faces with a few loose stones at the foot, findable by exploring
(and Old Tam's tips). The rim stays a wall everywhere else.
- **How:** hit a seam with a good enough pick, many times. Each stretch dug
needs **timber props** (wood) or it falls back in, and it's dark in there:
candles, torch stands or a lantern. The tunnel is a short, built-for-it
cave (a few bends, maybe a side chamber with ore or a hazard), so it
stays hand-made rather than a hole through the terrain mesh, which the
heightmap can't do.
- **Through:** the far end opens onto the next worldlet. Walking out loads
it (a short fade, "The Cove"), with your bag, cloak and stats as they are.
### Why go back
- **Each worldlet has things the others lack.** Clay and salt only in the
Valley and Cove; little wood in the Highlands; no bears outside the
Valley (so bear fat for candles and pemmican is a supply run); a fish or
herb only one place has. Food that keeps (pemmican, salted and smoked
meat, clay jars) becomes what you carry between them.
- **Your bases stay** where you built them: each worldlet keeps its own
save file, so your Valley hut, chests and jars are waiting.
- **Time:** a worldlet you're not in stands still (as a server does with
nobody on), so a base you left doesn't starve or get raided.
### Building it (phases)
| Phase | Delivers | Done when |
|---|---|---|
| **W0-W2** | the worldlet groundwork above (terrain pipeline, worldlet in the save, several water bodies) | as §8 |
| **J1** | seams in the rim, digging with props and light, a tunnel scene, travel between two worldlets in single-player, per-worldlet save files | dig through from the Valley, walk to the Cove, walk back to your hut |
| **W3** | the Cove (§5) as the second worldlet | its suites pass, and its tunnel back works |
| **J2** | gates and supply needs: tool tiers per tunnel, resources only some worldlets have | a test bot can't open the second tunnel without the Cove's pick |
| **J3** | multiplayer travel (see below) | two players in different worldlets on one server, or the rule we choose |
| **J4** | Highlands, Marsh, the Ember and its ending | the journey can be finished |
### Multiplayer: the hard part
A server today runs one island. Options:
- **Everyone together:** travel needs every player at the tunnel (like the
sleep rule). Simplest; one worldlet runs at a time.
- **Spread out:** the server runs each worldlet that has someone in it.
The most freedom, and the most work and CPU.
- **Party leader:** whoever goes through brings the others with a vote.
### Decisions (Paul, Nov 2026)
- **Extendable, not finished:** the Ember has a last boss, and after it you
keep playing. The chain is built so more worldlets (and crossings) can be
added after the Ember later: each worldlet names the next, nothing assumes
the Ember is the end.
- **The crossings:** tunnel, sail, climb, burrow, in that order.
- **Harder by biome, not just by numbers:** each worldlet has its own,
stronger animals and its own hazards. The Marsh, for one: mosquitoes whose
bites can make you sick, and bog, mist and the like. Others in the same
spirit per biome (to design with each worldlet).
- **Multiplayer: spread out.** Each player goes where they like; the server
runs every worldlet someone is in, and the rest stand still. Built in J3,
after single-player travel works. What that means for the code:
- one server process holds several worldlet scenes, each with its own
clock, weather, creatures and save file;
- every player belongs to one worldlet at a time, and the server only
sends them that worldlet's world, creatures, sounds and chat-free events
(chat and the player list stay server-wide);
- a crossing moves a player from one worldlet to another (save their
place, load or wake the other worldlet, send them its world);
- sleep: "everyone in bed" counts the players in that worldlet only;
- the Old Stag and other bosses scale with the players in their worldlet.
- **Boats: one each, no passengers.** A boat belongs to whoever built it
(like a bed): only the owner, and their friends under the server's locks
rule, can sail it, and nobody else can dismantle it. It stays moored where
it was last left (the Cove end or the Highlands end), so nobody is
stranded by someone else taking the only boat. Lose or wreck it and you
build another, which is harder where wood is scarce (the Highlands), so
plan the trip back. Each player has to earn their own crossing by boat.
- **The other crossings are shared:** a dug tunnel, a fixed climbing rope
and a dug burrow are changes to the world, there for everyone once one
player has made them (like a door or a bridge).
### Server cost, measured (0.3.1)
One worldlet, a dedicated server on the M5 Pro, never pausing: about
**12% of one CPU core and 270 MB of memory**, with nobody on. Players add
creatures, weather and pickups around them. So with *spread out*, the worst
case of 8 players in all 5 worldlets is about five of today's servers in
one process: roughly 1.4 GB and a little over half a core before players,
and worldlets with nobody in them stand still and cost next to nothing.
Comfortable on the home Mac; a small cloud server (2 GB) would be tight.
### (Answered) questions
1. **The ending:** a real ending at the Ember (light the beacon, a last
boss, the credits) or open-ended (the journey is the goal, keep living)?
2. **The crossings:** do the four above fit (tunnel, sail, climb, burrow),
and in that order? Should a worldlet have more than one way out (a hard
one and a long one)? And for the tunnel: only at marked seams, or
anywhere in the rim (more freedom, harder to make good tunnels)?
3. **Multiplayer:** everyone together, spread out, or a party vote?
4. **How much harder** each worldlet: stronger animals and weather only, or
new rules too (thirst in the Ember, sickness in the Marsh)?
5. **Order:** Cove second (sea fishing, gentle) as planned, or straight
into the Highlands for a harder step?

View File

@@ -29,8 +29,8 @@ application/icon_interpolation=4
application/bundle_identifier="com.pbailey.emberwild" application/bundle_identifier="com.pbailey.emberwild"
application/signature="" application/signature=""
application/app_category="Games" application/app_category="Games"
application/short_version="0.3.0" application/short_version="0.3.1"
application/version="0.3.0" application/version="0.3.1"
application/copyright="" application/copyright=""
display/high_res=true display/high_res=true
codesign/codesign=1 codesign/codesign=1
@@ -89,8 +89,8 @@ application/modify_resources=false
application/icon="" application/icon=""
application/console_wrapper_icon="" application/console_wrapper_icon=""
application/icon_interpolation=4 application/icon_interpolation=4
application/file_version="0.3.0" application/file_version="0.3.1"
application/product_version="0.3.0" application/product_version="0.3.1"
application/company_name="" application/company_name=""
application/product_name="Emberwild" application/product_name="Emberwild"
application/file_description="Emberwild" application/file_description="Emberwild"

View File

@@ -19,7 +19,7 @@ config/description="Low-poly survival game — Valheim look, Rust depth, self-ho
run/main_scene="res://scenes/title.tscn" run/main_scene="res://scenes/title.tscn"
config/features=PackedStringArray("4.7", "Forward Plus") config/features=PackedStringArray("4.7", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
config/version="0.3.0" config/version="0.3.1"
[autoload] [autoload]

64
scripts/building/lamp.gd Normal file
View File

@@ -0,0 +1,64 @@
extends "res://scripts/building/piece.gd"
## A light to place indoors (or anywhere): a tallow candle or a torch stand.
## Always lit, no fuel. Its item's "lamp" says how: {"range", "energy",
## "height" (of the flame), "flicker" (0 = steady), "color"}.
## Flames are a small additive glow; the light itself is an OmniLight3D.
var _light: OmniLight3D
var _flame: MeshInstance3D
var _noise := FastNoiseLite.new()
var _t := 0.0
var _base := 1.0
var _flicker := 0.0
var _h := 0.5
func _build() -> void:
add_to_group("lamp")
var l: Dictionary = def.get("lamp", {})
_base = float(l.get("energy", 1.0))
_flicker = float(l.get("flicker", 0.2))
_h = float(l.get("height", 0.5))
_noise.seed = int(get_instance_id() % 1000)
_noise.frequency = 2.0
_t = randf() * 100.0
_light = OmniLight3D.new()
var c: Array = l.get("color", [1.0, 0.7, 0.4])
_light.light_color = Color(c[0], c[1], c[2])
_light.light_energy = _base
_light.omni_range = float(l.get("range", 6.0))
_light.omni_attenuation = 1.3
_light.shadow_enabled = bool(l.get("shadows", false))
_light.position = Vector3(0, _h + 0.05, 0)
add_child(_light)
# The flame: a small glowing teardrop at the wick.
var m := StandardMaterial3D.new()
m.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
m.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
m.albedo_color = Color(1.0, 0.72, 0.32, 0.85)
var s := SphereMesh.new()
var fr := float(l.get("flame", 0.03))
s.radius = fr
s.height = fr * 3.2
s.radial_segments = 8
s.rings = 4
s.material = m
_flame = MeshInstance3D.new()
_flame.mesh = s
_flame.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
_flame.position = Vector3(0, _h, 0)
add_child(_flame)
func display_name() -> String:
return String(def.get("name", "Light"))
func interact_prompt(_player: Node) -> String:
return "%s (X to take it up)" % display_name()
func _process(delta: float) -> void:
if _flicker <= 0.0 or _light == null:
return
_t += delta
var f := _noise.get_noise_1d(_t * 9.0)
_light.light_energy = _base * (1.0 + f * _flicker)
_flame.scale = Vector3.ONE * (1.0 + f * _flicker * 0.8)

View File

@@ -118,6 +118,8 @@ static func build(key: String) -> ArrayMesh:
"pelt_rug": m = _rug(WOLF_FUR, 0.85) "pelt_rug": m = _rug(WOLF_FUR, 0.85)
"bear_cloak": m = _cloak(BEAR_FUR) "bear_cloak": m = _cloak(BEAR_FUR)
"clay_jar": m = _jar() "clay_jar": m = _jar()
"tallow_candle": m = _candle()
"torch_stand": m = _torch_stand()
"bedroll": m = _bedroll() "bedroll": m = _bedroll()
"fang_necklace": m = _necklace() "fang_necklace": m = _necklace()
"pemmican": m = _pebble(331, Color(0.42, 0.24, 0.16), Vector3(0.1, 0.05, 0.07), 0.15) "pemmican": m = _pebble(331, Color(0.42, 0.24, 0.16), Vector3(0.1, 0.05, 0.07), 0.15)
@@ -1033,6 +1035,29 @@ static func _jar() -> ArrayMesh:
_box(st, Vector3(0, 0.63, 0), Vector3(0.08, 0.04, 0.05), PLANK_DARK) _box(st, Vector3(0, 0.63, 0), Vector3(0.08, 0.04, 0.05), PLANK_DARK)
return P._commit(st) return P._commit(st)
## A squat tallow candle on a clay dish; the wick's top is at y = 0.2
## (lamp.gd puts the flame there).
static func _candle() -> ArrayMesh:
var st := P._begin()
var r := _rng(359)
P._cylinder(st, Vector3(0, 0.0, 0), Vector3(0, 0.02, 0), 0.08, 0.09, 8, CLAY_FIRED, r, -1.0, P._mul(CLAY_FIRED, 1.1))
P._cylinder(st, Vector3(0, 0.02, 0), Vector3(0, 0.16, 0), 0.04, 0.036, 8, Color(0.93, 0.89, 0.74), r, -1.0, Color(0.96, 0.93, 0.8))
P._cylinder(st, Vector3(0, 0.16, 0), Vector3(0, 0.19, 0), 0.004, 0.003, 4, ASH, r)
return P._commit(st)
## A torch in a wooden stand: three splayed feet, a post, the head at 1.4 m
## (lamp.gd's flame sits just above it).
static func _torch_stand() -> ArrayMesh:
var st := P._begin()
var r := _rng(367)
for i in range(3):
var a := TAU * i / 3.0
P._cylinder(st, Vector3(cos(a) * 0.28, 0.0, sin(a) * 0.28), Vector3(0, 0.45, 0), 0.03, 0.035, 5, _board(r, PLANK_DARK), r)
P._cylinder(st, Vector3(0, 0.0, 0), Vector3(0, 1.3, 0), 0.04, 0.035, 6, _board(r), r)
_box(st, Vector3(0, 0.5, 0), Vector3(0.12, 0.05, 0.12), BINDING)
P._cylinder(st, Vector3(0, 1.28, 0), Vector3(0, 1.42, 0), 0.05, 0.06, 6, CLOTH, r, -1.0, ASH)
return P._commit(st)
## A hide bedroll: a flat mat with its rolled-up end and a feather pillow. ## A hide bedroll: a flat mat with its rolled-up end and a feather pillow.
static func _bedroll() -> ArrayMesh: static func _bedroll() -> ArrayMesh:
var st := P._begin() var st := P._begin()

View File

@@ -32,7 +32,7 @@ signal clock(day: int, time: float) ## the server's time of day
const Identity = preload("res://scripts/net/identity.gd") const Identity = preload("res://scripts/net/identity.gd")
const ServerConfig = preload("res://scripts/net/server_config.gd") const ServerConfig = preload("res://scripts/net/server_config.gd")
const PROTOCOL := 14 ## 3: shared world; 4: the server decides; 5: wildlife; 6: rules and chat (M2.6); 7: the alpha, broken doors; 8: the bear; 9: crows and foxes; 10: the barrow (P3); 11: food spoilage, lightning (P4); 12: the standing stones and the Old Stag (P5); 13: sounds sent to players nearby (sfx.gd); 14: dropping items, any bag row in hand, new items (jar, bedroll, necklace, bear cloak, rug, pemmican) const PROTOCOL := 15 ## 3: shared world; 4: the server decides; 5: wildlife; 6: rules and chat (M2.6); 7: the alpha, broken doors; 8: the bear; 9: crows and foxes; 10: the barrow (P3); 11: food spoilage, lightning (P4); 12: the standing stones and the Old Stag (P5); 13: sounds sent to players nearby (sfx.gd); 14: dropping items, any bag row in hand, new items (jar, bedroll, necklace, bear cloak, rug, pemmican); 15: placed lights (candle, torch stand)
const DEFAULT_PORT := 24242 const DEFAULT_PORT := 24242
const HANDSHAKE_TIMEOUT := 10.0 const HANDSHAKE_TIMEOUT := 10.0
const JOIN_TIMEOUT := 20.0 ## includes the server building its terrain on first join const JOIN_TIMEOUT := 20.0 ## includes the server building its terrain on first join

View File

@@ -31,6 +31,7 @@ const PIECES := {
"hearth": preload("res://scripts/building/hearth.gd"), "hearth": preload("res://scripts/building/hearth.gd"),
"rug": preload("res://scripts/building/rug.gd"), "rug": preload("res://scripts/building/rug.gd"),
"jar": preload("res://scripts/building/jar.gd"), "jar": preload("res://scripts/building/jar.gd"),
"lamp": preload("res://scripts/building/lamp.gd"),
"stonecutter": preload("res://scripts/building/stonecutter.gd"), "stonecutter": preload("res://scripts/building/stonecutter.gd"),
} }
const BuildPiece = preload("res://scripts/building/build_piece.gd") const BuildPiece = preload("res://scripts/building/build_piece.gd")

View File

@@ -174,6 +174,19 @@ func _body() -> void:
break break
check(player.survival.diet.meat > d0.meat and player.survival.diet.plants > d0.plants, "it counts as meat and greens") check(player.survival.diet.meat > d0.meat and player.survival.diet.plants > d0.plants, "it counts as meat and greens")
print("lights indoors")
inv.add("bear_fat", 1)
inv.add("fiber", 1)
check(A.craft(player, "tallow_candle") and inv.count("tallow_candle") == 4, "4 tallow candles from a lump of bear fat and some fibre, by hand")
for id in ["tallow_candle", "torch_stand"]:
var lamp := _place(id, here + Vector3(-2, 0, 3))
var lights := lamp.find_children("*", "OmniLight3D", false, false)
check(lamp.is_in_group("lamp") and lights.size() == 1 and (lights[0] as OmniLight3D).omni_range >= 6.0,
"a %s, placed, gives a light (%.0f m)" % [DB.get_item(id).name.to_lower(), (lights[0] as OmniLight3D).omni_range if not lights.is_empty() else 0.0])
player.global_position = lamp.global_position + Vector3(1.0, 0, 0)
check(A.dismantle(player, lamp) and inv.count(id) >= 1, "and X takes it back up")
player.global_position = here
print("bait") print("bait")
var B: Script = load("res://scripts/fishing/bobber.gd") var B: Script = load("res://scripts/fishing/bobber.gd")
var bob: Node3D = B.new() var bob: Node3D = B.new()

View File

@@ -233,11 +233,15 @@ func _toast_has(s: String) -> bool:
return true return true
return false return false
## Teleports Alice and makes sure it sticks (test_net_life.gd's _tp says why).
func _tp(at: Vector3) -> void: func _tp(at: Vector3) -> void:
PN.ask("debug_tp", [at + Vector3(0, 0.05, 0)]) for _try in range(3):
await _until(func(): return player.global_position.distance_to(at) < 1.0, 2.0) PN.ask("debug_tp", [at + Vector3(0, 0.05, 0)])
player.set_physics_process(false) await _until(func(): return player.global_position.distance_to(at) < 1.0, 2.0)
await _sleep(0.3) player.set_physics_process(false)
await _sleep(0.5)
if player.global_position.distance_to(at) < 1.0:
return
func _give(id: String, n: int) -> int: func _give(id: String, n: int) -> int:
var before: int = player.inventory.count(id) var before: int = player.inventory.count(id)

View File

@@ -327,11 +327,18 @@ func _wolves() -> void:
# --- helpers -------------------------------------------------------------------- # --- helpers --------------------------------------------------------------------
## Teleports Alice (the server moves her) and makes sure it sticks: a last
## position update her game sent from before the move can reach the server
## after it, look like a jump, and get her moved back ("too fast"). So check
## she's still there a moment later, and go again if not.
func _tp(at: Vector3) -> void: func _tp(at: Vector3) -> void:
PN.ask("debug_tp", [at + Vector3(0, 0.05, 0)]) for _try in range(3):
await _until(func(): return player.global_position.distance_to(at) < 1.0, 2.0) PN.ask("debug_tp", [at + Vector3(0, 0.05, 0)])
player.set_physics_process(false) await _until(func(): return player.global_position.distance_to(at) < 1.0, 2.0)
await _sleep(0.3) player.set_physics_process(false)
await _sleep(0.5)
if player.global_position.distance_to(at) < 1.0:
return
func _give(id: String, n: int) -> int: func _give(id: String, n: int) -> int:
var before: int = player.inventory.count(id) var before: int = player.inventory.count(id)

View File

@@ -321,11 +321,15 @@ func _bob_slot(id: String) -> int:
return i return i
return -1 return -1
## Teleports Alice and makes sure it sticks (test_net_life.gd's _tp says why).
func _tp(at: Vector3) -> void: func _tp(at: Vector3) -> void:
PN.ask("debug_tp", [at + Vector3(0, 0.05, 0)]) for _try in range(3):
await _until(func(): return player.global_position.distance_to(at) < 1.0, 2.0) PN.ask("debug_tp", [at + Vector3(0, 0.05, 0)])
player.set_physics_process(false) await _until(func(): return player.global_position.distance_to(at) < 1.0, 2.0)
await _sleep(0.3) player.set_physics_process(false)
await _sleep(0.5)
if player.global_position.distance_to(at) < 1.0:
return
func _give(id: String, n: int) -> int: func _give(id: String, n: int) -> int:
var before: int = player.inventory.count(id) var before: int = player.inventory.count(id)

View File

@@ -199,6 +199,10 @@ Stone loses much less support going up, so it builds tall. Wood can sit on stone
You're **Sheltered** with a roof overhead and walls in at least six of eight directions. A roof alone keeps the rain off you and your fire. A campfire works fine indoors. The **stone hearth** warms a room half as wide again as a campfire, its chimney keeps the rain out, and it cooks. A **fur rug** in a sheltered room makes you Cozy. You're **Sheltered** with a roof overhead and walls in at least six of eight directions. A roof alone keeps the rain off you and your fire. A campfire works fine indoors. The **stone hearth** warms a room half as wide again as a campfire, its chimney keeps the rain out, and it cooks. A **fur rug** in a sheltered room makes you Cozy.
### Lights
Indoors at night it's dark unless something is burning. A **tallow candle** (a lump of bear fat and a fibre, by hand, makes 4) lights a small room with a steady glow for about 7 m. A **torch stand** (2 wood, 2 sticks, 2 fibre, by hand) flickers like a torch and lights about 11 m: a whole hut or a doorway. Both stay lit for good, need no fuel and give no warmth. `X` takes them back up.
### Beds ### Beds
`E` on a bed makes it your spawn point. Once it's yours, `E` sleeps until 06:00, if it's between 19:30 and 05:30, the bed is sheltered, and no wolves are prowling within 40 m. A **bedroll** (3 hide, 6 feathers, 4 fibre) works the same but needs no shelter: you can sleep under the open sky, as long as it isn't raining or snowing. While you sleep, fires burn, kilns and smelters work, things regrow and food spoils. You wake dry, warm, full of stamina, Rested and a little hungrier, and the game saves. `E` on a bed makes it your spawn point. Once it's yours, `E` sleeps until 06:00, if it's between 19:30 and 05:30, the bed is sheltered, and no wolves are prowling within 40 m. A **bedroll** (3 hide, 6 feathers, 4 fibre) works the same but needs no shelter: you can sleep under the open sky, as long as it isn't raining or snowing. While you sleep, fires burn, kilns and smelters work, things regrow and food spoils. You wake dry, warm, full of stamina, Rested and a little hungrier, and the game saves.