1use std::sync::OnceLock;
2
3use rustc_hash::FxHashMap;
4use simdnbt::ToNbtTag;
5use simdnbt::owned::NbtTag;
6use steel_utils::Identifier;
7
8use crate::REGISTRY;
9use crate::TaggedRegistryExt;
10use crate::sound_event::SoundEventRef;
11
12#[derive(Debug)]
13pub struct Biome {
14 pub key: Identifier,
15 pub has_precipitation: bool,
16 pub temperature: f32,
17 pub downfall: f32,
18 pub temperature_modifier: TemperatureModifier,
19 pub effects: BiomeEffects,
20 pub creature_spawn_probability: f32,
21 pub spawners: FxHashMap<String, Vec<SpawnerData>>,
22 pub spawn_costs: FxHashMap<Identifier, SpawnCost>,
23 pub carvers: Vec<Identifier>,
24 pub features: Vec<Vec<Identifier>>,
25 pub id: OnceLock<usize>,
27}
28
29impl Biome {
30 pub fn has_tag(&'static self, tag: &Identifier) -> bool {
32 REGISTRY.biomes.is_in_tag(self, tag)
33 }
34}
35
36#[derive(Debug)]
37pub struct BiomeEffects {
38 pub fog_color: i32,
39 pub sky_color: i32,
40 pub water_color: i32,
41 pub water_fog_color: i32,
42 pub foliage_color: Option<i32>,
43 pub grass_color: Option<i32>,
44 pub dry_foliage_color: Option<i32>,
45 pub grass_color_modifier: GrassColorModifier,
46 pub music: Option<Vec<WeightedMusic>>,
47 pub ambient_sound: Option<SoundEventRef>,
48 pub additions_sound: Option<AdditionsSound>,
49 pub mood_sound: Option<MoodSound>,
50 pub particle: Option<Particle>,
51}
52
53#[derive(Debug)]
54pub struct SpawnerData {
55 pub entity_type: Identifier,
56 pub weight: i32,
57 pub min_count: i32,
58 pub max_count: i32,
59}
60
61#[derive(Debug)]
62pub struct SpawnCost {
63 pub energy_budget: f64,
64 pub charge: f64,
65}
66
67#[derive(Debug, Default)]
68pub enum TemperatureModifier {
69 #[default]
70 None,
71 Frozen,
72}
73
74#[derive(Debug)]
75pub enum GrassColorModifier {
76 None,
77 DarkForest,
78 Swamp,
79}
80
81#[derive(Debug)]
82pub struct WeightedMusic {
83 pub data: Music,
84 pub weight: i32,
85}
86
87#[derive(Debug)]
88pub struct Music {
89 pub replace_current_music: bool,
90 pub max_delay: i32,
91 pub min_delay: i32,
92 pub sound: SoundEventRef,
93}
94
95#[derive(Debug)]
96pub struct AdditionsSound {
97 pub sound: SoundEventRef,
98 pub tick_chance: f64,
99}
100
101#[derive(Debug)]
102pub struct MoodSound {
103 pub sound: SoundEventRef,
104 pub tick_delay: i32,
105 pub block_search_extent: i32,
106 pub offset: f64,
107}
108
109#[derive(Debug)]
110pub struct Particle {
111 pub options: ParticleOptions,
112 pub probability: f32,
113}
114
115#[derive(Debug)]
116pub struct ParticleOptions {
117 pub particle_type: Identifier,
118}
119
120impl ToNbtTag for &Biome {
121 fn to_nbt_tag(self) -> NbtTag {
122 use simdnbt::owned::{NbtCompound, NbtList};
123 let mut compound = NbtCompound::new();
124 compound.insert("has_precipitation", self.has_precipitation);
125 compound.insert("temperature", self.temperature);
126 compound.insert("downfall", self.downfall);
127 compound.insert(
128 "temperature_modifier",
129 match self.temperature_modifier {
130 TemperatureModifier::None => "none",
131 TemperatureModifier::Frozen => "frozen",
132 },
133 );
134 compound.insert(
135 "creature_spawn_probability",
136 self.creature_spawn_probability,
137 );
138
139 let mut effects = NbtCompound::new();
141 effects.insert("fog_color", self.effects.fog_color);
142 effects.insert("sky_color", self.effects.sky_color);
143 effects.insert("water_color", self.effects.water_color);
144 effects.insert("water_fog_color", self.effects.water_fog_color);
145 if let Some(fc) = self.effects.foliage_color {
146 effects.insert("foliage_color", fc);
147 }
148 if let Some(gc) = self.effects.grass_color {
149 effects.insert("grass_color", gc);
150 }
151 if let Some(dfc) = self.effects.dry_foliage_color {
152 effects.insert("dry_foliage_color", dfc);
153 }
154 match self.effects.grass_color_modifier {
155 GrassColorModifier::None => {}
156 GrassColorModifier::DarkForest => {
157 effects.insert("grass_color_modifier", "dark_forest");
158 }
159 GrassColorModifier::Swamp => {
160 effects.insert("grass_color_modifier", "swamp");
161 }
162 }
163 if let Some(ambient_sound) = &self.effects.ambient_sound {
164 let s = ambient_sound.key.to_string();
165 effects.insert("ambient_sound", s.as_str());
166 }
167 if let Some(additions) = &self.effects.additions_sound {
168 let mut a = NbtCompound::new();
169 let s = additions.sound.key.to_string();
170 a.insert("sound", s.as_str());
171 a.insert("tick_chance", additions.tick_chance);
172 effects.insert("additions_sound", NbtTag::Compound(a));
173 }
174 if let Some(mood) = &self.effects.mood_sound {
175 let mut m = NbtCompound::new();
176 let s = mood.sound.key.to_string();
177 m.insert("sound", s.as_str());
178 m.insert("tick_delay", mood.tick_delay);
179 m.insert("block_search_extent", mood.block_search_extent);
180 m.insert("offset", mood.offset);
181 effects.insert("mood_sound", NbtTag::Compound(m));
182 }
183 if let Some(particle) = &self.effects.particle {
184 let mut p = NbtCompound::new();
185 let mut opts = NbtCompound::new();
186 let s = particle.options.particle_type.to_string();
187 opts.insert("type", s.as_str());
188 p.insert("options", NbtTag::Compound(opts));
189 p.insert("probability", particle.probability);
190 effects.insert("particle", NbtTag::Compound(p));
191 }
192 if let Some(music_list) = &self.effects.music {
193 let music_nbt: Vec<NbtCompound> = music_list
194 .iter()
195 .map(|wm| {
196 let mut wmc = NbtCompound::new();
197 let mut data = NbtCompound::new();
198 data.insert("replace_current_music", wm.data.replace_current_music);
199 data.insert("max_delay", wm.data.max_delay);
200 data.insert("min_delay", wm.data.min_delay);
201 let s = wm.data.sound.key.to_string();
202 data.insert("sound", s.as_str());
203 wmc.insert("data", NbtTag::Compound(data));
204 wmc.insert("weight", wm.weight);
205 wmc
206 })
207 .collect();
208 effects.insert("music", NbtTag::List(NbtList::Compound(music_nbt)));
209 }
210 compound.insert("effects", NbtTag::Compound(effects));
211
212 let mut spawners_compound = NbtCompound::new();
214 for (category, entries) in &self.spawners {
215 let category_entries: Vec<NbtCompound> = entries
216 .iter()
217 .map(|sd| {
218 let mut e = NbtCompound::new();
219 let s = sd.entity_type.to_string();
220 e.insert("type", s.as_str());
221 e.insert("weight", sd.weight);
222 e.insert("minCount", sd.min_count);
223 e.insert("maxCount", sd.max_count);
224 e
225 })
226 .collect();
227 spawners_compound.insert(
228 category.as_str(),
229 NbtTag::List(NbtList::Compound(category_entries)),
230 );
231 }
232 compound.insert("spawners", NbtTag::Compound(spawners_compound));
233
234 let mut spawn_costs_compound = NbtCompound::new();
236 for (entity_type, cost) in &self.spawn_costs {
237 let mut cost_compound = NbtCompound::new();
238 cost_compound.insert("charge", cost.charge);
239 cost_compound.insert("energy_budget", cost.energy_budget);
240 let s = entity_type.to_string();
241 spawn_costs_compound.insert(s.as_str(), NbtTag::Compound(cost_compound));
242 }
243 compound.insert("spawn_costs", NbtTag::Compound(spawn_costs_compound));
244
245 let mut carvers_compound = NbtCompound::new();
247 let air_carvers: Vec<String> = self.carvers.iter().map(|id| id.to_string()).collect();
248 carvers_compound.insert("air", NbtTag::List(NbtList::from(air_carvers)));
249 compound.insert("carvers", NbtTag::Compound(carvers_compound));
250
251 let features_nbt: Vec<NbtList> = self
253 .features
254 .iter()
255 .map(|step| {
256 let step_strings: Vec<String> = step.iter().map(|id| id.to_string()).collect();
257 NbtList::from(step_strings)
258 })
259 .collect();
260 compound.insert("features", NbtTag::List(NbtList::List(features_nbt)));
261
262 NbtTag::Compound(compound)
263 }
264}
265
266pub type BiomeRef = &'static Biome;
267
268pub struct BiomeRegistry {
269 biomes_by_id: Vec<BiomeRef>,
270 biomes_by_key: FxHashMap<Identifier, usize>,
271 tags: FxHashMap<Identifier, Vec<Identifier>>,
272 allows_registering: bool,
273}
274
275impl BiomeRegistry {
276 #[must_use]
277 pub fn new() -> Self {
278 Self {
279 biomes_by_id: Vec::new(),
280 biomes_by_key: FxHashMap::default(),
281 tags: FxHashMap::default(),
282 allows_registering: true,
283 }
284 }
285}
286
287impl BiomeRegistry {
288 pub fn register(&mut self, entry: BiomeRef) -> usize {
289 assert!(
290 self.allows_registering,
291 "Cannot register Biome after registry has been frozen"
292 );
293 let id = self.biomes_by_id.len();
294 let cached = entry.id.get_or_init(|| id);
295 assert_eq!(*cached, id, "biome registered with conflicting id");
296 self.biomes_by_id.push(entry);
297 self.biomes_by_key.insert(entry.key.clone(), id);
298 id
299 }
300
301 pub fn iter(&self) -> impl Iterator<Item = (usize, BiomeRef)> + '_ {
302 self.biomes_by_id
303 .iter()
304 .enumerate()
305 .map(|(id, &entry)| (id, entry))
306 }
307}
308
309impl Default for BiomeRegistry {
310 fn default() -> Self {
311 Self::new()
312 }
313}
314
315crate::impl_registry_ext!(BiomeRegistry, Biome, biomes_by_id, biomes_by_key);
316crate::impl_tagged_registry!(BiomeRegistry, biomes_by_key, "biome");
317
318impl crate::RegistryEntry for Biome {
319 fn key(&self) -> &Identifier {
320 &self.key
321 }
322
323 fn try_id(&self) -> Option<usize> {
324 self.id.get().copied()
325 }
326}