Skip to content

Commit

Permalink
setup working placement matches. Removes roles before completion, and…
Browse files Browse the repository at this point in the history
… doesn't mention or rank in leaderboard either.
  • Loading branch information
99oblivius committed Sep 28, 2024
1 parent cfbaad7 commit c008e1a
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 31 deletions.
2 changes: 1 addition & 1 deletion src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
MATCH_PLAYER_COUNT = 10
STARTING_MMR = 900
BASE_MMR_CHANGE = 40
PLACEMENT_MATCHES = 20
PLACEMENT_MATCHES = 10

REGION_TIMEZONES = {
"EUW": "Europe/London", # Western Europe
Expand Down
28 changes: 25 additions & 3 deletions src/matches/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@

import random
from typing import List
import numpy as np

from config import BASE_MMR_CHANGE, STARTING_MMR
from utils.models import MMBotMaps, MMBotUserMapPicks, Side
from utils.models import MMBotMaps, MMBotUserMapPicks, Side, MMBotUserMatchStats


def get_preferred_bans(maps: List[MMBotMaps], bans: List[str], total_bans: int=2) -> List[str]:
Expand Down Expand Up @@ -83,7 +84,7 @@ def calculate_mmr_change(
closeness_ratio = 4/9

if placements:
kd_rate = BASE_MMR_CHANGE / 4 * (5 + (kills+assists/5) - deaths) / 10
kd_rate = BASE_MMR_CHANGE / 3 * ((kills+assists/5) - deaths) / 10
else:
kd_rate = BASE_MMR_CHANGE / 6 * ((kills+assists/5) - deaths) / 10
r_ab = ally_team_avg_mmr - enemy_team_avg_mmr
Expand All @@ -96,4 +97,25 @@ def calculate_mmr_change(
new_r += kd_rate
return new_r


def calculate_placements_mmr(user_avg_score: int, guild_avg_scores: List[int], initial_mmr: int) -> int:
guild_mean = np.mean(guild_avg_scores)
guild_std = np.std(guild_avg_scores)

mmr_ranges = [
(-9999, -250),
(guild_mean - 2*guild_std, -250),
(guild_mean - guild_std, -100),
(guild_mean, 0),
(guild_mean + guild_std, 150),
(guild_mean + 2*guild_std, 300),
(9999, 300)
]

for n, (value, lower_mmr) in enumerate(mmr_ranges):
nvalue, upper_mmr = mmr_ranges[n+1]
if value <= user_avg_score < nvalue:
normalized_score = (user_avg_score - value) / (nvalue - value)
mmr_change = lerp(lower_mmr, upper_mmr, normalized_score)
break

return max(STARTING_MMR - 300, min(STARTING_MMR + 450, initial_mmr + mmr_change))
83 changes: 62 additions & 21 deletions src/matches/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@
)
from utils.logger import Logger as log, VariableLog
from utils.models import *
from utils.utils import format_duration, format_mm_attendance, generate_score_image, create_queue_embed
from utils.utils import format_duration, format_mm_attendance, generate_score_image, create_queue_embed, get_rank_role
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
from views.match.side_pick import ChosenSideView, SidePickView
from views.match.no_server_found import NoServerFoundView
from views.match.force_abandon import ForceAbandonView
from .functions import calculate_mmr_change, get_preferred_bans, get_preferred_map, get_preferred_side
from .functions import calculate_mmr_change, get_preferred_bans, get_preferred_map, get_preferred_side, calculate_placements_mmr
from .match_states import MatchState
from .ranked_teams import get_teams

Expand Down Expand Up @@ -98,6 +98,27 @@ async def show_no_server_found_message(self):
self.no_server_message = await self.match_channel.send(embed=embed, view=view)
await done_event.wait()

async def send_placements_reward_message(self, member: nextcord.Member, new_mmr: int):
guild = member.guild
ranks = await self.bot.store.get_ranks(guild.id)
rank_role: nextcord.Role = await get_rank_role(guild, ranks, new_mmr)
embed = nextcord.Embed(
title="You completed your placement matches!",
description=f"Congratulations you were placed in `{rank_role.name}`!",
color=rank_role.color)
try:
await member.send(embed=embed)
except (nextcord.Forbidden, nextcord.HTTPException):
pass
settings: BotSettings = await self.bot.store.get_settings(guild.id)

embed = nextcord.Embed(
title=f"Placements completed!",
description=f"{member.mention} finished their {PLACEMENT_MATCHES} placement games.\nThey will start their adventure in {rank_role.mention}!",
color=rank_role.color,
timestamp=datetime.now(timezone.utc))
await guild.get_channel(settings.mm_text_channel).send(embed=embed)

async def estimate_user_server_ping(self, user_id: int, serveraddr: str, ping_data: Dict[Tuple[int, str], Dict[str, float]]) -> int:
user_server = (user_id, serveraddr)
if user_server in ping_data and ping_data[user_server] is not None:
Expand Down Expand Up @@ -236,6 +257,7 @@ async def finalize_match(self, users_summary_data, team_scores, last_reply: dict

final_updates = {}
users_summary_stats = {}
placement_completions = []

guild = self.bot.get_guild(self.guild_id)
if not guild:
Expand All @@ -249,6 +271,8 @@ async def finalize_match(self, users_summary_data, team_scores, last_reply: dict
for player in self.players:
user_id = player.user_id
if user_id in self.persistent_player_stats:
member = guild.get_member(user_id)
games_played = played_games[player.user_id]
current_stats = self.persistent_player_stats[user_id]
ct_start = current_stats['ct_start']
win = team_scores[0] > team_scores[1] if ct_start else team_scores[1] > team_scores[0]
Expand All @@ -261,31 +285,37 @@ async def finalize_match(self, users_summary_data, team_scores, last_reply: dict
mmr_change = calculate_mmr_change(current_stats,
ally_team_score=ally_score, enemy_team_score=enemy_score,
ally_team_avg_mmr=ally_mmr, enemy_team_avg_mmr=enemy_mmr, win=win,
placements=played_games[player.user_id] <= PLACEMENT_MATCHES)
placements=games_played <= PLACEMENT_MATCHES)

current_stats.update({"win": win, "mmr_change": mmr_change})

summary_data = users_summary_data[user_id]
new_mmr = summary_data.mmr + current_stats['mmr_change']
if games_played == PLACEMENT_MATCHES:
placement_completions.append((member, new_mmr))
elif games_played >= PLACEMENT_MATCHES:
new_rank_id = next((r.role_id for r in sorted(ranks, key=lambda x: x.mmr_threshold, reverse=True) if new_mmr >= r.mmr_threshold), None)

new_rank_id = next((r.role_id for r in sorted(ranks, key=lambda x: x.mmr_threshold, reverse=True) if new_mmr >= r.mmr_threshold), None)

member = guild.get_member(user_id)
if member:
current_rank_role_ids = set(role.id for role in member.roles if role.id in rank_ids)

if new_rank_id not in current_rank_role_ids:

roles_to_remove = [guild.get_role(role_id) for role_id in current_rank_role_ids]
roles_to_remove = [role for role in roles_to_remove if role is not None]
if roles_to_remove:
asyncio.create_task(member.remove_roles(*roles_to_remove, reason="Updating MMR rank"))
log.info(f"Roles {', '.join(role.name for role in roles_to_remove)} removed from {member.display_name}")

new_role = guild.get_role(new_rank_id)
if new_role:
asyncio.create_task(member.add_roles(new_role, reason="Updating MMR rank"))
log.info(f"Role {new_role.name} added to {member.display_name}")
if member:
current_rank_role_ids = set(role.id for role in member.roles if role.id in rank_ids)

if new_rank_id not in current_rank_role_ids:

rank_roles = [guild.get_role(role_id) for role_id in current_rank_role_ids]
roles_to_remove = [role for role in roles_to_remove if role is not None]
if roles_to_remove:
asyncio.create_task(member.remove_roles(*roles_to_remove, reason="Updating MMR rank"))
log.info(f"Roles {', '.join(role.name for role in roles_to_remove)} removed from {member.display_name}")

new_role = guild.get_role(new_rank_id)
if new_role:
asyncio.create_task(member.add_roles(new_role, reason="Updating MMR rank"))
log.info(f"Role {new_role.name} added to {member.display_name}")
else:
if member:
current_rank_role_ids = set(role.id for role in member.roles if role.id in rank_ids)
rank_roles = [guild.get_role(role_id) for role_id in current_rank_role_ids]
asyncio.create_task(member.remove_roles(*rank_roles))

users_summary_stats[user_id] = self.update_summary_stats(summary_data, current_stats)
final_updates[user_id] = current_stats
Expand All @@ -300,6 +330,17 @@ async def finalize_match(self, users_summary_data, team_scores, last_reply: dict
await self.bot.store.upsert_users_match_stats(self.guild_id, self.match_id, final_updates)
await self.bot.store.set_users_summary_stats(self.guild_id, users_summary_stats)

users_placement_summary = {}
guild_avg_scores = sorted([stats['avg_score'] for stats in await self.bot.store.get_leaderboard(self.guild_id)])
for member, mmr in placement_completions:
user_avg_score = (await self.bot.store.get_avg_stats_last_n_games(self.guild_id, member.id, PLACEMENT_MATCHES))['avg_score']
new_mmr = await calculate_placements_mmr(user_avg_score, guild_avg_scores, mmr)
users_placement_summary[member.id] = { 'mmr': new_mmr }
asyncio.create_task(self.send_placements_reward_message(member, new_mmr))
log.info(f"User {player.user_id} has completed their placements and received {placements_reward} mmr")
if users_placement_summary:
await self.bot.store.set_users_summary_stats(self.guild_id, users_placement_summary)

async def start_requeue_players(self, settings: BotSettings, requeue_players: List[int]):
guild = self.bot.get_guild(settings.guild_id)

Expand Down
6 changes: 3 additions & 3 deletions src/utils/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from sqlalchemy.future import select
from sqlalchemy.orm import joinedload, selectinload, sessionmaker

from config import DATABASE_URL
from config import DATABASE_URL, PLACEMENT_MATCHES
from matches import MatchState
from utils.logger import Logger as log
from utils.utils import extract_late_time
Expand Down Expand Up @@ -452,7 +452,7 @@ async def get_leaderboard(self, guild_id: int) -> List[Dict[str, Any]]:
select(MMBotUserSummaryStats)
.where(
MMBotUserSummaryStats.guild_id == guild_id,
MMBotUserSummaryStats.games > 0)
MMBotUserSummaryStats.games > PLACEMENT_MATCHES)
.order_by(desc(MMBotUserSummaryStats.mmr)))
return [
{
Expand Down Expand Up @@ -520,7 +520,7 @@ async def get_leaderboard_with_previous_mmr(self, guild_id: int) -> List[Dict[st
(MMBotUserMatchStats.match_id == subquery.c.latest_match_id))
.where(
MMBotUserSummaryStats.guild_id == guild_id,
MMBotUserSummaryStats.games > 0)
MMBotUserSummaryStats.games > PLACEMENT_MATCHES)
.order_by(desc(MMBotUserSummaryStats.mmr)))

result = await session.execute(query)
Expand Down
6 changes: 3 additions & 3 deletions src/utils/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
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 config import VALORS_THEME1, VALORS_THEME1_1, VALORS_THEME1_2, VALORS_THEME2, REGION_TIMEZONES, PLACEMENT_MATCHES
from utils.models import MMBotRanks, MMBotUserMatchStats, BotSettings
from utils.utils import get_rank_color, get_rank_role, next_rank_role, replace_wide_chars_with_space, format_duration

Expand Down Expand Up @@ -377,11 +377,11 @@ def create_stats_embed(guild: Guild, user: User | Member, leaderboard_data, summ
if player['user_id'] == user.id:
ranked_position = ranked_players

rank_role = get_rank_role(guild, ranks, summary_data.mmr)
rank_role = None if summary_data.games < PLACEMENT_MATCHES else get_rank_role(guild, ranks, summary_data.mmr)
next_role, mmr_difference = next_rank_role(guild, ranks, summary_data.mmr)
embed = Embed(
title=f"[{ranked_position}/{ranked_players}] Stats for {user.display_name}",
description=f"Currently in {rank_role.mention}\n-# {floor(mmr_difference)} away from {next_role.mention}" if rank_role else None,
description=f"Currently in {rank_role.mention}\n-# {floor(mmr_difference)} away from {next_role.mention}" if rank_role else "Unranked",
color=rank_role.color if rank_role else 0)
embed.set_thumbnail(url=user.avatar.url if user.avatar else user.default_avatar.url)

Expand Down

0 comments on commit c008e1a

Please sign in to comment.