Skip to content

Commit 323c54b

Browse files
committed
feat(ScoreFilter): Add extensible score filter system
- Introduce the `IScoreFilter` interface and `ScoreFilterTagAttribute` attribute for defining and tagging filters - Implement various filters including Level, Rank, DXStar, Combo, Version, Sun, and Lock - Add the `ScoreFilterHelper` utility class to support dynamic discovery and retrieval of filter instances via tags - Integrate filters into `LxnsListService` and `DfListService`, replacing the original fixed condition filtering logic - Add the `ConstantMap` class to centrally manage version mapping and rating constant data - Adjust Drawer rendering logic to adapt to the new filter data flow
1 parent c6c4b47 commit 323c54b

15 files changed

+401
-52
lines changed

src/Render/Drawer.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,20 @@ public async Task<Image> DrawBestsAsync(CommonUser user, IReadOnlyList<CommonRec
3939
return await DrawAsync(scope, xmlPath);
4040
}
4141

42-
public async Task<Image> DrawListAsync(CommonUser user, IReadOnlyList<CommonRecord> records, int page, int total,
43-
List<int> counts, int startIndex, string condition, string prober, IEnumerable<string> tags) =>
44-
await DrawListAsync(user, records, page, total, counts, startIndex, condition, prober, tags, "./Resources/Layouts/list.xml");
42+
public async Task<Image> DrawListAsync(CommonUser user, IReadOnlyList<CommonRecord> records, int page,
43+
int[] counts, int totalCount, int startIndex, string condition, bool mayMask, string prober, IEnumerable<string> tags) =>
44+
await DrawListAsync(user, records, page, counts, totalCount, startIndex, condition, mayMask, prober, tags, "./Resources/Layouts/list.xml");
4545

