use std::sync::LazyLock; use std::io::prelude::*; use base64::{Engine as _, engine::general_purpose}; use flate2::read::GzDecoder; use roxmltree::Document; use std::collections::HashMap; pub static DEFAULT_EXTRA_STRING: LazyLock = LazyLock::new(|| { let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29"); return string; }); pub static DEFAULT_LEVEL_INFO: LazyLock = LazyLock::new(|| { let string = String::from(""); return string; }); macro_rules! object_prop_bool { ($key:expr, $name:ident) => { pub fn $name(&self) -> bool { self.raw.get($key).map_or(false, |value| value == "1") } }; } macro_rules! object_prop_int { ($key:expr, $name:ident) => { pub fn $name(&self) -> Option { self.raw.get($key).and_then(|s| s.parse().ok()) } }; } #[derive(Clone)] pub struct ObjectData { raw: HashMap } impl ObjectData { pub fn new(raw: HashMap) -> Self { ObjectData { raw } } pub fn id(&self) -> i32 { self.raw.get("1").unwrap_or(&String::new()).parse().unwrap_or(0) } pub fn x(&self) -> f64 { self.raw.get("2").unwrap_or(&String::new()).parse().unwrap_or(0.0) } pub fn y(&self) -> f64 { self.raw.get("3").unwrap_or(&String::new()).parse().unwrap_or(0.0) } object_prop_bool!("13", checked); object_prop_int!("80", item_block_id); } pub enum PortalSpeed { Slow, Normal, Medium, Fast, VeryFast } impl Into for PortalSpeed { fn into(self) -> f64 { match self { PortalSpeed::Slow => 251.16, PortalSpeed::Normal => 311.58, PortalSpeed::Medium => 387.42, PortalSpeed::Fast => 478.0, PortalSpeed::VeryFast => 576.0 } } } pub fn id_to_portal_speed(id: i32) -> Option { match id { 200 => Some(PortalSpeed::Slow), 201 => Some(PortalSpeed::Normal), 202 => Some(PortalSpeed::Medium), 203 => Some(PortalSpeed::Fast), 1334 => Some(PortalSpeed::VeryFast), _ => None, } } pub fn get_seconds_from_xpos(pos: f64, start_speed: PortalSpeed, portals: Vec) -> f64 { let mut speed: f64; let mut last_obj_pos = 0.0; let mut last_segment = 0.0; let mut segments = 0.0; speed = start_speed.into(); if portals.is_empty() { return pos / speed } for portal in portals { let mut s = portal.x() - last_obj_pos; if pos < s { s = s / speed; last_segment = s; segments += s; speed = id_to_portal_speed(portal.id()).expect("not a portal").into(); last_obj_pos = portal.x() } } return ((pos - last_segment) / speed) + segments; } pub fn measure_length(objects: Vec, ka4: i32) -> f64 { let start_speed = match ka4 { 0 => PortalSpeed::Normal, 1 => PortalSpeed::Slow, 2 => PortalSpeed::Medium, 3 => PortalSpeed::Fast, 4 => PortalSpeed::VeryFast, _ => PortalSpeed::Normal }; let max_x_pos = objects .iter() .fold(0.0, |max_x, obj| f64::max(max_x, obj.x())); let mut portals: Vec = objects .into_iter() .filter(|obj| id_to_portal_speed(obj.id()).is_some() && obj.checked()) .collect(); portals.sort_by(|a, b| a.x().partial_cmp(&b.x()).unwrap()); return get_seconds_from_xpos(max_x_pos, start_speed, portals) } pub fn secs_to_time(time: f64) -> i32 { match time { time if time < 10.0 => return 0, time if time < 30.0 => return 1, time if time < 60.0 => return 2, time if time < 120.0 => return 3, time if time >= 120.0 => return 4, _ => 0 } } pub fn array_to_hash(arr: Vec) -> HashMap { return arr.chunks(2) .map(|chunk| (chunk[0].clone(), chunk[1].clone())) .collect() } pub fn parse(raw_level_data: &str) -> Vec> { raw_level_data .trim_end_matches(';') .split(';') .map(|v| { let values: Vec = v.split(',').map(|s| s.to_string()).collect(); array_to_hash(values) }) .collect() } pub fn gmd_parse(gmd_file: &str) -> HashMap { let doc = Document::parse(gmd_file).expect("failed to parse gmd file"); let root = doc.root_element(); let mut result = Vec::new(); for child in root.children().filter(|node| node.node_type() != roxmltree::NodeType::Text) { if let Some(child_text) = child.children().next() { result.push(child_text.text().unwrap_or("").to_string()); } } return array_to_hash(result); } pub fn decode(level_data: String) -> Vec> { let decoded_bytes = general_purpose::URL_SAFE.decode(level_data).expect("couldnt decode b64"); let mut decoder = GzDecoder::new(&decoded_bytes[..]); let uncompressed_data = String::from_utf8(if decoded_bytes.starts_with(&[0x1F, 0x8B]) { // gzip!! let mut decompressed_data = Vec::new(); decoder.read_to_end(&mut decompressed_data).expect("err uncompressing level"); decompressed_data } else if decoded_bytes.starts_with(&[0x78]) { // zlib!! let mut decompressed_data = Vec::new(); decoder.read_to_end(&mut decompressed_data).expect("err uncompressing level"); decompressed_data } else { panic!("invalid compression method") }).expect("invalid utf-8 sequence"); return parse(uncompressed_data.as_str()) } pub fn to_objectdata(objects: Vec>) -> Vec { return objects .into_iter() .filter(|v| v.contains_key("1")) .map(|v| ObjectData::new(v)) .collect() }