1
Fork 0

Way too many changes

Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
prescientmoon 2024-08-12 03:13:41 +02:00
parent 48c1f74f93
commit c035ecbb52
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
13 changed files with 348 additions and 157 deletions
src/commands

View file

@ -1,7 +1,7 @@
use std::io::Cursor;
use chrono::DateTime;
use image::{ImageBuffer, Rgb};
use image::{DynamicImage, ImageBuffer, Rgb};
use plotters::{
backend::{BitMapBackend, PixelFormat, RGBPixel},
chart::{ChartBuilder, LabelAreaPosition},
@ -19,20 +19,22 @@ use sqlx::query_as;
use crate::{
arcaea::{
jacket::BITMAP_IMAGE_SIZE,
play::{compute_b30_ptt, get_b30_plays, DbPlay},
play::{compute_b30_ptt, get_best_plays, DbPlay},
score::Score,
},
assert_is_pookie,
assets::{
get_b30_background, get_count_background, get_difficulty_background, get_grade_background,
get_name_backgound, get_ptt_emblem, get_score_background, get_status_background,
get_top_backgound, EXO_FONT,
get_top_backgound, with_font, EXO_FONT,
},
bitmap::{Align, BitmapCanvas, Color, LayoutDrawer, LayoutManager, Rect},
context::{Context, Error},
get_user,
logs::debug_image_log,
recognition::fuzzy_song_name::guess_song_and_chart,
reply_errors,
user::discord_it_to_discord_user,
user::{discord_it_to_discord_user, User},
};
// {{{ Stats
@ -40,7 +42,7 @@ use crate::{
#[poise::command(
prefix_command,
slash_command,
subcommands("chart", "b30"),
subcommands("chart", "b30", "bany"),
subcommand_required
)]
pub async fn stats(_ctx: Context<'_>) -> Result<(), Error> {
@ -229,15 +231,28 @@ pub async fn plot(
Ok(())
}
// }}}
// {{{ B30
/// Show the 30 best scores
#[poise::command(prefix_command, slash_command)]
pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
let user = get_user!(&ctx);
// {{{ Render best plays
async fn best_plays(
ctx: &Context<'_>,
user: &User,
grid_size: (u32, u32),
require_full: bool,
) -> Result<(), Error> {
let user_ctx = ctx.data();
let plays = reply_errors!(
ctx,
get_b30_plays(&user_ctx.db, &user_ctx.song_cache, &user).await?
get_best_plays(
&user_ctx.db,
&user_ctx.song_cache,
&user,
if require_full {
grid_size.0 * grid_size.1
} else {
grid_size.0 * (grid_size.1.max(1) - 1)
} as usize,
(grid_size.0 * grid_size.1) as usize
)
.await?
);
// {{{ Layout
@ -258,7 +273,8 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
let bottom_in_area = layout.margin_xy(bottom_area, -20, -7);
let item_area = layout.glue_horizontally(top_area, bottom_area);
let item_with_margin = layout.margin_xy(item_area, 22, 17);
let (item_grid, item_origins) = layout.repeated_evenly(item_with_margin, (5, 6));
let (item_grid, item_origins) =
layout.repeated_evenly(item_with_margin, (grid_size.0, grid_size.1));
let root = layout.margin_uniform(item_grid, 30);
// }}}
// {{{ Rendering prep
@ -271,15 +287,21 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
// {{{ Render background
let bg = get_b30_background();
drawer.blit_rbg(
let scale = (drawer.layout.width(root) as f32 / bg.width() as f32)
.max(drawer.layout.height(root) as f32 / bg.height() as f32)
.max(1.0)
.ceil() as u32;
drawer.blit_rbg_scaled_up(
root,
// Align the center of the image with the center of the root
Rect::from_image(bg).align(
Rect::from_image(bg).scaled(scale).align(
(Align::Center, Align::Center),
drawer.layout.lookup(root).center(),
),
bg.dimensions(),
bg.as_raw(),
scale,
);
// }}}
@ -291,7 +313,11 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
let top_bg = get_top_backgound();
drawer.blit_rbg(top_area, (0, 0), top_bg.dimensions(), top_bg);
let (play, song, chart) = &plays[i];
let (play, song, chart) = if let Some(item) = plays.get(i) {
item
} else {
break;
};
// {{{ Display index
let bg = get_count_background();
@ -299,12 +325,11 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
// Draw background
drawer.blit_rbga(item_area, (-8, jacket_margin as i32), bg.dimensions(), bg);
EXO_FONT.with_borrow_mut(|font| {
with_font(&EXO_FONT, |faces| {
drawer.text(
item_area,
(bg_center.0 - 12, bg_center.1 - 3 + jacket_margin),
font,
faces,
crate::bitmap::TextStyle {
size: 25,
weight: Some(800),
@ -323,7 +348,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
drawer.blit_rbg(bottom_area, (0, 0), bg.dimensions(), bg.as_raw());
// Draw text
EXO_FONT.with_borrow_mut(|font| {
with_font(&EXO_FONT, |faces| {
let initial_size = 24;
let mut style = crate::bitmap::TextStyle {
size: initial_size,
@ -334,9 +359,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
drop_shadow: None,
};
while drawer
.canvas
.plan_text_rendering((0, 0), font, style, &song.title)?
while BitmapCanvas::plan_text_rendering((0, 0), faces, style, &song.title)?
.1
.width >= drawer.layout.width(bottom_in_area)
{
@ -350,7 +373,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
drawer.text(
bottom_in_area,
(0, drawer.layout.height(bottom_in_area) as i32 / 2),
font,
faces,
style,
&song.title,
)
@ -397,11 +420,11 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
let diff_area_center = diff_bg_area.center();
EXO_FONT.with_borrow_mut(|font| {
with_font(&EXO_FONT, |faces| {
drawer.text(
jacket_with_border,
(diff_area_center.0 + x_offset, diff_area_center.1),
font,
faces,
crate::bitmap::TextStyle {
size: 25,
weight: Some(600),
@ -432,14 +455,14 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
);
// }}}
// {{{ Display score text
EXO_FONT.with_borrow_mut(|font| {
with_font(&EXO_FONT, |faces| {
drawer.text(
jacket_area,
(
score_bg_pos.0 + 5,
score_bg_pos.1 + score_bg.height() as i32 / 2,
),
font,
faces,
crate::bitmap::TextStyle {
size: 23,
weight: Some(800),
@ -470,7 +493,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
);
// }}}
// {{{ Display status text
EXO_FONT.with_borrow_mut(|font| {
with_font(&EXO_FONT, |faces| {
let status = play
.short_status(chart)
.ok_or_else(|| format!("Could not get status for score {}", play.score))?;
@ -487,7 +510,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
drawer.text(
jacket_area,
(center.0 + x_offset, center.1),
font,
faces,
crate::bitmap::TextStyle {
size: if status == 'M' { 30 } else { 36 },
weight: Some(if status == 'M' { 800 } else { 500 }),
@ -516,14 +539,14 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
);
// }}}
// {{{ Display grade text
EXO_FONT.with_borrow_mut(|font| {
with_font(&EXO_FONT, |faces| {
let grade = play.score.grade();
let center = grade_bg_area.center();
drawer.text(
top_left_area,
(center.0, center.1),
font,
faces,
crate::bitmap::TextStyle {
size: 30,
weight: Some(650),
@ -537,7 +560,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
})?;
// }}}
// {{{ Display rating text
EXO_FONT.with_borrow_mut(|font| -> Result<(), Error> {
with_font(&EXO_FONT, |faces| -> Result<(), Error> {
let mut style = crate::bitmap::TextStyle {
size: 12,
weight: Some(600),
@ -550,7 +573,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
drawer.text(
top_left_area,
(top_left_center, 73),
font,
faces,
style,
"POTENTIAL",
)?;
@ -561,7 +584,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
drawer.text(
top_left_area,
(top_left_center, 94),
font,
faces,
style,
&format!("{:.2}", play.score.play_rating_f32(chart.chart_constant)),
)?;
@ -582,11 +605,18 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
}
let mut out_buffer = Vec::new();
let image: ImageBuffer<Rgb<u8>, _> =
ImageBuffer::from_raw(width, height, drawer.canvas.buffer).unwrap();
let mut image = DynamicImage::ImageRgb8(
ImageBuffer::from_raw(width, height, drawer.canvas.buffer.into_vec()).unwrap(),
);
debug_image_log(&image)?;
if image.height() > 4096 {
image = image.resize(4096, 4096, image::imageops::FilterType::Nearest);
}
let mut cursor = Cursor::new(&mut out_buffer);
image.write_to(&mut cursor, image::ImageFormat::Png)?;
image.write_to(&mut cursor, image::ImageFormat::WebP)?;
let reply = CreateReply::default()
.attachment(CreateAttachment::bytes(out_buffer, "b30.png"))
@ -599,3 +629,18 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
// }}}
// {{{ B30
/// Show the 30 best scores
#[poise::command(prefix_command, slash_command, user_cooldown = 30)]
pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
let user = get_user!(&ctx);
best_plays(&ctx, &user, (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> {
let user = get_user!(&ctx);
assert_is_pookie!(ctx, user);
best_plays(&ctx, &user, (width, height), false).await
}
// }}}

View file

@ -16,6 +16,17 @@ macro_rules! get_user {
}};
}
#[macro_export]
macro_rules! assert_is_pookie {
($ctx:expr, $user:expr) => {{
if !$user.is_pookie {
$ctx.reply("This feature is reserved for my pookies. Sowwy :3")
.await?;
return Ok(());
}
}};
}
#[macro_export]
macro_rules! reply_errors {
($ctx:expr, $value:expr) => {