46-
public async Task<Image> DrawListAsync(CommonUser user, IReadOnlyList<CommonRecord> records, int page, int total,
47-
List<int> counts, int startIndex, string condition, string prober, IEnumerable<string> tags, string xmlPath)
46+
public async Task<Image> DrawListAsync(CommonUser user, IReadOnlyList<CommonRecord> records, int page,
47+
int[] counts, int totalCount, int startIndex, string condition, bool mayMask, string prober, IEnumerable<string> tags, string xmlPath)
4848
{
49-
int totalCount = counts.Count > 0 ? counts[^1] : 0;
50-
bool mayMask = records.Any(r => r.DXScore is 0 && (r.DXStar > 0 || r.Rank > Ranks.A));
49+
int totalPages = (int)Math.Ceiling((double)totalCount / 55);
5150
Dictionary<string, object?> scope = new(StringComparer.OrdinalIgnoreCase)
5251
{
5352
["userInfo"] = user,
5453
["pageRecords"] = records,
5554
["pageNumber"] = page,
56-
["totalPages"] = total,
55+
["totalPages"] = totalPages,
5756
["rankCounts"] = counts[..7],
5857
["comboCounts"] = counts[7..^1],
5958
["totalCount"] = totalCount,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Limekuma.Prober.Common;
2+
3+
namespace Limekuma.ScoreFilter;
4+
5+
[ScoreFilterTag("combo")]
6+
public sealed class ComboScoreFilter : IScoreFilter
7+
{
8+
public bool MaskMutex => false;
9+
10+
public Func<CommonRecord, bool> GetFilter(string? condition)
11+
{
12+
if (!Enum.TryParse(condition, out ComboFlags comboFlag))
13+
{
14+
return _ => true;
15+
}
16+
17+
return x => x.ComboFlag >= comboFlag;
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Limekuma.Prober.Common;
2+
3+
namespace Limekuma.ScoreFilter;
4+
5+
[ScoreFilterTag("dx_star")]
6+
public sealed class DXStarScoreFilter : IScoreFilter
7+
{
8+
public bool MaskMutex => false;
9+
10+
public Func<CommonRecord, bool> GetFilter(string? condition)
11+
{
12+
if (!int.TryParse(condition, out int dxStar))
13+
{
14+
return _ => true;
15+
}
16+
17+
return x => x.DXStar >= dxStar;
18+
}
19+
}

src/ScoreFilter/IScoreFilter.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Limekuma.Prober.Common;
2+
3+
namespace Limekuma.ScoreFilter;
4+
5+
public interface IScoreFilter
6+
{
7+
Func<CommonRecord, bool> GetFilter(string? condition);
8+
bool MaskMutex { get; }
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Limekuma.Prober.Common;
2+
3+
namespace Limekuma.ScoreFilter;
4+
5+
[ScoreFilterTag("level")]
6+
public sealed class LevelScoreFilter : IScoreFilter
7+
{
8+
public bool MaskMutex => false;
9+
10+
public Func<CommonRecord, bool> GetFilter(string? condition) => x => x.Chart.Level == condition;
11+
}

src/ScoreFilter/LockScoreFilter.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Limekuma.Prober.Common;
2+
using Limekuma.Utils;
3+
4+
namespace Limekuma.ScoreFilter;
5+
6+
[ScoreFilterTag("lock")]
7+
public sealed class LockScoreFilter : IScoreFilter
8+
{
9+
public bool MaskMutex => true;
10+
11+
public Func<CommonRecord, bool> GetFilter(string? condition) => x =>
12+
{
13+
float sumScore = 0;
14+
for (int i = 0; i < 5; i++)
15+
{
16+
(int notes, int weight) = i switch
17+
{
18+
0 => (x.Chart.Notes.Tap, 1),
19+
1 => (x.Chart.Notes.Hold, 2),
20+
2 => (x.Chart.Notes.Slide, 3),
21+
3 => (x.Chart.Notes.Touch, 1),
22+
4 => (x.Chart.Notes.Break, 5),
23+
_ => throw new IndexOutOfRangeException()
24+
};
25+
26+
sumScore += notes * weight;
27+
}
28+
29+
sumScore *= 5;
30+
float minScore = Math.Min((1 - ((sumScore - 1) / sumScore)) * 100, x.Chart.Notes.Break > 0 ? 1f / x.Chart.Notes.Break / 2 : 101);
31+
(float minAcc, _, _) = ConstantMap.RatingMap[x.Rank];
32+
float maxAcc = minAcc + minScore;
33+
return x.Achievements >= maxAcc && x.Achievements < minAcc;
34+
};
35+
}

src/ScoreFilter/RankScoreFilter.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Limekuma.Prober.Common;
2+
3+
namespace Limekuma.ScoreFilter;
4+
5+
[ScoreFilterTag("rank")]
6+
public sealed class RankScoreFilter : IScoreFilter
7+
{
8+
public bool MaskMutex => false;
9+
10+
public Func<CommonRecord, bool> GetFilter(string? condition)
11+
{
12+
if (!Enum.TryParse(condition, out Ranks rank))
13+
{
14+
return _ => true;
15+
}
16+
17+
return x => x.Rank >= rank;
18+
}
19+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Limekuma.ScoreFilter;
2+
3+
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
4+
public sealed class ScoreFilterTagAttribute : Attribute
5+
{
6+
public string Tag { get; }
7+
8+
public ScoreFilterTagAttribute(string tag)
9+
{
10+
if (string.IsNullOrWhiteSpace(tag))
11+
{
12+
throw new ArgumentException("Tag is required.", nameof(tag));
13+
}
14+
15+
Tag = tag;
16+
}
17+
}

src/ScoreFilter/SunScoreFilter.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Limekuma.Prober.Common;
2+
using Limekuma.Utils;
3+
4+
namespace Limekuma.ScoreFilter;
5+
6+
[ScoreFilterTag("sun")]
7+
public sealed class SunScoreFilter : IScoreFilter
8+
{
9+
public bool MaskMutex => true;
10+
11+
public Func<CommonRecord, bool> GetFilter(string? condition) => x =>
12+
{
13+
float sumScore = 0;
14+
for (int i = 0; i < 5; i++)
15+
{
16+
(int notes, int weight) = i switch
17+
{
18+
0 => (x.Chart.Notes.Tap, 1),
19+
1 => (x.Chart.Notes.Hold, 2),
20+
2 => (x.Chart.Notes.Slide, 3),
21+
3 => (x.Chart.Notes.Touch, 1),
22+
4 => (x.Chart.Notes.Break, 5),
23+
_ => throw new IndexOutOfRangeException()
24+
};
25+
26+
sumScore += notes * weight;
27+
}
28+
29+
sumScore *= 5;
30+
float minScore = Math.Min((1 - ((sumScore - 1) / sumScore)) * 100, x.Chart.Notes.Break > 0 ? 1 / x.Chart.Notes.Break / 2 : 101);
31+
(float maxAcc, _, _) = ConstantMap.RatingMap[x.Rank];
32+
float minAcc = maxAcc - minScore;
33+
return x.Achievements >= minAcc && x.Achievements < maxAcc;
34+
};
35+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Limekuma.Prober.Common;
2+
using Limekuma.Utils;
3+
using System.Collections.Frozen;
4+
5+
namespace Limekuma.ScoreFilter;
6+
7+
[ScoreFilterTag("version")]
8+
public sealed class VersionScoreFilter : IScoreFilter
9+
{
10+
public bool MaskMutex => false;
11+
12+
public Func<CommonRecord, bool> GetFilter(string? condition)
13+
{
14+
if (string.IsNullOrWhiteSpace(condition))
15+
{
16+
return _ => true;
17+
}
18+
19+
if (!ConstantMap.VersionMap.TryGetValue(condition, out FrozenSet<string>? version))
20+
{
21+
return _ => true;
22+
}
23+
24+
return x => version.Contains(x.Chart.Song.Genre);
25+
}
26+
}

0 commit comments

Comments
 (0)