level parsing i think
This commit is contained in:
parent
22a87d6a38
commit
1e913b9ec8
8 changed files with 253 additions and 15 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -175,6 +181,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -337,6 +352,16 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -415,6 +440,7 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"diesel",
|
"diesel",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"flate2",
|
||||||
"maplit",
|
"maplit",
|
||||||
"password-auth",
|
"password-auth",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -690,6 +716,15 @@ version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
|
|
|
@ -5,8 +5,9 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.21.3"
|
base64 = "0.21.3"
|
||||||
diesel = { version = "=2.1.0", features = ["postgres"] }
|
diesel = { version = "=2.1.0", features = ["postgres", "64-column-tables"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
|
flate2 = "1.0.27"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
password-auth = "0.3.0"
|
password-auth = "0.3.0"
|
||||||
regex = "1.9.4"
|
regex = "1.9.4"
|
||||||
|
|
|
@ -25,6 +25,7 @@ CREATE TABLE levels (
|
||||||
song_id INTEGER NOT NULL,
|
song_id INTEGER NOT NULL,
|
||||||
|
|
||||||
length INTEGER NOT NULL,
|
length INTEGER NOT NULL,
|
||||||
|
length_real INTEGER NOT NULL,
|
||||||
objects INTEGER NOT NULL,
|
objects INTEGER NOT NULL,
|
||||||
coins INTEGER NOT NULL DEFAULT 0,
|
coins INTEGER NOT NULL DEFAULT 0,
|
||||||
has_ldm INTEGER NOT NULL DEFAULT 0,
|
has_ldm INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
|
@ -35,6 +35,7 @@ _these features are implemented_
|
||||||
## todo
|
## todo
|
||||||
|
|
||||||
- swap to chrono instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!)
|
- swap to chrono instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!)
|
||||||
- patch `upload_level` to use 2.2 unlisted(honsetly idk what this is but it exists?)
|
- patch `upload_level` to use 2.2 unlisted (friends only stuff)
|
||||||
- add level parsing (`upload_level` can use the correct `length`, `objects`, `coins`, and `two_player`)
|
- in our level parsing, check for dual portals rather than just starting dual
|
||||||
- <small>green name users...</small>
|
- <small>green name users...</small>
|
||||||
|
- maybe tone down the clone usage (this is for sure causing memory problems 😉)
|
|
@ -108,6 +108,7 @@ pub struct Level {
|
||||||
pub editor_time_copies: i32,
|
pub editor_time_copies: i32,
|
||||||
pub song_id: i32,
|
pub song_id: i32,
|
||||||
pub length: i32,
|
pub length: i32,
|
||||||
|
pub length_real: f64,
|
||||||
pub objects: i32,
|
pub objects: i32,
|
||||||
pub coins: i32,
|
pub coins: i32,
|
||||||
pub has_ldm: i32,
|
pub has_ldm: i32,
|
||||||
|
@ -142,6 +143,7 @@ pub struct NewLevel {
|
||||||
pub editor_time_copies: i32,
|
pub editor_time_copies: i32,
|
||||||
pub song_id: i32,
|
pub song_id: i32,
|
||||||
pub length: i32,
|
pub length: i32,
|
||||||
|
pub length_real: f64,
|
||||||
pub objects: i32,
|
pub objects: i32,
|
||||||
pub coins: i32,
|
pub coins: i32,
|
||||||
pub has_ldm: i32,
|
pub has_ldm: i32,
|
||||||
|
|
|
@ -46,6 +46,7 @@ diesel::table! {
|
||||||
editor_time_copies -> Int4,
|
editor_time_copies -> Int4,
|
||||||
song_id -> Int4,
|
song_id -> Int4,
|
||||||
length -> Int4,
|
length -> Int4,
|
||||||
|
length_real -> Int4,
|
||||||
objects -> Int4,
|
objects -> Int4,
|
||||||
coins -> Int4,
|
coins -> Int4,
|
||||||
has_ldm -> Int4,
|
has_ldm -> Int4,
|
||||||
|
|
|
@ -73,7 +73,30 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str
|
||||||
None => { extra_string = helpers::levels::DEFAULT_EXTRA_STRING.to_owned() }
|
None => { extra_string = helpers::levels::DEFAULT_EXTRA_STRING.to_owned() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// db shit
|
// level parsing
|
||||||
|
let level_raw_objects = helpers::levels::decode(input.levelString.clone());
|
||||||
|
let level_objects = helpers::levels::to_objectdata(level_raw_objects.clone());
|
||||||
|
let inner_level_string = level_raw_objects
|
||||||
|
.iter()
|
||||||
|
.find(|obj| !obj.contains_key("1") && obj.get("kA9") == Some(&"0".to_string()))
|
||||||
|
.expect("couldnt decode inner level string");
|
||||||
|
|
||||||
|
let level_length_secs = helpers::levels::measure_length(
|
||||||
|
level_objects.clone(),
|
||||||
|
inner_level_string.get("kA4").unwrap_or(&String::from("0")).parse::<i32>().expect("kA4 not int")
|
||||||
|
);
|
||||||
|
|
||||||
|
let coins_val = level_objects.iter().filter(|obj| obj.id() == 1329).count(); // 1329 is coin id
|
||||||
|
let objects_val = level_objects.len();
|
||||||
|
let two_player_val = if inner_level_string.get("kA10").unwrap_or(&String::from("0")).parse::<i32>().expect("kA10 not int") == 1 { 1 } else { 0 };
|
||||||
|
let level_length_val = helpers::levels::secs_to_time(level_length_secs);
|
||||||
|
|
||||||
|
// blocking
|
||||||
|
if coins_val > 3 {
|
||||||
|
return status::Custom(Status::Ok, "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// data base 🤣😁
|
||||||
use crate::models::{Level, NewLevel};
|
use crate::models::{Level, NewLevel};
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -110,11 +133,11 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str
|
||||||
editor_time.eq(input.wt.unwrap_or(0)),
|
editor_time.eq(input.wt.unwrap_or(0)),
|
||||||
editor_time_copies.eq(input.wt2.unwrap_or(0)),
|
editor_time_copies.eq(input.wt2.unwrap_or(0)),
|
||||||
song_id.eq(song_id_val),
|
song_id.eq(song_id_val),
|
||||||
length.eq(0), // unimplemeneted
|
length.eq(level_length_val),
|
||||||
objects.eq(0), // unimplemented
|
objects.eq(objects_val as i32),
|
||||||
coins.eq(0), // unimplemented
|
coins.eq(coins_val as i32),
|
||||||
has_ldm.eq(input.ldm.unwrap_or(0)),
|
has_ldm.eq(input.ldm.unwrap_or(0)),
|
||||||
two_player.eq(0) // unimplemented
|
two_player.eq(two_player_val)
|
||||||
))
|
))
|
||||||
.get_result::<Level, >(connection)
|
.get_result::<Level, >(connection)
|
||||||
.expect("failed to update level");
|
.expect("failed to update level");
|
||||||
|
@ -143,11 +166,12 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str
|
||||||
editor_time: input.wt.unwrap_or(0),
|
editor_time: input.wt.unwrap_or(0),
|
||||||
editor_time_copies: input.wt2.unwrap_or(0),
|
editor_time_copies: input.wt2.unwrap_or(0),
|
||||||
song_id: song_id_val,
|
song_id: song_id_val,
|
||||||
length: 0, // not implemeneted
|
length: level_length_val,
|
||||||
objects: 0, // not implemeneted
|
length_real: level_length_secs,
|
||||||
coins: 0, // not implemeneted
|
objects: objects_val as i32,
|
||||||
|
coins: coins_val as i32,
|
||||||
has_ldm: input.ldm.unwrap_or(0),
|
has_ldm: input.ldm.unwrap_or(0),
|
||||||
two_player: 0 // not implemented
|
two_player: two_player_val
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_level = diesel::insert_into(levels)
|
let inserted_level = diesel::insert_into(levels)
|
||||||
|
|
|
@ -1,7 +1,180 @@
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
|
||||||
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
|
|
||||||
pub static DEFAULT_EXTRA_STRING: LazyLock<String> = LazyLock::new(|| {
|
pub static DEFAULT_EXTRA_STRING: LazyLock<String> = LazyLock::new(|| {
|
||||||
let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29");
|
let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29");
|
||||||
|
|
||||||
return string;
|
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")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ObjectData {
|
||||||
|
raw: HashMap<String, String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectData {
|
||||||
|
pub fn new(raw: HashMap<String, String>) -> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod portal_speed {
|
||||||
|
pub enum PortalSpeed {
|
||||||
|
Slow,
|
||||||
|
Normal,
|
||||||
|
Medium,
|
||||||
|
Fast,
|
||||||
|
VeryFast
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortalSpeed {
|
||||||
|
pub fn portal_speed(&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<portal_speed::PortalSpeed> {
|
||||||
|
match id {
|
||||||
|
200 => Some(portal_speed::PortalSpeed::Slow),
|
||||||
|
201 => Some(portal_speed::PortalSpeed::Normal),
|
||||||
|
202 => Some(portal_speed::PortalSpeed::Medium),
|
||||||
|
203 => Some(portal_speed::PortalSpeed::Fast),
|
||||||
|
1334 => Some(portal_speed::PortalSpeed::VeryFast),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_seconds_from_xpos(pos: f64, start_speed: portal_speed::PortalSpeed, portals: Vec<ObjectData>) -> f64 {
|
||||||
|
let mut speed;
|
||||||
|
let mut last_obj_pos = 0.0;
|
||||||
|
let mut last_segment = 0.0;
|
||||||
|
let mut segments = 0.0;
|
||||||
|
|
||||||
|
speed = start_speed.portal_speed();
|
||||||
|
|
||||||
|
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").portal_speed();
|
||||||
|
|
||||||
|
last_obj_pos = portal.x()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((pos - last_segment) / speed) + segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn measure_length(objects: Vec<ObjectData>, ka4: i32) -> f64 {
|
||||||
|
let start_speed = match ka4 {
|
||||||
|
0 => portal_speed::PortalSpeed::Normal,
|
||||||
|
1 => portal_speed::PortalSpeed::Slow,
|
||||||
|
2 => portal_speed::PortalSpeed::Medium,
|
||||||
|
3 => portal_speed::PortalSpeed::Fast,
|
||||||
|
4 => portal_speed::PortalSpeed::VeryFast,
|
||||||
|
_ => portal_speed::PortalSpeed::Normal
|
||||||
|
};
|
||||||
|
|
||||||
|
let max_x_pos = objects
|
||||||
|
.iter()
|
||||||
|
.fold(0.0, |max_x, obj| f64::max(max_x, obj.x()));
|
||||||
|
|
||||||
|
let mut portals: Vec<ObjectData> = 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<String>) -> HashMap<String, String> {
|
||||||
|
return arr.chunks(2)
|
||||||
|
.map(|chunk| (chunk[0].clone(), chunk[1].clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(raw_level_data: &str) -> Vec<HashMap<String, String>> {
|
||||||
|
raw_level_data
|
||||||
|
.trim_end_matches(';')
|
||||||
|
.split(';')
|
||||||
|
.map(|v| {
|
||||||
|
let values: Vec<String> = v.split(',').map(|s| s.to_string()).collect();
|
||||||
|
array_to_hash(values)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(level_data: String) -> Vec<HashMap<String, String>> {
|
||||||
|
let decoded_bytes = general_purpose::URL_SAFE.decode(level_data).expect("couldnt decode b64");
|
||||||
|
|
||||||
|
let mut decoder = GzDecoder::new(&decoded_bytes[..]);
|
||||||
|
|
||||||
|
let mut uncompressed_data = String::new();
|
||||||
|
decoder.read_to_string(&mut uncompressed_data).expect("err unzipping level");
|
||||||
|
|
||||||
|
return parse(uncompressed_data.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_objectdata(objects: Vec<HashMap<String, String>>) -> Vec<ObjectData> {
|
||||||
|
return objects
|
||||||
|
.into_iter()
|
||||||
|
.filter(|v| v.contains_key("1"))
|
||||||
|
.map(|v| ObjectData::new(v))
|
||||||
|
.collect()
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue