Merge pull request #7564 from levzzz5154/modapi/level-world

Native mods: a level's seed, difficulty, game rules and weather through LevelRef (#7387)
This commit is contained in:
rdbt authored and GitHub committed 2026-11-07 12:34:52 +00:00
commit f5a6eb9835
6 files changed
+114 -1

No files matched your search

+1
View File
@@ -252,6 +252,7 @@ mod native_item_container;
mod native_item_energy;
mod native_item_fluids;
mod native_level_refs;
mod native_level_world;
mod native_lifecycle;
mod native_living_refs;
mod native_machines;
@@ -9,7 +9,7 @@ use horizon_client::world::World;
impl Game<'_> {
/// The world a handle names now, or None for a stale or foreign one.
fn native_level_world(&self, level: LevelRef) -> Option<&World> {
pub(super) fn native_level_world(&self, level: LevelRef) -> Option<&World> {
if self.remote.is_some() || self.native_lifecycle.epoch() != Some(level.epoch()) {
return None;
}
@@ -0,0 +1,68 @@
//! A level's world settings for native server mods, through a `LevelRef`
//! (docs/native-mods.md, "Entity and level handles", #7387): the seed, the
//! difficulty, game rules and weather. They return `None` on a client or for
//! a handle of an earlier open.
use super::Game;
use horizon_client::command::vanilla::DIFFICULTIES;
use horizon_client::mod_api::{GameRuleValue, LevelRef};
/// `Level.isRaining` and `isThundering` from the rain and thunder levels.
fn weather_flags((rain, thunder): (f32, f32)) -> (bool, bool) {
(rain > 0.2, thunder > 0.9)
}
impl Game<'_> {
/// The world's seed (`WorldOptions.seed`), as `/seed` shows it. API
/// @API:level_world@.
pub(super) fn native_level_seed(&self, level: LevelRef) -> Option<i64> {
self.native_level_world(level)?;
Some(self.level.seed)
}
/// The difficulty (`Level.getDifficulty`): `peaceful`, `easy`, `normal`
/// or `hard`, as `/difficulty` names it. API @API:level_world@.
pub(super) fn native_level_difficulty(&self, level: LevelRef) -> Option<&'static str> {
self.native_level_world(level)?;
DIFFICULTIES.get(usize::from(self.ui.difficulty)).copied()
}
/// Game rule `rule` (`minecraft:keep_inventory` or `keep_inventory`), as
/// `/gamerule` queries it: None for an unknown rule. API @API:level_world@.
pub(super) fn native_level_game_rule(
&self,
level: LevelRef,
rule: &str,
) -> Option<GameRuleValue> {
self.native_level_world(level)?;
let r = horizon_client::gamerules::rule(rule)?;
self.ui.rules.get(r.name)
}
/// The rain and thunder levels, each 0.0 to 1.0 (`Level.getRainLevel`,
/// `getThunderLevel`): both 0 in a level with no weather, as the Nether
/// and the End. API @API:level_world@.
pub(super) fn native_level_weather(&self, level: LevelRef) -> Option<(f32, f32)> {
let world = self.native_level_world(level)?;
if level.dimension() != super::Dim::Overworld.id() {
return Some((0.0, 0.0));
}
Some(world.weather.levels(1.0))
}
/// Whether it rains (`Level.isRaining`: a rain level above 0.2). API
/// @API:level_world@.
pub(super) fn native_level_is_raining(&self, level: LevelRef) -> Option<bool> {
Some(weather_flags(self.native_level_weather(level)?).0)
}
/// Whether it thunders (`Level.isThundering`: a thunder level above 0.9).
/// API @API:level_world@.
pub(super) fn native_level_is_thundering(&self, level: LevelRef) -> Option<bool> {
Some(weather_flags(self.native_level_weather(level)?).1)
}
}
#[cfg(test)]
mod tests {
#[test]
fn rain_and_thunder_start_above_vanillas_thresholds() {
assert_eq!(super::weather_flags((0.2, 0.0)), (false, false));
assert_eq!(super::weather_flags((0.21, 0.9)), (true, false));
assert_eq!(super::weather_flags((1.0, 0.95)), (true, true));
}
}
+1
View File
@@ -40,6 +40,7 @@ pub use crate::game::{
NativePlayer, NativePlayerRender, NativeReloadListener, NativeShake, NativeStructureBlock,
NativeStructureTemplate, NativeWorldDraw,
};
pub use crate::gamerules::Value as GameRuleValue;
pub use crate::gfx::native_render::{
BlockSpriteArrays, RenderHooks, RenderNeeds, RenderNeedsEvent, WorldDrawn, WorldDrawnEvent,
wgpu,
+3
View File
@@ -3711,6 +3711,9 @@ a dimension.
- `level.block_state(host, pos)` (`minecraft:oak_stairs[facing=east,…]`, as `/setblock` takes it), `block` (the name
alone), `block_property(host, pos, key)`, `is_loaded`, `height` (lowest `y` and height), `game_time` and `day_time`
read any level the server has loaded, not only the one in place. A block in a chunk that isn't loaded reads as `None`.
- `level.seed(host)`, `difficulty` (`"normal"`, as `/difficulty` names it), `game_rule(host, "keep_inventory")` (a
`GameRuleValue`, `Bool` or `Int`; None for an unknown rule), `weather` (rain and thunder levels, 0.0 to 1.0; both 0
where the level has no weather), `is_raining` and `is_thundering` read its world settings.
- `host.living(entity)` gives a `LivingRef` for a mob, a player, an armor stand or the dragon (vanilla's
`LivingEntity`); `living.entity()` goes back. `living.equipment(host, EquipmentSlot::Head)` and `hand_item(host,
Hand::Main)` read its equipment (an empty slot reads as `None`); `set_equipment(host, slot, stack)` puts a stack in a
@@ -0,0 +1,40 @@
{
"hooks": [],
"helpers": [
{
"name": "level_seed",
"target": "horizon_client::game::native_level_world::Game<'_>::native_level_seed",
"receiver": "level"
},
{
"name": "level_difficulty",
"target": "horizon_client::game::native_level_world::Game<'_>::native_level_difficulty",
"receiver": "level"
},
{
"name": "level_game_rule",
"target": "horizon_client::game::native_level_world::Game<'_>::native_level_game_rule",
"receiver": "level"
},
{
"name": "level_weather",
"target": "horizon_client::game::native_level_world::Game<'_>::native_level_weather",
"receiver": "level"
},
{
"name": "level_is_raining",
"target": "horizon_client::game::native_level_world::Game<'_>::native_level_is_raining",
"receiver": "level"
},
{
"name": "level_is_thundering",
"target": "horizon_client::game::native_level_world::Game<'_>::native_level_is_thundering",
"receiver": "level"
}
],
"rust_summary": "a level's world settings through a `LevelRef`: the seed, the difficulty, game rules and weather.",
"doc_summary": "`level.seed(host)`, `difficulty`, `game_rule(host, name)` (`GameRuleValue`), `weather`, `is_raining` and `is_thundering`. Bundles need \"hooks\": @API:level_world@.",
"comments": [
"crates/horizon-client/src/game/native_level_world.rs"
]
}