1use std::ops::{Deref, DerefMut};
12
13use bevy_app::prelude::*;
14use bevy_ecs::prelude::*;
15use chunkedge_ident::{ident, Ident};
16use chunkedge_nbt::serde::ser::CompoundSerializer;
17use serde::{Deserialize, Serialize};
18use tracing::error;
19
20use crate::codec::{RegistryCodec, RegistryValue};
21use crate::{Registry, RegistryIdx, RegistrySet};
22
23pub struct BiomePlugin;
24
25impl Plugin for BiomePlugin {
26 fn build(&self, app: &mut App) {
27 app.init_resource::<BiomeRegistry>()
28 .add_systems(PreStartup, load_default_biomes)
29 .add_systems(PostUpdate, update_biome_registry.before(RegistrySet));
30 }
31}
32
33fn load_default_biomes(mut reg: ResMut<BiomeRegistry>, codec: Res<RegistryCodec>) {
34 let mut helper = move || -> anyhow::Result<()> {
35 for value in codec.registry(BiomeRegistry::KEY) {
36 let biome = Biome::deserialize(value.element.clone())?;
37
38 reg.insert(value.name.clone(), biome);
39 }
40
41 reg.swap_to_front(ident!("plains"));
43
44 Ok(())
45 };
46
47 if let Err(e) = helper() {
48 error!("failed to load default biomes from registry codec: {e:#}");
49 }
50}
51
52fn update_biome_registry(reg: Res<BiomeRegistry>, mut codec: ResMut<RegistryCodec>) {
53 if reg.is_changed() {
54 let biomes = codec.registry_mut(BiomeRegistry::KEY);
55
56 biomes.clear();
57
58 biomes.extend(reg.iter().map(|(_, name, biome)| {
59 RegistryValue {
60 name: name.into(),
61 element: biome
62 .serialize(CompoundSerializer)
63 .expect("failed to serialize biome"),
64 }
65 }));
66 }
67}
68
69#[derive(Resource, Default, Debug)]
70pub struct BiomeRegistry {
71 reg: Registry<BiomeId, Biome>,
72}
73
74impl BiomeRegistry {
75 pub const KEY: Ident<&'static str> = ident!("worldgen/biome");
76}
77
78impl Deref for BiomeRegistry {
79 type Target = Registry<BiomeId, Biome>;
80
81 fn deref(&self) -> &Self::Target {
82 &self.reg
83 }
84}
85
86impl DerefMut for BiomeRegistry {
87 fn deref_mut(&mut self) -> &mut Self::Target {
88 &mut self.reg
89 }
90}
91
92#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
93pub struct BiomeId(u32);
94
95impl BiomeId {
96 pub const DEFAULT: Self = BiomeId(0);
97}
98
99impl RegistryIdx for BiomeId {
100 const MAX: usize = u32::MAX as usize;
101
102 #[inline]
103 fn to_index(self) -> usize {
104 self.0 as usize
105 }
106
107 #[inline]
108 fn from_index(idx: usize) -> Self {
109 Self(idx as u32)
110 }
111}
112
113#[derive(Serialize, Deserialize, Clone, Debug)]
114pub struct Biome {
115 pub has_precipitation: bool,
116 pub temperature: f32,
117 pub downfall: f32,
118 pub effects: BiomeEffects,
119}
120
121impl Default for Biome {
122 fn default() -> Self {
124 Self {
125 has_precipitation: true,
126 temperature: 0.8,
127 downfall: 0.4,
128 effects: BiomeEffects::default(),
129 }
130 }
131}
132
133#[derive(Serialize, Deserialize, Clone, Debug)]
134pub struct BiomeEffects {
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub mood_sound: Option<BiomeMoodSound>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub additions_sound: Option<BiomeAdditionsSound>,
139 #[serde(skip_serializing_if = "Vec::is_empty", default)]
140 pub music: Vec<BiomeMusic>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub music_volume: Option<f32>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub particle: Option<BiomeParticle>,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub sky_color: Option<u32>,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub foliage_color: Option<u32>,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub grass_color: Option<u32>,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub fog_color: Option<u32>,
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub water_color: Option<u32>,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 pub water_fog_color: Option<u32>,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub grass_color_modifier: Option<String>,
159}
160#[derive(Serialize, Deserialize, Clone, Debug)]
161pub struct BiomeMoodSound {
162 pub sound: Ident<String>,
163 pub tick_delay: u32,
164 pub block_search_extent: u32,
165 pub offset: f32,
166}
167
168#[derive(Serialize, Deserialize, Clone, Debug)]
169pub struct BiomeMusic {
170 pub data: BiomeMusicData,
171 pub weight: u32,
172}
173
174#[derive(Serialize, Deserialize, Clone, Debug)]
175pub struct BiomeMusicData {
176 pub sound: Ident<String>,
177 pub min_delay: u32,
178 pub max_delay: u32,
179 pub replace_current_music: bool,
180}
181
182#[derive(Serialize, Deserialize, Clone, Debug)]
183pub struct BiomeAdditionsSound {
184 pub sound: Ident<String>,
185 pub tick_chance: f32,
186}
187
188#[derive(Serialize, Deserialize, Clone, Debug)]
189pub struct BiomeParticle {
190 pub options: BiomeParticleOptions,
191 pub probability: f32,
192}
193
194#[derive(Serialize, Deserialize, Clone, Debug)]
195pub struct BiomeParticleOptions {
196 #[serde(rename = "type")]
197 pub kind: Ident<String>,
198}
199
200impl Default for BiomeEffects {
201 fn default() -> Self {
203 Self {
204 mood_sound: Some(BiomeMoodSound {
205 sound: ident!("minecraft:ambient.cave").into(),
206 tick_delay: 6000,
207 block_search_extent: 8,
208 offset: 2.0,
209 }),
210 music_volume: Some(1.0),
211 sky_color: Some(7907327),
212 fog_color: Some(12638463),
213 water_color: Some(4159204),
214 water_fog_color: Some(329011),
215 additions_sound: None,
216 music: Vec::new(),
217 particle: None,
218 foliage_color: None,
219 grass_color: None,
220 grass_color_modifier: None,
221 }
222 }
223}