diff --git a/django/discordbot/bot.py b/django/discordbot/bot.py index 3231171..d05e7e1 100644 --- a/django/discordbot/bot.py +++ b/django/discordbot/bot.py @@ -63,6 +63,14 @@ def create_invitation(squad, discord_name): return None +@sync_to_async +def calculate_preview_player_value(squad): + current_session = squad.sessions.filter(is_closed = False).order_by('-id').first() + if current_session: + return current_session.calculate_preview_player_value() + return None + + class BotError(Exception): def __init__(self, msg): @@ -134,6 +142,19 @@ async def who_is_your_creator(ctx): await ctx.response.send_message('The only and almighty, *TEH VOID OF THE AETHER!!1*'.upper()) +@bot.tree.command(description='Preview your updated 30-days average player value based on current session.') +async def preview(ctx): + squad = await get_squad(channel_id = ctx.channel_id) + if squad is None: + await ctx.response.send_message(f'No squad registered for this Discord channel') + else: + preview_value = await calculate_preview_player_value(squad) + if preview_value is None: + await ctx.response.send_message(f'No active session found or unable to calculate preview value.') + else: + await ctx.response.send_message(f'The preview of the updated 30-days average player value is: {preview_value:.2f}') + + if os.environ.get('CS2PB_DISCORD_ENABLED', False): log.info(f'Discord integration enabled') enabled = True diff --git a/django/stats/models.py b/django/stats/models.py index 2a8c624..81d4e23 100644 --- a/django/stats/models.py +++ b/django/stats/models.py @@ -201,6 +201,29 @@ def close(self) -> None: self.rising_star = top_player self.save() + def calculate_preview_player_value(self) -> Optional[float]: + """ + Calculate the preview of the updated 30-days average player value. + """ + if self.is_closed: + return None + + # Calculate the player value for each participant in the current session + player_values = [] + for m in self.squad.memberships.all(): + ctx = FeatureContext( + MatchParticipation.objects.filter(player = m.player, pmatch__sessions = self), + m.player, + ) + pv_today = Features.player_value(ctx) + if pv_today is not None: + player_values.append(pv_today) + + # Calculate the average player value + if player_values: + return sum(player_values) / len(player_values) + return None + @staticmethod def sessions_changed(sender, action, pk_set, instance, **kwargs): if action == 'pre_add': diff --git a/django/stats/tests.py b/django/stats/tests.py index 8bc2ae6..1a05b27 100644 --- a/django/stats/tests.py +++ b/django/stats/tests.py @@ -1335,3 +1335,140 @@ def test_rising_star_with_avatar(self): actual = attachment, expected = 'tests/data/radarplot_with_avatar.png', ) + + +class GamingSession__calculate_preview_player_value(TestCase): + + @testsuite.fake_api('accounts.models') + def setUp(self): + self.player1 = SteamProfile.objects.create(steamid = '12345678900000001') + self.player2 = SteamProfile.objects.create(steamid = '12345678900000002') + self.squad = Squad.objects.create(name = 'Test Squad', discord_channel_id = '1234') + SquadMembership.objects.create(squad = self.squad, player = self.player1) + SquadMembership.objects.create(squad = self.squad, player = self.player2) + + # Create a currently played session + self.session = models.GamingSession.objects.create(squad = self.squad) + self.match = models.Match.objects.create( + timestamp = int(time.time()), + score_team1 = 12, score_team2 = 13, + duration = 1653, + map_name = 'de_dust2', + ) + self.match.sessions.add(self.session) + self.participation1 = models.MatchParticipation.objects.create( + player = self.player1, + pmatch = self.match, + team = 1, + result = 'l', + kills = 20, + assists = 10, + deaths = 15, + score = 30, + mvps = 5, + headshots = 15, + adr = 120.5, + ) + self.participation2 = models.MatchParticipation.objects.create( + player = self.player2, + pmatch = self.match, + team = 1, + result = 'l', + kills = 10, + assists = 5, + deaths = 10, + score = 15, + mvps = 3, + headshots = 10, + adr = 90, + ) + + def test_calculate_preview_player_value(self): + preview_value = self.session.calculate_preview_player_value() + expected_value = (math.sqrt((20 / 15) * 120.5 / 100) + math.sqrt((10 / 10) * 90 / 100)) / 2 + self.assertAlmostEqual(preview_value, expected_value, places=2) + + def test_calculate_preview_player_value_closed_session(self): + self.session.is_closed = True + self.session.save() + preview_value = self.session.calculate_preview_player_value() + self.assertIsNone(preview_value) + + def test_calculate_preview_player_value_no_participation(self): + self.participation1.delete() + self.participation2.delete() + preview_value = self.session.calculate_preview_player_value() + self.assertIsNone(preview_value) + + +class DiscordBotPreviewCommandTest(TestCase): + + @testsuite.fake_api('accounts.models') + def setUp(self): + self.player1 = SteamProfile.objects.create(steamid = '12345678900000001') + self.player2 = SteamProfile.objects.create(steamid = '12345678900000002') + self.squad = Squad.objects.create(name = 'Test Squad', discord_channel_id = '1234') + SquadMembership.objects.create(squad = self.squad, player = self.player1) + SquadMembership.objects.create(squad = self.squad, player = self.player2) + + # Create a currently played session + self.session = models.GamingSession.objects.create(squad = self.squad) + self.match = models.Match.objects.create( + timestamp = int(time.time()), + score_team1 = 12, score_team2 = 13, + duration = 1653, + map_name = 'de_dust2', + ) + self.match.sessions.add(self.session) + self.participation1 = models.MatchParticipation.objects.create( + player = self.player1, + pmatch = self.match, + team = 1, + result = 'l', + kills = 20, + assists = 10, + deaths = 15, + score = 30, + mvps = 5, + headshots = 15, + adr = 120.5, + ) + self.participation2 = models.MatchParticipation.objects.create( + player = self.player2, + pmatch = self.match, + team = 1, + result = 'l', + kills = 10, + assists = 5, + deaths = 10, + score = 15, + mvps = 3, + headshots = 10, + adr = 90, + ) + + @patch('discordbot.bot.get_squad') + @patch('discordbot.bot.calculate_preview_player_value') + @patch('discordbot.bot.ctx') + async def test_preview_command(self, mock_ctx, mock_calculate_preview_player_value, mock_get_squad): + mock_get_squad.return_value = self.squad + mock_calculate_preview_player_value.return_value = 1.23 + + await discordbot.bot.preview(mock_ctx) + + mock_ctx.response.send_message.assert_called_once_with( + f'The preview of the updated 30-days average player value is: 1.23' + ) + + @patch('discordbot.bot.get_squad') + @patch('discordbot.bot.calculate_preview_player_value') + @patch('discordbot.bot.ctx') + async def test_preview_command_no_active_session(self, mock_ctx, mock_calculate_preview_player_value, mock_get_squad): + mock_get_squad.return_value = self.squad + mock_calculate_preview_player_value.return_value = None + + await discordbot.bot.preview(mock_ctx) + + mock_ctx.response.send_message.assert_called_once_with( + f'No active session found or unable to calculate preview value.' + )