Skip to content

Commit

Permalink
better leaderboard with multiple messages and better ranking
Browse files Browse the repository at this point in the history
  • Loading branch information
99oblivius committed Jul 26, 2024
1 parent 28e3b14 commit b68d606
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 61 deletions.
23 changes: 4 additions & 19 deletions src/cogs/settings/cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from config import *
from utils.logger import Logger as log
from utils.models import BotRegions, BotSettings, MMBotRanks, Platform, MMBotUsers, MMBotUserSummaryStats
from utils.statistics import create_leaderboard_embed
from utils.statistics import update_leaderboard
from views.register import RegistryButtonView


Expand Down Expand Up @@ -237,24 +237,9 @@ async def set_ranks(self, interaction: nextcord.Interaction,

@settings.subcommand(name="set_leaderboard", description="Set the channel and leaderboard message")
async def set_leaderboard(self, interaction: nextcord.Interaction):
settings = await self.bot.store.get_settings(interaction.guild.id)
channel = interaction.guild.get_channel(settings.leaderboard_channel)
if channel:
try:
old_message = await channel.fetch_message(settings.leaderboard_message)
await old_message.delete()
except nextcord.NotFound: pass

data = await self.bot.store.get_leaderboard(interaction.guild.id)
ranks = await self.bot.store.get_ranks(interaction.guild.id)
previous_data = await self.bot.store.get_last_mmr_for_users(interaction.guild.id)
embed = create_leaderboard_embed(interaction.guild, data, previous_data, ranks)
msg = await interaction.channel.send(embed=embed)
await self.bot.store.update(BotSettings,
guild_id=interaction.guild.id,
leaderboard_channel=interaction.channel.id,
leaderboard_message=msg.id)
await interaction.response.send_message(
await interaction.response.defer(ephemeral=True)
await update_leaderboard(self.bot.store, interaction.guild)
await interaction.followup.send(
f"Match Making Leaderboard set", ephemeral=True)

@nextcord.slash_command(name="change_mmr", description="Change a member's Match Making Rating", guild_ids=[GUILD_ID])
Expand Down
19 changes: 10 additions & 9 deletions src/matches/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from utils.logger import Logger as log, VariableLog
from utils.models import *
from utils.utils import format_duration, format_mm_attendance, generate_score_image
from utils.statistics import create_leaderboard_embed
from utils.statistics import update_leaderboard
from views.match.accept import AcceptView
from views.match.banning import BanView, ChosenBansView
from views.match.map_pick import ChosenMapView, MapPickView
Expand Down Expand Up @@ -908,20 +908,21 @@ def server_score(server_region):
await self.bot.rcon_manager.comp_mode(serveraddr, state=False, retry_attempts=1)
embed = nextcord.Embed(title="The match will terminate in 10 seconds", color=VALORS_THEME1)

channel = guild.get_channel(settings.leaderboard_channel)
try:
await self.match_thread.send(embed=embed)
except AttributeError:
pass
# channel = guild.get_channel(settings.leaderboard_channel)

guild = self.bot.get_guild(self.guild_id)
message = await channel.fetch_message(settings.leaderboard_message)
ranks = await self.bot.store.get_ranks(guild.id)
# guild = self.bot.get_guild(self.guild_id)
# message = await channel.fetch_message(settings.leaderboard_message)
# ranks = await self.bot.store.get_ranks(guild.id)

data = await self.bot.store.get_leaderboard(guild.id)
previous_data = await self.bot.store.get_last_mmr_for_users(guild.id)
embed = create_leaderboard_embed(guild, data, previous_data, ranks)
asyncio.create_task(message.edit(embed=embed))
# data = await self.bot.store.get_leaderboard(guild.id)
# previous_data = await self.bot.store.get_last_mmr_for_users(guild.id)
# embeds = create_leaderboard_embeds(guild, data, previous_data, ranks)
# asyncio.create_task(message.edit(embeds=embeds))
asyncio.create_task(update_leaderboard(self.bot.store, guild))
await asyncio.sleep(10)
# match_thread
try:
Expand Down
47 changes: 46 additions & 1 deletion src/utils/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,52 @@ async def get_last_mmr_for_users(self, guild_id: int) -> Dict[int, int]:
.where(MMBotUserMatchStats.guild_id == guild_id))

result = await session.execute(query)
return {row.user_id: row.mmr_before for row in result}
return { row.user_id: row.mmr_before for row in result }

async def get_leaderboard_with_previous_mmr(self, guild_id: int) -> List[Dict[str, Any]]:
async with self._session_maker() as session:
subquery = (
select(
MMBotUserMatchStats.user_id,
func.max(MMBotUserMatchStats.match_id).label('latest_match_id'))
.where(
MMBotUserMatchStats.guild_id == guild_id,
MMBotUserMatchStats.abandoned == False)
.group_by(MMBotUserMatchStats.user_id)
.subquery())

query = (
select(
MMBotUserSummaryStats,
MMBotUserMatchStats.mmr_before.label('previous_mmr'))
.join(
subquery,
(MMBotUserSummaryStats.user_id == subquery.c.user_id))
.join(
MMBotUserMatchStats,
(MMBotUserMatchStats.user_id == subquery.c.user_id) &
(MMBotUserMatchStats.match_id == subquery.c.latest_match_id))
.where(
MMBotUserSummaryStats.guild_id == guild_id,
MMBotUserSummaryStats.games > 0)
.order_by(desc(MMBotUserSummaryStats.mmr)))

result = await session.execute(query)
return [
{
"user_id": row.MMBotUserSummaryStats.user_id,
"mmr": row.MMBotUserSummaryStats.mmr,
"previous_mmr": row.previous_mmr,
"games": row.MMBotUserSummaryStats.games,
"wins": row.MMBotUserSummaryStats.wins,
"win_rate": row.MMBotUserSummaryStats.wins / row.MMBotUserSummaryStats.games if row.MMBotUserSummaryStats.games > 0 else 0,
"avg_kills": row.MMBotUserSummaryStats.total_kills / row.MMBotUserSummaryStats.games if row.MMBotUserSummaryStats.games > 0 else 0,
"avg_deaths": row.MMBotUserSummaryStats.total_deaths / row.MMBotUserSummaryStats.games if row.MMBotUserSummaryStats.games > 0 else 0,
"avg_assists": row.MMBotUserSummaryStats.total_assists / row.MMBotUserSummaryStats.games if row.MMBotUserSummaryStats.games > 0 else 0,
"avg_score": row.MMBotUserSummaryStats.total_score / row.MMBotUserSummaryStats.games if row.MMBotUserSummaryStats.games > 0 else 0,
}
for row in result
]

async def get_user_pick_preferences(self, guild_id: int, user_id: int) -> Dict[str, Dict[str, int]]:
async with self._session_maker() as session:
Expand Down
124 changes: 92 additions & 32 deletions src/utils/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@
from concurrent.futures import ThreadPoolExecutor

import nextcord
from nextcord import Embed, Guild, User, Member
from nextcord import Embed, Guild, User, Member, TextChannel, Interaction
import pandas as pd
import numpy as np
from scipy.fft import fft, ifft
import plotly.graph_objects as go
from plotly.subplots import make_subplots

from config import VALORS_THEME1, VALORS_THEME1_1, VALORS_THEME1_2, VALORS_THEME2, REGION_TIMEZONES
from utils.models import MMBotRanks, MMBotUserMatchStats
from utils.models import MMBotRanks, MMBotUserMatchStats, BotSettings
from utils.utils import get_rank_color, get_rank_role

async def create_graph_async(loop, graph_type, match_stats, ranks=None, preferences=None, play_periods=None, user_region=None):
Expand Down Expand Up @@ -406,30 +406,42 @@ def create_stats_embed(guild: Guild, user: User | Member, leaderboard_data, summ

return embed

def create_leaderboard_embed(guild: Guild, leaderboard_data: List[Dict[str, Any]], last_mmr: Dict[int, int], ranks: List[MMBotRanks]) -> Embed:
field_count = 0

async def create_leaderboard_embed(guild: Guild, leaderboard_data: List[Dict[str, Any]], ranks: List[MMBotRanks], start_rank: int) -> Tuple[Embed, int]:
valid_scores = [player['avg_score'] for player in leaderboard_data if guild.get_member(player['user_id'])]
avg_score = sum(valid_scores) / len(valid_scores) if valid_scores else 0
embed = Embed(title="Match Making Leaderboard", description=f"{len(valid_scores)} ranking players\nK/D/A and Score are mean averages")

embed = Embed()
if start_rank == 1:
embed.title = "Match Making Leaderboard"
embed.set_footer(text="K/D/A and Score are mean averages")

ranked_mmr = ((n, user_id) for n, (user_id, _) in enumerate(sorted(last_mmr.items(), key=lambda x: x[1], reverse=True), 1))
previous_positions = { user_id: n for n, user_id in ranked_mmr if guild.get_member(user_id) }
field_content = ""
players_added = 0

ranking_position = 0
for player in leaderboard_data:
if field_count > 25: break
previous_positions = { player['user_id']: i + 1 for i, player in enumerate(sorted(leaderboard_data, key=lambda x: x['previous_mmr'] or 0, reverse=True)) }

for ranking_position in range(start_rank, start_rank + 50):
if ranking_position > len(leaderboard_data):
break

player = leaderboard_data[ranking_position - 1]
member = guild.get_member(player['user_id'])
if member is None: continue

ranking_position += 1
if member is None:
continue

players_added += 1

previous_position = previous_positions.get(player['user_id'], None)
if previous_position is None: rank_change = "\u001b[35m·"
elif ranking_position < previous_position: rank_change = "\u001b[32m↑"
elif ranking_position > previous_position: rank_change = "\u001b[31m↓"
else: rank_change = "\u001b[36m|"
if previous_position is None:
rank_change = "\u001b[35m·"
else:
position_change = previous_position - ranking_position
if position_change > 0:
rank_change = f"\u001b[32m↑"
elif position_change < 0:
rank_change = f"\u001b[31m↓"
else:
rank_change = f"\u001b[36m|"

name = member.display_name[:11] + '…' if len(member.display_name) > 12 else member.display_name
name = name.ljust(12)
Expand All @@ -441,32 +453,80 @@ def create_leaderboard_embed(guild: Guild, leaderboard_data: List[Dict[str, Any]
a = floor(player['avg_assists'])
score = floor(player['avg_score'])

# Color coding
rank_color = get_rank_color(guild, float(mmr), ranks)
win_rate_color = "\u001b[32m" if win_rate > 60 else "\u001b[31m" if win_rate < 40 else "\u001b[0m"
score_color = "\u001b[32m" if score > avg_score else "\u001b[31m"

kda = f"{k}/{d}/{a}".rjust(8)
if k < d:
kda_formatted = kda.replace(str(d), f"\u001b[31m{d}\u001b[0m", 1)
else:
kda_formatted = kda
kda_formatted = kda.replace(str(d), f"\u001b[31m{d}\u001b[0m", 1) if k < d else kda

row = f"{ranking_position:3} {rank_change}\u001b[0m {name} | {rank_color}{mmr}\u001b[0m | {games} | {win_rate_color}{win_rate:3}%\u001b[0m | {kda_formatted} | {score_color}{score:2}\u001b[0m\n"

if ranking_position % 5 == 1:
if ranking_position == 1:
if players_added % 5 == 1:
if players_added == 1:
header = " R | Player | MMR | G | W% | K/D/A | S "
field_content = f"```ansi\n\u001b[1m{header}\u001b[0m\n{'─' * len(header)}\n{row}"
else:
field_content += "```"
embed.add_field(name="\u200b", value=field_content, inline=False)
embed.add_field(name="\u200b", value=field_content + "```", inline=False)
field_content = f"```ansi\n{row}"
field_count += 1
else:
field_content += row

if field_content:
field_content += "```"
embed.add_field(name="\u200b", value=field_content, inline=False)

return embed
embed.add_field(name="\u200b", value=field_content + "```", inline=False)

return embed, ranking_position + 1

async def update_leaderboard(store, guild: Guild):
settings = await store.get_settings(guild.id)
channel = guild.get_channel(settings.leaderboard_channel)
if not isinstance(channel, TextChannel):
return

data = await store.get_leaderboard_with_previous_mmr(guild.id)
ranks = await store.get_ranks(guild.id)

# Remove players who have left the server
data = [player for player in data if guild.get_member(player['user_id'])]

total_players = len(data)

header_embed = Embed(title="Match Making Leaderboard")
header_embed.add_field(name="Total Players", value=str(total_players), inline=True)
header_embed.set_footer(text="K/D/A and Score are mean averages")

try:
header_message = await channel.fetch_message(settings.leaderboard_message)
await header_message.edit(content=None, embed=header_embed)
except:
header_message = await channel.send(embed=header_embed)
await store.update(BotSettings,
guild_id=guild.id,
leaderboard_channel=channel.id,
leaderboard_message=header_message.id)

existing_messages = []
async for message in channel.history(after=header_message, limit=None):
if message.author == guild.me:
existing_messages.append(message)
else:
await message.delete()
existing_messages.sort(key=lambda m: m.created_at)

start_rank = 1
for i in range((len(data) - 1) // 50 + 1):
embed, next_start_rank = await create_leaderboard_embed(guild, data, ranks, start_rank)
if i < len(existing_messages):
try:
await existing_messages[i].edit(embed=embed)
except:
await channel.send(embed=embed)
else:
await channel.send(embed=embed)
start_rank = next_start_rank

for message in existing_messages[((len(data) - 1) // 50 + 1):]:
try:
await message.delete()
except:
pass

0 comments on commit b68d606

Please sign in to comment.