Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5e7c0e343 | ||
|
|
73599401bf | ||
|
|
a629ddba49 | ||
|
|
a3660f91cf | ||
|
|
4a691b72e1 | ||
|
|
8f3c58c810 | ||
|
|
07b8db1683 | ||
|
|
5672f0713d | ||
|
|
31f8e0d22c | ||
|
|
415e673b28 | ||
|
|
da29308522 | ||
|
|
79061d8572 | ||
|
|
1f67d58b92 | ||
|
|
c2c86b5f78 | ||
|
|
04b9d17826 | ||
|
|
3b611a6d34 | ||
|
|
27040d6805 | ||
|
|
2004fe193c | ||
|
|
9709d2c424 | ||
|
|
5bcfa23710 | ||
|
|
74bd8f5898 | ||
|
|
419c71e638 | ||
|
|
ec8caa9ef4 | ||
|
|
1bf33935e0 | ||
|
|
62b3d6c47f | ||
|
|
f9b138b304 | ||
|
|
86ebe07899 | ||
|
|
85bbed249b | ||
|
|
6544862885 | ||
|
|
109fff22d0 | ||
|
|
b938e045cb | ||
|
|
ad47cd53da | ||
|
|
0209c6149a | ||
|
|
b86fb879d2 | ||
|
|
2b7e52cd82 | ||
|
|
980ee53579 | ||
|
|
793b214786 | ||
|
|
9b88a10609 | ||
|
|
68e6d814d9 | ||
|
|
db4a659236 | ||
|
|
f4df2bc99d | ||
|
|
5584f74a8b | ||
|
|
77517be778 | ||
|
|
40f5e25c41 | ||
|
|
eaeb841c1d | ||
|
|
5f40fa3f7b | ||
|
|
4b2e687188 | ||
|
|
5778d61763 | ||
|
|
787d8d1d65 | ||
|
|
0550a4daa0 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -21,3 +21,8 @@ Thumbs.db
|
|||||||
|
|
||||||
# Cloudflare wrangler cache (tools/site deploys)
|
# Cloudflare wrangler cache (tools/site deploys)
|
||||||
.wrangler/
|
.wrangler/
|
||||||
|
|
||||||
|
# Old Tam's voice environments (tools/voice/make_voice.sh sets one up; .venv was Kokoro, before 0.6.2)
|
||||||
|
tools/voice/.venv/
|
||||||
|
tools/voice/.venv-qwen/
|
||||||
|
tools/voice/__pycache__/
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -16,7 +16,33 @@ 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).
|
**Cinderwild 0.6.2** is released (download page and player guide:
|
||||||
|
https://cinderwild.psbailey.uk, builds on the Gitea releases). Up to 0.6.0 it
|
||||||
|
was called Emberwild (a name already taken on Steam); before that, Valheimlike.
|
||||||
|
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,
|
||||||
|
the map (N) and compass.
|
||||||
|
- W0-W3, J1, J3 (`docs/WORLDLETS_DESIGN.md`): worldlets as data, the Cove
|
||||||
|
(beach, pier, sea fishing, a stream), and the cliff tunnel between them,
|
||||||
|
with a save file per worldlet; on a server, a server per land (the Valley's
|
||||||
|
starts the Cove's on the next UDP port), with chat, the player list,
|
||||||
|
admin commands and sleep across lands, and one clock everywhere.
|
||||||
|
- 0.6.0, the Cove's own (`docs/WORLDLETS_DESIGN.md` §5): the wreck, a
|
||||||
|
treasure map and a dig for each player, the drowned crew, crabs and gulls,
|
||||||
|
and a rowing boat to build and moor at the pier (sailing comes with the
|
||||||
|
Highlands). Report a bug in the pause menu; `docs/PLAYTEST.md` for testers.
|
||||||
|
Next: after the playtest, J2 (each tunnel's own gate and supplies), then the
|
||||||
|
Highlands (by sail).
|
||||||
|
Running from the source, `godot --path . -- --dev-worldlets` lets New World
|
||||||
|
start in any worldlet, to try one directly.
|
||||||
|
|
||||||
## 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).
|
||||||
@@ -36,7 +62,7 @@ godot --headless --path . -- --server
|
|||||||
|
|
||||||
## Packaged builds
|
## Packaged builds
|
||||||
```bash
|
```bash
|
||||||
tools/export.sh # build/Emberwild-<version>-macos.zip and -windows.zip
|
tools/export.sh # build/Cinderwild-<version>-macos.zip and -windows.zip
|
||||||
tools/export.sh macOS # or just one ("macOS" / "Windows")
|
tools/export.sh macOS # or just one ("macOS" / "Windows")
|
||||||
```
|
```
|
||||||
Needs the Godot export templates for your Godot version (Editor > Manage
|
Needs the Godot export templates for your Godot version (Editor > Manage
|
||||||
@@ -45,18 +71,19 @@ Export Templates). The version number is `application/short_version` in
|
|||||||
- **Mac:** a universal .app, ad-hoc signed but not notarised. On another Mac,
|
- **Mac:** a universal .app, ad-hoc signed but not notarised. On another Mac,
|
||||||
the first launch needs right-click > Open (or System Settings > Privacy &
|
the first launch needs right-click > Open (or System Settings > Privacy &
|
||||||
Security > Open Anyway).
|
Security > Open Anyway).
|
||||||
- **Windows:** unzip and run `Emberwild.exe` (keep `Emberwild.pck` beside
|
- **Windows:** unzip and run `Cinderwild.exe` (keep `Cinderwild.pck` beside
|
||||||
it). It's unsigned, so SmartScreen says "Windows protected your PC":
|
it). It's unsigned, so SmartScreen says "Windows protected your PC":
|
||||||
More info > Run anyway.
|
More info > Run anyway.
|
||||||
- A packaged copy is also a dedicated server:
|
- A packaged copy is also a dedicated server:
|
||||||
`Emberwild.app/Contents/MacOS/Emberwild --headless -- --server` (Mac) or
|
`Cinderwild.app/Contents/MacOS/Cinderwild --headless -- --server` (Mac) or
|
||||||
`Emberwild.exe --headless -- --server` (Windows). Saves and settings live in
|
`Cinderwild.exe --headless -- --server` (Windows). Saves and settings live in
|
||||||
the same user folder as a copy run from source.
|
the same user folder as a copy run from source.
|
||||||
|
|
||||||
## Trailer and download page
|
## Trailer and download page
|
||||||
```bash
|
```bash
|
||||||
tools/trailer/make_trailer.sh # build/Emberwild-trailer.mp4: recorded in-game (Movie Maker), needs ffmpeg
|
tools/trailer/make_trailer.sh # build/Cinderwild-trailer.mp4: recorded in-game (Movie Maker), needs ffmpeg
|
||||||
tools/site/make_site.sh # build/site/: index.html + poster.jpg + trailer.mp4 for the current release
|
tools/site/make_shots.sh # build/shots/*.jpg: the page's screenshots, rendered in a window
|
||||||
|
tools/site/make_site.sh # build/site/: index.html, guide.html, the shots, poster and trailer for the current release
|
||||||
```
|
```
|
||||||
The page's download links point at the Gitea release named in
|
The page's download links point at the Gitea release named in
|
||||||
`build/version.json`, so rebuild it after each `tools/export.sh` release.
|
`build/version.json`, so rebuild it after each `tools/export.sh` release.
|
||||||
|
|||||||
@@ -19,9 +19,54 @@ http://creativecommons.org/publicdomain/zero/1.0/". Thank you, Kenney.
|
|||||||
Which file is which is listed in `KENNEY` in `tools/audio/make_audio.py`,
|
Which file is which is listed in `KENNEY` in `tools/audio/make_audio.py`,
|
||||||
which also re-copies them (`--kenney <folder with the unzipped packs>`).
|
which also re-copies them (`--kenney <folder with the unzipped packs>`).
|
||||||
|
|
||||||
## Emberwild's own (CC0)
|
## SamsterBirdies (CC0)
|
||||||
|
`ambience/birds.ogg` is from "Bird ambience. Coniferous forest dawn chorus."
|
||||||
|
by SamsterBirdies (https://freesound.org/people/SamsterBirdies/sounds/745273/,
|
||||||
|
License: Creative Commons 0): its first 90 s (kept in
|
||||||
|
`tools/audio/sources/birds_samsterbirdies_745273.ogg`), softened a little and
|
||||||
|
made into a seamless loop by `tools/audio/make_audio.py birds`. Thank you.
|
||||||
|
|
||||||
|
## OwlStorm (CC0)
|
||||||
|
`ambience/crickets.ogg` is "Night Crickets Ambience on Rural Property" by
|
||||||
|
OwlStorm (https://freesound.org/people/OwlStorm/sounds/320145/, License:
|
||||||
|
Creative Commons 0; the preview is in `tools/audio/sources/`), with the low
|
||||||
|
rumble taken out and made into a seamless loop by
|
||||||
|
`tools/audio/make_audio.py crickets`. Thank you.
|
||||||
|
|
||||||
|
## Breviceps (CC0)
|
||||||
|
`ambience/owl_0.ogg` and `owl_1.ogg` are the two hoots in "Owl Hoot" by
|
||||||
|
Breviceps (https://freesound.org/people/Breviceps/sounds/465697/, License:
|
||||||
|
Creative Commons 0; the preview is in `tools/audio/sources/`), cut at 0.12 s
|
||||||
|
and 2.65 s (leaving out the short sound between them), then levelled. Thank you.
|
||||||
|
|
||||||
|
## Thunder (CC0)
|
||||||
|
`sfx/thunder/` is cut from six Freesound recordings, all License: Creative
|
||||||
|
Commons 0 (the previews are in `tools/audio/sources/thunder_*`), each round
|
||||||
|
its loudest strike, faded out and levelled by
|
||||||
|
`tools/audio/make_audio.py thunder`. Close cracks: "Thunder 08" by Emulius
|
||||||
|
(https://freesound.org/people/Emulius/sounds/744723/), "thunder11" by
|
||||||
|
saha213131 (https://freesound.org/people/saha213131/sounds/696550/) and
|
||||||
|
"Thunder strike" by AyaDrevis
|
||||||
|
(https://freesound.org/people/AyaDrevis/sounds/652690/). Distant rolls:
|
||||||
|
"Short_Thunder_Mid" by SholeColtis
|
||||||
|
(https://freesound.org/people/SholeColtis/sounds/683421/), "thunder7" by
|
||||||
|
nednednerb (https://freesound.org/people/nednednerb/sounds/38250/) and
|
||||||
|
"Thunder Clap OWB KY 441x16.wav" by Dave Welsh
|
||||||
|
(https://freesound.org/people/Dave%20Welsh/sounds/194364/). Thank you.
|
||||||
|
|
||||||
|
## Old Tam's voice
|
||||||
|
`voice/` is spoken by **Qwen3-TTS** (Qwen3-TTS-12Hz-1.7B by Alibaba's Qwen
|
||||||
|
team, Apache-2.0, https://huggingface.co/Qwen; run through mlx-audio, MIT).
|
||||||
|
His voice, a reedy old West Country farmer, was designed once from a written
|
||||||
|
description by Qwen3-TTS VoiceDesign (`tools/voice/old_tam/`), and every line
|
||||||
|
is spoken by cloning that recording. Each line's takes are checked against
|
||||||
|
the script by Whisper (OpenAI, MIT; mlx-whisper). The recordings are made from
|
||||||
|
our own text; rebuild them with `tools/voice/make_voice.sh` after changing any
|
||||||
|
line. (Up to 0.6.1 he was Kokoro-82M by hexgrad, Apache-2.0.)
|
||||||
|
|
||||||
|
## Cinderwild's own (CC0)
|
||||||
`music/`, `ambience/` and `sfx/synth/` are synthesised from scratch by
|
`music/`, `ambience/` and `sfx/synth/` are synthesised from scratch by
|
||||||
`tools/audio/make_audio.py` (no samples): the five music tracks (calm,
|
`tools/audio/make_audio.py` (no samples): the five music tracks (calm,
|
||||||
night, storm, danger, boss), the birds, crickets and fire crackle, and the
|
night, storm, danger, boss), the fire crackle, the creature voices, and the
|
||||||
swings, eating, splashes, the fishing reel and bite, the bow, the hurt thump
|
swings, eating, splashes, the fishing reel and bite, the bow, the hurt thump
|
||||||
and death. Released under CC0 like the rest of this folder.
|
and death. Released under CC0 like the rest of this folder.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
BIN
assets/audio/ambience/owl_0.ogg
Normal file
BIN
assets/audio/ambience/owl_0.ogg
Normal file
Binary file not shown.
BIN
assets/audio/ambience/owl_1.ogg
Normal file
BIN
assets/audio/ambience/owl_1.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/creatures/crab_click_0.ogg
Normal file
BIN
assets/audio/sfx/creatures/crab_click_0.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/creatures/crab_click_1.ogg
Normal file
BIN
assets/audio/sfx/creatures/crab_click_1.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/creatures/gull_cry_0.ogg
Normal file
BIN
assets/audio/sfx/creatures/gull_cry_0.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/creatures/gull_cry_1.ogg
Normal file
BIN
assets/audio/sfx/creatures/gull_cry_1.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/thunder/thunder_far_0.ogg
Normal file
BIN
assets/audio/sfx/thunder/thunder_far_0.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/thunder/thunder_far_1.ogg
Normal file
BIN
assets/audio/sfx/thunder/thunder_far_1.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/thunder/thunder_far_2.ogg
Normal file
BIN
assets/audio/sfx/thunder/thunder_far_2.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/thunder/thunder_near_0.ogg
Normal file
BIN
assets/audio/sfx/thunder/thunder_near_0.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/thunder/thunder_near_1.ogg
Normal file
BIN
assets/audio/sfx/thunder/thunder_near_1.ogg
Normal file
Binary file not shown.
BIN
assets/audio/sfx/thunder/thunder_near_2.ogg
Normal file
BIN
assets/audio/sfx/thunder/thunder_near_2.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_cold.ogg
Normal file
BIN
assets/audio/voice/hint_cold.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_cove.ogg
Normal file
BIN
assets/audio/voice/hint_cove.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_crab.ogg
Normal file
BIN
assets/audio/voice/hint_crab.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_died.ogg
Normal file
BIN
assets/audio/voice/hint_died.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_diet.ogg
Normal file
BIN
assets/audio/voice/hint_diet.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_freezing.ogg
Normal file
BIN
assets/audio/voice/hint_freezing.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_hard_winter.ogg
Normal file
BIN
assets/audio/voice/hint_hard_winter.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_howl.ogg
Normal file
BIN
assets/audio/voice/hint_howl.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_hungry.ogg
Normal file
BIN
assets/audio/voice/hint_hungry.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_night.ogg
Normal file
BIN
assets/audio/voice/hint_night.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_rotten.ogg
Normal file
BIN
assets/audio/voice/hint_rotten.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_seam.ogg
Normal file
BIN
assets/audio/voice/hint_seam.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_sheltered.ogg
Normal file
BIN
assets/audio/voice/hint_sheltered.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_sick.ogg
Normal file
BIN
assets/audio/voice/hint_sick.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_snow.ogg
Normal file
BIN
assets/audio/voice/hint_snow.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_stamina.ogg
Normal file
BIN
assets/audio/voice/hint_stamina.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_storm.ogg
Normal file
BIN
assets/audio/voice/hint_storm.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_wet.ogg
Normal file
BIN
assets/audio/voice/hint_wet.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/hint_wolf_moon.ogg
Normal file
BIN
assets/audio/voice/hint_wolf_moon.ogg
Normal file
Binary file not shown.
44
assets/audio/voice/lines.json
Normal file
44
assets/audio/voice/lines.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"hint_cold": "ac4139441b7a347a",
|
||||||
|
"hint_cove": "91eb0dcbc24afe5c",
|
||||||
|
"hint_crab": "3a88cd0772a7676d",
|
||||||
|
"hint_died": "7b60d478f6fdbcef",
|
||||||
|
"hint_diet": "b5e9600c2da38bdf",
|
||||||
|
"hint_freezing": "0a63a53be413d808",
|
||||||
|
"hint_hard_winter": "03e1316a475c3c43",
|
||||||
|
"hint_howl": "05792dda3f3f0b60",
|
||||||
|
"hint_hungry": "c99c9fbc83506c43",
|
||||||
|
"hint_night": "04fc83670a1fd9e9",
|
||||||
|
"hint_rotten": "ecb8aecde8c6c762",
|
||||||
|
"hint_seam": "05531946c57cb97d",
|
||||||
|
"hint_sheltered": "7d3ebd27c230bc79",
|
||||||
|
"hint_sick": "b039c2449304ac53",
|
||||||
|
"hint_snow": "90a2e7f6735e4c83",
|
||||||
|
"hint_stamina": "e3e6cad6202abcaa",
|
||||||
|
"hint_storm": "477b250b6a9c97cb",
|
||||||
|
"hint_wet": "ebf9b034487bf92e",
|
||||||
|
"hint_wolf_moon": "d66b43dc8a23b5ed",
|
||||||
|
"tutorial_animals": "20b2eabd8de50d58",
|
||||||
|
"tutorial_axe": "c9a632910c8eaf19",
|
||||||
|
"tutorial_bed": "46f0130e86504f5b",
|
||||||
|
"tutorial_berries": "31d4fd1560fcf46f",
|
||||||
|
"tutorial_campfire": "6a7790be69235e80",
|
||||||
|
"tutorial_cook": "180b4f1413608105",
|
||||||
|
"tutorial_diet": "d44066db5adc0f78",
|
||||||
|
"tutorial_finish": "a9960edb587c3176",
|
||||||
|
"tutorial_fish": "7945d260ceff912b",
|
||||||
|
"tutorial_flax": "7e4f439fc7079272",
|
||||||
|
"tutorial_hut": "57c364fcde4bbff9",
|
||||||
|
"tutorial_look": "4998d8b95ff4fa8e",
|
||||||
|
"tutorial_mushroom": "ae582e11c9214891",
|
||||||
|
"tutorial_pickaxe": "0697d3610e8a70c5",
|
||||||
|
"tutorial_rabbit": "62544c47f9813cc4",
|
||||||
|
"tutorial_rod": "0aeae228c3e1e2ed",
|
||||||
|
"tutorial_sleep": "51fc176c04af2683",
|
||||||
|
"tutorial_sticks": "5a350695e62a28ef",
|
||||||
|
"tutorial_torch": "4bdb8837ac441e6e",
|
||||||
|
"tutorial_tree": "efcbe43e71059ba1",
|
||||||
|
"tutorial_weapon": "4d04fcd046eae1e2",
|
||||||
|
"tutorial_wolf": "b0725af812cc8d90",
|
||||||
|
"tutorial_workbench": "dd05c80fb8b3c3b8"
|
||||||
|
}
|
||||||
45
assets/audio/voice/spoken.json
Normal file
45
assets/audio/voice/spoken.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"_voice": "c55ffaa0e19b744b",
|
||||||
|
"hint_cold": "ac4139441b7a347a",
|
||||||
|
"hint_cove": "91eb0dcbc24afe5c",
|
||||||
|
"hint_crab": "3a88cd0772a7676d",
|
||||||
|
"hint_died": "7b60d478f6fdbcef",
|
||||||
|
"hint_diet": "b5e9600c2da38bdf",
|
||||||
|
"hint_freezing": "0a63a53be413d808",
|
||||||
|
"hint_hard_winter": "03e1316a475c3c43",
|
||||||
|
"hint_howl": "05792dda3f3f0b60",
|
||||||
|
"hint_hungry": "c99c9fbc83506c43",
|
||||||
|
"hint_night": "04fc83670a1fd9e9",
|
||||||
|
"hint_rotten": "ecb8aecde8c6c762",
|
||||||
|
"hint_seam": "05531946c57cb97d",
|
||||||
|
"hint_sheltered": "7d3ebd27c230bc79",
|
||||||
|
"hint_sick": "b039c2449304ac53",
|
||||||
|
"hint_snow": "90a2e7f6735e4c83",
|
||||||
|
"hint_stamina": "e3e6cad6202abcaa",
|
||||||
|
"hint_storm": "477b250b6a9c97cb",
|
||||||
|
"hint_wet": "ebf9b034487bf92e",
|
||||||
|
"hint_wolf_moon": "d66b43dc8a23b5ed",
|
||||||
|
"tutorial_animals": "20b2eabd8de50d58",
|
||||||
|
"tutorial_axe": "c9a632910c8eaf19",
|
||||||
|
"tutorial_bed": "46f0130e86504f5b",
|
||||||
|
"tutorial_berries": "31d4fd1560fcf46f",
|
||||||
|
"tutorial_campfire": "6a7790be69235e80",
|
||||||
|
"tutorial_cook": "180b4f1413608105",
|
||||||
|
"tutorial_diet": "d44066db5adc0f78",
|
||||||
|
"tutorial_finish": "a9960edb587c3176",
|
||||||
|
"tutorial_fish": "7945d260ceff912b",
|
||||||
|
"tutorial_flax": "7e4f439fc7079272",
|
||||||
|
"tutorial_hut": "57c364fcde4bbff9",
|
||||||
|
"tutorial_look": "2345ea0b154d4014",
|
||||||
|
"tutorial_mushroom": "ae582e11c9214891",
|
||||||
|
"tutorial_pickaxe": "0697d3610e8a70c5",
|
||||||
|
"tutorial_rabbit": "62544c47f9813cc4",
|
||||||
|
"tutorial_rod": "0aeae228c3e1e2ed",
|
||||||
|
"tutorial_sleep": "51fc176c04af2683",
|
||||||
|
"tutorial_sticks": "5a350695e62a28ef",
|
||||||
|
"tutorial_torch": "4bdb8837ac441e6e",
|
||||||
|
"tutorial_tree": "efcbe43e71059ba1",
|
||||||
|
"tutorial_weapon": "4d04fcd046eae1e2",
|
||||||
|
"tutorial_wolf": "b0725af812cc8d90",
|
||||||
|
"tutorial_workbench": "dd05c80fb8b3c3b8"
|
||||||
|
}
|
||||||
BIN
assets/audio/voice/tutorial_animals.ogg
Normal file
BIN
assets/audio/voice/tutorial_animals.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_axe.ogg
Normal file
BIN
assets/audio/voice/tutorial_axe.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_bed.ogg
Normal file
BIN
assets/audio/voice/tutorial_bed.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_berries.ogg
Normal file
BIN
assets/audio/voice/tutorial_berries.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_campfire.ogg
Normal file
BIN
assets/audio/voice/tutorial_campfire.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_cook.ogg
Normal file
BIN
assets/audio/voice/tutorial_cook.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_diet.ogg
Normal file
BIN
assets/audio/voice/tutorial_diet.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_finish.ogg
Normal file
BIN
assets/audio/voice/tutorial_finish.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_fish.ogg
Normal file
BIN
assets/audio/voice/tutorial_fish.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_flax.ogg
Normal file
BIN
assets/audio/voice/tutorial_flax.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_hut.ogg
Normal file
BIN
assets/audio/voice/tutorial_hut.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_look.ogg
Normal file
BIN
assets/audio/voice/tutorial_look.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_mushroom.ogg
Normal file
BIN
assets/audio/voice/tutorial_mushroom.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_pickaxe.ogg
Normal file
BIN
assets/audio/voice/tutorial_pickaxe.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_rabbit.ogg
Normal file
BIN
assets/audio/voice/tutorial_rabbit.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_rod.ogg
Normal file
BIN
assets/audio/voice/tutorial_rod.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_sleep.ogg
Normal file
BIN
assets/audio/voice/tutorial_sleep.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_sticks.ogg
Normal file
BIN
assets/audio/voice/tutorial_sticks.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_torch.ogg
Normal file
BIN
assets/audio/voice/tutorial_torch.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_tree.ogg
Normal file
BIN
assets/audio/voice/tutorial_tree.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_weapon.ogg
Normal file
BIN
assets/audio/voice/tutorial_weapon.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_wolf.ogg
Normal file
BIN
assets/audio/voice/tutorial_wolf.ogg
Normal file
Binary file not shown.
BIN
assets/audio/voice/tutorial_workbench.ogg
Normal file
BIN
assets/audio/voice/tutorial_workbench.ogg
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"_comment": "Fishing by water type. world.water_at() says which type a spot is; a type with no entry has no fish. Weights are relative. 'dawn_dusk' replaces the weight at dawn (05:00-08:30) and dusk (17:00-20:30). Bite waits are seconds, scaled by the modifiers.",
|
"_comment": "Fishing by water type. world.water_at() says which type a spot is; a type with no entry has no fish. Weights are relative. 'dawn_dusk' replaces the weight at dawn (05:00-08:30) and dusk (17:00-20:30). Bite waits are seconds, scaled by the modifiers. A fish with its own 'min_depth' only bites in water at least that deep (cod: cast well out from the pier).",
|
||||||
"modifiers": {
|
"modifiers": {
|
||||||
"dawn_dusk_wait": 0.6,
|
"dawn_dusk_wait": 0.6,
|
||||||
"rain_wait": 0.8,
|
"rain_wait": 0.8,
|
||||||
@@ -24,6 +24,40 @@
|
|||||||
"dawn_dusk": 40
|
"dawn_dusk": 40
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"sea": {
|
||||||
|
"min_depth": 1.0,
|
||||||
|
"bite_wait": [
|
||||||
|
6.0,
|
||||||
|
16.0
|
||||||
|
],
|
||||||
|
"fish": [
|
||||||
|
{
|
||||||
|
"id": "mackerel",
|
||||||
|
"weight": 75,
|
||||||
|
"dawn_dusk": 65
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cod",
|
||||||
|
"weight": 25,
|
||||||
|
"dawn_dusk": 35,
|
||||||
|
"min_depth": 2.2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stream": {
|
||||||
|
"min_depth": 0.3,
|
||||||
|
"bite_wait": [
|
||||||
|
8.0,
|
||||||
|
18.0
|
||||||
|
],
|
||||||
|
"fish": [
|
||||||
|
{
|
||||||
|
"id": "trout",
|
||||||
|
"weight": 100,
|
||||||
|
"dawn_dusk": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,44 @@
|
|||||||
{
|
{
|
||||||
"guide": "Old Tam",
|
"guide": "Old Tam",
|
||||||
"_doc": "T4 (docs/TUTORIAL_DESIGN.md §5): one-off tips in normal worlds, each shown once per player (user://hints.json), in list order when several are due. 'when' is kind:value, with | for alternatives: status (a survival status, by prefix), weather (a state), has (an item in your bag), event (howl, died), or a plain check (night, stamina_low, wolf_moon, hard_winter). 'hold' is how long it must stay true first (seconds, default 1).",
|
"_doc": "T4 (docs/TUTORIAL_DESIGN.md §5): one-off tips in normal worlds, each shown once per player (user://hints.json), in list order when several are due. 'when' is kind:value, with | for alternatives: status (a survival status, by prefix), weather (a state), has (an item in your bag), event (howl, died), worldlet (where you are on the journey), or a plain check (night, stamina_low, wolf_moon, hard_winter, seam: holding the antler pickaxe with the cliff tunnel's seam still to dig). 'hold' is how long it must stay true first (seconds, default 1).",
|
||||||
"hints": [
|
"hints": [
|
||||||
{"id": "died", "when": "event:died", "title": "Back on your feet",
|
{"id": "died", "when": "event:died", "title": "Back on your feet",
|
||||||
"text": "Dying's not the end out here. Whatever you carried is waiting where you fell, so go back for it before anything else. Sleep in a bed once and that's where you'll wake."},
|
"text": "Dying's not the end out here, don't you fret. Whatever you were carrying is waiting where you fell, so go back for it first. Sleep in a bed once, and that's where you'll wake."},
|
||||||
{"id": "freezing", "when": "status:Freezing", "title": "Freezing",
|
{"id": "freezing", "when": "status:Freezing", "title": "Freezing",
|
||||||
"text": "You're freezing, and that eats at your health. Get to a fire or under a roof, now. A cloak helps too."},
|
"text": "You're freezing, and that'll eat away at your health! Get to a fire or under a roof, now. A cloak helps too."},
|
||||||
{"id": "cold", "when": "status:Cold", "hold": 3, "title": "Getting cold",
|
{"id": "cold", "when": "status:Cold", "hold": 3, "title": "Getting cold",
|
||||||
"text": "Feel that chill? A fire warms you, a roof and walls keep the wind off, and a hide cloak buys you time."},
|
"text": "Feel that chill? A fire warms you up, a roof and walls keep the wind off, and a hide cloak buys you a bit of time."},
|
||||||
{"id": "sick", "when": "status:Food poisoning", "title": "Bad food",
|
{"id": "sick", "when": "status:Food poisoning", "title": "Bad food",
|
||||||
"text": "Raw meat or something past its best: your belly's turned. It passes. Rest, don't eat for a bit, and cook your meat next time."},
|
"text": "Ooh, your belly's turned. Raw meat, or something past its best, I'd wager. It'll pass. Rest a while, don't eat for a bit, and cook your meat next time."},
|
||||||
{"id": "rotten", "when": "has:rotten_food", "title": "Food goes off",
|
{"id": "rotten", "when": "has:rotten_food", "title": "Food goes off",
|
||||||
"text": "Food doesn't keep forever. Eat the fresh stuff first, smoke meat and fish on a rack, or salt it. Rotten food's no good to anyone: throw it out."},
|
"text": "Food doesn't keep forever, you know. Eat the fresh stuff first, smoke your meat and fish on a rack, or salt it. Rotten food's no good to anyone, so throw it out."},
|
||||||
{"id": "wet", "when": "status:Wet", "hold": 2, "title": "Soaked",
|
{"id": "wet", "when": "status:Wet", "hold": 2, "title": "Soaked",
|
||||||
"text": "Wet clothes steal your warmth. Stand under a roof or by a fire and you'll dry off."},
|
"text": "You're soaked through! Wet clothes steal your warmth. Stand under a roof or by a fire and you'll dry off."},
|
||||||
{"id": "hungry", "when": "status:Hungry", "hold": 2, "title": "Hungry",
|
{"id": "hungry", "when": "status:Hungry", "hold": 2, "title": "Hungry",
|
||||||
"text": "Your belly's empty. Berries and mushrooms tide you over, but cooked meat and fish fill you up for longer."},
|
"text": "Your belly's empty. Berries and mushrooms will tide you over, but cooked meat and fish fill you up for longer."},
|
||||||
{"id": "diet", "when": "status:Poor diet|Malnourished", "hold": 2, "title": "Mix it up",
|
{"id": "diet", "when": "status:Poor diet|Malnourished", "hold": 2, "title": "Mix it up",
|
||||||
"text": "Eat the same thing every day and your body notices. Watch the three diet bars: meat, fish and greens. Keep them all topped up and you'll be stronger for it."},
|
"text": "Eat the same thing every day and your body notices, it does. Watch them three diet bars: meat, fish and greens. Keep them all topped up and you'll be stronger for it."},
|
||||||
{"id": "howl", "when": "event:howl", "title": "Wolves",
|
{"id": "howl", "when": "event:howl", "title": "Wolves",
|
||||||
"text": "Hear that? Wolves have moved into the hills. They'll come hunting soon after dark. Walls, a door and a fire are your friends."},
|
"text": "Hear that? Wolves have moved into the hills. They'll come hunting soon after dark. Walls, a door and a fire are your friends."},
|
||||||
{"id": "wolf_moon", "when": "wolf_moon", "title": "The red moon",
|
{"id": "wolf_moon", "when": "wolf_moon", "title": "The red moon",
|
||||||
"text": "A red moon. The pack's bolder tonight and the big one leads them. Stay behind a good door, or stand by a fire with a weapon ready."},
|
"text": "A red moon, look. The pack's bolder tonight, and the big one leads them. Stay behind a good door, or stand by a fire with a weapon ready."},
|
||||||
{"id": "night", "when": "night", "hold": 5, "title": "Nightfall",
|
{"id": "night", "when": "night", "hold": 5, "title": "Nightfall",
|
||||||
"text": "Dark and cold till morning. Keep near a fire or carry a torch. A bed under a roof lets you sleep through it."},
|
"text": "Dark and cold till morning, now. Keep near a fire or carry a torch. A bed under a roof lets you sleep right through it."},
|
||||||
{"id": "hard_winter", "when": "hard_winter", "hold": 3, "title": "Hard winter",
|
{"id": "hard_winter", "when": "hard_winter", "hold": 3, "title": "Hard winter",
|
||||||
"text": "A hard winter's come in: a few days of bitter cold, and blizzards that freeze you even by day. Stock wood and food, and stay close to home."},
|
"text": "A hard winter's come in. A few days of bitter cold, and blizzards that'll freeze you even by day. Stock up on wood and food, and stay close to home."},
|
||||||
{"id": "snow", "when": "weather:snow|blizzard", "hold": 3, "title": "Snow",
|
{"id": "snow", "when": "weather:snow|blizzard", "hold": 3, "title": "Snow",
|
||||||
"text": "Snow's falling. It's colder out in it, and heavy snow piles up on roofs. Keep your fire fed."},
|
"text": "Snow's falling. It's colder out in it, and heavy snow piles up on your roofs. Keep your fire fed."},
|
||||||
{"id": "storm", "when": "weather:storm", "hold": 3, "title": "Storm",
|
{"id": "storm", "when": "weather:storm", "hold": 3, "title": "Storm",
|
||||||
"text": "A storm. Lightning goes for the trees, so don't shelter under one. Your buildings are safe enough."},
|
"text": "A storm's coming in. Lightning goes for the trees, so don't you go sheltering under one. Your buildings are safe enough."},
|
||||||
{"id": "stamina", "when": "stamina_low", "title": "Out of breath",
|
{"id": "stamina", "when": "stamina_low", "title": "Out of breath",
|
||||||
"text": "You're spent. Stop swinging and running a moment and your stamina comes back. Rested by a fire, it comes back faster."},
|
"text": "You're all puffed out. Stop swinging and running a moment, and your stamina comes back. Rested by a fire, it comes back faster."},
|
||||||
{"id": "sheltered", "when": "status:Sheltered", "hold": 3, "title": "A proper shelter",
|
{"id": "sheltered", "when": "status:Sheltered", "hold": 3, "title": "A proper shelter",
|
||||||
"text": "Roof and walls: the weather can't touch you here. Sit by a fire in here a little while and you'll be Rested."}
|
"text": "Roof and walls, that's the way. The weather can't touch you in here. Sit by a fire inside a little while and you'll be Rested."},
|
||||||
|
{"id": "seam", "when": "seam", "hold": 3, "title": "The cracked seam",
|
||||||
|
"text": "That antler pick bites rock that iron only scratches. Out on the clifftop, near the edge, there's a seam of cracked rock, and a way down to the sea behind it if you dig. Take wood to prop it as you go, and lights. It's black as pitch down there."},
|
||||||
|
{"id": "cove", "when": "worldlet:cove", "hold": 3, "title": "The Cove",
|
||||||
|
"text": "Well, you made it down! Sand, salt and sea fish. Fish off the end of the pier: mackerel bite close in, and cod further out where it's deep. The sea cave takes you back up the tunnel to home. Watch out for the gulls: they'll have any food you leave on the sand, and fish off a smoking rack. And at dusk the crabs come up the beach, so give them room, or they'll pinch."},
|
||||||
|
{"id": "crab", "when": "has:crab_meat", "hold": 2, "title": "Crab",
|
||||||
|
"text": "Two lots of crab meat cook up into a good crab supper at a campfire. Raw, it's as risky as raw fish."}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
410
data/items.json
410
data/items.json
@@ -280,13 +280,49 @@
|
|||||||
"name": "Wood Gable",
|
"name": "Wood Gable",
|
||||||
"category": "building",
|
"category": "building",
|
||||||
"stack": 20,
|
"stack": 20,
|
||||||
"description": "Triangle that closes the end of a roof. R to flip it.",
|
"description": "Triangle that closes the end of a roof. R to flip it. On a wider house, stand it on a half wall to close a taller roof's end.",
|
||||||
"mesh": "wood_gable",
|
"mesh": "wood_gable",
|
||||||
"place": {
|
"place": {
|
||||||
"piece": "build",
|
"piece": "build",
|
||||||
"kind": "gable"
|
"kind": "gable"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "wood_half_wall",
|
||||||
|
"name": "Wood Half Wall",
|
||||||
|
"category": "building",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "2 x 1 m. A low wall or railing, or on top of a wall, under a gable, to close the end of a wider house's taller roof.",
|
||||||
|
"mesh": "wood_half_wall",
|
||||||
|
"place": {
|
||||||
|
"piece": "build",
|
||||||
|
"kind": "half_wall"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wood_roof_ridge",
|
||||||
|
"name": "Wood Roof Ridge",
|
||||||
|
"category": "building",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "A 2 m ridge cap: on a 6 m-wide house, it bridges the gap between the two sides' roofs, at the top.",
|
||||||
|
"mesh": "wood_roof_ridge",
|
||||||
|
"place": {
|
||||||
|
"piece": "build",
|
||||||
|
"kind": "roof_ridge"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wood_ridge_gable",
|
||||||
|
"name": "Wood Ridge Gable",
|
||||||
|
"category": "building",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "A small triangle that closes the end under a roof ridge. Stand it on a half wall.",
|
||||||
|
"mesh": "wood_ridge_gable",
|
||||||
|
"place": {
|
||||||
|
"piece": "build",
|
||||||
|
"kind": "ridge_gable"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "bed",
|
"id": "bed",
|
||||||
"name": "Bed",
|
"name": "Bed",
|
||||||
@@ -347,7 +383,7 @@
|
|||||||
"name": "Hide",
|
"name": "Hide",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 20,
|
"stack": 20,
|
||||||
"description": "Raw hide from a deer or boar. Makes a warm cloak.",
|
"description": "Rawhide from an animal. For cloaks and bedrolls.",
|
||||||
"mesh": "hide"
|
"mesh": "hide"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -355,7 +391,7 @@
|
|||||||
"name": "Feather",
|
"name": "Feather",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 50,
|
"stack": 50,
|
||||||
"description": "Found in meadows. Fletching for arrows.",
|
"description": "Fletching for arrows, and stuffing for a bedroll.",
|
||||||
"mesh": "feather"
|
"mesh": "feather"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -443,7 +479,7 @@
|
|||||||
"name": "Wolf Pelt",
|
"name": "Wolf Pelt",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 20,
|
"stack": 20,
|
||||||
"description": "Thick grey fur. The warmest thing on the island.",
|
"description": "A grey wolf pelt. For wolf and alpha cloaks, or a rug.",
|
||||||
"mesh": "wolf_pelt"
|
"mesh": "wolf_pelt"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -543,6 +579,105 @@
|
|||||||
},
|
},
|
||||||
"keeps": 4
|
"keeps": 4
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "mackerel",
|
||||||
|
"name": "Mackerel",
|
||||||
|
"category": "food",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "A quick, striped sea fish, caught off the pier. Grill it at a campfire. (Fish)",
|
||||||
|
"mesh": "mackerel",
|
||||||
|
"food": {
|
||||||
|
"hunger": 5,
|
||||||
|
"health": -2,
|
||||||
|
"diet": {
|
||||||
|
"fish": 10
|
||||||
|
},
|
||||||
|
"risk": 0.2
|
||||||
|
},
|
||||||
|
"keeps": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cod",
|
||||||
|
"name": "Cod",
|
||||||
|
"category": "food",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "A big sea fish from deeper water: cast well out. (Fish)",
|
||||||
|
"mesh": "cod",
|
||||||
|
"food": {
|
||||||
|
"hunger": 8,
|
||||||
|
"health": -2,
|
||||||
|
"diet": {
|
||||||
|
"fish": 16
|
||||||
|
},
|
||||||
|
"risk": 0.2
|
||||||
|
},
|
||||||
|
"keeps": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grilled_mackerel",
|
||||||
|
"name": "Grilled Mackerel",
|
||||||
|
"category": "food",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "Oily and good. (Fish)",
|
||||||
|
"mesh": "grilled_mackerel",
|
||||||
|
"food": {
|
||||||
|
"hunger": 22,
|
||||||
|
"health": 8,
|
||||||
|
"diet": {
|
||||||
|
"fish": 36
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keeps": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "crab_meat",
|
||||||
|
"name": "Crab Meat",
|
||||||
|
"category": "food",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "Sweet white meat from a crab off the Cove's beach. Cook it at a campfire. (Fish)",
|
||||||
|
"mesh": "crab_meat",
|
||||||
|
"food": {
|
||||||
|
"hunger": 4,
|
||||||
|
"health": -2,
|
||||||
|
"diet": {
|
||||||
|
"fish": 8
|
||||||
|
},
|
||||||
|
"risk": 0.2
|
||||||
|
},
|
||||||
|
"keeps": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cooked_crab",
|
||||||
|
"name": "Cooked Crab",
|
||||||
|
"category": "food",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "Boiled pink in its shell. Worth the pinches. (Fish)",
|
||||||
|
"mesh": "cooked_crab",
|
||||||
|
"food": {
|
||||||
|
"hunger": 20,
|
||||||
|
"health": 10,
|
||||||
|
"diet": {
|
||||||
|
"fish": 32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keeps": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grilled_cod",
|
||||||
|
"name": "Grilled Cod",
|
||||||
|
"category": "food",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "Thick white flakes: a proper meal. (Fish)",
|
||||||
|
"mesh": "grilled_cod",
|
||||||
|
"food": {
|
||||||
|
"hunger": 32,
|
||||||
|
"health": 12,
|
||||||
|
"diet": {
|
||||||
|
"fish": 52
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keeps": 4
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "iron_nugget",
|
"id": "iron_nugget",
|
||||||
"name": "Iron Nugget",
|
"name": "Iron Nugget",
|
||||||
@@ -564,7 +699,7 @@
|
|||||||
"name": "Clay",
|
"name": "Clay",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 50,
|
"stack": 50,
|
||||||
"description": "Grey clay from the lake shore. For kilns and smelters.",
|
"description": "Grey clay from the lake shore. For kilns, smelters and, fired at a campfire, clay jars that keep food fresh.",
|
||||||
"mesh": "clay"
|
"mesh": "clay"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -928,7 +1063,7 @@
|
|||||||
"name": "Alpha Pelt",
|
"name": "Alpha Pelt",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 5,
|
"stack": 5,
|
||||||
"description": "The alpha's black fur, thick as a blanket.",
|
"description": "The alpha's dark pelt. For the alpha cloak or a fang necklace.",
|
||||||
"mesh": "alpha_pelt"
|
"mesh": "alpha_pelt"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -936,7 +1071,7 @@
|
|||||||
"name": "Wolf Fang",
|
"name": "Wolf Fang",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 20,
|
"stack": 20,
|
||||||
"description": "A long yellowed fang from the alpha. A trophy, and a fine clasp.",
|
"description": "A long yellowed fang from the alpha. For the alpha cloak, and a fang necklace that blunts wolf bites.",
|
||||||
"mesh": "wolf_fang"
|
"mesh": "wolf_fang"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -956,7 +1091,7 @@
|
|||||||
"name": "Bear Hide",
|
"name": "Bear Hide",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 5,
|
"stack": 5,
|
||||||
"description": "A huge brown hide. Makes a fur rug.",
|
"description": "A huge brown hide. For a fur rug or a bear cloak, the warmest there is.",
|
||||||
"mesh": "bear_hide"
|
"mesh": "bear_hide"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -964,7 +1099,7 @@
|
|||||||
"name": "Bear Fat",
|
"name": "Bear Fat",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 20,
|
"stack": 20,
|
||||||
"description": "Rendered fat. Burns three times as long as wood in a fire.",
|
"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
|
||||||
},
|
},
|
||||||
@@ -996,7 +1131,7 @@
|
|||||||
"name": "Rotten Food",
|
"name": "Rotten Food",
|
||||||
"category": "resource",
|
"category": "resource",
|
||||||
"stack": 50,
|
"stack": 50,
|
||||||
"description": "It's gone off. Good for nothing: throw it out.",
|
"description": "It's gone off. Q drops it. Carried while fishing it's bait: bites come sooner, and each bite takes one.",
|
||||||
"mesh": "rotten_food"
|
"mesh": "rotten_food"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1007,6 +1142,119 @@
|
|||||||
"description": "Crusted on the rocks above the sea cliffs. Cures meat and fish so they keep for weeks.",
|
"description": "Crusted on the rocks above the sea cliffs. Cures meat and fish so they keep for weeks.",
|
||||||
"mesh": "salt"
|
"mesh": "salt"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "driftwood",
|
||||||
|
"name": "Driftwood",
|
||||||
|
"category": "resource",
|
||||||
|
"stack": 30,
|
||||||
|
"description": "Sea-bleached wood washed up on the Cove's beach. It burns well, and it's light and tough: what a rowing boat is built from.",
|
||||||
|
"mesh": "driftwood",
|
||||||
|
"fuel": 48.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "seagrass",
|
||||||
|
"name": "Seagrass",
|
||||||
|
"category": "resource",
|
||||||
|
"stack": 50,
|
||||||
|
"description": "Long, tough ribbons of seagrass washed up in the Cove's shallows. Twisted together it makes a rope that won't rot in salt water.",
|
||||||
|
"mesh": "seagrass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rope",
|
||||||
|
"name": "Seagrass Rope",
|
||||||
|
"category": "resource",
|
||||||
|
"stack": 30,
|
||||||
|
"description": "Twisted from seagrass: for rigging and lashing a boat.",
|
||||||
|
"mesh": "rope"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pine_resin",
|
||||||
|
"name": "Pine Resin",
|
||||||
|
"category": "resource",
|
||||||
|
"stack": 50,
|
||||||
|
"description": "Sticky amber resin from a felled pine. Boiled with salt it makes pitch.",
|
||||||
|
"mesh": "pine_resin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pitch",
|
||||||
|
"name": "Pitch",
|
||||||
|
"category": "resource",
|
||||||
|
"stack": 30,
|
||||||
|
"description": "Black, tarry pitch from pine resin and salt, boiled over a fire. It keeps the sea out of a boat's seams.",
|
||||||
|
"mesh": "pitch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rowing_boat",
|
||||||
|
"name": "Rowing Boat",
|
||||||
|
"category": "tool",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "A small driftwood boat, seams pitched tight. Carry it to the mooring post at the end of the Cove's pier to tie it up. Where it'll take you, a ship's compass will tell.",
|
||||||
|
"mesh": "rowing_boat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "treasure_map",
|
||||||
|
"name": "Treasure Map",
|
||||||
|
"category": "tool",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "A torn chart from a wrecked ship's sea chest, with an X inked in the hills above the Cove. Right-click to read it: the X goes on your map (N).",
|
||||||
|
"mesh": "treasure_map",
|
||||||
|
"read": {
|
||||||
|
"marks": "treasure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ship_compass",
|
||||||
|
"name": "Ship's Compass",
|
||||||
|
"category": "tool",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "A brass sea compass from the pirate chest, its needle still true. No boat leaves for the Highlands without one.",
|
||||||
|
"mesh": "ship_compass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "spyglass",
|
||||||
|
"name": "Spyglass",
|
||||||
|
"category": "tool",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "A captain's brass telescope. Hold right-click to look through it.",
|
||||||
|
"mesh": "spyglass",
|
||||||
|
"zoom": 4.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pirate_gold",
|
||||||
|
"name": "Pirate Gold",
|
||||||
|
"category": "placeable",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "Old gold coins from the pirate chest. Nothing on the island takes coin (yet), but a heap of it looks fine in a hut.",
|
||||||
|
"mesh": "pirate_gold",
|
||||||
|
"place": {
|
||||||
|
"piece": "trophy",
|
||||||
|
"footprint": [
|
||||||
|
0.6,
|
||||||
|
0.6
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cutlass",
|
||||||
|
"name": "Cutlass",
|
||||||
|
"category": "weapon",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "A pirate's curved blade: quick, and it hits harder than the iron spear.",
|
||||||
|
"mesh": "cutlass",
|
||||||
|
"weapon": {
|
||||||
|
"damage": 30,
|
||||||
|
"reach": 2.3,
|
||||||
|
"stamina": 8,
|
||||||
|
"cooldown": 0.45
|
||||||
|
},
|
||||||
|
"durability": 220,
|
||||||
|
"repair": {
|
||||||
|
"inputs": {
|
||||||
|
"iron_bar": 1
|
||||||
|
},
|
||||||
|
"station": "forge"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "smoking_rack",
|
"id": "smoking_rack",
|
||||||
"name": "Smoking Rack",
|
"name": "Smoking Rack",
|
||||||
@@ -1129,6 +1377,148 @@
|
|||||||
},
|
},
|
||||||
"station": "forge"
|
"station": "forge"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clay_jar",
|
||||||
|
"name": "Clay Jar",
|
||||||
|
"category": "placeable",
|
||||||
|
"stack": 5,
|
||||||
|
"description": "Fired clay with a lid. Holds 6 stacks, and food kept in it lasts three times as long.",
|
||||||
|
"mesh": "clay_jar",
|
||||||
|
"place": {
|
||||||
|
"piece": "jar",
|
||||||
|
"footprint": [
|
||||||
|
0.7,
|
||||||
|
0.7
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bedroll",
|
||||||
|
"name": "Bedroll",
|
||||||
|
"category": "placeable",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "Hide stuffed with feathers. Sets your spawn point like a bed, and you can sleep the night out under the open sky (but not in rain or snow).",
|
||||||
|
"mesh": "bedroll",
|
||||||
|
"place": {
|
||||||
|
"piece": "bed",
|
||||||
|
"footprint": [
|
||||||
|
0.9,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
"open_air": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pelt_rug",
|
||||||
|
"name": "Wolf-pelt Rug",
|
||||||
|
"category": "placeable",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "Wolf pelts stitched into a rug. In a sheltered room: a little warmer, and you get Rested twice as fast.",
|
||||||
|
"mesh": "pelt_rug",
|
||||||
|
"place": {
|
||||||
|
"piece": "rug",
|
||||||
|
"footprint": [
|
||||||
|
1.4,
|
||||||
|
1.8
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fang_necklace",
|
||||||
|
"name": "Fang Necklace",
|
||||||
|
"category": "tool",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "The alpha's fangs on a strip of its hide. Carry it anywhere in your bag and wolves, the alpha too, bite 30% less hard.",
|
||||||
|
"mesh": "fang_necklace",
|
||||||
|
"charm": {
|
||||||
|
"against": "wolf",
|
||||||
|
"damage": 0.7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bear_cloak",
|
||||||
|
"name": "Bear Cloak",
|
||||||
|
"category": "tool",
|
||||||
|
"stack": 1,
|
||||||
|
"description": "Right-click to wear it (+40 warmth, rain barely touches you): the warmest cloak there is. Take it off from the inventory screen.",
|
||||||
|
"mesh": "bear_cloak",
|
||||||
|
"wear": {
|
||||||
|
"warmth": 40,
|
||||||
|
"wet_chill": 0.3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pemmican",
|
||||||
|
"name": "Pemmican",
|
||||||
|
"category": "food",
|
||||||
|
"stack": 20,
|
||||||
|
"description": "Cooked meat and berries pounded into bear fat. Filling, counts as meat and greens, and keeps 30 days.",
|
||||||
|
"mesh": "pemmican",
|
||||||
|
"keeps": 30,
|
||||||
|
"food": {
|
||||||
|
"hunger": 40,
|
||||||
|
"health": 10,
|
||||||
|
"diet": {
|
||||||
|
"meat": 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,37 @@
|
|||||||
"wood": 1
|
"wood": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "wood_half_wall",
|
||||||
|
"output": "wood_half_wall",
|
||||||
|
"count": 1,
|
||||||
|
"station": "workbench",
|
||||||
|
"category": "building",
|
||||||
|
"inputs": {
|
||||||
|
"wood": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wood_roof_ridge",
|
||||||
|
"output": "wood_roof_ridge",
|
||||||
|
"count": 1,
|
||||||
|
"station": "workbench",
|
||||||
|
"category": "building",
|
||||||
|
"inputs": {
|
||||||
|
"wood": 2,
|
||||||
|
"fiber": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wood_ridge_gable",
|
||||||
|
"output": "wood_ridge_gable",
|
||||||
|
"count": 1,
|
||||||
|
"station": "workbench",
|
||||||
|
"category": "building",
|
||||||
|
"inputs": {
|
||||||
|
"wood": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "bed",
|
"id": "bed",
|
||||||
"output": "bed",
|
"output": "bed",
|
||||||
@@ -249,6 +280,36 @@
|
|||||||
"trout": 1
|
"trout": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "grilled_mackerel",
|
||||||
|
"output": "grilled_mackerel",
|
||||||
|
"count": 1,
|
||||||
|
"station": "campfire",
|
||||||
|
"category": "cooking",
|
||||||
|
"inputs": {
|
||||||
|
"mackerel": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cooked_crab",
|
||||||
|
"output": "cooked_crab",
|
||||||
|
"count": 1,
|
||||||
|
"station": "campfire",
|
||||||
|
"category": "cooking",
|
||||||
|
"inputs": {
|
||||||
|
"crab_meat": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grilled_cod",
|
||||||
|
"output": "grilled_cod",
|
||||||
|
"count": 1,
|
||||||
|
"station": "campfire",
|
||||||
|
"category": "cooking",
|
||||||
|
"inputs": {
|
||||||
|
"cod": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "charcoal_kiln",
|
"id": "charcoal_kiln",
|
||||||
"output": "charcoal_kiln",
|
"output": "charcoal_kiln",
|
||||||
@@ -578,6 +639,28 @@
|
|||||||
"salt": 1
|
"salt": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "salted_mackerel",
|
||||||
|
"output": "salted_fish",
|
||||||
|
"count": 2,
|
||||||
|
"station": "",
|
||||||
|
"category": "cooking",
|
||||||
|
"inputs": {
|
||||||
|
"mackerel": 2,
|
||||||
|
"salt": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "salted_cod",
|
||||||
|
"output": "salted_fish",
|
||||||
|
"count": 2,
|
||||||
|
"station": "",
|
||||||
|
"category": "cooking",
|
||||||
|
"inputs": {
|
||||||
|
"cod": 1,
|
||||||
|
"salt": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "antler_pickaxe",
|
"id": "antler_pickaxe",
|
||||||
"output": "antler_pickaxe",
|
"output": "antler_pickaxe",
|
||||||
@@ -589,6 +672,132 @@
|
|||||||
"iron_bar": 1,
|
"iron_bar": 1,
|
||||||
"wood": 2
|
"wood": 2
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clay_jar",
|
||||||
|
"output": "clay_jar",
|
||||||
|
"count": 1,
|
||||||
|
"station": "campfire",
|
||||||
|
"category": "furniture",
|
||||||
|
"inputs": {
|
||||||
|
"clay": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bedroll",
|
||||||
|
"output": "bedroll",
|
||||||
|
"count": 1,
|
||||||
|
"station": "workbench",
|
||||||
|
"category": "furniture",
|
||||||
|
"inputs": {
|
||||||
|
"hide": 3,
|
||||||
|
"feather": 6,
|
||||||
|
"fiber": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pelt_rug",
|
||||||
|
"output": "pelt_rug",
|
||||||
|
"count": 1,
|
||||||
|
"station": "workbench",
|
||||||
|
"category": "furniture",
|
||||||
|
"inputs": {
|
||||||
|
"wolf_pelt": 3,
|
||||||
|
"fiber": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fang_necklace",
|
||||||
|
"output": "fang_necklace",
|
||||||
|
"count": 1,
|
||||||
|
"station": "workbench",
|
||||||
|
"category": "clothing",
|
||||||
|
"inputs": {
|
||||||
|
"wolf_fang": 2,
|
||||||
|
"alpha_pelt": 1,
|
||||||
|
"fiber": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bear_cloak",
|
||||||
|
"output": "bear_cloak",
|
||||||
|
"count": 1,
|
||||||
|
"station": "workbench",
|
||||||
|
"category": "clothing",
|
||||||
|
"inputs": {
|
||||||
|
"bear_hide": 2,
|
||||||
|
"hide": 2,
|
||||||
|
"fiber": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pemmican",
|
||||||
|
"output": "pemmican",
|
||||||
|
"count": 3,
|
||||||
|
"station": "campfire",
|
||||||
|
"category": "cooking",
|
||||||
|
"inputs": {
|
||||||
|
"cooked_meat": 2,
|
||||||
|
"berries": 3,
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rope",
|
||||||
|
"output": "rope",
|
||||||
|
"count": 1,
|
||||||
|
"station": "",
|
||||||
|
"category": "tools",
|
||||||
|
"inputs": {
|
||||||
|
"seagrass": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pitch",
|
||||||
|
"output": "pitch",
|
||||||
|
"count": 1,
|
||||||
|
"station": "campfire",
|
||||||
|
"category": "building",
|
||||||
|
"inputs": {
|
||||||
|
"pine_resin": 2,
|
||||||
|
"salt": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rowing_boat",
|
||||||
|
"output": "rowing_boat",
|
||||||
|
"count": 1,
|
||||||
|
"station": "workbench",
|
||||||
|
"category": "building",
|
||||||
|
"inputs": {
|
||||||
|
"driftwood": 12,
|
||||||
|
"rope": 4,
|
||||||
|
"pitch": 3,
|
||||||
|
"nails": 6
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"id": "look",
|
"id": "look",
|
||||||
"chapter": "tools",
|
"chapter": "tools",
|
||||||
"title": "Look around",
|
"title": "Look around",
|
||||||
"text": "Awake at last! Washed up on my island, have you. Stretch your legs: WASD to walk, the mouse to look. Shift if you're in a hurry.",
|
"text": "Ha! Awake at last! Washed up on my island, eh? Well, stretch your legs. Use WASD to move, the mouse to look around, and Shift to run.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"walk": 10
|
"walk": 10
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,9 @@
|
|||||||
"id": "sticks",
|
"id": "sticks",
|
||||||
"chapter": "tools",
|
"chapter": "tools",
|
||||||
"title": "Gather sticks and stones",
|
"title": "Gather sticks and stones",
|
||||||
"text": "Empty hands are no use here. Look at a stick or a stone on the ground and press E. Three of each, mind.",
|
"text": "Now then! Empty hands are no use to anyone out here. Look at a stick or a stone on the ground and press E to pick it up. Why don't you go ahead and grab three of each.",
|
||||||
"marker": {
|
"marker": {
|
||||||
"nearest": "pickup:stick"
|
"nearest": "pickup:stick|stone"
|
||||||
},
|
},
|
||||||
"goal": {
|
"goal": {
|
||||||
"all": [
|
"all": [
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"id": "flax",
|
"id": "flax",
|
||||||
"chapter": "tools",
|
"chapter": "tools",
|
||||||
"title": "Pick wild flax",
|
"title": "Pick wild flax",
|
||||||
"text": "See the pale, tall plants? Wild flax. Pick some. The fibre makes torches, bows and a decent bed.",
|
"text": "See them tall, pale plants? That's wild flax. Pick a few of those. The fibre makes bows, a decent bed, and torches. You'll need a torch before long. It gets pretty dark here.",
|
||||||
"marker": {
|
"marker": {
|
||||||
"nearest": "pickup:fiber"
|
"nearest": "pickup:fiber"
|
||||||
},
|
},
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
"id": "axe",
|
"id": "axe",
|
||||||
"chapter": "tools",
|
"chapter": "tools",
|
||||||
"title": "Craft a stone axe",
|
"title": "Craft a stone axe",
|
||||||
"text": "Now an axe. Two sticks and two flint: flint lies about, more by the water. Tab opens your pack, and the recipe's in there.",
|
"text": "Right, now you'll be wanting an axe. That's made from two sticks and two flint. Flint can be found lying all over the place, slightly darker than a stone. You'll find more by the water. If you press Tab to open your pack, you'll find the recipe in there.",
|
||||||
"marker": {
|
"marker": {
|
||||||
"nearest": "pickup:flint",
|
"nearest": "pickup:flint",
|
||||||
"unless_have": {
|
"unless_have": {
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
"id": "tree",
|
"id": "tree",
|
||||||
"chapter": "tools",
|
"chapter": "tools",
|
||||||
"title": "Chop down a tree",
|
"title": "Chop down a tree",
|
||||||
"text": "Axe in hand (the number keys), hold the left button at a trunk and keep at it. A tree gives wood, and sticks when it falls.",
|
"text": "Got your axe? Good. Pick it with the number keys so it's in your hand, then click the left mouse button at a tree trunk a few times. Down it comes, and you'll get wood, and a few sticks besides.",
|
||||||
"marker": {
|
"marker": {
|
||||||
"nearest": "tree"
|
"nearest": "tree"
|
||||||
},
|
},
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
"id": "pickaxe",
|
"id": "pickaxe",
|
||||||
"chapter": "tools",
|
"chapter": "tools",
|
||||||
"title": "Craft a pickaxe and break a boulder",
|
"title": "Craft a pickaxe and break a boulder",
|
||||||
"text": "Rocks are next. A pickaxe takes two sticks, three stone and a flint. Then go and break a boulder for stone and flint of your own.",
|
"text": "Now for the rocks. Make yourself a pickaxe, from two sticks, three stone and a flint. Then go and give one of them big boulders a good few whacks, and you'll have stone and flint of your own.",
|
||||||
"marker": {
|
"marker": {
|
||||||
"nearest": "rock"
|
"nearest": "rock"
|
||||||
},
|
},
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"id": "campfire",
|
"id": "campfire",
|
||||||
"chapter": "food",
|
"chapter": "food",
|
||||||
"title": "Build a campfire",
|
"title": "Build a campfire",
|
||||||
"text": "Five stone and four sticks make a campfire. Pick it in your pack, then place it with the left button (R turns it). Fire gives light, warmth and somewhere to cook. Feed it wood with E.",
|
"text": "Now, a fire. Five stone and four sticks make a campfire. Pick it in your pack, then click the left mouse button to put it down. R turns it round, if you're fussy. A fire gives you light, warmth, and somewhere to cook. Keep it fed with wood: look at it and press E.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"placed": "campfire"
|
"placed": "campfire"
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"id": "berries",
|
"id": "berries",
|
||||||
"chapter": "food",
|
"chapter": "food",
|
||||||
"title": "Pick berries and eat some",
|
"title": "Pick berries and eat some",
|
||||||
"text": "Those red berries on the bushes are safe. Pick some with E, then hold them and press the right button to eat.",
|
"text": "Hungry? Them red berries on the bushes are safe enough. Pick some with E, then hold them with the number keys and click the right mouse button to eat. Go on, have a few.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"all": [
|
"all": [
|
||||||
{
|
{
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
"id": "mushroom",
|
"id": "mushroom",
|
||||||
"chapter": "food",
|
"chapter": "food",
|
||||||
"title": "Roast a mushroom",
|
"title": "Roast a mushroom",
|
||||||
"text": "Mushrooms grow under the trees. Raw ones are all right; roasted at the fire, much better. Find one and roast it (stand by the fire and open your pack).",
|
"text": "Mushrooms grow in the shade, under the trees. Raw ones are all right, but roasted at the fire they're a sight better. Find one, then stand by your fire and open your pack to roast it.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"all": [
|
"all": [
|
||||||
{
|
{
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
"id": "diet",
|
"id": "diet",
|
||||||
"chapter": "food",
|
"chapter": "food",
|
||||||
"title": "Mind your diet",
|
"title": "Mind your diet",
|
||||||
"text": "See the three Diet bars, bottom left? Meat, Fish, Greens. Each food fills one. Let one run low and you stop healing; let two, and you'll waste away on a full belly. Keep all three up and you heal faster. Press Enter when you've got that.",
|
"text": "Now, see them three Diet bars, bottom left? Meat, fish and greens. Each food fills one of them. Let one run low and you'll stop healing. Let two run low, and you'll waste away on a full belly. Keep all three topped up and you'll heal faster. Press Enter when you've got all that.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"continue": true
|
"continue": true
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
"id": "workbench",
|
"id": "workbench",
|
||||||
"chapter": "hunt",
|
"chapter": "hunt",
|
||||||
"title": "Build a workbench",
|
"title": "Build a workbench",
|
||||||
"text": "Better tools want a workbench nearby. Ten wood and two flint. Chop a few more trees if you're short.",
|
"text": "Better tools want a workbench close by. That's ten wood and two flint. If you're short, go and chop down a few more trees. Then put it down near your fire.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"placed": "workbench"
|
"placed": "workbench"
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
"id": "weapon",
|
"id": "weapon",
|
||||||
"chapter": "hunt",
|
"chapter": "hunt",
|
||||||
"title": "Make a weapon",
|
"title": "Make a weapon",
|
||||||
"text": "You'll not catch your supper with your bare hands. A club is four wood. A spear, at the bench, reaches further.",
|
"text": "You'll not catch your supper with your bare hands, will you? A club is four wood, and you can make that anywhere. A spear reaches further, but you'll need to be at the bench for that one.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"any": [
|
"any": [
|
||||||
{
|
{
|
||||||
@@ -203,7 +203,7 @@
|
|||||||
"id": "rabbit",
|
"id": "rabbit",
|
||||||
"chapter": "hunt",
|
"chapter": "hunt",
|
||||||
"title": "Hunt a rabbit",
|
"title": "Hunt a rabbit",
|
||||||
"text": "There, rabbits! They bolt if you run at them. Walk up quiet, weapon in hand, and strike.",
|
"text": "There! Rabbits! Run at them and they'll bolt, so creep up quiet, weapon in hand, and give them a whack.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"killed": "rabbit"
|
"killed": "rabbit"
|
||||||
},
|
},
|
||||||
@@ -222,7 +222,7 @@
|
|||||||
"id": "cook",
|
"id": "cook",
|
||||||
"chapter": "hunt",
|
"chapter": "hunt",
|
||||||
"title": "Butcher it and cook the meat",
|
"title": "Butcher it and cook the meat",
|
||||||
"text": "E on the carcass to butcher it. Raw meat turns your stomach; cooked at the fire it heals you and keeps you Well fed.",
|
"text": "Now look at it and press E to butcher it. Don't you go eating that meat raw, it'll turn your stomach. Cook it at the fire, and it'll heal you up and keep you Well fed.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"all": [
|
"all": [
|
||||||
{
|
{
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
"id": "animals",
|
"id": "animals",
|
||||||
"chapter": "hunt",
|
"chapter": "hunt",
|
||||||
"title": "Know your animals",
|
"title": "Know your animals",
|
||||||
"text": "Deer are quick: a bow's your friend there. Boars are calm until you hit one, then they come for you. Wolves you'll meet soon enough. Press Enter.",
|
"text": "Deer are quick, so a bow's your friend there. Boars are calm enough until you hit one, then they'll come right for you. And wolves? Well, you'll meet them soon enough. Press Enter when you're ready.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"continue": true
|
"continue": true
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@
|
|||||||
"id": "rod",
|
"id": "rod",
|
||||||
"chapter": "fish",
|
"chapter": "fish",
|
||||||
"title": "Make a fishing rod",
|
"title": "Make a fishing rod",
|
||||||
"text": "Two sticks, three fibre and a flint, at the workbench.",
|
"text": "Fancy a bit of fish? Make yourself a rod at the workbench, from two sticks, three fibre and a flint.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"crafted": "fishing_rod"
|
"crafted": "fishing_rod"
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@
|
|||||||
"id": "fish",
|
"id": "fish",
|
||||||
"chapter": "fish",
|
"chapter": "fish",
|
||||||
"title": "Catch a fish and grill it",
|
"title": "Catch a fish and grill it",
|
||||||
"text": "Down to the lake. Rod in hand, the left button casts. When the float dips, click quick, before it gets away. Grill what you catch at the fire.",
|
"text": "Down to the lake with you. Rod in hand, click the left mouse button to cast. When the float dips, click again, quick as you can, before it gets away. Then grill what you catch at the fire.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"all": [
|
"all": [
|
||||||
{
|
{
|
||||||
@@ -285,7 +285,7 @@
|
|||||||
"id": "hut",
|
"id": "hut",
|
||||||
"chapter": "night",
|
"chapter": "night",
|
||||||
"title": "Build a little hut",
|
"title": "Build a little hut",
|
||||||
"text": "Nights get cold, and things prowl. Build a hut: I've marked out a good spot by your fire, pale as a ghost. Floors, walls and a door, then the roof, all from the bench. Put a piece near its place in the outline and it'll go right in. Done when you can stand inside out of the weather.",
|
"text": "Nights get cold out here, and things prowl about. You'll want a hut. I've marked out a good spot by your fire, pale as a ghost. Floors, walls and a door, then the roof, all made at the bench. Put each piece near its place in the outline and it'll go right in. You're done when you can stand inside, out of the weather.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"shelter": true
|
"shelter": true
|
||||||
},
|
},
|
||||||
@@ -300,7 +300,7 @@
|
|||||||
"id": "bed",
|
"id": "bed",
|
||||||
"chapter": "night",
|
"chapter": "night",
|
||||||
"title": "Make a bed and sleep spot",
|
"title": "Make a bed and sleep spot",
|
||||||
"text": "Eight wood and six fibre make a bed. Put it inside, then E on it: that's where you'll wake if anything happens to you.",
|
"text": "Now a bed. That's eight wood and six fibre. Put it inside your hut, then look at it and press E. That's where you'll wake up if anything happens to you.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"all": [
|
"all": [
|
||||||
{
|
{
|
||||||
@@ -316,7 +316,7 @@
|
|||||||
"id": "torch",
|
"id": "torch",
|
||||||
"chapter": "night",
|
"chapter": "night",
|
||||||
"title": "Night is coming: make a torch",
|
"title": "Night is coming: make a torch",
|
||||||
"text": "There, the sun's going down. It gets dark and cold. A torch (a stick and two fibre) lights the way and keeps you a little warm.",
|
"text": "There, the sun's going down. It gets dark and cold, mind. Make yourself a torch, from a stick and two fibre. It lights the way, and keeps you a little warm besides.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"crafted": "torch"
|
"crafted": "torch"
|
||||||
},
|
},
|
||||||
@@ -328,7 +328,7 @@
|
|||||||
"id": "wolf",
|
"id": "wolf",
|
||||||
"chapter": "night",
|
"chapter": "night",
|
||||||
"title": "Wolves",
|
"title": "Wolves",
|
||||||
"text": "Hush. See it, at the edge of the light? Wolves. From the fourth night they hunt. They won't come close to a lit fire, a torch makes them think twice, and they can't bite through walls. Just watch.",
|
"text": "Hush now. See it, there, at the edge of the light? A wolf. From the fourth night they hunt. They won't come near a lit fire, a torch makes them think twice, and they can't bite through walls. Just you watch.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"demo_wolf": true
|
"demo_wolf": true
|
||||||
},
|
},
|
||||||
@@ -340,7 +340,7 @@
|
|||||||
"id": "sleep",
|
"id": "sleep",
|
||||||
"chapter": "night",
|
"chapter": "night",
|
||||||
"title": "Sleep till morning",
|
"title": "Sleep till morning",
|
||||||
"text": "Off to bed with you. You can only sleep at night, under a roof with walls round you, and not with wolves about.",
|
"text": "Off to bed with you. You can only sleep at night, under a roof with walls round you, and not with wolves about. Look at your bed and press E.",
|
||||||
"goal": {
|
"goal": {
|
||||||
"slept": true
|
"slept": true
|
||||||
},
|
},
|
||||||
@@ -351,6 +351,6 @@
|
|||||||
],
|
],
|
||||||
"finish": {
|
"finish": {
|
||||||
"title": "You'll do",
|
"title": "You'll do",
|
||||||
"text": "That's the island, near enough: gather, craft, eat a bit of everything, keep warm and dry, and be indoors or by a fire when the wolves are about. E picks up and uses, Tab is your pack, X takes a thing down, R turns what you're placing, the right button eats or wears. Off you go, then."
|
"text": "Well, you'll do. That's the island, near enough. Gather, craft, eat a bit of everything, keep warm and dry, and be indoors or by a fire when the wolves are about. Remember: E picks up and uses, Tab is your pack, X takes a thing down, R turns what you're placing, and the right mouse button eats or wears. Off you go, then."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
data/worldlets/cove.json
Normal file
24
data/worldlets/cove.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"_doc": "The Cove (docs/WORLDLETS_DESIGN.md §5, W3): the second worldlet, reached from the Valley down the cliff tunnel (J1). Low hills run down to a wide sandy bay with a pier (scripts/world/pier.gd) for sea fishing; a small pond inland; a stream from a spring under the mountains down to the bay (a 'stream' block: its points, spring first, in world metres); mountains behind. Not a start: you get here on the journey.",
|
||||||
|
"id": "cove",
|
||||||
|
"name": "Cove",
|
||||||
|
"description": "Low hills running down to a sheltered sandy bay, with a pier out over the water for sea fishing.",
|
||||||
|
"start": false,
|
||||||
|
"size": 320,
|
||||||
|
"resolution": 256,
|
||||||
|
"terrain": [
|
||||||
|
{"type": "hills", "height_scale": 5.5, "detail": 1.2},
|
||||||
|
{"type": "rim", "play_radius": 100.0, "rim_width": 30.0, "mountain_height": 52.0, "rim_warp": 14.0, "wall_frac": 0.45},
|
||||||
|
{"type": "beach", "angle_deg": 90.0, "half_width_deg": 55.0, "fade_deg": 22.0, "shore_offset": -16.0, "slope": 0.08, "current_out": 22.0, "wall_out": 36.0},
|
||||||
|
{"type": "lake", "center": [-30.0, -40.0], "radius": 11.0, "water_level": -2.6},
|
||||||
|
{"type": "stream", "points": [[46.0, -80.0], [34.0, -48.0], [40.0, -14.0], [30.0, 22.0], [36.0, 52.0], [33.0, 78.0], [33.0, 104.0]], "width": 2.4, "depth": 0.6},
|
||||||
|
{"type": "ocean_edge"}
|
||||||
|
],
|
||||||
|
"climate": {"snowline": 42.0},
|
||||||
|
"places": [{"type": "den"}, {"type": "pier", "at": "beach"}, {"type": "wreck"}],
|
||||||
|
"wildlife": ["crabs", "gulls"],
|
||||||
|
"next": [
|
||||||
|
{"to": "valley", "by": "tunnel", "where": "sea_cave"},
|
||||||
|
{"to": "highlands", "by": "sail", "where": "pier"}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
data/worldlets/valley.json
Normal file
19
data/worldlets/valley.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"_doc": "A worldlet (docs/WORLDLETS_DESIGN.md): its name and description (the New World page), whether a new world can start here (\"start\"; the others are reached on the journey), its size, and its terrain as building blocks applied in order (scripts/world/terrain.gd: hills, rim, sea_cliff, lake, ocean_edge). The numbers here are the Valley exactly as it was hand-coded: tests/test_worldlets.gd checks it still makes the same height grid.",
|
||||||
|
"id": "valley",
|
||||||
|
"name": "Valley",
|
||||||
|
"description": "A sheltered valley ringed by mountains, with a lake in the middle and sea cliffs on one side.",
|
||||||
|
"start": true,
|
||||||
|
"size": 320,
|
||||||
|
"resolution": 256,
|
||||||
|
"terrain": [
|
||||||
|
{"type": "hills", "height_scale": 9.0, "detail": 1.4},
|
||||||
|
{"type": "rim", "play_radius": 100.0, "rim_width": 30.0, "mountain_height": 58.0, "rim_warp": 16.0, "wall_frac": 0.45},
|
||||||
|
{"type": "sea_cliff", "angle_deg": -75.0, "half_width_deg": 38.0, "fade_deg": 22.0, "sea_level": -24.0, "cliff_top": 0.5, "cliff_offset": 4.0, "drop_width": 6.0},
|
||||||
|
{"type": "lake", "center": [14.0, -46.0], "radius": 24.0, "water_level": -2.6},
|
||||||
|
{"type": "ocean_edge"}
|
||||||
|
],
|
||||||
|
"climate": {"snowline": 40.0},
|
||||||
|
"places": [{"type": "den"}, {"type": "barrow"}, {"type": "stones"}, {"type": "bear"}],
|
||||||
|
"next": [{"to": "cove", "by": "tunnel", "where": "sea_cliff"}]
|
||||||
|
}
|
||||||
@@ -25,8 +25,8 @@ elsewhere, use **Tailscale** (recommended) or port forwarding (below).
|
|||||||
- **Leaving** (Disconnect, or quitting the game) saves the island and
|
- **Leaving** (Disconnect, or quitting the game) saves the island and
|
||||||
stops the server. Next time, Host Game carries on with the same island
|
stops the server. Next time, Host Game carries on with the same island
|
||||||
(the seed field is locked once it exists).
|
(the seed field is locked once it exists).
|
||||||
- Its files: `~/Library/Application Support/Godot/app_userdata/Emberwild/host/`
|
- Its files: `~/Library/Application Support/Godot/app_userdata/Cinderwild/host/`
|
||||||
(macOS; `%APPDATA%\Godot\app_userdata\Emberwild\host\` on Windows):
|
(macOS; `%APPDATA%\Godot\app_userdata\Cinderwild\host\` on Windows):
|
||||||
`server.json`, `server.log`, and the world in `worlds/server-world/`.
|
`server.json`, `server.log`, and the world in `worlds/server-world/`.
|
||||||
- macOS may ask once whether Godot may accept incoming network
|
- macOS may ask once whether Godot may accept incoming network
|
||||||
connections: say **Allow**, or friends can't connect.
|
connections: say **Allow**, or friends can't connect.
|
||||||
@@ -47,14 +47,14 @@ The first player to join a new server becomes its admin (so join it
|
|||||||
yourself before telling friends the address). More admins: `/admin <name>`.
|
yourself before telling friends the address). More admins: `/admin <name>`.
|
||||||
|
|
||||||
**As a service** (starts on boot, restarts if it stops):
|
**As a service** (starts on boot, restarts if it stops):
|
||||||
- **macOS:** `server/com.emberwild.server.plist`: edit the paths in it,
|
- **macOS:** `server/com.cinderwild.server.plist`: edit the paths in it,
|
||||||
then `cp server/com.emberwild.server.plist ~/Library/LaunchAgents/` and
|
then `cp server/com.cinderwild.server.plist ~/Library/LaunchAgents/` and
|
||||||
`launchctl load -w ~/Library/LaunchAgents/com.emberwild.server.plist`.
|
`launchctl load -w ~/Library/LaunchAgents/com.cinderwild.server.plist`.
|
||||||
It runs while you're logged in (set the Mac to log in automatically for an
|
It runs while you're logged in (set the Mac to log in automatically for an
|
||||||
always-on server). Log: `~/Library/Logs/emberwild-server.log`.
|
always-on server). Log: `~/Library/Logs/cinderwild-server.log`.
|
||||||
- **Linux:** `server/emberwild-server.service`: the steps are in the file
|
- **Linux:** `server/cinderwild-server.service`: the steps are in the file
|
||||||
(`sudo systemctl enable --now emberwild-server`; logs with
|
(`sudo systemctl enable --now cinderwild-server`; logs with
|
||||||
`journalctl -u emberwild-server -f`).
|
`journalctl -u cinderwild-server -f`).
|
||||||
|
|
||||||
As a service there's no terminal to type commands into: use chat in game,
|
As a service there's no terminal to type commands into: use chat in game,
|
||||||
or edit `server.json` and restart.
|
or edit `server.json` and restart.
|
||||||
@@ -76,7 +76,7 @@ the ones you'll want first:
|
|||||||
| `difficulty` | normal | easy / normal / hard |
|
| `difficulty` | normal | easy / normal / hard |
|
||||||
| `sleep` | all_online | or `half`, `anyone`, `off` |
|
| `sleep` | all_online | or `half`, `anyone`, `off` |
|
||||||
| `backups_kept`, `backup_hours` | 10, 6 | see below |
|
| `backups_kept`, `backup_hours` | 10, 6 | see below |
|
||||||
| `check_updates` | true | at start-up, say in the log if a newer Emberwild is out (and whether its protocol differs, so new players couldn't join) |
|
| `check_updates` | true | at start-up, say in the log if a newer Cinderwild is out (and whether its protocol differs, so new players couldn't join) |
|
||||||
| `allowlist`, `banned`, `admins` | | player ids (use `/ban`, `/admin` rather than editing) |
|
| `allowlist`, `banned`, `admins` | | player ids (use `/ban`, `/admin` rather than editing) |
|
||||||
|
|
||||||
## Backups
|
## Backups
|
||||||
@@ -113,15 +113,22 @@ To check it works: `tailscale ping <server name>` from the friend's
|
|||||||
computer.
|
computer.
|
||||||
|
|
||||||
### Port forwarding
|
### Port forwarding
|
||||||
On your router, forward **UDP 24242** to the server computer's local
|
On your router, forward **UDP 24242 and 24243** to the server computer's
|
||||||
address (give that computer a fixed local address first). Friends join
|
local address (give that computer a fixed local address first). Friends
|
||||||
with your public IP address. It needs no extra software, but your home IP
|
join with your public IP address. It needs no extra software, but your
|
||||||
and that port are public, so set a `password`.
|
home IP and those ports are public, so set a `password`.
|
||||||
|
|
||||||
|
Why two: each land of the journey runs as its own server on the next port
|
||||||
|
up (24243 is the Cove's, when the server's on 24242). The server players
|
||||||
|
join (the Valley's) starts it when someone first goes there and stops it
|
||||||
|
when it's been empty a couple of minutes; everyone still joins on 24242.
|
||||||
|
Host Game and the server's log both say which ports to forward. With
|
||||||
|
Tailscale, or on the same Wi-Fi, there's nothing to do.
|
||||||
|
|
||||||
### A cloud server
|
### A cloud server
|
||||||
Any small Linux server works (it needs little: a couple of GB of memory).
|
Any small Linux server works (it needs little: a couple of GB of memory).
|
||||||
Install Godot, clone the game, use the systemd file, and open UDP 24242 in
|
Install Godot, clone the game, use the systemd file, and open UDP 24242
|
||||||
the provider's firewall.
|
and 24243 in the provider's firewall.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
- **"Different game version":** the server and the player are on different
|
- **"Different game version":** the server and the player are on different
|
||||||
@@ -132,6 +139,11 @@ the provider's firewall.
|
|||||||
- **"Port 24242 is already in use":** another server is running on this
|
- **"Port 24242 is already in use":** another server is running on this
|
||||||
computer (a dedicated one, or a Host Game that didn't stop). Stop it, or
|
computer (a dedicated one, or a Host Game that didn't stop). Stop it, or
|
||||||
host on another port.
|
host on another port.
|
||||||
|
- **"Lost the connection to the Cove":** that land's server stopped (it
|
||||||
|
logs to `land-cove.log` beside `server.json`). Join again: the server
|
||||||
|
starts it afresh and sends you back there.
|
||||||
|
- **Stuck going on to the Cove from the internet:** UDP 24243 isn't
|
||||||
|
forwarded (see above).
|
||||||
- **The server says the game's scripts didn't load:** it's a fresh copy
|
- **The server says the game's scripts didn't load:** it's a fresh copy
|
||||||
that hasn't been imported, or Godot is older than 4.7. `run_server.sh`
|
that hasn't been imported, or Godot is older than 4.7. `run_server.sh`
|
||||||
handles both; see `docs/SETUP_ON_NEW_MACHINE.md`.
|
handles both; see `docs/SETUP_ON_NEW_MACHINE.md`.
|
||||||
|
|||||||
80
docs/PLAYTEST.md
Normal file
80
docs/PLAYTEST.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Cinderwild 0.6.0 playtest
|
||||||
|
|
||||||
|
Thanks for playing. This build is the **Valley** and the **Cove**, and
|
||||||
|
we'd like to know what breaks, what's confusing, and what's no fun. The
|
||||||
|
game's player guide is on the download page: https://cinderwild.psbailey.uk
|
||||||
|
|
||||||
|
## Reporting a bug
|
||||||
|
|
||||||
|
Press **Esc** in the game, then **Report a bug**:
|
||||||
|
|
||||||
|
- **Open the bug report page** opens a new issue on the Cinderwild Gitea page
|
||||||
|
in your browser, already filled in with your version, computer, mode,
|
||||||
|
land, the in-game time and where you are. Say what happened, what you
|
||||||
|
expected, and how to make it happen again, then press **Create Issue**.
|
||||||
|
Posting needs a (free) account on the Gitea server, and you need to be
|
||||||
|
signed in. Nothing is sent from the game itself.
|
||||||
|
- **Copy the details** puts the same details on your clipboard, if you'd
|
||||||
|
rather send them by message.
|
||||||
|
- **Show the log file** opens the folder with `godot.log`. For a crash or an
|
||||||
|
error message, attach it to the issue (drag it into the comment box).
|
||||||
|
`godot.log` is the latest run; the last few runs before it are kept
|
||||||
|
beside it, named with the date and time.
|
||||||
|
|
||||||
|
Where the log is:
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Mac | `~/Library/Application Support/Godot/app_userdata/Cinderwild/logs/godot.log` |
|
||||||
|
| Windows | `%APPDATA%\Godot\app_userdata\Cinderwild\logs\godot.log` |
|
||||||
|
| Linux | `~/.local/share/godot/app_userdata/Cinderwild/logs/godot.log` |
|
||||||
|
|
||||||
|
A screenshot helps for anything that looks wrong (Cmd+Shift+4 on a Mac,
|
||||||
|
Win+Shift+S on Windows).
|
||||||
|
|
||||||
|
One problem per issue, please; a second issue is better than a long one.
|
||||||
|
|
||||||
|
## What to try
|
||||||
|
|
||||||
|
Play however you like; these are the parts that are new or that we'd most
|
||||||
|
like tried.
|
||||||
|
|
||||||
|
**The Valley**
|
||||||
|
- The tutorial with Old Tam (New World, Tutorial), or start a normal world
|
||||||
|
and listen for his tips.
|
||||||
|
- Make a camp, survive a few nights, a winter, a wolf moon.
|
||||||
|
- Iron, stone building, the barrow, the standing stones and the Old Stag.
|
||||||
|
- Out on the clifftop, the cracked seam: dig the cliff tunnel down to the
|
||||||
|
Cove (an antler pickaxe, wood to prop it, lights).
|
||||||
|
|
||||||
|
**The Cove (new in 0.6.0)**
|
||||||
|
- The wreck at the far end of the beach: the captain's sea chest has a
|
||||||
|
treasure map for each player. Right-click the map, find your X (N for the
|
||||||
|
map), and dig it up with a pickaxe. Don't dig at night, or do.
|
||||||
|
- The pier: mackerel close in, cod from the far end.
|
||||||
|
- Gulls, after food on the sand and fish on a smoking rack.
|
||||||
|
- Crabs on the beach from dusk till dawn.
|
||||||
|
- Build a rowing boat (seagrass rope, pitch from pine resin and salt,
|
||||||
|
driftwood and nails, at a workbench) and tie it up at the end of the pier.
|
||||||
|
|
||||||
|
**Online**
|
||||||
|
- Host Game from the title screen, and have a friend join. Over the
|
||||||
|
internet, forward UDP ports **24242** and **24243** (the Cove has its own
|
||||||
|
server on the next port).
|
||||||
|
- Go between the lands together and apart, chat, sleep (everyone in every
|
||||||
|
land has to be in bed), and leave and come back.
|
||||||
|
|
||||||
|
## Not in this build (these aren't bugs)
|
||||||
|
|
||||||
|
- **Setting sail.** The boat ties up but can't leave yet: the Highlands are
|
||||||
|
the next part of the journey.
|
||||||
|
- **Pirate gold** has no use yet beyond being a trophy: there's no trader.
|
||||||
|
- **The ship's compass** does nothing yet; it's what the boat will need.
|
||||||
|
|
||||||
|
## Things we'd especially like to hear
|
||||||
|
|
||||||
|
- Anywhere you got stuck, or didn't know what to do next.
|
||||||
|
- Anything that felt too hard, too easy, or too slow (night, hunger, cold,
|
||||||
|
the drowned crew, the crabs).
|
||||||
|
- Frame-rate drops, and on what computer.
|
||||||
|
- Anything online that went out of step between players.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -85,8 +85,30 @@ 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); the map on `N`, filling in as you explore, and a
|
||||||
|
compass along the top (0.4.0, Gitea issue #1).
|
||||||
|
- More places: the worldlets and the journey (`docs/WORLDLETS_DESIGN.md`).
|
||||||
|
*Done (0.4.0):* W0-W3 and J1, the Cove reached down the cliff tunnel
|
||||||
|
(single-player). *(0.5.0):* J3, the journey on a server: a server per
|
||||||
|
land, players in different lands at once, chat, the player list, admin
|
||||||
|
and sleep across lands. *(0.6.0, a playtest):* the Cove's own: the wreck,
|
||||||
|
a treasure dig for each player and its drowned crew, crabs, gulls, and a
|
||||||
|
rowing boat moored at the pier; Report a bug in the pause menu. *(0.6.1)*
|
||||||
|
the new name, Cinderwild. *(0.6.2)* playtest fixes: animals off the cliffs,
|
||||||
|
wider and odd-shaped houses, real thunder, the tutorial's daylight, and Old
|
||||||
|
Tam rewritten in a new voice. Next J2,
|
||||||
|
then J4 (the Highlands by sail).
|
||||||
|
- 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, then Qwen3-TTS from 0.6.2).
|
||||||
|
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
|
||||||
@@ -95,6 +117,66 @@ phases M2.0-M2.7).
|
|||||||
game icon on the .exe (rcedit), Linux, and the update check below.
|
game icon on the .exe (rcedit), Linux, and the update check below.
|
||||||
- **Updates**: the game (and a dedicated server) needs to notice when a new
|
- **Updates**: the game (and a dedicated server) needs to notice when a new
|
||||||
version is out, download what's needed and install it. See below.
|
version is out, download what's needed and install it. See below.
|
||||||
|
- **A single-player web version** (Paul, Nov 2026: after the playtest):
|
||||||
|
play straight from the download page, with "Download for the full game
|
||||||
|
and multiplayer" beside it. Godot's web export, single-threaded, on the
|
||||||
|
Compatibility renderer (WebGL 2). What we found (0.6.1):
|
||||||
|
- *The look:* rendered with that renderer (`build/web_look/`, from
|
||||||
|
`tools/site/shots.gd` run with `--rendering-method gl_compatibility`),
|
||||||
|
it's still clearly the game, but harsher: no volumetric fog or haze,
|
||||||
|
no SSAO/SSIL (darker, sharper shadows), deeper greens. Night and fire
|
||||||
|
light hold up. **The sea renders flat white**: `shaders/water.gdshader`
|
||||||
|
needs a Compatibility path. Ordinary distance fog would bring some
|
||||||
|
atmosphere back; grass density and tree counts likely need a lower
|
||||||
|
setting for browsers (speed not measured yet).
|
||||||
|
- *Single-player only:* browsers can't use ENet (UDP), and can't start
|
||||||
|
the Host Game or per-land server processes. Joining from a browser
|
||||||
|
would mean a WebSocket transport on the server, served over wss://
|
||||||
|
(a certificate per server): a separate, bigger job, if ever.
|
||||||
|
- *Saves* go in the browser's storage (IndexedDB): kept, but per browser
|
||||||
|
and lost if site data is cleared. Maybe an export/import button.
|
||||||
|
- *Input:* the pointer lock needs a click, and Esc releases it in the
|
||||||
|
browser, so the pause menu needs another key too (P).
|
||||||
|
- *Size and hosting:* the engine (godot.wasm, 39.5 MB) plus the game data
|
||||||
|
(about 22 MB) download first, then cache. Cloudflare Pages refuses files
|
||||||
|
over 25 MiB, so the .wasm has to live elsewhere (the Gitea release, or
|
||||||
|
another host) or be split.
|
||||||
|
- *Size of the job:* one step to build it and see how it runs, another to
|
||||||
|
fix the water, tune the graphics and controls, and put it on the page.
|
||||||
|
- **Cloud saves** (Paul, Nov 2026: with the web version, and useful before
|
||||||
|
it, desktop to desktop): carry a single-player world between the desktop
|
||||||
|
game and the browser, **both ways**.
|
||||||
|
- *In the game:* pause menu **Save to cloud** (a cloud name and a password
|
||||||
|
the first time; after that it can upload on each save: autosave, sleep,
|
||||||
|
quit); title screen **Load from cloud** (name and password, pick a
|
||||||
|
world, it downloads and you carry on). What travels: the world folder
|
||||||
|
(`meta.json`, `world.json`), its explored map (`maps/`), and which of
|
||||||
|
Old Tam's tips you've seen. Not online worlds (they live on their server).
|
||||||
|
- *Size:* a world is 8-32 KB on disk, a few KB compressed: uploads are
|
||||||
|
instant and storage is next to free.
|
||||||
|
- *Where:* a Cloudflare Worker beside the site (`/api/...`), saves in R2
|
||||||
|
(or D1). Free tier is plenty. (Paul's own server would do, but is one
|
||||||
|
more service to keep running.)
|
||||||
|
- *Safety:* passwords never stored, only a salted hash (PBKDF2, WebCrypto
|
||||||
|
in the Worker); HTTPS only; wrong guesses lock a name out for a while;
|
||||||
|
a size cap and a world count per name. Option: encrypt the save with
|
||||||
|
the password in the game before upload, so the server holds only
|
||||||
|
ciphertext (but a forgotten password loses the cloud copy; the local
|
||||||
|
copy is unaffected).
|
||||||
|
- *Playing in two places:* each upload carries a counter. If the cloud's
|
||||||
|
copy is newer than the one you last downloaded, the game asks ("The
|
||||||
|
cloud has a newer save (from the web, 20 minutes ago): use that, or
|
||||||
|
keep this one?") instead of overwriting it.
|
||||||
|
- *Versions:* the same save format on desktop and web; a save from a
|
||||||
|
newer version than the game says so rather than loading it badly.
|
||||||
|
- *Size of the job:* one step for the Worker and storage (with tests), one
|
||||||
|
for the game side (name and password, the buttons, the newer-copy
|
||||||
|
question). Paul creates the Worker and its storage in his Cloudflare
|
||||||
|
account; the code and setup steps come with it.
|
||||||
|
- **Later, maybe:** Android tablets (Nov 2026: left for now). Most input is
|
||||||
|
already named actions, so a touch layer (stick, look, contextual buttons)
|
||||||
|
is possible; the Mobile renderer has the same lost effects as the web, and
|
||||||
|
a tablet can join but not host (no server processes).
|
||||||
|
|
||||||
### Updates (to design with the packaged builds)
|
### Updates (to design with the packaged builds)
|
||||||
Needed because every network change bumps the protocol (12 as of P5), and
|
Needed because every network change bumps the protocol (12 as of P5), and
|
||||||
@@ -102,7 +184,7 @@ an out-of-date game can't join an up-to-date server: today that means
|
|||||||
`git pull` and a restart on every machine.
|
`git pull` and a restart on every machine.
|
||||||
|
|
||||||
*Built so far (the check):* `scripts/core/updates.gd` asks the feed in
|
*Built so far (the check):* `scripts/core/updates.gd` asks the feed in
|
||||||
Project Settings (`emberwild/updates/feed`, now the git server's
|
Project Settings (`cinderwild/updates/feed`, now the git server's
|
||||||
`releases/latest` API; Gitea, Forgejo and GitHub answer alike) once at
|
`releases/latest` API; Gitea, Forgejo and GitHub answer alike) once at
|
||||||
start-up. It reads the release's `version.json` asset (written by
|
start-up. It reads the release's `version.json` asset (written by
|
||||||
`tools/export.sh`: version, protocol, per-platform file, sha256 and size).
|
`tools/export.sh`: version, protocol, per-platform file, sha256 and size).
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ server/run_server.sh --server-name "Paul's island" --password secret --port 2424
|
|||||||
It checks the Godot version, imports a fresh copy, then starts the server on
|
It checks the Godot version, imports a fresh copy, then starts the server on
|
||||||
UDP 24242 with no window. Settings live in `server.json` (its path is printed
|
UDP 24242 with no window. Settings live in `server.json` (its path is printed
|
||||||
at start-up; on macOS it's under
|
at start-up; on macOS it's under
|
||||||
`~/Library/Application Support/Godot/app_userdata/Emberwild/server/`).
|
`~/Library/Application Support/Godot/app_userdata/Cinderwild/server/`).
|
||||||
The shared island is saved in `worlds/server-world/` in that same folder,
|
The shared island is saved in `worlds/server-world/` in that same folder,
|
||||||
within 10 seconds of any change (and when someone leaves). Stop it with
|
within 10 seconds of any change (and when someone leaves). Stop it with
|
||||||
Ctrl+C. Players join with **Join Game** on the title screen. Over
|
Ctrl+C. Players join with **Join Game** on the title screen. Over
|
||||||
@@ -82,13 +82,13 @@ godot --headless --path . --script res://tests/test_survival_stats.gd
|
|||||||
|
|
||||||
## Save files
|
## Save files
|
||||||
Worlds are per machine, under
|
Worlds are per machine, under
|
||||||
`~/Library/Application Support/Godot/app_userdata/Emberwild/worlds/` on
|
`~/Library/Application Support/Godot/app_userdata/Cinderwild/worlds/` on
|
||||||
macOS (`%APPDATA%\Godot\app_userdata\Emberwild\worlds\` on Windows). Copy a
|
macOS (`%APPDATA%\Godot\app_userdata\Cinderwild\worlds\` on Windows). Copy a
|
||||||
world's folder across to move it to another machine.
|
world's folder across to move it to another machine.
|
||||||
|
|
||||||
The game used to be called Valheimlike, and its data lived in
|
The game used to be called Valheimlike, and its data lived in
|
||||||
`app_userdata/Valheimlike/`. The first time Emberwild starts it copies worlds,
|
`app_userdata/Valheimlike/`. The first time Cinderwild starts it copies worlds,
|
||||||
your identity and server settings across to `app_userdata/Emberwild/`
|
your identity and server settings across to `app_userdata/Cinderwild/`
|
||||||
(`scripts/core/boot.gd`). The old folder is left alone as a backup, and you
|
(`scripts/core/boot.gd`). The old folder is left alone as a backup, and you
|
||||||
can delete it once you're happy.
|
can delete it once you're happy.
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ small Diet bars, and a low group blinks.
|
|||||||
- **Autosave** every 2 minutes, on Save & Quit (to the title or desktop) and
|
- **Autosave** every 2 minutes, on Save & Quit (to the title or desktop) and
|
||||||
when the window is closed.
|
when the window is closed.
|
||||||
- Saves live in `user://worlds/<id>/` (on macOS:
|
- Saves live in `user://worlds/<id>/` (on macOS:
|
||||||
`~/Library/Application Support/Godot/app_userdata/Emberwild/worlds/`) as
|
`~/Library/Application Support/Godot/app_userdata/Cinderwild/worlds/`) as
|
||||||
`meta.json` + `world.json`. Writes are atomic, and the previous save is
|
`meta.json` + `world.json`. Writes are atomic, and the previous save is
|
||||||
kept as `world.json.bak`.
|
kept as `world.json.bak`.
|
||||||
- A save stores only what the player changed: player position and look,
|
- A save stores only what the player changed: player position and look,
|
||||||
|
|||||||
@@ -1,7 +1,168 @@
|
|||||||
# Worldlets design
|
# Worldlets design
|
||||||
|
|
||||||
Status: **proposal**. Nothing here is built yet, apart from the first piece
|
Status: **W0-W3, J1 and J3a built**: the Cove (with its stream), the cliff
|
||||||
of groundwork (`world.water_at()` and `data/fish.json`, see §6).
|
tunnel, and the journey on a server (a server per land; chat and the
|
||||||
|
player list across lands are J3b). The rest is planned.
|
||||||
|
|
||||||
|
*As built in J3a (a server per land):*
|
||||||
|
- `scripts/net/lands.gd` (autoload `Lands`): the server players join is the
|
||||||
|
hub. `Lands.ensure(land)` starts a land server (`--server --land cove
|
||||||
|
--port <hub+1..> --hub-port <hub>`, same config and world folder, output
|
||||||
|
to `land-<land>.log` beside server.json) and waits for it to say it's
|
||||||
|
listening. Hub and land servers talk over TCP on 127.0.0.1, port hub +
|
||||||
|
100, one JSON object per line: hello/ready, the player count, the clock
|
||||||
|
(the hub's, every 10 s: one clock everywhere), how far the tunnel's dug,
|
||||||
|
one-time passes, "stop", "host_left". A land server stops after 2 minutes
|
||||||
|
empty, when the hub stops, or if it loses the hub.
|
||||||
|
- A land server's saves: `Saves.land` makes it load and save its own
|
||||||
|
worldlet's file (`worldlets/cove.json`) in the hub's world folder.
|
||||||
|
Characters (`players/<id>.json`) are shared, and say which land they're
|
||||||
|
in (`land`) and, mid-crossing, where they'll come out (`arrive`).
|
||||||
|
- Crossing (`PlayerNet.cross`): the server saves the character as heading
|
||||||
|
there, makes sure the other server's up, and sends the game
|
||||||
|
`_s2c_travel {land, port, pass}`; the game drops the connection quietly
|
||||||
|
and joins that port with the pass (`Net.join(address, "", pass)`), checked
|
||||||
|
after the identity proof: a land server lets nobody in without one. The
|
||||||
|
world reloads on `Net.arrived`. Joining the hub when your character's in
|
||||||
|
another land sends you on there; a crossing that fails goes back to the
|
||||||
|
title. A hosted game: the host crossing doesn't stop the hub; the host
|
||||||
|
leaving the game from another land stops everything.
|
||||||
|
- The tunnel online: built on the server and in every game. The server
|
||||||
|
decides blows on its faces (`"hit"` finds them by uid), props
|
||||||
|
(`"tunnel_prop"`), going in and out (`"tunnel_go"`: a server teleport;
|
||||||
|
the move check allows the tunnel only from inside it) and the crossings
|
||||||
|
(`"cross"`), and tells everyone how it stands (`_s2c_tunnel`; it's in the
|
||||||
|
shared state a joining player gets, and saved with the Valley).
|
||||||
|
- `tests/test_net_lands.gd`: Alice digs and props online, lights it,
|
||||||
|
crosses to the Cove (bag and day kept) while Bob stays in the Valley,
|
||||||
|
comes back up, then leaves from the Cove and rejoining sends her there.
|
||||||
|
|
||||||
|
*As built in J3b (everyone, everywhere):*
|
||||||
|
- Chat and server messages go to every land (`Chat._all` relays through
|
||||||
|
the hub, `Lands.relay_line`).
|
||||||
|
- The hub keeps the **roster** (everyone on every land, with their land);
|
||||||
|
every land and every player's game gets it (`Net.roster`,
|
||||||
|
`Net.everyone()`): the pause menu's player list and `/players` show
|
||||||
|
where each player is.
|
||||||
|
- Commands typed in another land run **on the hub** (it alone keeps
|
||||||
|
server.json) and answer back there; after one that changes server.json
|
||||||
|
(`/set`, `/ban`, `/admin`, `/friend`...) every land reloads it.
|
||||||
|
`/kick` and `/ban` reach the land the player's in; `/weather`, `/save`
|
||||||
|
and `/backup` reach every land.
|
||||||
|
- **Sleep needs everyone in every land:** land servers report their beds,
|
||||||
|
the hub decides, and every land passes the night together. Getting into
|
||||||
|
bed while others are up: "You're in bed: waiting for everyone else to go
|
||||||
|
to bed (1 of 2 asleep)", and every land hears "Bob is in bed (1 of 2
|
||||||
|
asleep)" in chat.
|
||||||
|
- `tests/test_net_lands.gd` covers it all, Alice in the Cove and Bob in the
|
||||||
|
Valley.
|
||||||
|
|
||||||
|
*As built in J3c:*
|
||||||
|
- A land server that stops (crashed, or killed) tells its players "Lost the
|
||||||
|
connection to the Cove. Join the server again to carry on there."; the
|
||||||
|
hub notices it's gone (even mid-join) and joining again starts it afresh
|
||||||
|
and sends them back (their characters were saved by its autosave).
|
||||||
|
- The extra ports: `Lands.extra_ports()` / `ports_text()` ("UDP 24242 and
|
||||||
|
24243 (the Cove)"), on the Host page, in the "You're hosting" message, in
|
||||||
|
a hub's start-up log, and in docs/HOSTING.md.
|
||||||
|
- Tested in `test_net_lands.gd` (the Cove's server killed, Alice rejoins)
|
||||||
|
and `test_worldlet_pick.gd` (the Host page).
|
||||||
|
|
||||||
|
*As built in J1:*
|
||||||
|
- `scripts/world/tunnel.gd`: the seam (an outcrop on the clifftop strip,
|
||||||
|
7 m in from the edge, its angle from the seed) and the tunnel space, out
|
||||||
|
of sight 260 m under the island: a CSG rock block with the landings and
|
||||||
|
six sloping stretches cut out (6 m on, 4 m down each, switching back on
|
||||||
|
two lanes: 24 m down), then a sea cave. Faces (`tunnel_face.gd`) answer
|
||||||
|
like boulders, so the pick works on them: tier 3 only ("Too hard: you
|
||||||
|
need a better pick"), 10 blows for the seam and 15 per stretch. Each
|
||||||
|
stretch must be propped (2 wood, [E] at its mouth) before the next face
|
||||||
|
takes a blow ("It's loose..."): 10 wood all the way down.
|
||||||
|
- Underground: no sun, moon, sky reflections or sky-tinted fog
|
||||||
|
(`day_night.underground`), no weather, no lake water, sheltered. Candles
|
||||||
|
and torch stands place anywhere on its floor and save as ordinary pieces.
|
||||||
|
- Walking out of the sea cave's mouth travels (`world.travel()`): the
|
||||||
|
worldlet you leave is saved, your character and the clock go with you,
|
||||||
|
and the other loads, arriving at its sea cave (`sea_cave.gd`, on the
|
||||||
|
Cove's beach where the headland starts). [E] at the cave goes back up the
|
||||||
|
same tunnel to its bottom, once it's dug through.
|
||||||
|
- Saves: `world.json` is the home worldlet's state, as before;
|
||||||
|
others are `worldlets/<id>.json`; meta.json's `worldlet` is where you
|
||||||
|
are, `home` where you began, `arrive` where the next load puts you.
|
||||||
|
- `tests/test_tunnel.gd`: dig through, walk every stretch down and back
|
||||||
|
up, light it, reload, out to the Cove, back up, out at the top, and the
|
||||||
|
chest at home is still there.
|
||||||
|
- Not yet: Old Tam's tip about the seam, the Guide page, and travel on a
|
||||||
|
server (J3: the tunnel isn't built online). The journey between
|
||||||
|
worldlets is §10, and the two ways to play (the journey, or one land) are in
|
||||||
|
its decisions.
|
||||||
|
|
||||||
|
*As built in W3:*
|
||||||
|
- `data/worldlets/cove.json`: low hills, mountains behind, a wide sandy
|
||||||
|
bay (a `beach` block) and a small pond. Not a start; its `next` is back
|
||||||
|
to the Valley (tunnel, J1) and on to the Highlands (sail).
|
||||||
|
- The pier (`scripts/world/pier.gd`), a world "place" (`"places":
|
||||||
|
[{"type": "pier"}]`): planks on posts from where the sand is at deck
|
||||||
|
height to 24 m past the waterline. Worked out from the terrain, so it's
|
||||||
|
the same for everyone and never saved or dismantled; scenery keeps clear
|
||||||
|
of it. ~1.9 m of sea at its end, ~2.7 m a cast beyond.
|
||||||
|
- Sea fish in `data/fish.json`: mackerel (common) and cod, which only bites
|
||||||
|
in water 2.2 m or deeper (a per-fish `min_depth`). Grilled at a campfire,
|
||||||
|
salted, or smoked, like the lake's.
|
||||||
|
- New World has no "Where": the journey always begins in the Valley (the
|
||||||
|
Host page keeps it, for One land mode). Started with
|
||||||
|
`godot --path . -- --dev-worldlets`, New World and Host offer every
|
||||||
|
worldlet, the others marked "(testing)", to try one directly.
|
||||||
|
- No sea stacks without a sea cliff.
|
||||||
|
- Tests: `test_cove` (walk onto the pier, catch a mackerel off its end, cod
|
||||||
|
only in deep water), and the Cove in `test_terrain_edges` and
|
||||||
|
`test_boundary`.
|
||||||
|
- The stream (after 0.4.0): a `stream` block, a line of points from a
|
||||||
|
spring under the mountains to the bay. Its water surface is the running
|
||||||
|
minimum of the ground along it (so it only ever runs downhill, down to
|
||||||
|
sea level), the bed is cut 0.6 m under that and the banks rise a little
|
||||||
|
above it; its water is its own ribbon mesh (`terrain._stream_mesh()`),
|
||||||
|
murkier than the lake. `water_at()` answers "stream" (trout bite there,
|
||||||
|
`data/fish.json`), the map shows it, and scenery, the den, the barrow and
|
||||||
|
the stones keep clear of it (`terrain.near_stream()`).
|
||||||
|
|
||||||
|
*As built in W2:*
|
||||||
|
- `lake` blocks can repeat: every lake and pond is its own body
|
||||||
|
(`terrain.lakes`, `world.water_bodies()`), all at one surface
|
||||||
|
(`water_level`, set by the first). The first is still "the lake" to the
|
||||||
|
older code (ice, its fish, the tutorial).
|
||||||
|
- A `beach` block (angle, width, `shore_offset`, `slope`, `current_out`,
|
||||||
|
`wall_out`): the land eases to a waterline and shelves gently into the
|
||||||
|
sea, which is then the same surface as the ponds (one ocean sheet, no
|
||||||
|
inland disc). The invisible wall stands well out in the water, and before
|
||||||
|
it a current (`terrain.current_at()`, `world._current()`) carries
|
||||||
|
swimmers back faster than they can swim, with a toast.
|
||||||
|
- `water_at()` tells the sea off a beach from a pond.
|
||||||
|
- Test-only worldlets live in `tests/worldlets/` (never listed or offered).
|
||||||
|
`testbay.json` (two ponds and a beach) runs through
|
||||||
|
`test_water_bodies`, `test_terrain_edges` (8 seeds) and `test_boundary`
|
||||||
|
(swimming out across the beach).
|
||||||
|
|
||||||
|
*As built in W1:* a "Where" picker on the New World and Host Game pages
|
||||||
|
(worldlets with `"start": true`; only the Valley, so it's greyed out), the
|
||||||
|
worldlet in the Load list, and a hosted island keeps its worldlet like its
|
||||||
|
seed.
|
||||||
|
|
||||||
|
*As built in W0:*
|
||||||
|
- `data/worldlets/valley.json`: the Valley as data, its terrain as five
|
||||||
|
building blocks in order (`hills`, `rim`, `sea_cliff`, `lake`,
|
||||||
|
`ocean_edge`) with every number that used to be hard-coded.
|
||||||
|
- `terrain.gd` runs a worldlet's blocks (`use_worldlet()`); each block is
|
||||||
|
the old code's arithmetic, unchanged. `tests/test_worldlets.gd` checks
|
||||||
|
four seeds give exactly the same height grid as before (SHA-256 of the
|
||||||
|
grid, taken from the old code).
|
||||||
|
- The worldlet is chosen like the seed: a server's (it sends its real one),
|
||||||
|
else the save's `meta.json` "worldlet", else the Valley (worlds from
|
||||||
|
before W0). New worlds and servers record it.
|
||||||
|
- The WorldInfo API on the world: `worldlet_id()`, `water_bodies()`,
|
||||||
|
`climate()`, `crossings()` (the planned ways on), beside `water_at()`.
|
||||||
|
Systems still read the terrain's numbers directly; they move over to
|
||||||
|
these as each new worldlet needs them to differ (W1-W3).
|
||||||
|
|
||||||
## 1. What a worldlet is
|
## 1. What a worldlet is
|
||||||
A **worldlet** is a small, complete world type: a ~320 m playable area with
|
A **worldlet** is a small, complete world type: a ~320 m playable area with
|
||||||
@@ -122,6 +283,68 @@ The steps:
|
|||||||
- Later: tides (sea level shifting slowly), a raft or rowing boat, and
|
- Later: tides (sea level shifting slowly), a raft or rowing boat, and
|
||||||
driftwood on the beach.
|
driftwood on the beach.
|
||||||
|
|
||||||
|
### The Cove's own dangers and the pirate treasure (Paul, Nov 2026, for 0.6.0)
|
||||||
|
Paused at the Cove for a playtest before the Highlands. The Cove gets its
|
||||||
|
own set pieces instead of copies of the Valley's:
|
||||||
|
- **Set pieces per land**, listed in each worldlet's data (`"places"`). The
|
||||||
|
Valley keeps the wolf den, the barrow, the standing stones (the Old Stag)
|
||||||
|
and the bear; the Cove keeps the wolves only, and gets the wreck and the
|
||||||
|
treasure.
|
||||||
|
- **The wreck:** a beached ship on the rocks at the far end of the bay, its
|
||||||
|
timbers along the sand (driftwood). Its captain's sea chest: some nails,
|
||||||
|
rope, coins and a **torn treasure map**.
|
||||||
|
- **The map:** reading it puts an X on your N map: a mound of loose earth
|
||||||
|
by a marker stone in the Cove's hills (your own spot: see below).
|
||||||
|
- **The dig:** a pickaxe, many blows, turns up the **pirate chest**: a
|
||||||
|
**ship's compass** (what the boat will need to sail to the Highlands: the
|
||||||
|
next crossing's gate, J2), a **spyglass** (zooms your view while held),
|
||||||
|
**pirate gold**, and a **cutlass** (quick, harder-hitting than the iron
|
||||||
|
spear).
|
||||||
|
- **One treasure each, each in its own place** (online too): the wreck's
|
||||||
|
chest gives each player their own map, once, and each map's X is
|
||||||
|
somewhere different: picked from the world's seed and the player's id
|
||||||
|
(so it's fixed for them), among dry, gentle spots in the Cove's hills
|
||||||
|
away from the other places. You only see your own mound; the server knows
|
||||||
|
every player's spot and what each has found, and checks the right one
|
||||||
|
when you dig.
|
||||||
|
- **Gold** is a **trophy you can place** in your hut (a heap of coins) for
|
||||||
|
now; what else it's for waits until there's a trader.
|
||||||
|
- **The drowned crew:** the treasure's guards (the Cove's barrow guards).
|
||||||
|
Two or three rise from the shallows round the dig at night, or as the
|
||||||
|
chest comes up, for whoever's digging. Slow, hit hard, unafraid of fire,
|
||||||
|
they drag at you in the water.
|
||||||
|
- **Crabs** on the beach at dusk (pinch if trodden on; food), and **gulls**,
|
||||||
|
thieves like the crows (fish off drying racks, things dropped on sand).
|
||||||
|
- **The boat** (J2's first part): driftwood, seagrass rope and pitch (pine
|
||||||
|
resin and salt), at a workbench, placed at the pier. One each, moored,
|
||||||
|
yours and your friends'. Setting sail says the Highlands are uncharted
|
||||||
|
for now.
|
||||||
|
- **Report a bug** in the pause menu, and a short playtest guide.
|
||||||
|
- Order: set pieces per land, the wreck and map, the dig and the drowned
|
||||||
|
crew, crabs and gulls, the boat, report a bug; then 0.6.0 (protocol 18).
|
||||||
|
|
||||||
|
**Built (Nov 2026), and where it differs from the plan above:**
|
||||||
|
- Places: `"places"` in `data/worldlets/*.json` (`world.has_place()`); the
|
||||||
|
Cove's extra wildlife in `"wildlife"` (`world.has_wildlife()`).
|
||||||
|
- The wreck (`scripts/world/wreck.gd`): the sea chest gives a map, 4 nails
|
||||||
|
and 2 salt (no rope or coins: gold comes from the dig). Each X is one of
|
||||||
|
12 hiding spots picked from the seed (`world.treasure_spots()`), chosen
|
||||||
|
per player by their id.
|
||||||
|
- The dig (`treasure.gd`, `treasure_mound.gd`, `crew_site.gd`): 18 HP, any
|
||||||
|
pickaxe; three drowned rise round the mound itself (not from the
|
||||||
|
shallows), at the first blow by night or as the chest comes up by day.
|
||||||
|
- Crabs (`crab.gd`, `shore.gd`): up to 5 per player on the sand from 17:30
|
||||||
|
to 06:30, pinch for 5 within about 2 m, let you go past 6 m; crab meat.
|
||||||
|
Gulls (`gull.gd`, `thieves.gd`): food on the sand, and fish on a smoking
|
||||||
|
rack; they take one and fly off (only food, not tools left on the sand).
|
||||||
|
- The boat (`moorings.gd`, `boat.gd`, `mooring_post.gd`): a rowing boat
|
||||||
|
item (12 driftwood, 4 rope, 3 pitch, 6 nails), tied up at the post at the
|
||||||
|
end of the pier rather than placed freely: 6 berths, one boat each, saved
|
||||||
|
with the Cove. Pine resin drops from felled pines in every land.
|
||||||
|
- Report a bug (`scripts/core/bug_report.gd`): opens the repo's new-issue
|
||||||
|
page filled in (the tester posts it, signed in); no token in the game.
|
||||||
|
Testers' notes: `docs/PLAYTEST.md`.
|
||||||
|
|
||||||
## 6. Done already (groundwork)
|
## 6. Done already (groundwork)
|
||||||
- `world.water_at(x, z)` returns `{type, surface, depth}` for the lake and
|
- `world.water_at(x, z)` returns `{type, surface, depth}` for the lake and
|
||||||
the sea, and fishing uses it instead of `terrain.water_level`.
|
the sea, and fishing uses it instead of `terrain.water_level`.
|
||||||
@@ -164,3 +387,227 @@ 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.
|
||||||
|
|
||||||
|
### The first tunnel: down the sea cliff to the Cove
|
||||||
|
The Valley's sea side ends at a sheer cliff, its top about level with the
|
||||||
|
valley and the sea some 25 m below. The Cove is down there, so the first
|
||||||
|
tunnel goes **down**, inside the cliff, from the clifftop to the shore.
|
||||||
|
- **The seam:** a cracked outcrop on the clifftop strip near the edge (the
|
||||||
|
sea_cliff block marks where), with old pick marks and loose stones at its
|
||||||
|
foot. It can't be dug until you have the antler pickaxe (tier 3); a lesser
|
||||||
|
pick just chips it ("Too hard: you need a better pick").
|
||||||
|
- **Digging down:** the tunnel is about six stretches, each a few metres on
|
||||||
|
and a few down: a sloping passage that turns back on itself like a
|
||||||
|
stair, so the 25 m drop fits inside the cliff. A stretch takes many pick
|
||||||
|
blows (stamina, and wear on the pick) to open.
|
||||||
|
- **Props:** each new stretch needs a **timber prop** (2 wood) set at its
|
||||||
|
mouth before you can dig the next. Leave one out and the face won't take
|
||||||
|
another blow ("It's loose: prop it first"); nothing caves in on you, so
|
||||||
|
it's a cost, not a trap. That's 10 wood all the way down.
|
||||||
|
- **Light:** the tunnel has no daylight at all. Every stretch is pitch dark
|
||||||
|
until you bring light: a torch in your hand works, but your hands are full
|
||||||
|
with the pick, so the way to do it is to set **candles and torch stands**
|
||||||
|
as you go (tallow candles from bear fat, torch stands from wood). Placed
|
||||||
|
lights stay, so the way is lit for the trip back and for friends.
|
||||||
|
- **The bottom:** the last stretch breaks out into a sea cave, its mouth
|
||||||
|
opening on the Cove's shingle beach. Walking out of the mouth is the
|
||||||
|
crossing: a fade, then the Cove, arriving at the cave's Cove end. The way
|
||||||
|
back starts there and climbs the same tunnel.
|
||||||
|
- **What's saved:** the tunnel belongs to the Valley's save: how far it's
|
||||||
|
dug, the props, the lights. On a server it's shared: anyone can dig on
|
||||||
|
from where someone stopped, and the lights are everyone's.
|
||||||
|
- **How it's built:** the tunnel is its own small space (a "between" scene
|
||||||
|
next to the worldlet), made from a kit of rock pieces (straight, turn,
|
||||||
|
slope down, the cave mouth) laid out from the seed, with box-shaped
|
||||||
|
colliders. The heightmap can't have holes or overhangs, so the passage is
|
||||||
|
never cut into the terrain itself: you walk into the seam and you're in
|
||||||
|
the tunnel scene.
|
||||||
|
|
||||||
|
### 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:** there's one clock for every land (see the decisions below). A
|
||||||
|
worldlet you're not in doesn't run, but catches up on the time when you
|
||||||
|
come back, as after a night's sleep; nothing roams it meanwhile, so a base
|
||||||
|
you left isn't 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. Built in J3, after single-player travel
|
||||||
|
works.
|
||||||
|
- **A server per land (Paul, Nov 2026)**, rather than one process holding
|
||||||
|
several worldlets (which would mean making every network module, the
|
||||||
|
saves and the actions handle several worlds at once):
|
||||||
|
- the Valley's server is the **hub**: the address everyone joins. It
|
||||||
|
starts a server for another land (the next UDP port: 24243 the Cove,
|
||||||
|
and so on) when someone first goes there, and stops it a couple of
|
||||||
|
minutes after its last player leaves;
|
||||||
|
- each land server is today's server, with its own worldlet, save file,
|
||||||
|
weather and creatures; it keeps a private link to the hub on the host
|
||||||
|
machine;
|
||||||
|
- characters live in one shared folder. A crossing saves your character
|
||||||
|
first, marks you as on your way, and hands your game the land's port
|
||||||
|
and a one-time pass; your game reconnects there ("On to the Cove...").
|
||||||
|
Nothing can be lost or copied on the way: crash mid-way and you come
|
||||||
|
back where you were going. Joining the hub when your character's in
|
||||||
|
another land sends you straight on there;
|
||||||
|
- chat, the player list (with where each player is) and admin commands
|
||||||
|
go through the hub to every land;
|
||||||
|
- the tunnel belongs to the Valley's server; the Cove's asks the hub
|
||||||
|
whether it's dug through;
|
||||||
|
- from the internet, friends need those extra ports forwarded too (on a
|
||||||
|
home network, nothing to do). The Host page will list them;
|
||||||
|
- the Old Stag and other bosses scale with the players in their land.
|
||||||
|
- Phases: **J3a** the tunnel online, land servers on demand, the
|
||||||
|
crossing and the shared characters; **J3b** chat, the player list and
|
||||||
|
admin across lands; **J3c** the awkward cases (a land server crashing,
|
||||||
|
rejoining mid-crossing, the Host page's ports).
|
||||||
|
- **One clock for every land (Paul, Nov 2026).** The day and hour are the
|
||||||
|
same everywhere, so the lands never drift apart:
|
||||||
|
- sleeping until morning needs **everyone, in every land**, in bed (on a
|
||||||
|
server the hub counts them all, and every land skips to morning
|
||||||
|
together);
|
||||||
|
- a land nobody is in doesn't run, but when someone arrives it catches up
|
||||||
|
on the time since anyone was last there, as after a night's sleep:
|
||||||
|
kilns and smelters work, fires burn down, food goes off, trees and
|
||||||
|
berries grow back. (Built for single-player already: `world.catch_up()`.)
|
||||||
|
No creatures roam it meanwhile, so a base you left isn't raided.
|
||||||
|
- **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).
|
||||||
|
- **Two ways to play (Paul, Nov 2026).**
|
||||||
|
- **The journey** (the standard game), single-player and multiplayer
|
||||||
|
alike: every new world starts in the Valley and the worldlets come in
|
||||||
|
their fixed order (Valley, Cove, Highlands, Marsh, Ember). Each
|
||||||
|
worldlet's shape is set by its definition; the seed only changes the
|
||||||
|
detail (hills, trees, where the den is), so the journey is the same
|
||||||
|
adventure for everyone.
|
||||||
|
- **One land** (a hosting choice): no journey, no crossings, the server
|
||||||
|
is locked to one worldlet, and the host picks which, from all of them.
|
||||||
|
Co-op or PvP as now (the rules preset). In code: a `mode` in
|
||||||
|
`server.json` ("journey" / "one_land"); in one-land mode the Host
|
||||||
|
page's "Where" lists every worldlet rather than only the starts, and
|
||||||
|
the crossings (`world.crossings()`) answer none, so a tunnel seam
|
||||||
|
can't be dug. Kept, like the seed and the worldlet, once the island
|
||||||
|
exists. Worth building once the Cove exists (until then there's only
|
||||||
|
the Valley to pick).
|
||||||
|
|
||||||
|
### 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 aren't running and cost nothing (each starts its own server when someone goes there).
|
||||||
|
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?
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ runnable=true
|
|||||||
dedicated_server=false
|
dedicated_server=false
|
||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
include_filter="data/*.json"
|
include_filter="data/*.json, data/worldlets/*.json"
|
||||||
exclude_filter="tests/*, docs/*, tools/*, server/*"
|
exclude_filter="tests/*, docs/*, tools/*, server/*"
|
||||||
export_path="build/macos/Emberwild.zip"
|
export_path="build/macos/Cinderwild.zip"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
@@ -26,11 +26,11 @@ custom_template/release=""
|
|||||||
debug/export_console_wrapper=0
|
debug/export_console_wrapper=0
|
||||||
application/icon=""
|
application/icon=""
|
||||||
application/icon_interpolation=4
|
application/icon_interpolation=4
|
||||||
application/bundle_identifier="com.pbailey.emberwild"
|
application/bundle_identifier="com.pbailey.cinderwild"
|
||||||
application/signature=""
|
application/signature=""
|
||||||
application/app_category="Games"
|
application/app_category="Games"
|
||||||
application/short_version="0.2.0"
|
application/short_version="0.6.2"
|
||||||
application/version="0.2.0"
|
application/version="0.6.2"
|
||||||
application/copyright=""
|
application/copyright=""
|
||||||
display/high_res=true
|
display/high_res=true
|
||||||
codesign/codesign=1
|
codesign/codesign=1
|
||||||
@@ -64,9 +64,9 @@ runnable=true
|
|||||||
dedicated_server=false
|
dedicated_server=false
|
||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
include_filter="data/*.json"
|
include_filter="data/*.json, data/worldlets/*.json"
|
||||||
exclude_filter="tests/*, docs/*, tools/*, server/*"
|
exclude_filter="tests/*, docs/*, tools/*, server/*"
|
||||||
export_path="build/windows/Emberwild/Emberwild.exe"
|
export_path="build/windows/Cinderwild/Cinderwild.exe"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
@@ -89,11 +89,11 @@ 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.2.0"
|
application/file_version="0.6.2"
|
||||||
application/product_version="0.2.0"
|
application/product_version="0.6.2"
|
||||||
application/company_name=""
|
application/company_name=""
|
||||||
application/product_name="Emberwild"
|
application/product_name="Cinderwild"
|
||||||
application/file_description="Emberwild"
|
application/file_description="Cinderwild"
|
||||||
application/copyright=""
|
application/copyright=""
|
||||||
application/trademarks=""
|
application/trademarks=""
|
||||||
application/export_angle=0
|
application/export_angle=0
|
||||||
|
|||||||
2
play.sh
2
play.sh
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Starts Emberwild (the game, with its window and title screen).
|
# Starts Cinderwild (the game, with its window and title screen).
|
||||||
#
|
#
|
||||||
# ./play.sh
|
# ./play.sh
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
|||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Emberwild"
|
config/name="Cinderwild"
|
||||||
config/description="Low-poly survival game — Valheim look, Rust depth, self-hosted, moddable."
|
config/description="Low-poly survival game — Valheim look, Rust depth, self-hosted, moddable."
|
||||||
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.2.0"
|
config/version="0.6.2"
|
||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
@@ -31,12 +31,14 @@ Hints="*res://scripts/core/hints.gd"
|
|||||||
Updates="*res://scripts/core/updates.gd"
|
Updates="*res://scripts/core/updates.gd"
|
||||||
Saves="*res://scripts/core/saves.gd"
|
Saves="*res://scripts/core/saves.gd"
|
||||||
Net="*res://scripts/net/net.gd"
|
Net="*res://scripts/net/net.gd"
|
||||||
|
Lands="*res://scripts/net/lands.gd"
|
||||||
WorldNet="*res://scripts/net/world_net.gd"
|
WorldNet="*res://scripts/net/world_net.gd"
|
||||||
PlayerNet="*res://scripts/net/player_net.gd"
|
PlayerNet="*res://scripts/net/player_net.gd"
|
||||||
CreatureNet="*res://scripts/net/creature_net.gd"
|
CreatureNet="*res://scripts/net/creature_net.gd"
|
||||||
Chat="*res://scripts/net/chat.gd"
|
Chat="*res://scripts/net/chat.gd"
|
||||||
Sfx="*res://scripts/core/sfx.gd"
|
Sfx="*res://scripts/core/sfx.gd"
|
||||||
Music="*res://scripts/core/music.gd"
|
Music="*res://scripts/core/music.gd"
|
||||||
|
Narrator="*res://scripts/core/narrator.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
@@ -91,9 +93,9 @@ weather_wind={
|
|||||||
"value": 1.0
|
"value": 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
[emberwild]
|
[cinderwild]
|
||||||
|
|
||||||
updates/feed="https://git.psbailey.uk/api/v1/repos/Admin/Emberwild/releases/latest"
|
updates/feed="https://git.psbailey.uk/api/v1/repos/Admin/Cinderwild/releases/latest"
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
|
|||||||
@@ -43,5 +43,5 @@ offset_left = 16.0
|
|||||||
offset_top = 12.0
|
offset_top = 12.0
|
||||||
offset_right = 600.0
|
offset_right = 600.0
|
||||||
offset_bottom = 60.0
|
offset_bottom = 60.0
|
||||||
text = "E interact · LMB use/place · RMB eat · R rotate · X dismantle · Tab inventory & crafting · Esc menu & controls"
|
text = "E interact · LMB use/place · RMB eat · R rotate · X dismantle · Q drop · K/M bag row · N map · Tab inventory & crafting · Esc menu & controls"
|
||||||
label_settings = SubResource("Label_1")
|
label_settings = SubResource("Label_1")
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ extends "res://scripts/building/piece.gd"
|
|||||||
## Bed: E sets your spawn point here. Once it's yours, E at night sleeps until
|
## Bed: E sets your spawn point here. Once it's yours, E at night sleeps until
|
||||||
## morning, if the bed is under a roof with walls around it.
|
## morning, if the bed is under a roof with walls around it.
|
||||||
|
|
||||||
|
func _build() -> void:
|
||||||
|
add_to_group("bed") # (a bed or a bedroll: either is a spawn point)
|
||||||
|
|
||||||
func display_name() -> String:
|
func display_name() -> String:
|
||||||
return "Bed"
|
return String(def.get("name", "Bed"))
|
||||||
|
|
||||||
|
## A bedroll: you can sleep in the open (world.sleep_block_reason).
|
||||||
|
func open_air() -> bool:
|
||||||
|
return bool(def.get("place", {}).get("open_air", false))
|
||||||
|
|
||||||
func is_spawn() -> bool:
|
func is_spawn() -> bool:
|
||||||
var w := get_parent().get_parent()
|
var w := get_parent().get_parent()
|
||||||
@@ -11,8 +18,8 @@ func is_spawn() -> bool:
|
|||||||
|
|
||||||
func interact_prompt(_player: Node) -> String:
|
func interact_prompt(_player: Node) -> String:
|
||||||
if not is_spawn():
|
if not is_spawn():
|
||||||
return "Bed [E] Set spawn point"
|
return "%s [E] Set spawn point" % display_name()
|
||||||
return "Bed (your spawn point) [E] Sleep"
|
return "%s (your spawn point) [E] Sleep" % display_name()
|
||||||
|
|
||||||
func interact(player: Node) -> void:
|
func interact(player: Node) -> void:
|
||||||
Actions.use_bed(player, self)
|
Actions.use_bed(player, self)
|
||||||
|
|||||||
1
scripts/building/bed.gd.uid
Normal file
1
scripts/building/bed.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://nx08dmty60ny
|
||||||
@@ -61,6 +61,15 @@ static func _define() -> Dictionary:
|
|||||||
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0)],
|
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0)],
|
||||||
"center": Vector3(0.33, 0.33, 0),
|
"center": Vector3(0.33, 0.33, 0),
|
||||||
}
|
}
|
||||||
|
# Half wall: 2 m wide, 1 m tall (0.6.2, Gitea #7). On a wall's top, under
|
||||||
|
# a gable, it closes the end of a taller roof (a wider house, with a
|
||||||
|
# second row of roof panels); on a floor, it's a low wall or a railing.
|
||||||
|
k["half_wall"] = {
|
||||||
|
"shapes": [_box(Vector3(0, 0.5, 0), Vector3(2, 1, 0.2))],
|
||||||
|
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0),
|
||||||
|
Vector3(-1, 1, 0), Vector3(0, 1, 0), Vector3(1, 1, 0)],
|
||||||
|
"center": Vector3(0, 0.5, 0),
|
||||||
|
}
|
||||||
# --- P2 (docs/PROGRESSION_DESIGN.md §3) ---
|
# --- P2 (docs/PROGRESSION_DESIGN.md §3) ---
|
||||||
# Pillar: a 0.4 m post standing on a floor corner or edge, carrying what's
|
# Pillar: a 0.4 m post standing on a floor corner or edge, carrying what's
|
||||||
# on top (a floor's corner meets its top).
|
# on top (a floor's corner meets its top).
|
||||||
@@ -95,6 +104,29 @@ static func _define() -> Dictionary:
|
|||||||
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0)],
|
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0)],
|
||||||
"center": Vector3(0, 1.5, 0),
|
"center": Vector3(0, 1.5, 0),
|
||||||
}
|
}
|
||||||
|
# Roof ridge (0.6.2, Gitea #7): a 2 m cap, an upside-down V at the roof's
|
||||||
|
# slope, for houses whose width leaves a 2 m gap between the two sides'
|
||||||
|
# top rows (6 m, 10 m). Origin = centre at the level of the rows' top
|
||||||
|
# edges (local z = +-1), rising 0.5 m to the peak along z = 0.
|
||||||
|
var ns := Vector3(0, cos(ROOF_ANGLE), sin(ROOF_ANGLE))
|
||||||
|
var nn := Vector3(0, cos(ROOF_ANGLE), -sin(ROOF_ANGLE))
|
||||||
|
var half := sqrt(1.25)
|
||||||
|
k["roof_ridge"] = {
|
||||||
|
"shapes": [
|
||||||
|
{"type": "box", "center": Vector3(0, 0.25, 0.5) + ns * ROOF_T * 0.5, "size": Vector3(2, ROOF_T, half), "basis": Basis(Vector3.RIGHT, ROOF_ANGLE)},
|
||||||
|
{"type": "box", "center": Vector3(0, 0.25, -0.5) + nn * ROOF_T * 0.5, "size": Vector3(2, ROOF_T, half), "basis": Basis(Vector3.RIGHT, -ROOF_ANGLE)},
|
||||||
|
_box(Vector3(0, 0.55, 0), Vector3(2, 0.08, 0.3))], # (the ridge board: no gap at the peak for rain, or the shelter test)
|
||||||
|
"snaps": [Vector3(-1, 0, 1), Vector3(0, 0, 1), Vector3(1, 0, 1),
|
||||||
|
Vector3(-1, 0, -1), Vector3(0, 0, -1), Vector3(1, 0, -1)],
|
||||||
|
"center": Vector3(0, 0.25, 0),
|
||||||
|
}
|
||||||
|
# Ridge gable: the small triangle (2 m wide, 0.5 m tall, peak in the
|
||||||
|
# middle) that closes the end under a roof ridge, on a half wall or a wall.
|
||||||
|
k["ridge_gable"] = {
|
||||||
|
"shapes": [_prism([Vector2(-1, 0), Vector2(1, 0), Vector2(0, 0.5)], 0.2)],
|
||||||
|
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 0.5, 0)],
|
||||||
|
"center": Vector3(0, 0.17, 0),
|
||||||
|
}
|
||||||
var n := Vector3(0, cos(ROOF_ANGLE), sin(ROOF_ANGLE)) # panel normal (up and out)
|
var n := Vector3(0, cos(ROOF_ANGLE), sin(ROOF_ANGLE)) # panel normal (up and out)
|
||||||
# (Ends exactly at the ridge. The seam where two roofs meet is handled by
|
# (Ends exactly at the ridge. The seam where two roofs meet is handled by
|
||||||
# build_system.roofed_at sampling several rays.)
|
# build_system.roofed_at sampling several rays.)
|
||||||
|
|||||||
1
scripts/building/build_piece.gd.uid
Normal file
1
scripts/building/build_piece.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dsbvhwas6wsxf
|
||||||
@@ -162,6 +162,23 @@ func recompute() -> Array:
|
|||||||
|
|
||||||
# --- placement --------------------------------------------------------------
|
# --- placement --------------------------------------------------------------
|
||||||
|
|
||||||
|
## Roof pieces: these may pass through one another.
|
||||||
|
const ROOF_KINDS := ["roof", "roof_ridge"]
|
||||||
|
|
||||||
|
## A roof piece of `kind` already sitting at `xf` (the same place, facing
|
||||||
|
## the same way), so a second one isn't put inside the first.
|
||||||
|
func _same_roof_at(kind: String, xf: Transform3D, exclude: Node) -> bool:
|
||||||
|
for p in build_pieces():
|
||||||
|
if p == exclude or p.kind != kind:
|
||||||
|
continue
|
||||||
|
var pxf: Transform3D = p.global_transform
|
||||||
|
var facing: float = pxf.basis.z.normalized().dot(xf.basis.z.normalized())
|
||||||
|
if kind == "roof_ridge":
|
||||||
|
facing = absf(facing) # (it's the same both ways round)
|
||||||
|
if pxf.origin.distance_to(xf.origin) < 0.15 and facing > 0.99:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
## Why a building piece can't go at `xf` ("" = it can).
|
## Why a building piece can't go at `xf` ("" = it can).
|
||||||
func check(def: Dictionary, xf: Transform3D, exclude: Node = null) -> String:
|
func check(def: Dictionary, xf: Transform3D, exclude: Node = null) -> String:
|
||||||
var kind: String = def.place.kind
|
var kind: String = def.place.kind
|
||||||
@@ -172,6 +189,9 @@ func check(def: Dictionary, xf: Transform3D, exclude: Node = null) -> String:
|
|||||||
if c.y < terrain.water_level + 0.1:
|
if c.y < terrain.water_level + 0.1:
|
||||||
return "Can't build in water"
|
return "Can't build in water"
|
||||||
var space := world.get_world_3d().direct_space_state
|
var space := world.get_world_3d().direct_space_state
|
||||||
|
var roofy: bool = kind in ROOF_KINDS
|
||||||
|
if roofy and _same_roof_at(kind, xf, exclude):
|
||||||
|
return "There's already a roof there"
|
||||||
for s in info.shapes:
|
for s in info.shapes:
|
||||||
var made := BuildPiece.make_shape(s, OVERLAP_MARGIN)
|
var made := BuildPiece.make_shape(s, OVERLAP_MARGIN)
|
||||||
var q := PhysicsShapeQueryParameters3D.new()
|
var q := PhysicsShapeQueryParameters3D.new()
|
||||||
@@ -182,9 +202,13 @@ func check(def: Dictionary, xf: Transform3D, exclude: Node = null) -> String:
|
|||||||
if exclude != null:
|
if exclude != null:
|
||||||
ex.append(exclude.get_rid())
|
ex.append(exclude.get_rid())
|
||||||
q.exclude = ex
|
q.exclude = ex
|
||||||
var hits := space.intersect_shape(q, 1)
|
var hits := space.intersect_shape(q, 8)
|
||||||
if not hits.is_empty():
|
for h in hits:
|
||||||
last_blocker = hits[0].collider
|
# Roofs can run through other roofs (Gitea #7: an L, a wing at an
|
||||||
|
# angle, roofs at odd heights), but nothing else.
|
||||||
|
if roofy and h.collider != null and String(h.collider.get("kind")) in ROOF_KINDS:
|
||||||
|
continue
|
||||||
|
last_blocker = h.collider
|
||||||
return "Something's in the way"
|
return "Something's in the way"
|
||||||
var mat := BuildPiece.material_of(def)
|
var mat := BuildPiece.material_of(def)
|
||||||
if support_for(kind, xf, exclude, mat) < MIN_SUPPORT:
|
if support_for(kind, xf, exclude, mat) < MIN_SUPPORT:
|
||||||
|
|||||||
1
scripts/building/build_system.gd.uid
Normal file
1
scripts/building/build_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://gj02g6p3xfip
|
||||||
1
scripts/building/door.gd.uid
Normal file
1
scripts/building/door.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dhsyx4mrm28oe
|
||||||
1
scripts/building/forge.gd.uid
Normal file
1
scripts/building/forge.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cl5ip4achfdwo
|
||||||
1
scripts/building/hearth.gd.uid
Normal file
1
scripts/building/hearth.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://mjs6qb5p3q1a
|
||||||
29
scripts/building/jar.gd
Normal file
29
scripts/building/jar.gd
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
extends "res://scripts/building/piece.gd"
|
||||||
|
## Clay jar: 6 slots of storage, opened with E like a chest, and food kept in
|
||||||
|
## it lasts three times as long (Inventory.keep_mult).
|
||||||
|
|
||||||
|
const SLOTS := 6
|
||||||
|
const KEEP := 3.0
|
||||||
|
var inventory := Inventory.new(SLOTS)
|
||||||
|
|
||||||
|
func _build() -> void:
|
||||||
|
inventory.keep_mult = KEEP
|
||||||
|
|
||||||
|
func display_name() -> String:
|
||||||
|
return "Clay Jar"
|
||||||
|
|
||||||
|
func dismantle_block_reason() -> String:
|
||||||
|
for s in inventory.slots:
|
||||||
|
if s != null:
|
||||||
|
return "Empty the jar first"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
func interact_prompt(_player: Node) -> String:
|
||||||
|
var used := 0
|
||||||
|
for s in inventory.slots:
|
||||||
|
if s != null:
|
||||||
|
used += 1
|
||||||
|
return "Clay jar (%d/%d, food keeps 3x as long) [E] Open" % [used, SLOTS]
|
||||||
|
|
||||||
|
func interact(player: Node) -> void:
|
||||||
|
player.open_inventory(self)
|
||||||
1
scripts/building/jar.gd.uid
Normal file
1
scripts/building/jar.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ciqsy8msggcke
|
||||||
64
scripts/building/lamp.gd
Normal file
64
scripts/building/lamp.gd
Normal 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)
|
||||||
1
scripts/building/lamp.gd.uid
Normal file
1
scripts/building/lamp.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cwvlv0fbxhlgj
|
||||||
@@ -23,6 +23,8 @@ const RECIPES := {
|
|||||||
{"inputs": {"raw_meat": 2, "wood": 1}, "output": "smoked_meat", "count": 2, "seconds": 60.0},
|
{"inputs": {"raw_meat": 2, "wood": 1}, "output": "smoked_meat", "count": 2, "seconds": 60.0},
|
||||||
{"inputs": {"perch": 2, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0},
|
{"inputs": {"perch": 2, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0},
|
||||||
{"inputs": {"trout": 1, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0},
|
{"inputs": {"trout": 1, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0},
|
||||||
|
{"inputs": {"mackerel": 2, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0},
|
||||||
|
{"inputs": {"cod": 1, "wood": 1}, "output": "smoked_fish", "count": 2, "seconds": 60.0},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
## What the prompt says it makes, and what it needs when idle.
|
## What the prompt says it makes, and what it needs when idle.
|
||||||
|
|||||||
1
scripts/building/processor.gd.uid
Normal file
1
scripts/building/processor.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://jvcfwahbmdsm
|
||||||
1
scripts/building/rug.gd.uid
Normal file
1
scripts/building/rug.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://d21if6sepl888
|
||||||
1
scripts/building/stonecutter.gd.uid
Normal file
1
scripts/building/stonecutter.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dk1ge2xxgiev1
|
||||||
@@ -31,6 +31,10 @@ func _ready() -> void:
|
|||||||
_bind("inventory", [KEY_TAB, KEY_I])
|
_bind("inventory", [KEY_TAB, KEY_I])
|
||||||
_bind("rotate_piece", [KEY_R])
|
_bind("rotate_piece", [KEY_R])
|
||||||
_bind("dismantle", [KEY_X])
|
_bind("dismantle", [KEY_X])
|
||||||
|
_bind("drop", [KEY_Q]) # (shift+Q drops just one)
|
||||||
|
_bind("row_up", [KEY_K]) # the 1-8 keys pick from the bag row above...
|
||||||
|
_bind("row_down", [KEY_M]) # ...or below
|
||||||
|
_bind("map", [KEY_N]) # the map (Gitea issue #1)
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
_bind("hotbar_%d" % (i + 1), [KEY_1 + i])
|
_bind("hotbar_%d" % (i + 1), [KEY_1 + i])
|
||||||
_bind_mouse("attack", MOUSE_BUTTON_LEFT)
|
_bind_mouse("attack", MOUSE_BUTTON_LEFT)
|
||||||
@@ -254,6 +258,14 @@ func harvest_hit(player: Node3D, node: Node3D, hit_point: Vector3) -> bool:
|
|||||||
return false
|
return false
|
||||||
var held: Dictionary = ItemDB.get_item(player.held_item_id())
|
var held: Dictionary = ItemDB.get_item(player.held_item_id())
|
||||||
var tool: Dictionary = held.get("tool", {})
|
var tool: Dictionary = held.get("tool", {})
|
||||||
|
if node.has_method("refusal"): # (the cliff tunnel's faces: their own rules first)
|
||||||
|
var why: String = node.refusal()
|
||||||
|
if not why.is_empty():
|
||||||
|
notify.emit(why)
|
||||||
|
return false
|
||||||
|
if tool.get("kind", "") == node.tool_kind and int(tool.get("tier", 0)) < node.min_tier:
|
||||||
|
notify.emit("Too hard: you need a better pick")
|
||||||
|
return false
|
||||||
if tool.get("kind", "") != node.tool_kind or int(tool.get("tier", 0)) < node.min_tier:
|
if tool.get("kind", "") != node.tool_kind or int(tool.get("tier", 0)) < node.min_tier:
|
||||||
notify.emit("Needs a %s (Tab to craft)" % ItemDB.item_name("stone_" + node.tool_kind))
|
notify.emit("Needs a %s (Tab to craft)" % ItemDB.item_name("stone_" + node.tool_kind))
|
||||||
return false
|
return false
|
||||||
@@ -736,8 +748,53 @@ static func harvest_sound(tool_kind: String, felled: bool) -> String:
|
|||||||
|
|
||||||
## A piece going down: wood, stone, or something soft.
|
## A piece going down: wood, stone, or something soft.
|
||||||
static func place_sound(id: String) -> String:
|
static func place_sound(id: String) -> String:
|
||||||
if id.contains("stone") or id in ["campfire", "forge", "hearth", "stonecutter"]:
|
if id.contains("stone") or id.contains("jar") or id in ["campfire", "forge", "hearth", "stonecutter"]:
|
||||||
return "place_stone"
|
return "place_stone"
|
||||||
if id.contains("rug") or id.contains("bed") or id == "smoking_rack":
|
if id.contains("rug") or id.contains("bed") or id == "smoking_rack":
|
||||||
return "place_soft"
|
return "place_soft"
|
||||||
return "place_wood"
|
return "place_wood"
|
||||||
|
|
||||||
|
# --- charms and bait ------------------------------------------------------------------
|
||||||
|
|
||||||
|
## A charm carried anywhere in the bag (the fang necklace: {"against": "wolf",
|
||||||
|
## "damage": 0.7}) takes the edge off hits from what it's against. `by` is the
|
||||||
|
## hit's "a wolf" / "a alpha wolf" / "a spirit wolf" (survival.damage).
|
||||||
|
static func charm_mult(inv: Inventory, by: String) -> float:
|
||||||
|
var m := 1.0
|
||||||
|
if by.is_empty():
|
||||||
|
return m
|
||||||
|
for s in inv.slots:
|
||||||
|
if s == null:
|
||||||
|
continue
|
||||||
|
var c: Dictionary = ItemDB.get_item(String(s.id)).get("charm", {})
|
||||||
|
if not c.is_empty() and by.to_lower().contains(String(c.get("against", "?"))):
|
||||||
|
m = minf(m, float(c.get("damage", 1.0)))
|
||||||
|
return m
|
||||||
|
|
||||||
|
const BAIT := "rotten_food"
|
||||||
|
const BAIT_WAIT := 0.6 ## carried while fishing: bites come this much sooner
|
||||||
|
|
||||||
|
## Drops the stack in `slot` (or `count` of it) at your feet, a little in
|
||||||
|
## front (Q drops what's in your hand). Online the server does it, for everyone.
|
||||||
|
func drop(player: Node3D, slot: int, count: int = 0) -> bool:
|
||||||
|
if _online():
|
||||||
|
return _ask("drop", [slot, count])
|
||||||
|
var s = player.inventory.get_slot(slot)
|
||||||
|
if s == null:
|
||||||
|
return false
|
||||||
|
var n: int = int(s.count) if count <= 0 else mini(count, int(s.count))
|
||||||
|
# (on a server the player is a proxy under PlayerNet, not a child of the world)
|
||||||
|
var world: Node = WorldNet.world if Net.is_server() else player.get_parent()
|
||||||
|
if world == null or not world.has_method("drop_item"):
|
||||||
|
return false
|
||||||
|
var fwd: Vector3 = -player.global_basis.z
|
||||||
|
fwd.y = 0.0
|
||||||
|
var at: Vector3 = player.global_position + fwd.normalized() * 0.9
|
||||||
|
at.y = world.terrain.sample_height(at.x, at.z) + 0.05 if world.get("terrain") != null else player.global_position.y
|
||||||
|
var extra := Inventory.extra_of(s)
|
||||||
|
if world.drop_item(String(s.id), n, Transform3D(Basis(), at), extra) == null:
|
||||||
|
return false
|
||||||
|
player.inventory.take_from(slot, n)
|
||||||
|
Sfx.at("pickup", at)
|
||||||
|
notify.emit("Dropped %d %s" % [n, ItemDB.item_name(String(s.id))])
|
||||||
|
return true
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ extends Node
|
|||||||
## Autoload "Boot", first in the list: one-off start-up housekeeping that has
|
## Autoload "Boot", first in the list: one-off start-up housekeeping that has
|
||||||
## to happen before anything reads user://.
|
## to happen before anything reads user://.
|
||||||
##
|
##
|
||||||
## The game was called "Valheimlike" until it became "Emberwild". Godot names
|
## The game has been renamed twice: "Valheimlike", then "Emberwild" (0.1.0 to
|
||||||
## the user-data folder after the project, so the rename moved it from
|
## 0.6.0), now "Cinderwild". Godot names the user-data folder after the
|
||||||
## .../app_userdata/Valheimlike to .../app_userdata/Emberwild. The first time
|
## project, so each rename moved it (.../app_userdata/<name>). The first time
|
||||||
## Emberwild starts, this copies saved worlds, your identity (key, name, look)
|
## Cinderwild starts, this copies saved worlds, your identity (key, name,
|
||||||
## and server settings across. It copies rather than moves, so the old folder
|
## look), settings and server settings across from the newest old folder there
|
||||||
## stays as a backup, and it never overwrites anything already in the new
|
## is (it already holds what came from the one before). It copies rather than moves, so the old folders stay
|
||||||
## folder. Test folders (test_*) and logs are skipped.
|
## as a backup, and it never overwrites anything already in the new folder.
|
||||||
|
## Test folders (test_*) and logs are skipped.
|
||||||
|
|
||||||
const OLD_NAME := "Valheimlike"
|
const OLD_NAMES := ["Emberwild", "Valheimlike"] ## newest first
|
||||||
const MARKER := "migrated_from_valheimlike.txt"
|
const MARKER := "migrated_from_%s.txt"
|
||||||
const SKIP := ["logs", "shader_cache", "vulkan", "test_worlds", "test_net"]
|
const SKIP := ["logs", "shader_cache", "vulkan", "test_worlds", "test_net"]
|
||||||
|
|
||||||
var copied := 0
|
var copied := 0
|
||||||
@@ -22,14 +23,25 @@ func _enter_tree() -> void:
|
|||||||
## Returns the number of files copied (0 if there was nothing to do).
|
## Returns the number of files copied (0 if there was nothing to do).
|
||||||
func migrate_user_data() -> int:
|
func migrate_user_data() -> int:
|
||||||
var new_dir := OS.get_user_data_dir()
|
var new_dir := OS.get_user_data_dir()
|
||||||
var old_dir := new_dir.get_base_dir().path_join(OLD_NAME)
|
for old_name in OLD_NAMES:
|
||||||
|
var old_dir := new_dir.get_base_dir().path_join(old_name)
|
||||||
|
if DirAccess.dir_exists_absolute(old_dir) and old_dir != new_dir:
|
||||||
|
# Only the newest: it already holds whatever came across from the
|
||||||
|
# one before (a world deleted since shouldn't come back).
|
||||||
|
return migrate_from(old_dir, new_dir, old_name)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
## Copies `old_dir` into `new_dir` once (a marker file in `new_dir` says it's
|
||||||
|
## been done). Returns the number of files copied.
|
||||||
|
func migrate_from(old_dir: String, new_dir: String, old_name: String) -> int:
|
||||||
if new_dir == old_dir or not DirAccess.dir_exists_absolute(old_dir):
|
if new_dir == old_dir or not DirAccess.dir_exists_absolute(old_dir):
|
||||||
return 0
|
return 0
|
||||||
if FileAccess.file_exists(new_dir.path_join(MARKER)):
|
var marker := new_dir.path_join(MARKER % old_name.to_lower())
|
||||||
|
if FileAccess.file_exists(marker):
|
||||||
return 0
|
return 0
|
||||||
copied = 0
|
copied = 0
|
||||||
_copy_tree(old_dir, new_dir)
|
_copy_tree(old_dir, new_dir)
|
||||||
var f := FileAccess.open(new_dir.path_join(MARKER), FileAccess.WRITE)
|
var f := FileAccess.open(marker, FileAccess.WRITE)
|
||||||
if f != null:
|
if f != null:
|
||||||
f.store_string("Copied %d files from %s on %s. The old folder was left as it was.\n" % [
|
f.store_string("Copied %d files from %s on %s. The old folder was left as it was.\n" % [
|
||||||
copied, old_dir, Time.get_datetime_string_from_system()])
|
copied, old_dir, Time.get_datetime_string_from_system()])
|
||||||
@@ -43,6 +55,8 @@ func _copy_tree(from: String, to: String) -> void:
|
|||||||
if da == null:
|
if da == null:
|
||||||
return
|
return
|
||||||
for f in da.get_files():
|
for f in da.get_files():
|
||||||
|
if f.begins_with("migrated_from_"):
|
||||||
|
continue # (the old folder's own markers)
|
||||||
var dst := to.path_join(f)
|
var dst := to.path_join(f)
|
||||||
if not FileAccess.file_exists(dst) and DirAccess.copy_absolute(from.path_join(f), dst) == OK:
|
if not FileAccess.file_exists(dst) and DirAccess.copy_absolute(from.path_join(f), dst) == OK:
|
||||||
copied += 1
|
copied += 1
|
||||||
|
|||||||
1
scripts/core/boot.gd.uid
Normal file
1
scripts/core/boot.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://frm15unnr4w5
|
||||||
88
scripts/core/bug_report.gd
Normal file
88
scripts/core/bug_report.gd
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
extends RefCounted
|
||||||
|
## Report a bug (0.6.0, the playtest; pause menu): opens a new issue on the
|
||||||
|
## Cinderwild repo in your browser, already filled in with a title to edit,
|
||||||
|
## a few questions to answer, and the details we'd ask for anyway (version,
|
||||||
|
## computer, single-player or online, which land, the day and time, where you
|
||||||
|
## are). You read it, add what happened, and press Create Issue: nothing is
|
||||||
|
## sent from the game itself. Posting needs an account on the Gitea server.
|
||||||
|
##
|
||||||
|
## The game's log (Godot writes it to user://logs/godot.log) helps with a
|
||||||
|
## crash or an error: the pause menu shows where it is and opens its folder,
|
||||||
|
## to attach the file to the issue.
|
||||||
|
|
||||||
|
const REPO := "https://git.psbailey.uk/Admin/Cinderwild"
|
||||||
|
|
||||||
|
## An autoload (Net, Updates), looked up at run time: this script is also
|
||||||
|
## loaded by tests that start before the autoloads are compiled in.
|
||||||
|
static func _al(n: String) -> Node:
|
||||||
|
return (Engine.get_main_loop() as SceneTree).root.get_node_or_null(n)
|
||||||
|
|
||||||
|
## Where this game's log is, on disk.
|
||||||
|
static func log_path() -> String:
|
||||||
|
return ProjectSettings.globalize_path("user://logs/godot.log")
|
||||||
|
|
||||||
|
## The same, with your home folder as "~" (so your user name isn't in an
|
||||||
|
## issue anyone can read).
|
||||||
|
static func log_path_short() -> String:
|
||||||
|
var home := OS.get_environment("HOME") if OS.get_name() != "Windows" else OS.get_environment("USERPROFILE")
|
||||||
|
var p := log_path()
|
||||||
|
if not home.is_empty() and p.begins_with(home):
|
||||||
|
p = "~" + p.substr(home.length())
|
||||||
|
return p
|
||||||
|
|
||||||
|
static func log_dir() -> String:
|
||||||
|
return ProjectSettings.globalize_path("user://logs")
|
||||||
|
|
||||||
|
## How you're playing: "single-player", or online with the server's rules.
|
||||||
|
static func mode(world: Node) -> String:
|
||||||
|
if world == null or not world.has_method("is_online") or not world.is_online():
|
||||||
|
return "single-player"
|
||||||
|
var net := _al("Net")
|
||||||
|
var rules := "PvP" if bool(net.server_info.get("pvp", false)) else "co-op"
|
||||||
|
if bool(net.server_info.get("raiding", false)):
|
||||||
|
rules += ", raiding"
|
||||||
|
return "online (%s%s)" % [rules, ", hosting" if net.hosting else ""]
|
||||||
|
|
||||||
|
## The details, one per line (what's in the issue, and what "Copy" copies).
|
||||||
|
static func details(world: Node) -> Dictionary:
|
||||||
|
var d := {
|
||||||
|
"Version": "Cinderwild %s (protocol %d)" % [String(ProjectSettings.get_setting("application/config/version", "?")), int(_al("Net").PROTOCOL)],
|
||||||
|
"Computer": "%s %s" % [OS.get_name(), OS.get_version()],
|
||||||
|
"Graphics": "%s (%s)" % [RenderingServer.get_video_adapter_name(), RenderingServer.get_current_rendering_method()],
|
||||||
|
"Mode": mode(world),
|
||||||
|
}
|
||||||
|
if world != null and is_instance_valid(world) and world.has_method("worldlet_id"):
|
||||||
|
d["Land"] = preload("res://scripts/world/terrain.gd").worldlet_name(String(world.worldlet_id()))
|
||||||
|
if world.get("day_night") != null:
|
||||||
|
var h: float = world.day_night.hours()
|
||||||
|
d["Time"] = "day %d, %02d:%02d" % [int(world.day_night.day), int(h), int(fmod(h, 1.0) * 60.0)]
|
||||||
|
if world.get("player") != null and is_instance_valid(world.player):
|
||||||
|
var p: Vector3 = world.player.global_position
|
||||||
|
d["Where"] = "%.0f, %.0f, %.0f" % [p.x, p.y, p.z]
|
||||||
|
if world.get("terrain") != null:
|
||||||
|
d["Seed"] = str(world.terrain.noise_seed)
|
||||||
|
return d
|
||||||
|
|
||||||
|
static func details_text(world: Node) -> String:
|
||||||
|
var d := details(world)
|
||||||
|
var lines: Array[String] = []
|
||||||
|
for k in d:
|
||||||
|
lines.append("- %s: %s" % [k, d[k]])
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
static func body(world: Node) -> String:
|
||||||
|
return "\n".join([
|
||||||
|
"**What happened?**", "", "",
|
||||||
|
"**What did you expect to happen?**", "", "",
|
||||||
|
"**How can we make it happen again?** (what you did just before)", "1. ", "",
|
||||||
|
"**Screenshot or log** (optional: drag a screenshot here, or the log file from %s)" % log_path_short(), "",
|
||||||
|
"---", details_text(world)])
|
||||||
|
|
||||||
|
## The new-issue page, filled in.
|
||||||
|
static func issue_url(world: Node, title: String = "") -> String:
|
||||||
|
var t := title if not title.is_empty() else "Bug: "
|
||||||
|
return "%s/issues/new?title=%s&body=%s" % [REPO, t.uri_encode(), body(world).uri_encode()]
|
||||||
|
|
||||||
|
## Opens it in the browser. Returns false when the browser couldn't be opened.
|
||||||
|
static func open(world: Node) -> bool:
|
||||||
|
return OS.shell_open(issue_url(world)) == OK
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user