mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #5897 from Vaspyyy/feat/native-biome-colors
🤖 Agent: Client hook native_biome_colors + native_refresh_biome_colors as an API fragment: runs only when a mod asks for a refresh (rebuilds sections like a Biome Blend change), nothing per frame; Biomes::with_colors unit-tested.
This commit is contained in:
6 files changed
+203
No files matched your search
@@ -387,6 +387,52 @@ impl Biomes {
|
||||
}
|
||||
}
|
||||
|
||||
/// A biome's colours as a mod may change them (`native_biome_colors`), each
|
||||
/// 0xRRGGBB: what tints grass (before the dark forest's modifier; the swamp's
|
||||
/// noise colours replace it), leaves and vines, dry foliage (leaf litter) and
|
||||
/// water.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct BiomeColors {
|
||||
pub grass: u32,
|
||||
pub foliage: u32,
|
||||
pub dry_foliage: u32,
|
||||
pub water: u32,
|
||||
}
|
||||
|
||||
impl Biomes {
|
||||
/// This table with each biome's colours passed through `f`, given the
|
||||
/// biome's name (`minecraft:plains`) and its colours; None when `f`
|
||||
/// changed none. Bits past 0xFFFFFF are dropped.
|
||||
pub fn with_colors(&self, mut f: impl FnMut(&str, &mut BiomeColors)) -> Option<Self> {
|
||||
let mut out = self.clone();
|
||||
let mut changed = false;
|
||||
for b in &mut out.list {
|
||||
let before = BiomeColors {
|
||||
grass: b.grass,
|
||||
foliage: b.foliage,
|
||||
dry_foliage: b.dry_foliage,
|
||||
water: b.water,
|
||||
};
|
||||
let mut colors = before;
|
||||
f(&b.name, &mut colors);
|
||||
let after = BiomeColors {
|
||||
grass: colors.grass & 0xFF_FFFF,
|
||||
foliage: colors.foliage & 0xFF_FFFF,
|
||||
dry_foliage: colors.dry_foliage & 0xFF_FFFF,
|
||||
water: colors.water & 0xFF_FFFF,
|
||||
};
|
||||
if after != before {
|
||||
changed = true;
|
||||
b.grass = after.grass;
|
||||
b.foliage = after.foliage;
|
||||
b.dry_foliage = after.dry_foliage;
|
||||
b.water = after.water;
|
||||
}
|
||||
}
|
||||
changed.then_some(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// A 24-bit colour as sRGB components in 0..1.
|
||||
pub fn rgb(c: u32) -> [f32; 3] {
|
||||
[16, 8, 0].map(|shift| (c >> shift & 255) as f32 / 255.0)
|
||||
@@ -534,6 +580,81 @@ mod pack_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod color_tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
fn biomes() -> Biomes {
|
||||
let maps = Colormaps {
|
||||
grass: Vec::new(),
|
||||
foliage: Vec::new(),
|
||||
dry: Vec::new(),
|
||||
};
|
||||
let effects = |grass: u32, modifier: &str| {
|
||||
json!({"effects": {"grass_color": grass, "foliage_color": 0x112233,
|
||||
"dry_foliage_color": 0x445566, "water_color": 0x3F76E4,
|
||||
"grass_color_modifier": modifier}})
|
||||
};
|
||||
let list = vec![
|
||||
BiomeInfo::from_json("minecraft:plains".into(), &effects(0x91BD59, "none"), &maps),
|
||||
BiomeInfo::from_json(
|
||||
"minecraft:dark_forest".into(),
|
||||
&effects(0x91BD59, "dark_forest"),
|
||||
&maps,
|
||||
),
|
||||
];
|
||||
Biomes {
|
||||
builtin: Arc::new(list.clone()),
|
||||
index: list
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, b)| (b.name.clone(), i as u16))
|
||||
.collect(),
|
||||
list,
|
||||
info_noise: SimplexNoise::with_skipped_offsets(&mut LegacyRandom::new(2345), true),
|
||||
colormaps: Arc::new(maps),
|
||||
packs: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_change_reaches_the_tints_the_mesher_reads() {
|
||||
let autumn = biomes()
|
||||
.with_colors(|name, c| {
|
||||
c.foliage = 0xD2691E;
|
||||
if name == "minecraft:plains" {
|
||||
c.grass = 0xBFA055;
|
||||
}
|
||||
})
|
||||
.expect("changed");
|
||||
let plains = autumn.id("minecraft:plains");
|
||||
assert_eq!(autumn.grass(plains, 0.0, 0.0), 0xBFA055);
|
||||
assert_eq!(autumn.get(plains).foliage, 0xD2691E);
|
||||
assert_eq!(autumn.get(plains).dry_foliage, 0x445566);
|
||||
assert_eq!(autumn.get(plains).water, 0x3F76E4);
|
||||
// The dark forest's modifier still darkens its (unchanged) grass:
|
||||
// ((0x91BD59 & 0xFEFEFE) + 0x28340A) >> 1.
|
||||
let dark = autumn.id("minecraft:dark_forest");
|
||||
assert_eq!(autumn.grass(dark, 0.0, 0.0), 0x5C7831);
|
||||
assert_eq!(autumn.get(dark).foliage, 0xD2691E);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_change_gives_none_and_alpha_bits_are_dropped() {
|
||||
assert!(biomes().with_colors(|_, _| {}).is_none());
|
||||
assert!(
|
||||
biomes()
|
||||
.with_colors(|_, c| c.water |= 0xFF00_0000)
|
||||
.is_none()
|
||||
);
|
||||
let blue = biomes()
|
||||
.with_colors(|_, c| c.water = 0xFF00_00FF)
|
||||
.expect("changed");
|
||||
assert_eq!(blue.get(0).water, 0x0000FF);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod blend_tests {
|
||||
use super::*;
|
||||
|
||||
@@ -187,6 +187,7 @@ mod moving_pistons;
|
||||
mod music;
|
||||
mod names;
|
||||
mod narrator;
|
||||
mod native_biome_colors;
|
||||
mod native_block_entity_draw;
|
||||
mod native_components;
|
||||
mod native_criteria;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//! Biome colours a client mod changes (`native_biome_colors`), for seasons:
|
||||
//! the grass, foliage, dry foliage and water tints the mesher blends, as a
|
||||
//! resource pack's colormaps and biome effects set them, changed while the
|
||||
//! game runs.
|
||||
|
||||
use super::Game;
|
||||
use horizon_client::streaming::WorkerContext;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Native mods call it; nothing in Horizon does.
|
||||
#[allow(dead_code)]
|
||||
impl Game<'_> {
|
||||
/// Asks `native_biome_colors` again for every biome and, when the colours
|
||||
/// differ from those in use, rebuilds every loaded section and the far
|
||||
/// terrain with them (`LevelRenderer.allChanged`, as changing Biome Blend
|
||||
/// does). Call it when your colours change (a new season, a new day of a
|
||||
/// slow fade) and once when the player joins, not every frame: a rebuild
|
||||
/// takes the workers a few seconds. API @API:biome_colors@.
|
||||
pub(super) fn native_refresh_biome_colors(&mut self) {
|
||||
let base = self.res.biomes().clone();
|
||||
let biomes = base
|
||||
.with_colors(|biome, colors| self.native_biome_colors(biome, colors))
|
||||
.map_or(base, Arc::new);
|
||||
if Arc::ptr_eq(&biomes, &self.pack.context.biomes) {
|
||||
return;
|
||||
}
|
||||
let old = &self.pack.context;
|
||||
let context = Arc::new(WorkerContext {
|
||||
assets: old.assets.clone(),
|
||||
render: old.render.clone(),
|
||||
tables: old.tables.clone(),
|
||||
biomes,
|
||||
light: old.light.clone(),
|
||||
});
|
||||
self.pack.context = context.clone();
|
||||
self.clear_lod();
|
||||
self.streaming.reload_resources(&mut self.world, context);
|
||||
}
|
||||
}
|
||||
@@ -282,6 +282,7 @@ use super::{
|
||||
};
|
||||
use horizon_client::{
|
||||
atlas_sources::qualified,
|
||||
biome::BiomeColors,
|
||||
block_entities::Pos,
|
||||
chat::ChatSource,
|
||||
containers::{BlockEntity, MOD_MENUS, ModBlockEntity, ModContainer, ModFluidTanks},
|
||||
@@ -1362,6 +1363,17 @@ impl Game<'_> {
|
||||
/// one that pack is for. Asked once a frame while a pack is on; a value
|
||||
/// that isn't finite reads as 0 (`native_shader_params_now`). API
|
||||
/// @API:native_shader_params@.
|
||||
/// Change the colours of `biome` (`minecraft:plains`) on this client: the
|
||||
/// grass, foliage (leaves, vines), dry foliage (leaf litter) and water
|
||||
/// tints, 0xRRGGBB, as its biome file and the colormaps set them. Asked
|
||||
/// for every biome when a mod calls `native_refresh_biome_colors`, which
|
||||
/// rebuilds the terrain with them; a season mod answers from its season
|
||||
/// and calls that when the season turns. The dark forest's modifier
|
||||
/// still applies to the grass set here; the swamp's noise colours
|
||||
/// replace it. API @API:biome_colors@.
|
||||
pub(super) fn native_biome_colors(&self, biome: &str, colors: &mut BiomeColors) {
|
||||
let _ = (biome, colors);
|
||||
}
|
||||
pub(super) fn native_shader_params(&self, partial: f32, params: &mut [[f32; 4]; 8]) {
|
||||
let _ = (partial, params);
|
||||
}
|
||||
|
||||
@@ -1133,6 +1133,22 @@ explosion's centre (camera-relative, from `frame.camera_position`) and its age i
|
||||
ray marched in a post pass against `scene_depth`. A value that isn't finite reads as 0, and all are 0 when no mod sets
|
||||
them. The mod's server half can send what happened to each client half with mod messages (API 40).
|
||||
|
||||
### Biome colours
|
||||
|
||||
`crates/horizon-client/src/game/native_biome_colors.rs`: the grass, leaf and water colours a client mod changes while the
|
||||
game runs, for seasons (a golden autumn, a pale winter) or a world that greens as it heals.
|
||||
|
||||
- `native_biome_colors(biome, colors)` (client) changes a biome's `BiomeColors`: `grass`, `foliage` (leaves, vines),
|
||||
`dry_foliage` (leaf litter) and `water`, 0xRRGGBB, starting from the biome file and the colormaps. The dark forest's
|
||||
modifier still darkens the grass set here; the swamp's noise colours replace it.
|
||||
- It is asked for every biome when a mod calls `native_refresh_biome_colors()`. When the colours differ from those in use,
|
||||
every loaded section and the far terrain are rebuilt with them, as changing Biome Blend does. Call it when your colours
|
||||
change and once when the player joins, not every tick: a rebuild takes the workers a few seconds. A slow fade is a
|
||||
call a day, not a frame.
|
||||
- Block-breaking and falling-leaf particles keep the biome's own colours for now.
|
||||
- A season's mood in a shader pack (warmer grading, a lower sun, more haze) comes from the same mod through
|
||||
`native_shader_params` (`frame.mods`); its server half can send the season to each client with mod messages.
|
||||
|
||||
### Sounds a client mod plays
|
||||
|
||||
`native_play_local_sound(sound, at, volume, pitch, delay)` (client) plays sound event `sound` at `at` for this player only,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "biome_colors",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_biome_colors",
|
||||
"side": "client"
|
||||
}
|
||||
],
|
||||
"rust_summary": "biome colours for client mods (`native_biome_colors`, `native_refresh_biome_colors`).",
|
||||
"doc_summary": "biome colours for client mods, for seasons: `native_biome_colors(biome, colors)` changes a biome's grass, foliage, dry\nfoliage and water tints, asked for every biome when a mod calls `native_refresh_biome_colors()`, which rebuilds the\nloaded terrain with them. Bundles that use it need `\"hooks\": @API:biome_colors@`.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_biome_colors.rs"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user