use std::collections::BinaryHeap; use std::collections::HashMap; use std::ops::Deref; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; use serde_derive::Deserialize; use serde_derive::Serialize; #[allow(dead_code)] type UnprocessedAreas = Vec; type StepOneUnprocessedAreas = HashMap; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct UnprocessedArea { #[serde(rename = "name")] pub name: String, #[serde(rename = "id")] pub named_id: String, #[serde(rename = "area_level")] pub area_level: i64, #[serde(rename = "act")] pub act: i64, #[serde(rename = "is_town")] pub is_town: bool, #[serde(rename = "has_waypoint")] pub has_waypoint: bool, #[serde(rename = "connections")] pub connections_world_areas_keys: Vec, } pub type AreaName = String; #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct WorldArea { pub name: String, pub named_id: String, pub area_level: i64, pub act: i64, pub is_town: bool, pub has_waypoint: bool, pub connections_world_areas_keys: Vec, } pub type AreaId = usize; pub type WorldAreasMap = HashMap; #[allow(dead_code)] pub fn load_world_areas_map(content: &'static str) -> WorldAreasMap { serde_json::from_str::(content).expect("Could not load world areas json") } #[macro_export] macro_rules! load_world_areas { ($e:expr) => { crate::world_area::load_world_areas_map(&include_str!($e)) }; } #[allow(dead_code)] pub struct WorldAreas { pub areas_map: WorldAreasMap, matcher: Matcher, } impl WorldAreas { #[allow(dead_code)] pub fn new(areas: WorldAreasMap, matcher: Matcher) -> Self { Self { areas_map: areas, matcher, } } #[allow(dead_code)] pub fn fuzzy_area(&self, count: usize, area: &str) -> Vec { let mut matches = Vec::new(); for (_key_id, v) in &self.areas_map { if let Some(mut score) = self.matcher.fuzzy_match(&v.name, area) { if v.name.contains(area) { score = i64::MAX; } let f_entry = FEntry { area: v.clone(), score, }; matches.push(f_entry); } } matches.sort_by(|entry1, entry2| { entry1 .score .cmp(&entry2.score) .reverse() .then(entry1.area.area_level.cmp(&entry2.area.area_level)) }); let matches_areas = matches .into_iter() .map(|entry| entry.area) .take(count) .collect::>(); matches_areas } #[allow(dead_code)] pub fn by_area_id(&self, key: String) -> Option<&WorldArea> { self.areas_map.get(&key) } } #[derive(PartialEq, Eq, Clone, Debug)] struct FEntry { area: WorldArea, score: i64, } impl Ord for FEntry { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other).unwrap() } } impl PartialOrd for FEntry { fn partial_cmp(&self, other: &Self) -> Option { Some(self.score.cmp(&other.score)) } } pub struct Matcher(SkimMatcherV2); impl Matcher { #[allow(dead_code)] pub fn new() -> Self { let matcher = SkimMatcherV2::default().ignore_case().use_cache(true); Matcher(matcher) } } impl Deref for Matcher { type Target = SkimMatcherV2; fn deref(&self) -> &Self::Target { &self.0 } } impl Matcher { #[allow(dead_code)] pub fn fuzzy_area(&self, areas: &WorldAreasMap, area: &str) -> Vec { let mut matches: BinaryHeap = BinaryHeap::new(); for (_key_id, v) in areas { if let Some(score) = self.fuzzy_match(&v.name, area) { let f_entry = FEntry { area: v.clone(), score, }; matches.push(f_entry); } } let mut values = matches.into_iter(); let mut return_vec = Vec::new(); for _i in 0..30 { if let Some(value) = values.next() { return_vec.push(value.area.clone()); } else { break; } } return_vec } } #[allow(dead_code)] pub fn repack(content: &str, out_path: &str) { let areas_json = serde_json::from_str::(content).unwrap().into_iter().map(|(k,v)| v).collect::>(); let area_map = areas_json .into_iter() .filter_map(|w_a| { if w_a.act < 11 && !w_a.connections_world_areas_keys.is_empty() && !w_a.named_id.starts_with("Map") && !w_a.named_id.starts_with("Descent") && !w_a.named_id.starts_with("EndlessLedge") { Some(( w_a.named_id.clone(), WorldArea { name: w_a.name, named_id: w_a.named_id, area_level: w_a.area_level, act: w_a.act, is_town: w_a.is_town, has_waypoint: w_a.has_waypoint, connections_world_areas_keys: w_a.connections_world_areas_keys, }, )) } else { None } }) .collect::(); let new_json = serde_json::to_string(&area_map).unwrap(); std::fs::write(out_path, &new_json).expect("Could not write to file"); } #[allow(dead_code)] pub fn repack_full(content: &str, out_path: &str) { let areas_json = serde_json::from_str::(content).unwrap().into_iter().map(|(k,v)| v).collect::>(); let area_map = areas_json .into_iter() .map(|w_a| { ( w_a.named_id.clone(), WorldArea { name: w_a.name, named_id: w_a.named_id, area_level: w_a.area_level, act: w_a.act, is_town: w_a.is_town, has_waypoint: w_a.has_waypoint, connections_world_areas_keys: w_a.connections_world_areas_keys, }, ) }) .collect::(); let new_json = serde_json::to_string(&area_map).unwrap(); std::fs::write(out_path, &new_json).expect("Could not write to file"); }