Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow daily challenge-related medals from being awarded on non-daily-challenge score submissions #310

Merged
merged 3 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,25 @@ public void MedalNotAwardedIfNoDailyChallengesOnRecord()
}

[Fact]
public void MedalAwardedIfAtLeastOneDailyChallengeOnRecord()
public void MedalAwardedOnDailyChallengeCompletion()
{
using (var db = Processor.GetDatabaseConnection())
db.Execute("INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, 1)");
SetScoreForBeatmap(beatmap.beatmap_id);

ulong roomId = CreateMultiplayerRoom("daily challenge", "playlists", "daily_challenge");
ulong playlistItemId = CreatePlaylistItem(beatmap, roomId);

SetMultiplayerScoreForBeatmap(beatmap.beatmap_id, playlistItemId);
AssertSingleMedalAwarded(336);
}

[Fact]
public void MedalNotAwardedOnRandomBeatmapCompletionWithPastDailyStreakOnRecord()
{
using (var db = Processor.GetDatabaseConnection())
db.Execute("INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, 1)");
SetScoreForBeatmap(beatmap.beatmap_id);
AssertNoMedalsAwarded();
}
}
}
61 changes: 33 additions & 28 deletions osu.Server.Queues.ScoreStatisticsProcessor.Tests/DatabaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,40 +99,45 @@ protected ScoreItem SetScoreForBeatmap(uint beatmapId, Action<ScoreItem>? scoreS

scoreSetup?.Invoke(score);

conn.Execute("INSERT INTO `scores` (`id`, `user_id`, `ruleset_id`, `beatmap_id`, `has_replay`, `preserve`, `ranked`, "
+ "`rank`, `passed`, `accuracy`, `max_combo`, `total_score`, `data`, `pp`, `legacy_score_id`, `legacy_total_score`, "
+ "`started_at`, `ended_at`, `build_id`) "
+ "VALUES (@id, @user_id, @ruleset_id, @beatmap_id, @has_replay, @preserve, @ranked, "
+ "@rank, @passed, @accuracy, @max_combo, @total_score, @data, @pp, @legacy_score_id, @legacy_total_score,"
+ "@started_at, @ended_at, @build_id)",
new
{
score.Score.id,
score.Score.user_id,
score.Score.ruleset_id,
score.Score.beatmap_id,
score.Score.has_replay,
score.Score.preserve,
score.Score.ranked,
rank = score.Score.rank.ToString(),
score.Score.passed,
score.Score.accuracy,
score.Score.max_combo,
score.Score.total_score,
score.Score.data,
score.Score.pp,
score.Score.legacy_score_id,
score.Score.legacy_total_score,
score.Score.started_at,
score.Score.ended_at,
score.Score.build_id,
});
InsertScore(conn, score);
PushToQueueAndWaitForProcess(score);

return score;
}
}

protected static void InsertScore(MySqlConnection conn, ScoreItem score)
{
conn.Execute("INSERT INTO `scores` (`id`, `user_id`, `ruleset_id`, `beatmap_id`, `has_replay`, `preserve`, `ranked`, "
+ "`rank`, `passed`, `accuracy`, `max_combo`, `total_score`, `data`, `pp`, `legacy_score_id`, `legacy_total_score`, "
+ "`started_at`, `ended_at`, `build_id`) "
+ "VALUES (@id, @user_id, @ruleset_id, @beatmap_id, @has_replay, @preserve, @ranked, "
+ "@rank, @passed, @accuracy, @max_combo, @total_score, @data, @pp, @legacy_score_id, @legacy_total_score,"
+ "@started_at, @ended_at, @build_id)",
new
{
score.Score.id,
score.Score.user_id,
score.Score.ruleset_id,
score.Score.beatmap_id,
score.Score.has_replay,
score.Score.preserve,
score.Score.ranked,
rank = score.Score.rank.ToString(),
score.Score.passed,
score.Score.accuracy,
score.Score.max_combo,
score.Score.total_score,
score.Score.data,
score.Score.pp,
score.Score.legacy_score_id,
score.Score.legacy_total_score,
score.Score.started_at,
score.Score.ended_at,
score.Score.build_id,
});
}

