veloren_voxygen/
profile.rs1use crate::hud;
2use common::{character::CharacterId, uuid::Uuid};
3use hashbrown::HashMap;
4use serde::{Deserialize, Serialize};
5use std::{
6 fs,
7 io::Write,
8 path::{Path, PathBuf},
9};
10use tracing::warn;
11
12#[derive(Clone, Debug, Serialize, Deserialize)]
14#[serde(default)]
15pub struct CharacterProfile {
16 pub hotbar_slots: [Option<hud::HotbarSlotContents>; 10],
18}
19
20const fn default_slots() -> [Option<hud::HotbarSlotContents>; 10] {
21 [None, None, None, None, None, None, None, None, None, None]
22}
23
24impl Default for CharacterProfile {
25 fn default() -> Self {
26 CharacterProfile {
27 hotbar_slots: default_slots(),
28 }
29 }
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize)]
34#[serde(default)]
35pub struct ServerProfile {
36 pub characters: HashMap<CharacterId, CharacterProfile>,
38 pub selected_character: Option<CharacterId>,
40 pub spectate_position: Option<vek::Vec3<f32>>,
42 pub accepted_rules: Option<u64>,
44}
45
46impl Default for ServerProfile {
47 fn default() -> Self {
48 ServerProfile {
49 characters: HashMap::new(),
50 selected_character: None,
51 spectate_position: None,
52 accepted_rules: None,
53 }
54 }
55}
56
57#[derive(Default, Clone, Debug, Serialize, Deserialize)]
63#[serde(default)]
64pub struct Profile {
65 pub servers: HashMap<String, ServerProfile>,
66 pub mutelist: HashMap<Uuid, String>,
67 #[serde(skip)]
70 pub transient_character: Option<CharacterProfile>,
71}
72
73impl Profile {
74 pub fn load(config_dir: &Path) -> Self {
76 let path = Profile::get_path(config_dir);
77
78 if let Ok(file) = fs::File::open(&path) {
79 match ron::de::from_reader(file) {
80 Ok(profile) => return profile,
81 Err(e) => {
82 warn!(
83 ?e,
84 ?path,
85 "Failed to parse profile file! Falling back to default."
86 );
87 let new_path = path.with_extension("invalid.ron");
89 if let Err(e) = fs::rename(path.clone(), new_path.clone()) {
90 warn!(?e, ?path, ?new_path, "Failed to rename profile file.");
91 }
92 },
93 }
94 }
95 let default_profile = Self::default();
99 default_profile.save_to_file_warn(config_dir);
100 default_profile
101 }
102
103 pub fn save_to_file_warn(&self, config_dir: &Path) {
105 if let Err(e) = self.save_to_file(config_dir) {
106 warn!(?e, "Failed to save profile");
107 }
108 }
109
110 pub fn get_hotbar_slots(
121 &self,
122 server: &str,
123 character_id: Option<CharacterId>,
124 ) -> [Option<hud::HotbarSlotContents>; 10] {
125 match character_id {
126 Some(character_id) => self
127 .servers
128 .get(server)
129 .and_then(|s| s.characters.get(&character_id)),
130 None => self.transient_character.as_ref(),
131 }
132 .map(|c| c.hotbar_slots.clone())
133 .unwrap_or_else(default_slots)
134 }
135
136 pub fn set_hotbar_slots(
148 &mut self,
149 server: &str,
150 character_id: Option<CharacterId>,
151 slots: [Option<hud::HotbarSlotContents>; 10],
152 ) {
153 match character_id {
154 Some(character_id) => self.servers
155 .entry(server.to_string())
156 .or_default()
157 .characters
159 .entry(character_id)
160 .or_default(),
161 None => self.transient_character.get_or_insert_default(),
162 }
163 .hotbar_slots = slots;
164 }
165
166 pub fn get_selected_character(&self, server: &str) -> Option<CharacterId> {
175 self.servers
176 .get(server)
177 .map(|s| s.selected_character)
178 .unwrap_or_default()
179 }
180
181 pub fn set_selected_character(
191 &mut self,
192 server: &str,
193 selected_character: Option<CharacterId>,
194 ) {
195 self.servers
196 .entry(server.to_string())
197 .or_default()
198 .selected_character = selected_character;
199 }
200
201 pub fn get_spectate_position(&self, server: &str) -> Option<vek::Vec3<f32>> {
210 self.servers
211 .get(server)
212 .map(|s| s.spectate_position)
213 .unwrap_or_default()
214 }
215
216 pub fn set_spectate_position(
226 &mut self,
227 server: &str,
228 spectate_position: Option<vek::Vec3<f32>>,
229 ) {
230 self.servers
231 .entry(server.to_string())
232 .or_default()
233 .spectate_position = spectate_position;
234 }
235
236 fn save_to_file(&self, config_dir: &Path) -> std::io::Result<()> {
238 let path = Profile::get_path(config_dir);
239 if let Some(dir) = path.parent() {
240 fs::create_dir_all(dir)?;
241 }
242 let mut config_file = fs::File::create(path)?;
243
244 let s: &str = &ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap();
245 config_file.write_all(s.as_bytes()).unwrap();
246 Ok(())
247 }
248
249 fn get_path(config_dir: &Path) -> PathBuf { config_dir.join("profile.ron") }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_get_slots_with_empty_profile() {
258 let profile = Profile::default();
259 let slots = profile.get_hotbar_slots("TestServer", Some(CharacterId(12345)));
260 assert_eq!(slots, [(); 10].map(|()| None))
261 }
262
263 #[test]
264 fn test_set_slots_with_empty_profile() {
265 let mut profile = Profile::default();
266 let slots = [(); 10].map(|()| None);
267 profile.set_hotbar_slots("TestServer", Some(CharacterId(12345)), slots);
268 }
269}