diff --git a/crates/horizon-client/src/game/mods.rs b/crates/horizon-client/src/game/mods.rs
index 7f9714a18..e96744775 100644
--- a/crates/horizon-client/src/game/mods.rs
+++ b/crates/horizon-client/src/game/mods.rs
@@ -599,6 +599,9 @@ impl Game<'_> {
.is_some()
.then(|| self.mods.world_id.clone())
.flatten(),
+ world_seed: (self.player.is_some()
+ && self.mods.world_id.as_deref().is_some_and(|w| w.starts_with("world:")))
+ .then_some(self.level.seed),
}
}
/// The first block along the player's view within
diff --git a/crates/horizon-mod-sdk/wit/horizon.wit b/crates/horizon-mod-sdk/wit/horizon.wit
index 805c2a515..774cc5851 100644
--- a/crates/horizon-mod-sdk/wit/horizon.wit
+++ b/crates/horizon-mod-sdk/wit/horizon.wit
@@ -51,6 +51,10 @@ interface game {
/// world, `server:
` on a server; none on the title screen.
/// Mods keep their state apart by it.
world-id: func() -> option;
+ /// The open saved world's seed (AirDef fork, API level 25): with the
+ /// folder name it tells a new world from an old one that had the same
+ /// name. None on the title screen and on a server.
+ world-seed: func() -> option;
/// A block along the player's view.
record hit {
/// The block hit.
diff --git a/crates/horizon-mods/src/api_level.rs b/crates/horizon-mods/src/api_level.rs
index 579af9e25..3af36e7c1 100644
--- a/crates/horizon-mods/src/api_level.rs
+++ b/crates/horizon-mods/src/api_level.rs
@@ -7,13 +7,13 @@
//! version's level so the launcher can tell before it installs.
/// The newest level this Horizon serves.
-pub const LEVEL: u32 = 24;
+pub const LEVEL: u32 = 25;
/// Every function the game offers mods, as `interface.function`, with its
/// level. Level 0 is the contract of Horizon 0.2.67 to 0.2.69. A new
/// function takes the next level and raises [`LEVEL`] (the `api_reference`
/// test fails until it is listed here).
-pub const FUNCTIONS: [(&str, u32); 90] = [
+pub const FUNCTIONS: [(&str, u32); 91] = [
("log.write", 0),
("game.get-player", 0),
("game.get-clock", 0),
@@ -118,6 +118,8 @@ pub const FUNCTIONS: [(&str, u32); 90] = [
("render.set-canvas", 24),
("render.canvas-events", 24),
("render.canvas-open", 24),
+ // AirDef fork: the open world's seed, so a new world is not an old one of the same name.
+ ("game.world-seed", 25),
];
/// What the game's program carries so the launcher can read its level
diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs
index 81286975f..910986549 100644
--- a/crates/horizon-mods/src/runtime.rs
+++ b/crates/horizon-mods/src/runtime.rs
@@ -189,6 +189,8 @@ pub struct Snapshot {
pub creative: bool,
/// The open world (`game.world-id`, AirDef fork).
pub world: Option,
+ /// The open saved world's seed (`game.world-seed`, AirDef fork).
+ pub world_seed: Option,
}
/// Capability `recipes`'s tables: `items` in the registry's order, which
@@ -1930,6 +1932,9 @@ impl game::Host for State {
fn world_id(&mut self) -> Option {
self.snapshot.world.clone()
}
+ fn world_seed(&mut self) -> Option {
+ self.snapshot.world_seed
+ }
fn raycast(&mut self, distance: f32) -> Option {
let (at, hit) = self.snapshot.view_hit.as_ref()?;
// NaN compares false: no hit.
diff --git a/docs/api/mods-reference.md b/docs/api/mods-reference.md
index cf6e61440..3ab23b490 100644
--- a/docs/api/mods-reference.md
+++ b/docs/api/mods-reference.md
@@ -126,6 +126,7 @@ A block along the player's view.
| `get-player` | `func() -> option` | None on the title screen. |
| `get-clock` | `func() -> option` | None on the title screen. |
| `world-id` | `func() -> option` | Which world is open (AirDef fork): `world:` for a saved world, `server:` on a server; none on the title screen. Mods keep their state apart by it. |
+| `world-seed` | `func() -> option` | The open saved world's seed (AirDef fork, API level 25): with the folder name it tells a new world from an old one that had the same name. None on the title screen and on a server. |
| `raycast` | `func(distance: f32) -> option` | The first block along the player's view (fluids pass), up to `distance` blocks (at most 1024; past the loaded chunks, across the distant terrain when LOD Distance is on), as of this tick. None when nothing is hit, and on the title screen. |
## `chat`