steel_core/server/
worlds.rs1use std::sync::Arc;
4
5use rustc_hash::FxHashMap;
6use small_map::FxSmallMap;
7use steel_utils::Identifier;
8
9use crate::config::{ResolvedDomainConfig, ResolvedWorldConfig};
10use crate::world::World;
11
12pub(crate) const OVERWORLD_WORLD_NAME: &str = "overworld";
13pub(crate) const NETHER_WORLD_NAME: &str = "the_nether";
14pub(crate) const END_WORLD_NAME: &str = "the_end";
15
16pub struct WorldMap {
18 worlds: FxSmallMap<8, Identifier, Arc<World>>,
19 default_domain: String,
20 default_worlds: FxHashMap<String, Identifier>,
21 nether_portal_targets: FxHashMap<Identifier, Identifier>,
22 end_portal_targets: FxHashMap<Identifier, Identifier>,
23}
24
25impl WorldMap {
26 #[must_use]
28 pub fn new(
29 default_domain: String,
30 domains: &[ResolvedDomainConfig],
31 world_configs: &[ResolvedWorldConfig],
32 ) -> Self {
33 let mut default_worlds = FxHashMap::default();
34 for domain in domains {
35 default_worlds.insert(domain.name.clone(), domain.default_world.clone());
36 }
37 let mut nether_portal_targets = FxHashMap::default();
38 let mut end_portal_targets = FxHashMap::default();
39 for world in world_configs {
40 if let Some(target) = &world.nether_portal_target {
41 nether_portal_targets.insert(world.key.clone(), target.clone());
42 }
43 if let Some(target) = &world.end_portal_target {
44 end_portal_targets.insert(world.key.clone(), target.clone());
45 }
46 }
47 Self {
48 worlds: FxSmallMap::default(),
49 default_domain,
50 default_worlds,
51 nether_portal_targets,
52 end_portal_targets,
53 }
54 }
55
56 pub fn insert(&mut self, key: Identifier, world: Arc<World>) {
58 self.worlds.insert(key, world);
59 }
60
61 #[must_use]
63 pub fn get(&self, key: &Identifier) -> Option<&Arc<World>> {
64 self.worlds.get(key)
65 }
66
67 pub fn values(&self) -> impl Iterator<Item = &Arc<World>> {
69 self.worlds.values()
70 }
71
72 pub fn keys(&self) -> impl Iterator<Item = &Identifier> {
74 self.worlds.keys()
75 }
76
77 pub fn iter(&self) -> impl Iterator<Item = (&Identifier, &Arc<World>)> {
79 self.worlds.iter()
80 }
81
82 #[must_use]
84 pub fn len(&self) -> usize {
85 self.worlds.len()
86 }
87
88 #[must_use]
90 pub fn is_empty(&self) -> bool {
91 self.worlds.is_empty()
92 }
93
94 #[must_use]
96 pub fn default_domain(&self) -> &str {
97 &self.default_domain
98 }
99
100 #[must_use]
102 pub fn has_domain(&self, domain: &str) -> bool {
103 self.default_worlds.contains_key(domain)
104 }
105
106 pub fn domain_names(&self) -> impl Iterator<Item = &str> {
108 self.default_worlds.keys().map(String::as_str)
109 }
110
111 #[must_use]
113 pub fn default_world(&self, domain: &str) -> Option<&Arc<World>> {
114 self.default_worlds
115 .get(domain)
116 .and_then(|key| self.worlds.get(key))
117 }
118
119 #[must_use]
121 pub fn server_default_world(&self) -> Option<&Arc<World>> {
122 self.default_world(self.default_domain())
123 }
124
125 #[must_use]
127 pub fn worlds_in_domain(&self, domain: &str) -> Vec<Arc<World>> {
128 self.worlds
129 .values()
130 .filter(|world| world.domain() == domain)
131 .cloned()
132 .collect()
133 }
134
135 #[must_use]
137 pub fn resolve_portal_target(
138 &self,
139 source_world: &World,
140 target_world_name: &str,
141 ) -> Option<Arc<World>> {
142 let key = Identifier::new(
143 source_world.domain().to_owned(),
144 target_world_name.to_owned(),
145 );
146 self.worlds.get(&key).cloned()
147 }
148
149 #[must_use]
151 pub fn resolve_nether_portal_target(&self, source_world: &World) -> Option<Arc<World>> {
152 if let Some(target) = self.nether_portal_targets.get(&source_world.key) {
153 return self.worlds.get(target).cloned();
154 }
155
156 self.resolve_portal_target(
157 source_world,
158 nether_portal_target_world_name(source_world.key.path.as_ref()),
159 )
160 }
161
162 #[must_use]
167 pub fn resolve_end_entry_portal_target(&self, source_world: &World) -> Option<Arc<World>> {
168 if let Some(target) = self.end_portal_targets.get(&source_world.key) {
169 return self.worlds.get(target).cloned();
170 }
171
172 end_entry_portal_target_world_name(source_world.key.path.as_ref())
173 .and_then(|target| self.resolve_portal_target(source_world, target))
174 }
175}
176
177fn nether_portal_target_world_name(source_world_name: &str) -> &'static str {
178 if source_world_name == NETHER_WORLD_NAME {
179 OVERWORLD_WORLD_NAME
180 } else {
181 NETHER_WORLD_NAME
182 }
183}
184
185fn end_entry_portal_target_world_name(source_world_name: &str) -> Option<&'static str> {
186 if source_world_name == END_WORLD_NAME {
187 None
188 } else {
189 Some(END_WORLD_NAME)
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::{end_entry_portal_target_world_name, nether_portal_target_world_name};
196
197 #[test]
198 fn nether_portal_target_names_follow_vanilla_level_keys() {
199 assert_eq!(nether_portal_target_world_name("overworld"), "the_nether");
200 assert_eq!(nether_portal_target_world_name("the_end"), "the_nether");
201 assert_eq!(nether_portal_target_world_name("the_nether"), "overworld");
202 }
203
204 #[test]
205 fn end_entry_portal_target_name_is_only_for_non_end_sources() {
206 assert_eq!(
207 end_entry_portal_target_world_name("overworld"),
208 Some("the_end")
209 );
210 assert_eq!(
211 end_entry_portal_target_world_name("the_nether"),
212 Some("the_end")
213 );
214 assert_eq!(end_entry_portal_target_world_name("the_end"), None);
215 }
216}