private static ulong scoreIDSource;

protected void PushToQueueAndWaitForProcess(ScoreItem item)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Dapper;
using MySqlConnector;
using osu.Server.Queues.ScoreStatisticsProcessor.Models;
using osu.Server.Queues.ScoreStatisticsProcessor.Processors;
using Xunit;
Expand All @@ -29,6 +31,10 @@ protected MedalAwarderTest(AssemblyName[]? externalProcessorAssemblies = null)
db.Execute("TRUNCATE TABLE osu_beatmappacks_items");

db.Execute("TRUNCATE TABLE daily_challenge_user_stats");

db.Execute("TRUNCATE TABLE multiplayer_score_links");
db.Execute("TRUNCATE TABLE multiplayer_playlist_items");
db.Execute("TRUNCATE TABLE multiplayer_rooms");
}
}

Expand Down Expand Up @@ -89,6 +95,54 @@ private void onMedalAwarded(MedalProcessor.AwardedMedal awarded)
}
}

protected ulong CreateMultiplayerRoom(string roomName, string roomType, string roomCategory = "normal")
{
using var conn = Processor.GetDatabaseConnection();
return conn.QuerySingle<ulong>(
"INSERT INTO `multiplayer_rooms` (`name`, `type`, `category`) VALUES (@name, @type, @category); SELECT LAST_INSERT_ID();",
new
{
name = roomName,
type = roomType,
category = roomCategory,
});
}

protected ulong CreatePlaylistItem(Beatmap beatmap, ulong roomId)
{
using var conn = Processor.GetDatabaseConnection();
return conn.QuerySingle<ulong>(
"INSERT INTO `multiplayer_playlist_items` (`room_id`, `owner_id`, `beatmap_id`, `ruleset_id`) VALUES (@room_id, 1, @beatmap_id, @ruleset_id); SELECT LAST_INSERT_ID();",
new
{
beatmap_id = beatmap.beatmap_id,
ruleset_id = beatmap.playmode,
room_id = roomId,
});
}

protected ScoreItem SetMultiplayerScoreForBeatmap(uint beatmapId, ulong playlistItemId, Action<ScoreItem>? scoreSetup = null)
{
using (MySqlConnection conn = Processor.GetDatabaseConnection())
{
var score = CreateTestScore(beatmapId: beatmapId);

scoreSetup?.Invoke(score);

InsertScore(conn, score);
conn.Execute("INSERT INTO `multiplayer_score_links` (`user_id`, `playlist_item_id`, `score_id`) VALUES (@user_id, @playlist_item_id, @score_id)",
new
{
user_id = score.Score.user_id,
playlist_item_id = playlistItemId,
score_id = score.Score.id,
});
PushToQueueAndWaitForProcess(score);

return score;
}
}

