1
Fork 0

Back up everything before history rewrite

Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
prescientmoon 2024-08-16 15:38:00 +02:00
parent c035ecbb52
commit 3f922fcf6c
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
6 changed files with 175 additions and 83 deletions

View file

@ -1,4 +1,4 @@
use std::{fs, path::PathBuf};
use std::{fs, io::Cursor, path::PathBuf};
use image::{imageops::FilterType, GenericImageView, Rgba};
use num::Integer;
@ -153,9 +153,17 @@ impl JacketCache {
image.resize(BITMAP_IMAGE_SIZE, BITMAP_IMAGE_SIZE, FilterType::Nearest);
if should_blur_jacket_art() {
image = image.blur(20.0);
image = image.blur(40.0);
}
let encoded_pic = {
let mut processed_pic = Vec::new();
image.write_to(
&mut Cursor::new(&mut processed_pic),
image::ImageFormat::Jpeg,
)?;
processed_pic.leak()
};
let bitmap: &'static _ = Box::leak(Box::new(image.into_rgb8()));
if name == "base" {
@ -163,7 +171,7 @@ impl JacketCache {
for chart in song_cache.charts_mut() {
if chart.song_id == song_id && chart.cached_jacket.is_none() {
chart.cached_jacket = Some(Jacket {
raw: contents,
raw: encoded_pic,
bitmap,
});
}
@ -171,7 +179,7 @@ impl JacketCache {
} else if difficulty.is_some() {
let chart = song_cache.lookup_chart_mut(chart_id).unwrap();
chart.cached_jacket = Some(Jacket {
raw: contents,
raw: encoded_pic,
bitmap,
});
}

View file

@ -11,7 +11,7 @@ use crate::context::{Error, UserContext};
use crate::user::User;
use super::chart::SongCache;
use super::score::Score;
use super::score::{Score, ScoringSystem};
// {{{ Create play
#[derive(Debug, Clone)]
@ -127,7 +127,7 @@ pub struct DbPlay {
impl DbPlay {
#[inline]
pub fn to_play(self) -> Play {
pub fn into_play(self) -> Play {
Play {
id: self.id as u32,
chart_id: self.chart_id as u32,
@ -175,6 +175,20 @@ pub struct Play {
}
impl Play {
// {{{ Query the underlying score
#[inline]
pub fn score(&self, system: ScoringSystem) -> Score {
match system {
ScoringSystem::Standard => self.score,
ScoringSystem::EX => self.zeta_score,
}
}
#[inline]
pub fn play_rating(&self, system: ScoringSystem, chart_constant: u32) -> i32 {
self.score(system).play_rating(chart_constant)
}
// }}}
// {{{ Play => distribution
pub fn distribution(&self, note_count: u32) -> Option<(u32, u32, u32, u32)> {
if let Some(fars) = self.far_notes {
@ -278,7 +292,8 @@ impl Play {
song.title, chart.difficulty
)
})?
.map(|p| p.to_play());
.map(|p| p.into_play());
let prev_score = prev_play.as_ref().map(|p| p.score);
let prev_zeta_score = prev_play.as_ref().map(|p| p.zeta_score);
// }}}
@ -360,9 +375,10 @@ pub async fn get_best_plays<'a>(
db: &SqlitePool,
song_cache: &'a SongCache,
user: &User,
scoring_system: ScoringSystem,
min_amount: usize,
max_amount: usize,
) -> Result<Result<PlayCollection<'a>, &'static str>, Error> {
) -> Result<Result<PlayCollection<'a>, String>, Error> {
// {{{ DB data fetching
let plays: Vec<DbPlay> = query_as(
"
@ -381,7 +397,10 @@ pub async fn get_best_plays<'a>(
// }}}
if plays.len() < min_amount {
return Ok(Err("Not enough plays found"));
return Ok(Err(format!(
"Not enough plays found ({} out of a minimum of {min_amount})",
plays.len()
)));
}
// {{{ B30 computation
@ -390,13 +409,13 @@ pub async fn get_best_plays<'a>(
let mut plays: Vec<(Play, &Song, &Chart)> = plays
.into_iter()
.map(|play| {
let play = play.to_play();
let play = play.into_play();
let (song, chart) = song_cache.lookup_chart(play.chart_id)?;
Ok((play, song, chart))
})
.collect::<Result<Vec<_>, Error>>()?;
plays.sort_by_key(|(play, _, chart)| -play.score.play_rating(chart.chart_constant));
plays.sort_by_key(|(play, _, chart)| -play.play_rating(scoring_system, chart.chart_constant));
plays.truncate(max_amount);
// }}}
@ -404,11 +423,12 @@ pub async fn get_best_plays<'a>(
}
#[inline]
pub fn compute_b30_ptt(plays: &PlayCollection<'_>) -> i32 {
pub fn compute_b30_ptt(scoring_system: ScoringSystem, plays: &PlayCollection<'_>) -> i32 {
plays
.iter()
.map(|(play, _, chart)| play.score.play_rating(chart.chart_constant))
.map(|(play, _, chart)| play.play_rating(scoring_system, chart.chart_constant))
.sum::<i32>()
/ plays.len() as i32
.checked_div(plays.len() as i32)
.unwrap_or(0)
}
// }}}

View file

@ -6,6 +6,21 @@ use crate::context::Error;
use super::chart::Chart;
// {{{ Scoring system
#[derive(Debug, Clone, Copy, poise::ChoiceParameter)]
pub enum ScoringSystem {
Standard,
// Inspired by sdvx's EX-scoring
EX,
}
impl Default for ScoringSystem {
fn default() -> Self {
Self::Standard
}
}
// }}}
// {{{ Grade
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Grade {
@ -140,7 +155,7 @@ impl Score {
if play_rating >= prev_play_rating {
write!(buffer, " (+{:.2})", play_rating - prev_play_rating)?;
} else {
write!(buffer, " (-{:.2})", play_rating - prev_play_rating)?;
write!(buffer, " ({:.2})", play_rating - prev_play_rating)?;
}
}

View file

@ -1,7 +1,7 @@
use std::{cell::RefCell, env::var, path::PathBuf, str::FromStr, sync::OnceLock, thread::LocalKey};
use freetype::{Face, Library};
use image::{imageops::FilterType, ImageBuffer, Rgb, Rgba};
use image::{ImageBuffer, Rgb, Rgba};
use crate::{arcaea::chart::Difficulty, timed};
@ -55,94 +55,101 @@ pub fn with_font<T>(
#[inline]
pub fn should_skip_jacket_art() -> bool {
static CELL: OnceLock<bool> = OnceLock::new();
*CELL.get_or_init(|| var("SHIMMERING_NO_JACKETS").unwrap_or_default() == "1")
var("SHIMMERING_NO_JACKETS").unwrap_or_default() == "1"
}
#[inline]
pub fn should_blur_jacket_art() -> bool {
static CELL: OnceLock<bool> = OnceLock::new();
*CELL.get_or_init(|| var("SHIMMERING_BLUR_JACKETS").unwrap_or_default() == "1")
var("SHIMMERING_BLUR_JACKETS").unwrap_or_default() == "1"
}
pub fn get_b30_background() -> &'static ImageBuffer<Rgb<u8>, Vec<u8>> {
static CELL: OnceLock<ImageBuffer<Rgb<u8>, Vec<u8>>> = OnceLock::new();
CELL.get_or_init(|| {
timed!("load_b30_background", {
let raw_b30_background = image::open(get_assets_dir().join("b30_background.jpg"))
.expect("Could not open b30 background");
raw_b30_background
.resize(
8 * raw_b30_background.width(),
8 * raw_b30_background.height(),
FilterType::Lanczos3,
)
.blur(7.0)
.into_rgb8()
raw_b30_background.blur(7.0).into_rgb8()
})
})
}
pub fn get_count_background() -> &'static ImageBuffer<Rgba<u8>, Vec<u8>> {
static CELL: OnceLock<ImageBuffer<Rgba<u8>, Vec<u8>>> = OnceLock::new();
CELL.get_or_init(|| {
timed!("load_count_backound", {
image::open(get_assets_dir().join("count_background.png"))
.expect("Could not open count background")
.into_rgba8()
})
})
}
pub fn get_score_background() -> &'static ImageBuffer<Rgba<u8>, Vec<u8>> {
static CELL: OnceLock<ImageBuffer<Rgba<u8>, Vec<u8>>> = OnceLock::new();
CELL.get_or_init(|| {
timed!("load_score_background", {
image::open(get_assets_dir().join("score_background.png"))
.expect("Could not open score background")
.into_rgba8()
})
})
}
pub fn get_status_background() -> &'static ImageBuffer<Rgba<u8>, Vec<u8>> {
static CELL: OnceLock<ImageBuffer<Rgba<u8>, Vec<u8>>> = OnceLock::new();
CELL.get_or_init(|| {
timed!("load_status_background", {
image::open(get_assets_dir().join("status_background.png"))
.expect("Could not open status background")
.into_rgba8()
})
})
}
pub fn get_grade_background() -> &'static ImageBuffer<Rgba<u8>, Vec<u8>> {
static CELL: OnceLock<ImageBuffer<Rgba<u8>, Vec<u8>>> = OnceLock::new();
CELL.get_or_init(|| {
timed!("load_grade_background", {
image::open(get_assets_dir().join("grade_background.png"))
.expect("Could not open grade background")
.into_rgba8()
})
})
}
pub fn get_top_backgound() -> &'static ImageBuffer<Rgb<u8>, Vec<u8>> {
static CELL: OnceLock<ImageBuffer<Rgb<u8>, Vec<u8>>> = OnceLock::new();
CELL.get_or_init(|| {
timed!("load_top_background", {
image::open(get_assets_dir().join("top_background.png"))
.expect("Could not open top background")
.into_rgb8()
})
})
}
pub fn get_name_backgound() -> &'static ImageBuffer<Rgb<u8>, Vec<u8>> {
static CELL: OnceLock<ImageBuffer<Rgb<u8>, Vec<u8>>> = OnceLock::new();
CELL.get_or_init(|| {
timed!("load_name_background", {
image::open(get_assets_dir().join("name_background.png"))
.expect("Could not open name background")
.into_rgb8()
})
})
}
pub fn get_ptt_emblem() -> &'static ImageBuffer<Rgba<u8>, Vec<u8>> {
static CELL: OnceLock<ImageBuffer<Rgba<u8>, Vec<u8>>> = OnceLock::new();
CELL.get_or_init(|| {
timed!("load_ptt_emblem", {
image::open(get_assets_dir().join("ptt_emblem.png"))
.expect("Could not open ptt emblem")
.into_rgba8()
})
})
}
pub fn get_difficulty_background(
@ -150,6 +157,7 @@ pub fn get_difficulty_background(
) -> &'static ImageBuffer<Rgba<u8>, Vec<u8>> {
static CELL: OnceLock<[ImageBuffer<Rgba<u8>, Vec<u8>>; 5]> = OnceLock::new();
&CELL.get_or_init(|| {
timed!("load_difficulty_background", {
let assets_dir = get_assets_dir();
Difficulty::DIFFICULTY_SHORTHANDS.map(|shorthand| {
image::open(assets_dir.join(format!("diff_{}.png", shorthand.to_lowercase())))
@ -159,5 +167,6 @@ pub fn get_difficulty_background(
))
.into_rgba8()
})
})
})[difficulty.to_index()]
}

View file

@ -20,7 +20,7 @@ use crate::{
arcaea::{
jacket::BITMAP_IMAGE_SIZE,
play::{compute_b30_ptt, get_best_plays, DbPlay},
score::Score,
score::{Score, ScoringSystem},
},
assert_is_pookie,
assets::{
@ -92,7 +92,7 @@ pub async fn best(
song.title, chart.difficulty
)
})?
.to_play();
.into_play();
let (embed, attachment) = play
.to_embed(
@ -117,11 +117,13 @@ pub async fn best(
#[poise::command(prefix_command, slash_command)]
pub async fn plot(
ctx: Context<'_>,
scoring_system: Option<ScoringSystem>,
#[rest]
#[description = "Name of chart to show (difficulty at the end)"]
name: String,
) -> Result<(), Error> {
let user = get_user!(&ctx);
let scoring_system = scoring_system.unwrap_or_default();
let (song, chart) = guess_song_and_chart(&ctx.data(), &name)?;
@ -152,7 +154,12 @@ pub async fn plot(
let min_time = plays.iter().map(|p| p.created_at).min().unwrap();
let max_time = plays.iter().map(|p| p.created_at).max().unwrap();
let mut min_score = plays.iter().map(|p| p.score).min().unwrap();
let mut min_score = plays
.iter()
.map(|p| p.clone().into_play().score(scoring_system))
.min()
.unwrap()
.0 as i64;
if min_score > 9_900_000 {
min_score = 9_800_000;
@ -202,20 +209,26 @@ pub async fn plot(
.draw()?;
let mut points: Vec<_> = plays
.iter()
.map(|play| (play.created_at.and_utc().timestamp_millis(), play.score))
.into_iter()
.map(|play| {
(
play.created_at.and_utc().timestamp_millis(),
play.into_play().score(scoring_system),
)
})
.collect();
points.sort();
points.dedup();
chart.draw_series(LineSeries::new(points.iter().map(|(t, s)| (*t, *s)), &BLUE))?;
chart.draw_series(LineSeries::new(
points.iter().map(|(t, s)| (*t, s.0 as i64)),
&BLUE,
))?;
chart.draw_series(
points
.iter()
.map(|(t, s)| Circle::new((*t, *s), 3, plotters::style::Color::filled(&BLUE))),
)?;
chart.draw_series(points.iter().map(|(t, s)| {
Circle::new((*t, s.0 as i64), 3, plotters::style::Color::filled(&BLUE))
}))?;
root.present()?;
}
@ -235,6 +248,7 @@ pub async fn plot(
async fn best_plays(
ctx: &Context<'_>,
user: &User,
scoring_system: ScoringSystem,
grid_size: (u32, u32),
require_full: bool,
) -> Result<(), Error> {
@ -245,10 +259,11 @@ async fn best_plays(
&user_ctx.db,
&user_ctx.song_cache,
&user,
scoring_system,
if require_full {
grid_size.0 * grid_size.1
} else {
grid_size.0 * (grid_size.1.max(1) - 1)
grid_size.0 * (grid_size.1.max(1) - 1) + 1
} as usize,
(grid_size.0 * grid_size.1) as usize
)
@ -471,7 +486,7 @@ async fn best_plays(
stroke: Some((Color::BLACK, 1.5)),
drop_shadow: None,
},
&format!("{:0>10}", format!("{}", play.score)),
&format!("{:0>10}", format!("{}", play.score(scoring_system))),
)
})?;
// }}}
@ -494,9 +509,12 @@ async fn best_plays(
// }}}
// {{{ Display status text
with_font(&EXO_FONT, |faces| {
let status = play
.short_status(chart)
.ok_or_else(|| format!("Could not get status for score {}", play.score))?;
let status = play.short_status(chart).ok_or_else(|| {
format!(
"Could not get status for score {}",
play.score(scoring_system)
)
})?;
let x_offset = match status {
'P' => 2,
@ -540,7 +558,7 @@ async fn best_plays(
// }}}
// {{{ Display grade text
with_font(&EXO_FONT, |faces| {
let grade = play.score.grade();
let grade = play.score(scoring_system).grade();
let center = grade_bg_area.center();
drawer.text(
@ -586,7 +604,10 @@ async fn best_plays(
(top_left_center, 94),
faces,
style,
&format!("{:.2}", play.score.play_rating_f32(chart.chart_constant)),
&format!(
"{:.2}",
play.play_rating(scoring_system, chart.chart_constant) as f32 / 100.0
),
)?;
Ok(())
@ -622,7 +643,7 @@ async fn best_plays(
.attachment(CreateAttachment::bytes(out_buffer, "b30.png"))
.content(format!(
"Your ptt is {:.2}",
compute_b30_ptt(&plays) as f32 / 100.0
compute_b30_ptt(scoring_system, &plays) as f32 / 100.0
));
ctx.send(reply).await?;
@ -632,15 +653,34 @@ async fn best_plays(
// {{{ B30
/// Show the 30 best scores
#[poise::command(prefix_command, slash_command, user_cooldown = 30)]
pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
pub async fn b30(ctx: Context<'_>, scoring_system: Option<ScoringSystem>) -> Result<(), Error> {
let user = get_user!(&ctx);
best_plays(&ctx, &user, (5, 6), true).await
best_plays(
&ctx,
&user,
scoring_system.unwrap_or_default(),
(5, 6),
true,
)
.await
}
#[poise::command(prefix_command, slash_command, hide_in_help, global_cooldown = 5)]
pub async fn bany(ctx: Context<'_>, width: u32, height: u32) -> Result<(), Error> {
pub async fn bany(
ctx: Context<'_>,
scoring_system: Option<ScoringSystem>,
width: u32,
height: u32,
) -> Result<(), Error> {
let user = get_user!(&ctx);
assert_is_pookie!(ctx, user);
best_plays(&ctx, &user, (width, height), false).await
best_plays(
&ctx,
&user,
scoring_system.unwrap_or_default(),
(width, height),
false,
)
.await
}
// }}}

View file

@ -333,7 +333,7 @@ impl CharMeasurements {
.ok_or_else(|| "No chars in cache")?;
println!("char '{}', distance {}", best_match.1, best_match.0);
if best_match.0 <= 1.0 {
if best_match.0 <= 0.75 {
result.push(best_match.1);
}
}