Merge pull request #1 from Suiranoil/dev
Use image instead of embed for displaying teams
This commit is contained in:
@@ -14,3 +14,7 @@ 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 = "*"
|
||||||
BIN
assets/fonts/big-noodle-titling.ttf
Normal file
BIN
assets/fonts/big-noodle-titling.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/big-noodle-too-oblique.ttf
Normal file
BIN
assets/fonts/big-noodle-too-oblique.ttf
Normal file
Binary file not shown.
BIN
assets/images/teams.png
Normal file
BIN
assets/images/teams.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 385 KiB |
@@ -1,20 +1,20 @@
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serenity::async_trait;
|
use serenity::async_trait;
|
||||||
use serenity::builder::{CreateApplicationCommand, CreateEmbed};
|
use serenity::builder::CreateApplicationCommand;
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
use serenity::futures::future::join_all;
|
use serenity::futures::future::join_all;
|
||||||
use serenity::futures::StreamExt;
|
use serenity::futures::StreamExt;
|
||||||
use serenity::model::application::command::CommandOptionType;
|
use serenity::model::application::command::CommandOptionType;
|
||||||
use serenity::model::application::component::ButtonStyle;
|
use serenity::model::application::component::ButtonStyle;
|
||||||
use serenity::model::application::interaction::message_component::MessageComponentInteraction;
|
|
||||||
use serenity::model::application::interaction::{
|
use serenity::model::application::interaction::{
|
||||||
application_command::ApplicationCommandInteraction, InteractionResponseType,
|
application_command::ApplicationCommandInteraction, InteractionResponseType,
|
||||||
};
|
};
|
||||||
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::prelude::{AttachmentType, GuildId, Message};
|
||||||
use serenity::model::Permissions;
|
use serenity::model::Permissions;
|
||||||
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;
|
||||||
@@ -22,6 +22,7 @@ use crate::database::models::lobby::Model;
|
|||||||
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;
|
||||||
@@ -246,7 +247,6 @@ 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 = PlayerQuery::players_by_user_ids(db.connection(), users).await;
|
||||||
|
|
||||||
let players = match players {
|
let players = match players {
|
||||||
@@ -336,10 +336,35 @@ 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("")
|
|
||||||
|
let player_names = team1_names
|
||||||
|
.into_iter()
|
||||||
|
.chain(team2_names.into_iter())
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
let team1_rank = team1.average_rating(&players);
|
||||||
|
let team2_rank = team2.average_rating(&players);
|
||||||
|
|
||||||
|
image_gen.draw_teams_to_png(
|
||||||
|
player_names,
|
||||||
|
[team1_rank.value as i32, team2_rank.value as i32],
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let attachment = AttachmentType::Bytes {
|
||||||
|
data: Cow::Owned(image_data),
|
||||||
|
filename: "teams.png".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg = interaction
|
||||||
|
.channel_id
|
||||||
|
.send_message(ctx, |message| {
|
||||||
|
message
|
||||||
|
.content(format!("<@{}>", interaction.user.id.0))
|
||||||
|
.add_file(attachment)
|
||||||
.components(|components| {
|
.components(|components| {
|
||||||
components.create_action_row(|row| {
|
components.create_action_row(|row| {
|
||||||
row.create_button(|button| {
|
row.create_button(|button| {
|
||||||
@@ -363,42 +388,45 @@ impl LobbyCommand {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.embed(|embed| {
|
|
||||||
embed
|
|
||||||
.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?;
|
||||||
.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(10 * 60))
|
.timeout(Duration::from_secs(10 * 60))
|
||||||
@@ -412,23 +440,29 @@ 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 +471,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)
|
||||||
@@ -470,29 +505,19 @@ impl LobbyCommand {
|
|||||||
.iter()
|
.iter()
|
||||||
.any(|(_, i)| *i == index && index.is_some())
|
.any(|(_, i)| *i == index && index.is_some())
|
||||||
{
|
{
|
||||||
member.move_to_voice_channel(ctx, red_channel.id).await?;
|
member.move_to_voice_channel(ctx, blue_channel.id).await?;
|
||||||
} else if team2
|
} else if team2
|
||||||
.players
|
.players
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(_, i)| *i == index && index.is_some())
|
.any(|(_, i)| *i == index && index.is_some())
|
||||||
{
|
{
|
||||||
member.move_to_voice_channel(ctx, blue_channel.id).await?;
|
member.move_to_voice_channel(ctx, red_channel.id).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let embed = interaction.message.embeds.get(0).unwrap();
|
|
||||||
interaction
|
|
||||||
.delete_original_interaction_response(ctx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// create msg without interaction
|
|
||||||
let msg = interaction
|
|
||||||
.channel_id
|
|
||||||
.send_message(ctx, |message| {
|
|
||||||
message
|
message
|
||||||
.content(format!("<@{}>", interaction.user.id.0))
|
.edit(ctx, |message| {
|
||||||
.set_embeds(vec![CreateEmbed::from(embed.clone())])
|
message.components(|components| {
|
||||||
.components(|components| {
|
|
||||||
components
|
components
|
||||||
.create_action_row(|row| {
|
.create_action_row(|row| {
|
||||||
row.create_button(|button| {
|
row.create_button(|button| {
|
||||||
@@ -552,12 +577,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 +594,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 +671,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(())
|
||||||
}
|
}
|
||||||
|
|||||||
82
src/image_manipulation.rs
Normal file
82
src/image_manipulation.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use image::{codecs::png, ImageEncoder};
|
||||||
|
use imageproc::drawing::text_size;
|
||||||
|
use rusttype::{Font, Scale};
|
||||||
|
use serenity::prelude::TypeMapKey;
|
||||||
|
use std::{io::BufWriter, sync::Arc};
|
||||||
|
|
||||||
|
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_to_png(&self, player_names: Vec<String>, teams_rating: [i32; 2]) -> Vec<u8> {
|
||||||
|
let mut image: image::ImageBuffer<image::Rgb<u8>, Vec<u8>> = 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]),
|
||||||
|
370 - size.0 / 2 + 540 * i as i32,
|
||||||
|
100 - size.1,
|
||||||
|
rating_text_scale,
|
||||||
|
&self.text_font,
|
||||||
|
&rating,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = BufWriter::new(Vec::new());
|
||||||
|
png::PngEncoder::new(&mut buf)
|
||||||
|
.write_image(
|
||||||
|
image.as_raw(),
|
||||||
|
image.width(),
|
||||||
|
image.height(),
|
||||||
|
image::ColorType::Rgb8,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
buf.into_inner().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ImageGeneratorContainer;
|
||||||
|
|
||||||
|
impl TypeMapKey for ImageGeneratorContainer {
|
||||||
|
type Value = Arc<ImageGenerator<'static>>;
|
||||||
|
}
|
||||||
10
src/main.rs
10
src/main.rs
@@ -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();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::database::models::role::Role;
|
|||||||
use crate::mixer::player::Player;
|
use crate::mixer::player::Player;
|
||||||
use crate::mixer::team::Team;
|
use crate::mixer::team::Team;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct PlayerRoleEntry {
|
struct PlayerRoleEntry {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
@@ -64,7 +65,7 @@ pub fn mix_players(players: &[Player], slots: Vec<Role>) -> Option<(Team, Team)>
|
|||||||
let mut best_team2 = None;
|
let mut best_team2 = None;
|
||||||
let mut best_diff = None;
|
let mut best_diff = None;
|
||||||
|
|
||||||
let threshold = 300.0;
|
let threshold = 150.0;
|
||||||
|
|
||||||
// this is awful, but it works
|
// this is awful, but it works
|
||||||
for tank1_combo in &tank_combos {
|
for tank1_combo in &tank_combos {
|
||||||
@@ -192,7 +193,7 @@ pub fn mix_players(players: &[Player], slots: Vec<Role>) -> Option<(Team, Team)>
|
|||||||
.abs();
|
.abs();
|
||||||
let diff = diff_rating;
|
let diff = diff_rating;
|
||||||
|
|
||||||
if diff + threshold < best_diff.unwrap_or(f32::MAX) {
|
if diff < best_diff.unwrap_or(f32::MAX) {
|
||||||
if diff < threshold {
|
if diff < threshold {
|
||||||
return Some((team1, team2));
|
return Some((team1, team2));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user