public override void Dispose()
{
base.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,39 @@ public MonthlyShrubTest()
[InlineData(0)]
[InlineData(9)]
[InlineData(26)]
public void MedalNotAwardedIfNotEnoughDailyChallengesOnRecord(int dailyChallengeCount)
public void MedalNotAwardedIfNotEnoughDailyChallengesOnRecord(int bestStreak)
{
using (var db = Processor.GetDatabaseConnection())
db.Execute($"INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, {dailyChallengeCount})");
db.Execute($"INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, {bestStreak})");

ulong roomId = CreateMultiplayerRoom("daily challenge", "playlists", "daily_challenge");
ulong playlistItemId = CreatePlaylistItem(beatmap, roomId);

SetMultiplayerScoreForBeatmap(beatmap.beatmap_id, playlistItemId);
SetScoreForBeatmap(beatmap.beatmap_id);
AssertNoMedalsAwarded();
}

[Fact]
public void MedalAwardedIfAtLeastThirtyDailyChallengesOnRecord()
public void MedalAwardedOnDailyChallengeIfLongestHistoricalStreakAtLeastThirtyDays()
{
using (var db = Processor.GetDatabaseConnection())
db.Execute("INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, 30)");
SetScoreForBeatmap(beatmap.beatmap_id);

ulong roomId = CreateMultiplayerRoom("daily challenge", "playlists", "daily_challenge");
ulong playlistItemId = CreatePlaylistItem(beatmap, roomId);

SetMultiplayerScoreForBeatmap(beatmap.beatmap_id, playlistItemId);
AssertSingleMedalAwarded(338);
}

[Fact]
public void MedalNotAwardedOutsideOfDailyChallengeEvenWithLongEnoughHistoricalBestStreak()
{
using (var db = Processor.GetDatabaseConnection())
db.Execute("INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, 33)");
SetScoreForBeatmap(beatmap.beatmap_id);
AssertNoMedalsAwarded();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,38 @@ public WeeklySaplingTest()
[InlineData(0)]
[InlineData(4)]
[InlineData(6)]
public void MedalNotAwardedIfNotEnoughDailyChallengesOnRecord(int dailyChallengeCount)
public void MedalNotAwardedIfNotEnoughDailyChallengesOnRecord(int bestStreak)
{
using (var db = Processor.GetDatabaseConnection())
db.Execute($"INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, {dailyChallengeCount})");
SetScoreForBeatmap(beatmap.beatmap_id);
db.Execute($"INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, {bestStreak})");

ulong roomId = CreateMultiplayerRoom("daily challenge", "playlists", "daily_challenge");
ulong playlistItemId = CreatePlaylistItem(beatmap, roomId);

SetMultiplayerScoreForBeatmap(beatmap.beatmap_id, playlistItemId);
AssertNoMedalsAwarded();
}

[Fact]
public void MedalAwardedIfAtLeastSevenDailyChallengesOnRecord()
public void MedalAwardedOnDailyChallengeIfLongestHistoricalStreakAtLeastSevenDays()
{
using (var db = Processor.GetDatabaseConnection())
db.Execute("INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, 7)");
SetScoreForBeatmap(beatmap.beatmap_id);

ulong roomId = CreateMultiplayerRoom("daily challenge", "playlists", "daily_challenge");
ulong playlistItemId = CreatePlaylistItem(beatmap, roomId);

SetMultiplayerScoreForBeatmap(beatmap.beatmap_id, playlistItemId);
AssertSingleMedalAwarded(337);
}

[Fact]
public void MedalNotAwardedOutsideOfDailyChallengeEvenWithLongEnoughHistoricalBestStreak()
{
using (var db = Processor.GetDatabaseConnection())
db.Execute("INSERT INTO `daily_challenge_user_stats` (`user_id`, `daily_streak_best`) VALUES (2, 9)");
SetScoreForBeatmap(beatmap.beatmap_id);
AssertNoMedalsAwarded();
}
}
}
12 changes: 12 additions & 0 deletions osu.Server.Queues.ScoreStatisticsProcessor/Helpers/MedalHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,17 @@ public static bool UserPassedPack(MedalAwarderContext context, bool noReductionM

return completed >= countForPack;
}

public static bool IsDailyChallengeScore(MedalAwarderContext context)
{
return context.Connection.QuerySingleOrDefault<string?>(
"""
SELECT `multiplayer_rooms`.`category` FROM `scores`
JOIN `multiplayer_score_links` ON `multiplayer_score_links`.`score_id` = `scores`.`id`
JOIN `multiplayer_playlist_items` ON `multiplayer_playlist_items`.`id` = `multiplayer_score_links`.`playlist_item_id`
JOIN `multiplayer_rooms` ON `multiplayer_rooms`.`id` = `multiplayer_playlist_items`.`room_id`
""",
transaction: context.Transaction) == "daily_challenge";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using JetBrains.Annotations;
using osu.Server.Queues.ScoreStatisticsProcessor.Helpers;
using osu.Server.Queues.ScoreStatisticsProcessor.Models;

namespace osu.Server.Queues.ScoreStatisticsProcessor.Processors.MedalAwarders
Expand All @@ -15,6 +16,9 @@ public class DailyChallengeMedalAwarder : IMedalAwarder

public IEnumerable<Medal> Check(IEnumerable<Medal> medals, MedalAwarderContext context)
{
if (!MedalHelpers.IsDailyChallengeScore(context))
yield break;

foreach (var medal in medals)
{
switch (medal.achievement_id)
Expand Down
Loading