This commit is contained in:
Lionarius
2023-04-19 19:22:40 +03:00
parent 193edbb9a7
commit ba041a91f0
8 changed files with 221 additions and 121 deletions

View File

@@ -13,4 +13,8 @@ itertools = "*"
shuttle-secrets = "*" shuttle-secrets = "*"
shuttle-serenity = "*" shuttle-serenity = "*"
shuttle-runtime = "*" shuttle-runtime = "*"
shuttle-shared-db = { version = "*", features = ["postgres"] } shuttle-shared-db = { version = "*", features = ["postgres"] }
rusttype = "*"
image = "*"
imageproc = "*"

Binary file not shown.

Binary file not shown.

BIN
assets/images/teams.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

View File

@@ -1,4 +1,5 @@
use itertools::Itertools; use itertools::Itertools;
use sea_orm::{EntityTrait, QueryFilter, ColumnTrait};
use serenity::async_trait; use serenity::async_trait;
use serenity::builder::{CreateApplicationCommand, CreateEmbed}; use serenity::builder::{CreateApplicationCommand, CreateEmbed};
use serenity::client::Context; use serenity::client::Context;
@@ -13,15 +14,19 @@ use serenity::model::application::interaction::{
use serenity::model::channel::{ChannelType, PermissionOverwrite, PermissionOverwriteType}; use serenity::model::channel::{ChannelType, PermissionOverwrite, PermissionOverwriteType};
use serenity::model::id::{ChannelId, RoleId, UserId}; use serenity::model::id::{ChannelId, RoleId, UserId};
use serenity::model::Permissions; use serenity::model::Permissions;
use serenity::model::prelude::{AttachmentType, Message, GuildId};
use serenity::utils::Colour; use serenity::utils::Colour;
use sqlx::types::chrono::Utc; use sqlx::types::chrono::Utc;
use std::borrow::Cow;
use std::time::Duration; use std::time::Duration;
use crate::bot::commands::MixerCommand; use crate::bot::commands::MixerCommand;
use crate::database::models::lobby::Model; use crate::database::models::lobby::Model;
use crate::database::models::player;
use crate::database::models::role::Role; use crate::database::models::role::Role;
use crate::database::queries::prelude::*; use crate::database::queries::prelude::*;
use crate::database::DatabaseContainer; use crate::database::DatabaseContainer;
use crate::image_manipulation::ImageGeneratorContainer;
use crate::mixer::mixer; use crate::mixer::mixer;
use crate::mixer::player::Player; use crate::mixer::player::Player;
use crate::mixer::team::Team; use crate::mixer::team::Team;
@@ -247,7 +252,10 @@ impl LobbyCommand {
let members = main_channel.members(ctx).await?; let members = main_channel.members(ctx).await?;
let users = members.iter().map(|m| m.user.id).collect::<Vec<UserId>>(); let users = members.iter().map(|m| m.user.id).collect::<Vec<UserId>>();
let players = PlayerQuery::players_by_user_ids(db.connection(), users).await; let players = player::Entity::find()
.filter(player::Column::Id.is_in(1..11))
.all(db.connection()).await.ok();
// let players = PlayerQuery::players_by_user_ids(db.connection(), users).await;
let players = match players { let players = match players {
Some(p) => p, Some(p) => p,
@@ -336,69 +344,85 @@ impl LobbyCommand {
let team1_names = join_all(team1_names).await; let team1_names = join_all(team1_names).await;
let team2_names = join_all(team2_names).await; let team2_names = join_all(team2_names).await;
interaction let image_data = {
.edit_original_interaction_response(ctx, |response| { let data = ctx.data.read().await;
response let image_gen = data.get::<ImageGeneratorContainer>().unwrap();
.content("")
.components(|components| { let player_names = team1_names.into_iter().chain(team2_names.into_iter()).collect_vec();
components.create_action_row(|row| {
row.create_button(|button| { let team1_rank = team1.average_rating(&players);
button let team2_rank = team2.average_rating(&players);
.custom_id("cancel")
.label("Cancel") image_gen.draw_teams(player_names, [team1_rank.value as i32, team2_rank.value as i32])
.style(ButtonStyle::Danger) };
});
row.create_button(|button| { let attachment = AttachmentType::Bytes {
button data: Cow::Owned(image_data),
.custom_id("swap") filename: "teams.png".to_string(),
.label("Swap") };
.disabled(true)
.style(ButtonStyle::Primary) let msg = interaction.channel_id.send_message(ctx, |message| {
}); message
row.create_button(|button| { .add_file(attachment)
button .components(|components| {
.custom_id("start") components.create_action_row(|row| {
.label("Start") row.create_button(|button| {
.style(ButtonStyle::Success) button
}) .custom_id("cancel")
.label("Cancel")
.style(ButtonStyle::Danger)
});
row.create_button(|button| {
button
.custom_id("swap")
.label("Swap")
.disabled(true)
.style(ButtonStyle::Primary)
});
row.create_button(|button| {
button
.custom_id("start")
.label("Start")
.style(ButtonStyle::Success)
}) })
}) })
.embed(|embed| { })
embed }).await?;
.title("Teams")
.fields(vec![
("Team 1", "", false),
("Tank", &team1_names[0], true),
(
"Dps",
&format!("{}\n{}", team1_names[1], team1_names[2]),
true,
),
(
"Support",
&format!("{}\n{}", team1_names[3], team1_names[4]),
true,
),
("Team 2", "", false),
("Tank", &team2_names[0], true),
(
"Dps",
&format!("{}\n{}", team2_names[1], team2_names[2]),
true,
),
(
"Support",
&format!("{}\n{}", team2_names[3], team2_names[4]),
true,
),
])
.colour(Colour::new(0xcfa22f))
})
})
.await
.unwrap();
let msg = interaction.get_interaction_response(ctx).await.unwrap(); interaction.delete_original_interaction_response(ctx).await?;
// interaction
// .edit_original_interaction_response(ctx, |response| {
// response
// .content("")
// .components(|components| {
// components.create_action_row(|row| {
// row.create_button(|button| {
// button
// .custom_id("cancel")
// .label("Cancel")
// .style(ButtonStyle::Danger)
// });
// row.create_button(|button| {
// button
// .custom_id("swap")
// .label("Swap")
// .disabled(true)
// .style(ButtonStyle::Primary)
// });
// row.create_button(|button| {
// button
// .custom_id("start")
// .label("Start")
// .style(ButtonStyle::Success)
// })
// })
// })
// })
// .await
// .unwrap();
// let msg = interaction.get_interaction_response(ctx).await.unwrap();
let collector = msg let collector = msg
.await_component_interactions(ctx) .await_component_interactions(ctx)
.timeout(Duration::from_secs(2 * 60)) .timeout(Duration::from_secs(2 * 60))
@@ -412,23 +436,21 @@ impl LobbyCommand {
if let Some(interaction) = interactions.first() { if let Some(interaction) = interactions.first() {
match interaction.data.custom_id.as_str() { match interaction.data.custom_id.as_str() {
"start" => { "start" => {
self.process_valid_teams_start(ctx, interaction, lobby, &team1, &team2, players) self.process_valid_teams_start(ctx, lobby, &team1, &team2, players, interaction.user.id, msg)
.await? .await?
} }
"cancel" => { "cancel" => {
self.process_valid_teams_cancel(ctx, interaction, &team1, &team2) self.process_valid_teams_cancel(ctx, &team1, &team2, msg)
.await? .await?
} }
"swap" => { "swap" => {
self.process_valid_teams_swap(ctx, interaction, &team1, &team2) self.process_valid_teams_swap(ctx, &team1, &team2, msg)
.await? .await?
} }
_ => {} _ => {}
} }
} else { } else {
interaction msg.delete(ctx).await?;
.delete_original_interaction_response(ctx)
.await?;
} }
Ok(()) Ok(())
@@ -437,11 +459,12 @@ impl LobbyCommand {
async fn process_valid_teams_start( async fn process_valid_teams_start(
&self, &self,
ctx: &Context, ctx: &Context,
interaction: &MessageComponentInteraction,
lobby: Model, lobby: Model,
team1: &Team, team1: &Team,
team2: &Team, team2: &Team,
players: Vec<Player>, players: Vec<Player>,
author: UserId,
mut message: Message
) -> serenity::Result<()> { ) -> serenity::Result<()> {
let main_channel = ChannelId::from(lobby.main_voice_id as u64) let main_channel = ChannelId::from(lobby.main_voice_id as u64)
.to_channel(ctx) .to_channel(ctx)
@@ -480,51 +503,41 @@ impl LobbyCommand {
} }
} }
let embed = interaction.message.embeds.get(0).unwrap(); message.edit(ctx, |message| {
interaction message
.delete_original_interaction_response(ctx) .content(format!("<@{}>", author.0))
.await?; .components(|components| {
components
// create msg without interaction .create_action_row(|row| {
let msg = interaction row.create_button(|button| {
.channel_id button
.send_message(ctx, |message| { .custom_id("win_team1")
message .label("Team 1 win")
.content(format!("<@{}>", interaction.user.id.0)) .style(ButtonStyle::Success)
.set_embeds(vec![CreateEmbed::from(embed.clone())]) })
.components(|components| { .create_button(|button| {
components button
.create_action_row(|row| { .custom_id("draw")
row.create_button(|button| { .label("Draw")
button .style(ButtonStyle::Secondary)
.custom_id("win_team1") })
.label("Team 1 win") .create_button(|button| {
.style(ButtonStyle::Success) button
}) .custom_id("win_team2")
.create_button(|button| { .label("Team 2 win")
button .style(ButtonStyle::Success)
.custom_id("draw") })
.label("Draw") })
.style(ButtonStyle::Secondary) .create_action_row(|row| {
}) row.create_button(|button| {
.create_button(|button| { button
button .custom_id("cancel")
.custom_id("win_team2") .label("Cancel game")
.label("Team 2 win") .style(ButtonStyle::Danger)
.style(ButtonStyle::Success) })
})
})
.create_action_row(|row| {
row.create_button(|button| {
button
.custom_id("cancel")
.label("Cancel game")
.style(ButtonStyle::Danger)
})
})
}) })
}) })
.await?; }).await?;
// let msg = interaction.edit_original_interaction_response(ctx, |message| { // let msg = interaction.edit_original_interaction_response(ctx, |message| {
// message.components(|components| { // message.components(|components| {
@@ -552,12 +565,12 @@ impl LobbyCommand {
// }) // })
// }).await?; // }).await?;
let collector = msg let collector = message
.await_component_interactions(ctx) .await_component_interactions(ctx)
.timeout(Duration::from_secs(30 * 60)) .timeout(Duration::from_secs(30 * 60))
.guild_id(interaction.guild_id.unwrap()) .guild_id(GuildId::from(lobby.guild_id as u64))
.channel_id(interaction.channel_id) .channel_id(message.channel_id)
.author_id(interaction.user.id) .author_id(author)
.collect_limit(1) .collect_limit(1)
.build(); .build();
@@ -569,7 +582,7 @@ impl LobbyCommand {
"draw" => score = 0.5, "draw" => score = 0.5,
"win_team2" => score = 0.0, "win_team2" => score = 0.0,
"cancel" => { "cancel" => {
return msg.delete(ctx).await; return message.delete(ctx).await;
// return interaction.delete_original_interaction_response(ctx).await; // return interaction.delete_original_interaction_response(ctx).await;
} }
_ => {} _ => {}
@@ -646,25 +659,25 @@ impl LobbyCommand {
drop(data); drop(data);
} }
msg.delete(ctx).await message.delete(ctx).await
} }
async fn process_valid_teams_cancel( async fn process_valid_teams_cancel(
&self, &self,
ctx: &Context, ctx: &Context,
interaction: &MessageComponentInteraction,
team1: &Team, team1: &Team,
team2: &Team, team2: &Team,
message: Message
) -> serenity::Result<()> { ) -> serenity::Result<()> {
interaction.delete_original_interaction_response(ctx).await message.delete(ctx).await
} }
async fn process_valid_teams_swap( async fn process_valid_teams_swap(
&self, &self,
ctx: &Context, ctx: &Context,
interaction: &MessageComponentInteraction,
team1: &Team, team1: &Team,
team2: &Team, team2: &Team,
message: Message
) -> serenity::Result<()> { ) -> serenity::Result<()> {
Ok(()) Ok(())
} }

73
src/image_manipulation.rs Normal file
View File

@@ -0,0 +1,73 @@
use std::{sync::Arc, io::Read};
use image::EncodableLayout;
use imageproc::drawing::text_size;
use itertools::Itertools;
use rusttype::{Font, Scale};
use serenity::prelude::TypeMapKey;
pub struct ImageGenerator<'a> {
pub player_font: Font<'a>,
pub text_font: Font<'a>,
pub teams_image: image::ImageBuffer<image::Rgb<u8>, Vec<u8>>
}
impl<'a> ImageGenerator<'a> {
pub fn draw_teams(&self, player_names: Vec<String>, teams_rating: [i32; 2]) -> Vec<u8> {
let mut image = self.teams_image.clone();
let player_text_scale = Scale::uniform(60.0);
for i in 0..2 {
for j in 0..5 {
let player_name = match player_names.get(i * 5 + j) {
Some(name) => name,
None => "Unknown"
};
let size = text_size(player_text_scale, &self.player_font, player_name);
let scale = if size.0 > 340 {
Scale::uniform(340.0 / size.0 as f32 * player_text_scale.x)
} else {
player_text_scale
};
let size = text_size(scale, &self.player_font, player_name);
let x: i32 = 83 + 540 * i as i32 - 2;
let y: i32 = 182 + 70 * j as i32 - size.1 + ((size.1 as f32 * 1.0 / 5.0) / 10.0) as i32;
imageproc::drawing::draw_text_mut(
&mut image,
image::Rgb([255, 255, 255]),
x,
y,
scale,
&self.player_font,
player_name
);
}
}
let rating_text_scale = Scale::uniform(86.5);
for i in 0..2 {
let rating = teams_rating[i].to_string();
let size = text_size(rating_text_scale, &self.player_font, &rating);
imageproc::drawing::draw_text_mut(
&mut image,
image::Rgb([255, 255, 255]),
365 - size.0 / 2 + 540 * i as i32,
100 - size.1,
rating_text_scale,
&self.text_font,
&rating,
);
}
image.as_bytes().iter().cloned().collect_vec()
}
}
pub struct ImageGeneratorContainer;
impl TypeMapKey for ImageGeneratorContainer {
type Value = Arc<ImageGenerator<'static>>;
}

View File

@@ -2,8 +2,11 @@ mod algorithm;
mod bot; mod bot;
mod database; mod database;
mod mixer; mod mixer;
mod image_manipulation;
use bot::commands::creator::CreatorCommand; use bot::commands::creator::CreatorCommand;
use image_manipulation::{ImageGenerator, ImageGeneratorContainer};
use rusttype::Font;
use serenity::model::prelude::UserId; use serenity::model::prelude::UserId;
use serenity::prelude::{GatewayIntents, TypeMapKey}; use serenity::prelude::{GatewayIntents, TypeMapKey};
use serenity::Client; use serenity::Client;
@@ -74,6 +77,13 @@ async fn serenity(
.unwrap(), .unwrap(),
); );
data.insert::<CreatorContainer>(Arc::new(creator)); data.insert::<CreatorContainer>(Arc::new(creator));
let image_generator = ImageGenerator {
player_font: Font::try_from_bytes(include_bytes!("../assets/fonts/big-noodle-too-oblique.ttf")).unwrap(),
text_font: Font::try_from_bytes(include_bytes!("../assets/fonts/big-noodle-titling.ttf")).unwrap(),
teams_image: image::load_from_memory(include_bytes!("../assets/images/teams.png")).unwrap().to_rgb8()
};
data.insert::<ImageGeneratorContainer>(Arc::new(image_generator));
} }
let shard_manager = client.shard_manager.clone(); let shard_manager = client.shard_manager.clone();