minecraftrs/world/
mod.rs

1use crate::block::{BlockRegistry, Block};
2use crate::biome::{BiomeRegistry, Biome};
3use crate::version::Version;
4use crate::rand::jrand;
5use std::collections::hash_map::Entry;
6use std::collections::HashMap;
7use std::rc::Rc;
8
9pub mod chunk;
10pub mod loader;
11pub mod gen;
12
13use loader::{ChunkError, ChunkLoader};
14use chunk::Chunk;
15
16
17/// Combine a chunk coordinate pair into 64 bits for hashing.
18#[inline]
19pub fn combine_chunk_coords(cx: i32, cz: i32) -> u64 {
20    cx as u32 as u64 | ((cz as u32 as u64) << 32)
21}
22
23
24/// World info are used to be shared with the chunk loader.
25pub struct WorldInfo {
26    pub version: Version,
27    pub seed: i64,
28    pub block_registry: BlockRegistry,
29    pub biome_registry: BiomeRegistry
30}
31
32/// A world for a specific version with specific registries and chunk loaders.
33pub struct World {
34    info: Rc<WorldInfo>,
35    loader: Box<dyn ChunkLoader>,
36    chunks: HashMap<u64, Chunk>
37}
38
39impl World {
40
41    /// Create a new world with a specific seed & version.
42    pub fn new(seed: i64, version: Version) -> World {
43
44        let block_registry = BlockRegistry::from(version);
45        let biome_registry = BiomeRegistry::from(version);
46
47        let info = Rc::new(WorldInfo {
48            version,
49            seed,
50            block_registry,
51            biome_registry
52        });
53
54        World {
55            loader: gen::for_world(Rc::clone(&info)),
56            chunks: HashMap::new(),
57            info
58        }
59
60    }
61
62    /// Create a new world with a specific version, and a randomly
63    /// generated seed.
64    pub fn new_seeded(version: Version) -> World {
65        Self::new(jrand::gen_seed(), version)
66    }
67
68    pub fn get_info(&self) -> &WorldInfo {
69        &self.info
70    }
71
72    /// Return the list of cached chunks.
73    pub fn get_chunks(&self) -> &HashMap<u64, Chunk> {
74        &self.chunks
75    }
76
77    // PROVIDE CHUNKS //
78
79    /// Provide an existing chunk, if the chunk is not cached the world's
80    /// chunk loader is called. If you need a chunk
81    pub fn provide_chunk(&mut self, cx: i32, cz: i32) -> Result<&Chunk, ChunkError> {
82        match self.chunks.entry(combine_chunk_coords(cx, cz)) {
83            Entry::Occupied(o) => Ok(o.into_mut()),
84            Entry::Vacant(v) => {
85                Ok(v.insert(self.loader.load_chunk(cx, cz)?))
86            }
87        }
88    }
89
90    /// Provide an existing chunk at specific block position, if the chunk is
91    /// not cached the world's chunk loader is called.
92    pub fn provide_chunk_at(&mut self, x: i32, z: i32) -> Result<&Chunk, ChunkError> {
93        self.provide_chunk(x >> 4, z >> 4)
94    }
95
96    // CHUNKS //
97
98    /// Get a chunk reference at specific coordinates.
99    pub fn get_chunk(&self, cx: i32, cz: i32) -> Option<&Chunk> {
100        self.chunks.get(&combine_chunk_coords(cx, cz))
101    }
102
103    /// Get a mutable chunk reference at specific coordinates.
104    pub fn get_chunk_mut(&mut self, cx: i32, cz: i32) -> Option<&mut Chunk> {
105        self.chunks.get_mut(&combine_chunk_coords(cx, cz))
106    }
107
108    /// Get a chunk reference at specific blocks coordinates.
109    pub fn get_chunk_at(&self, x: i32, z: i32) -> Option<&Chunk> {
110        self.get_chunk(x >> 4, z >> 4)
111    }
112
113    /// Get a mutable chunk reference at specific blocks coordinates.
114    pub fn get_chunk_mut_at(&mut self, x: i32, z: i32) -> Option<&mut Chunk> {
115        self.get_chunk_mut(x >> 4, z >> 4)
116    }
117
118    // SAFE FUNCTION FOR CHUNKS //
119
120    fn with_chunk_at<'a, F, R>(&'a self, x: i32, y: i32, z: i32, func: F) -> Option<R>
121        where F: FnOnce(&'a Chunk, usize, usize, usize) -> Option<R>
122    {
123        if y < 0 {
124            None
125        } else {
126            let chunk = self.get_chunk_at(x, z)?;
127            let y = y as usize;
128            if y >= chunk.get_max_height() {
129                None
130            } else {
131                func(chunk, (x & 15) as usize, y, (z & 15) as usize)
132            }
133        }
134    }
135
136    fn with_chunk_mut_at<'a, F>(&'a mut self, x: i32, y: i32, z: i32, func: F)
137        where F: FnOnce(&'a mut Chunk, usize, usize, usize)
138    {
139        if y >= 0 {
140            if let Some(chunk) = self.get_chunk_mut_at(x, z) {
141                let y = y as usize;
142                if y < chunk.get_max_height() {
143                    func(chunk, (x & 15) as usize, y, (z & 15) as usize)
144                }
145            }
146        }
147    }
148
149    // RAW BLOCKS //
150
151    /// Get a block id at specific position, if the position is invalid, or
152    /// the target chunk not loaded, `None` is returned.
153    pub fn get_block_id(&self, x: i32, y: i32, z: i32) -> Option<u16> {
154        self.with_chunk_at(x, y, z, |c, x, y, z| {
155            Some(c.get_block_id(x, y, z))
156        })
157    }
158
159    // ACTUAL BLOCKS //
160
161    /// Get a block at specific position, if the position is invalid, or
162    /// the target chunk not loaded, `None` is returned.
163    pub fn get_block(&self, x: i32, y: i32, z: i32) -> Option<&Block> {
164        self.info.block_registry.0.get_from_id(self.get_block_id(x, y, z)?)
165    }
166
167    /// Set a block at specific position, if the position is invalid nothing happens.
168    pub fn set_block(&mut self, x: i32, y: i32, z: i32, block: Option<&Block>) {
169        self.with_chunk_mut_at(x, y, z, |c, x, y, z| {
170            c.set_block(x, y, z, block);
171        });
172    }
173
174    // ACTUAL BIOMES //
175
176    pub fn get_biome_2d(&self, x: i32, z: i32) -> Option<&Biome> {
177        self.with_chunk_at(x, 0, z, |c, x, _, z| {
178            Some(c.get_biome_2d(x, z))
179        })
180    }
181
182    pub fn get_biome_3d(&self, x: i32, y: i32, z: i32) -> Option<&Biome> {
183        self.with_chunk_at(x, y, z, |c, x, y, z| {
184            Some(c.get_biome_3d(x, y, z))
185        })
186    }
187
188    pub fn set_biome_2d(&mut self, x: i32, z: i32, biome: &Biome) {
189        self.with_chunk_mut_at(x, 0, z, |c, x, _, z| {
190            c.set_biome_2d(x, z, biome);
191        })
192    }
193
194    pub fn set_biome_3d(&mut self, x: i32, y: i32, z: i32, biome: &Biome) {
195        self.with_chunk_mut_at(x, y, z, |c, x, y, z| {
196            c.set_biome_3d(x, y, z, biome);
197        })
198    }
199
200    // LEGACY BIOMES //
201
202    /*/// Get a biome id at specific position, if the position is invalid, or
203    /// the target chunk not loaded, `None` is returned.
204    pub fn get_biome_id(&self, x: i32, y: i32, z: i32) -> Option<u8> {
205        self.with_chunk_at(x, y, z, |c, x, y, z| {
206            Some(c.get_biome_id(x, y, z))
207        })
208    }
209
210    /// Get a biome at specific position, if the position is invalid, or
211    /// the target chunk not loader, `None` is returned.
212    pub fn get_biome(&self, x: i32, y: i32, z: i32) -> Option<&Biome> {
213        self.info.biome_registry.0.get_from_id(self.get_biome_id(x, y, z)?)
214    }
215
216    /// Set a biome at specific position, if the position is invalid nothing happens.
217    pub fn set_biome(&mut self, x: i32, y: i32, z: i32, biome: Option<&Biome>) {
218        self.with_chunk_mut_at(x, y, z, |c, x, y, z| {
219            c.set_biome(x, y, z, biome);
220        });
221    }*/
222
223}