use proto::{Avatar, AvatarPathData}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use crate::structs::{ // AllowedLanguages, avatar::{AvatarJson, MultiPathAvatar}, battle::BattleConfig, lightcone::Lightcone, persistent::Persistent, relic::Relic, scene::{Position, Scene}, }; #[derive(Debug, Serialize, Deserialize)] pub struct FreesrData { pub lightcones: Vec, pub relics: Vec, pub avatars: HashMap, #[serde(default)] pub battle_config: BattleConfig, // Non freesr-data.json fields #[serde(skip_serializing, skip_deserializing)] pub lineups: BTreeMap, #[serde(skip_serializing, skip_deserializing)] pub position: Position, #[serde(skip_serializing, skip_deserializing)] pub scene: Scene, #[serde(skip_serializing, skip_deserializing)] pub main_character: MultiPathAvatar, #[serde(skip_serializing, skip_deserializing)] pub march_type: MultiPathAvatar, } impl FreesrData { pub fn get_avatar_proto(&self, avatar_id: u32, mc_id: u32, march_id: u32) -> Option { let avatar = self.avatars.get(&avatar_id)?; let lightcone = self.lightcones.iter().find(|l| l.equip_avatar == avatar_id); Some(avatar.to_avatar_proto(lightcone, mc_id, march_id)) } pub fn get_avatar_path_data_proto(&self, avatar_id: u32) -> Option { let avatar = self.avatars.get(&avatar_id)?; Some( avatar.to_avatar_path_data_proto( self.lightcones.iter().find(|v| v.equip_avatar == avatar_id), self.relics .iter() .filter(|relic| relic.equip_avatar == avatar_id) .collect(), ), ) } } impl FreesrData { pub async fn load() -> anyhow::Result { let mut freesr_data: FreesrData = tokio::fs::read_to_string("freesr-data.json") .await .map_err(|e| anyhow::anyhow!("failed to read freesr-data.json: {e}")) .and_then(|v| { serde_json::from_str::(&v) .map_err(|e| anyhow::anyhow!("freesr-data.json is broken: {e}, pls redownload")) })?; let persistent: Persistent = serde_json::from_str( &tokio::fs::read_to_string("persistent") .await .unwrap_or_default(), ) .unwrap_or_default(); freesr_data.lineups = persistent.lineups; freesr_data.position = persistent.position; freesr_data.scene = persistent.scene; freesr_data.main_character = persistent.main_character; freesr_data.march_type = persistent.march_type; // freesr_data.game_language = persistent.game_language; // freesr_data.voice_langauge = persistent.voice_language; // remove unequipped lightcones if freesr_data.lightcones.len() > 1500 { freesr_data.lightcones.retain(|v| v.equip_avatar != 0); } // remove unequipped relics if freesr_data.relics.len() > 3000 { freesr_data.relics.retain(|v| v.equip_avatar != 0); } freesr_data.verify_lineup().await; if freesr_data.march_type as u32 > 8000 { freesr_data.march_type = MultiPathAvatar::MarchHunt; } Ok(freesr_data) } pub async fn update(&mut self) -> anyhow::Result<()> { *self = Self::load().await?; Ok(()) } async fn verify_lineup(&mut self) { if self.lineups.is_empty() { self.lineups = BTreeMap::::from([(0, 8001), (1, 0), (2, 0), (3, 0)]) } else if self.lineups.len() < 4 { for i in self.lineups.len()..4 { self.lineups.insert(i as u32, 0); } } self.save_persistent().await; } pub async fn save_persistent(&self) { if let Ok(data) = serde_json::to_string_pretty(&Persistent { lineups: self.lineups.clone(), main_character: self.main_character, position: self.position.clone(), scene: self.scene.clone(), march_type: self.march_type, // game_language: self.game_language, // voice_language: self.voice_langauge, }) { let _ = tokio::fs::write("persistent", data).await; tracing::info!("persistent saved"); } } }