프로젝트 소스 공개
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ProjectQuint.Core.Progression
|
||||
{
|
||||
public readonly struct CollectionGroupSummary
|
||||
{
|
||||
public CollectionGroupSummary(
|
||||
string id,
|
||||
string displayName,
|
||||
int played,
|
||||
int normalClear,
|
||||
int hardClear,
|
||||
int failed,
|
||||
int fullCombo,
|
||||
int allPure)
|
||||
{
|
||||
Id = id ?? string.Empty;
|
||||
DisplayName = string.IsNullOrWhiteSpace(displayName) ? "UNKNOWN" : displayName;
|
||||
Played = Math.Max(0, played);
|
||||
NormalClear = Math.Max(0, normalClear);
|
||||
HardClear = Math.Max(0, hardClear);
|
||||
Failed = Math.Max(0, failed);
|
||||
FullCombo = Math.Max(0, fullCombo);
|
||||
AllPure = Math.Max(0, allPure);
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string DisplayName { get; }
|
||||
public int Played { get; }
|
||||
public int NormalClear { get; }
|
||||
public int HardClear { get; }
|
||||
public int Failed { get; }
|
||||
public int FullCombo { get; }
|
||||
public int AllPure { get; }
|
||||
public int Cleared => NormalClear + HardClear;
|
||||
public double ClearRate => Played <= 0 ? 0.0 : Cleared * 100.0 / Played;
|
||||
}
|
||||
|
||||
public sealed class ProfileCollectionSnapshot
|
||||
{
|
||||
public static readonly ProfileCollectionSnapshot Empty = new ProfileCollectionSnapshot(
|
||||
default,
|
||||
Array.Empty<CollectionGroupSummary>(),
|
||||
Array.Empty<CollectionGroupSummary>(),
|
||||
Array.Empty<CollectionGroupSummary>());
|
||||
|
||||
public ProfileCollectionSnapshot(
|
||||
CollectionGroupSummary total,
|
||||
IReadOnlyList<CollectionGroupSummary> packs,
|
||||
IReadOnlyList<CollectionGroupSummary> keyModes,
|
||||
IReadOnlyList<CollectionGroupSummary> difficulties)
|
||||
{
|
||||
Total = total;
|
||||
Packs = packs ?? Array.Empty<CollectionGroupSummary>();
|
||||
KeyModes = keyModes ?? Array.Empty<CollectionGroupSummary>();
|
||||
Difficulties = difficulties ?? Array.Empty<CollectionGroupSummary>();
|
||||
}
|
||||
|
||||
public CollectionGroupSummary Total { get; }
|
||||
public IReadOnlyList<CollectionGroupSummary> Packs { get; }
|
||||
public IReadOnlyList<CollectionGroupSummary> KeyModes { get; }
|
||||
public IReadOnlyList<CollectionGroupSummary> Difficulties { get; }
|
||||
}
|
||||
|
||||
public readonly struct CourseCollectionEntry
|
||||
{
|
||||
public CourseCollectionEntry(
|
||||
string courseId,
|
||||
string providerDisplayName,
|
||||
string courseName,
|
||||
bool cleared,
|
||||
int playCount,
|
||||
int clearCount,
|
||||
int bestStageReached,
|
||||
double bestAccuracyPercent,
|
||||
int bestTrophyRank,
|
||||
string bestTrophyName)
|
||||
{
|
||||
CourseId = courseId ?? string.Empty;
|
||||
ProviderDisplayName = providerDisplayName ?? string.Empty;
|
||||
CourseName = string.IsNullOrWhiteSpace(courseName) ? "UNKNOWN COURSE" : courseName;
|
||||
Cleared = cleared;
|
||||
PlayCount = Math.Max(0, playCount);
|
||||
ClearCount = Math.Max(0, clearCount);
|
||||
BestStageReached = Math.Max(0, bestStageReached);
|
||||
BestAccuracyPercent = Math.Max(0.0, Math.Min(100.0, bestAccuracyPercent));
|
||||
BestTrophyRank = Math.Max(0, bestTrophyRank);
|
||||
BestTrophyName = bestTrophyName ?? string.Empty;
|
||||
}
|
||||
|
||||
public string CourseId { get; }
|
||||
public string ProviderDisplayName { get; }
|
||||
public string CourseName { get; }
|
||||
public bool Cleared { get; }
|
||||
public int PlayCount { get; }
|
||||
public int ClearCount { get; }
|
||||
public int BestStageReached { get; }
|
||||
public double BestAccuracyPercent { get; }
|
||||
public int BestTrophyRank { get; }
|
||||
public string BestTrophyName { get; }
|
||||
}
|
||||
|
||||
public readonly struct CourseCertificationBadge
|
||||
{
|
||||
public CourseCertificationBadge(CourseCollectionEntry course)
|
||||
{
|
||||
CourseId = course.CourseId;
|
||||
ProviderDisplayName = course.ProviderDisplayName;
|
||||
CourseName = course.CourseName;
|
||||
BestAccuracyPercent = course.BestAccuracyPercent;
|
||||
TrophyRank = course.BestTrophyRank;
|
||||
TrophyName = course.BestTrophyName;
|
||||
}
|
||||
|
||||
public string CourseId { get; }
|
||||
public string ProviderDisplayName { get; }
|
||||
public string CourseName { get; }
|
||||
public double BestAccuracyPercent { get; }
|
||||
public int TrophyRank { get; }
|
||||
public string TrophyName { get; }
|
||||
public string CertificationLabel => string.IsNullOrWhiteSpace(ProviderDisplayName)
|
||||
? CourseName
|
||||
: $"{ProviderDisplayName} // {CourseName}";
|
||||
}
|
||||
|
||||
public sealed class ProfileCourseSnapshot
|
||||
{
|
||||
public static readonly ProfileCourseSnapshot Empty = new ProfileCourseSnapshot(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
Array.Empty<CourseCollectionEntry>());
|
||||
|
||||
public ProfileCourseSnapshot(
|
||||
int recordedCourses,
|
||||
int clearedCourses,
|
||||
int totalPlayCount,
|
||||
IReadOnlyList<CourseCollectionEntry> entries,
|
||||
string featuredBadgeCourseId = null)
|
||||
{
|
||||
RecordedCourses = Math.Max(0, recordedCourses);
|
||||
ClearedCourses = Math.Max(0, clearedCourses);
|
||||
TotalPlayCount = Math.Max(0, totalPlayCount);
|
||||
Entries = entries ?? Array.Empty<CourseCollectionEntry>();
|
||||
CertificationBadges = BuildCertificationBadges(Entries);
|
||||
FeaturedBadgeCourseId = ResolveFeaturedBadgeCourseId(
|
||||
CertificationBadges,
|
||||
featuredBadgeCourseId);
|
||||
}
|
||||
|
||||
public int RecordedCourses { get; }
|
||||
public int ClearedCourses { get; }
|
||||
public int TotalPlayCount { get; }
|
||||
public IReadOnlyList<CourseCollectionEntry> Entries { get; }
|
||||
public IReadOnlyList<CourseCertificationBadge> CertificationBadges { get; }
|
||||
public string FeaturedBadgeCourseId { get; }
|
||||
public int BadgeCount => CertificationBadges.Count;
|
||||
public bool HasFeaturedBadge => BadgeCount > 0;
|
||||
public CourseCertificationBadge FeaturedBadge
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var badge in CertificationBadges)
|
||||
{
|
||||
if (string.Equals(badge.CourseId, FeaturedBadgeCourseId, StringComparison.OrdinalIgnoreCase))
|
||||
return badge;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<CourseCertificationBadge> BuildCertificationBadges(
|
||||
IReadOnlyList<CourseCollectionEntry> entries)
|
||||
{
|
||||
var badges = new List<CourseCertificationBadge>();
|
||||
foreach (var entry in entries ?? Array.Empty<CourseCollectionEntry>())
|
||||
{
|
||||
if (entry.Cleared)
|
||||
badges.Add(new CourseCertificationBadge(entry));
|
||||
}
|
||||
return badges;
|
||||
}
|
||||
|
||||
private static string ResolveFeaturedBadgeCourseId(
|
||||
IReadOnlyList<CourseCertificationBadge> badges,
|
||||
string requestedCourseId)
|
||||
{
|
||||
var requested = (requestedCourseId ?? string.Empty).Trim();
|
||||
foreach (var badge in badges ?? Array.Empty<CourseCertificationBadge>())
|
||||
{
|
||||
if (string.Equals(badge.CourseId, requested, StringComparison.OrdinalIgnoreCase))
|
||||
return badge.CourseId;
|
||||
}
|
||||
return badges != null && badges.Count > 0 ? badges[0].CourseId : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abc0e008a277e4267aad53209773e6bb
|
||||
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using ProjectQuint.Core.Charts;
|
||||
|
||||
namespace ProjectQuint.Core.Progression
|
||||
{
|
||||
public readonly struct RatingContribution
|
||||
{
|
||||
public RatingContribution(string chartId, double playRating)
|
||||
{
|
||||
ChartId = chartId ?? string.Empty;
|
||||
PlayRating = Math.Max(0.0, playRating);
|
||||
}
|
||||
|
||||
public string ChartId { get; }
|
||||
public double PlayRating { get; }
|
||||
}
|
||||
|
||||
public readonly struct RatingChartEntry
|
||||
{
|
||||
public RatingChartEntry(
|
||||
string chartId,
|
||||
string title,
|
||||
string artist,
|
||||
string difficultyLabel,
|
||||
double accuracyPercent,
|
||||
bool hardClear,
|
||||
double playRating)
|
||||
{
|
||||
ChartId = chartId ?? string.Empty;
|
||||
Title = string.IsNullOrWhiteSpace(title) ? "UNKNOWN CHART" : title;
|
||||
Artist = artist ?? string.Empty;
|
||||
DifficultyLabel = difficultyLabel ?? string.Empty;
|
||||
AccuracyPercent = Math.Max(0.0, Math.Min(100.0, accuracyPercent));
|
||||
HardClear = hardClear;
|
||||
PlayRating = Math.Max(0.0, playRating);
|
||||
}
|
||||
|
||||
public string ChartId { get; }
|
||||
public string Title { get; }
|
||||
public string Artist { get; }
|
||||
public string DifficultyLabel { get; }
|
||||
public double AccuracyPercent { get; }
|
||||
public bool HardClear { get; }
|
||||
public double PlayRating { get; }
|
||||
}
|
||||
|
||||
public readonly struct RatingSummary
|
||||
{
|
||||
public RatingSummary(
|
||||
string providerId,
|
||||
string providerDisplayName,
|
||||
int keyCount,
|
||||
bool hasScratch,
|
||||
double value,
|
||||
int filledSlots,
|
||||
IReadOnlyList<RatingChartEntry> bestPlays = null,
|
||||
int activityPlayCount = 0)
|
||||
{
|
||||
ProviderId = (providerId ?? string.Empty).Trim();
|
||||
ProviderDisplayName = string.IsNullOrWhiteSpace(providerDisplayName)
|
||||
? ProviderId
|
||||
: providerDisplayName.Trim();
|
||||
KeyCount = Math.Max(0, keyCount);
|
||||
HasScratch = hasScratch;
|
||||
Value = Math.Max(0.0, value);
|
||||
FilledSlots = Math.Max(0, Math.Min(RatingRules.BestCount, filledSlots));
|
||||
BestPlays = bestPlays ?? Array.Empty<RatingChartEntry>();
|
||||
ActivityPlayCount = Math.Max(0, activityPlayCount);
|
||||
}
|
||||
|
||||
public string ProviderId { get; }
|
||||
public string ProviderDisplayName { get; }
|
||||
public int KeyCount { get; }
|
||||
public bool HasScratch { get; }
|
||||
public double Value { get; }
|
||||
public int FilledSlots { get; }
|
||||
public IReadOnlyList<RatingChartEntry> BestPlays { get; }
|
||||
public int ActivityPlayCount { get; }
|
||||
public bool HasContext =>
|
||||
!string.IsNullOrWhiteSpace(ProviderId) &&
|
||||
KeyCount >= 4 && KeyCount <= 7;
|
||||
|
||||
public string ContextLabel => !HasContext
|
||||
? "RATING"
|
||||
: $"{ProviderDisplayName.ToUpperInvariant()} {RatingRules.KeyModeLabel(KeyCount, HasScratch)} RATING";
|
||||
}
|
||||
|
||||
public readonly struct RatingUpdate
|
||||
{
|
||||
public RatingUpdate(
|
||||
bool eligible,
|
||||
double playRating,
|
||||
RatingSummary previous,
|
||||
RatingSummary current)
|
||||
{
|
||||
Eligible = eligible;
|
||||
PlayRating = Math.Max(0.0, playRating);
|
||||
Previous = previous;
|
||||
Current = current;
|
||||
}
|
||||
|
||||
public bool Eligible { get; }
|
||||
public double PlayRating { get; }
|
||||
public RatingSummary Previous { get; }
|
||||
public RatingSummary Current { get; }
|
||||
public double Delta => Current.Value - Previous.Value;
|
||||
}
|
||||
|
||||
public readonly struct ProfileProgressSnapshot
|
||||
{
|
||||
public ProfileProgressSnapshot(
|
||||
string displayName,
|
||||
long totalXp,
|
||||
int validPlayCount,
|
||||
double activePlaySeconds,
|
||||
string lastRatingProviderId,
|
||||
string lastRatingProviderDisplayName,
|
||||
int lastRatingKeyCount,
|
||||
bool lastRatingHasScratch)
|
||||
{
|
||||
DisplayName = string.IsNullOrWhiteSpace(displayName) ? "PLAYER" : displayName;
|
||||
TotalXp = Math.Max(0, totalXp);
|
||||
Level = ProfileLevelRules.LevelForTotalXp(TotalXp);
|
||||
LevelStartXp = ProfileLevelRules.TotalXpForLevel(Level);
|
||||
NextLevelXp = ProfileLevelRules.TotalXpForLevel(Level + 1);
|
||||
ValidPlayCount = Math.Max(0, validPlayCount);
|
||||
ActivePlaySeconds = Math.Max(0.0, activePlaySeconds);
|
||||
LastRatingProviderId = (lastRatingProviderId ?? string.Empty).Trim();
|
||||
LastRatingProviderDisplayName = string.IsNullOrWhiteSpace(lastRatingProviderDisplayName)
|
||||
? LastRatingProviderId
|
||||
: lastRatingProviderDisplayName.Trim();
|
||||
LastRatingKeyCount = Math.Max(0, lastRatingKeyCount);
|
||||
LastRatingHasScratch = lastRatingHasScratch;
|
||||
}
|
||||
|
||||
public string DisplayName { get; }
|
||||
public long TotalXp { get; }
|
||||
public int Level { get; }
|
||||
public long LevelStartXp { get; }
|
||||
public long NextLevelXp { get; }
|
||||
public int ValidPlayCount { get; }
|
||||
public double ActivePlaySeconds { get; }
|
||||
public string LastRatingProviderId { get; }
|
||||
public string LastRatingProviderDisplayName { get; }
|
||||
public int LastRatingKeyCount { get; }
|
||||
public bool LastRatingHasScratch { get; }
|
||||
public long XpIntoLevel => TotalXp - LevelStartXp;
|
||||
public long XpForLevel => Math.Max(1, NextLevelXp - LevelStartXp);
|
||||
public double LevelProgress => Math.Max(0.0, Math.Min(1.0, XpIntoLevel / (double)XpForLevel));
|
||||
}
|
||||
|
||||
public readonly struct ProfileProgressUpdate
|
||||
{
|
||||
public ProfileProgressUpdate(
|
||||
bool applied,
|
||||
int xpAwarded,
|
||||
ProfileProgressSnapshot previous,
|
||||
ProfileProgressSnapshot current,
|
||||
bool firstPlay,
|
||||
bool firstNormalClear,
|
||||
bool firstHardClear,
|
||||
bool firstFullCombo,
|
||||
bool firstAllPure)
|
||||
{
|
||||
Applied = applied;
|
||||
XpAwarded = Math.Max(0, xpAwarded);
|
||||
Previous = previous;
|
||||
Current = current;
|
||||
FirstPlay = firstPlay;
|
||||
FirstNormalClear = firstNormalClear;
|
||||
FirstHardClear = firstHardClear;
|
||||
FirstFullCombo = firstFullCombo;
|
||||
FirstAllPure = firstAllPure;
|
||||
}
|
||||
|
||||
public bool Applied { get; }
|
||||
public int XpAwarded { get; }
|
||||
public ProfileProgressSnapshot Previous { get; }
|
||||
public ProfileProgressSnapshot Current { get; }
|
||||
public bool FirstPlay { get; }
|
||||
public bool FirstNormalClear { get; }
|
||||
public bool FirstHardClear { get; }
|
||||
public bool FirstFullCombo { get; }
|
||||
public bool FirstAllPure { get; }
|
||||
public bool LeveledUp => Current.Level > Previous.Level;
|
||||
}
|
||||
|
||||
public static class RatingRules
|
||||
{
|
||||
public const int RulesVersion = 5;
|
||||
public const int BestCount = 30;
|
||||
public const double HardClearBonus = 0.50;
|
||||
public const double MinimumChartConstant = 0.50;
|
||||
|
||||
private static readonly double[] AccuracyAnchors =
|
||||
{
|
||||
0.0, 80.0, 90.0, 95.0, 97.0, 98.0, 99.0, 99.5, 100.0,
|
||||
};
|
||||
|
||||
private static readonly double[] FactorAnchors =
|
||||
{
|
||||
0.0, 0.30, 0.60, 0.80, 0.88, 0.93, 0.97, 0.985, 1.0,
|
||||
};
|
||||
|
||||
public static double AccuracyFactor(double accuracyPercent)
|
||||
{
|
||||
var accuracy = Math.Max(0.0, Math.Min(100.0, accuracyPercent));
|
||||
for (var index = 1; index < AccuracyAnchors.Length; index++)
|
||||
{
|
||||
if (accuracy > AccuracyAnchors[index])
|
||||
continue;
|
||||
|
||||
var start = AccuracyAnchors[index - 1];
|
||||
var end = AccuracyAnchors[index];
|
||||
var progress = end <= start ? 0.0 : (accuracy - start) / (end - start);
|
||||
return FactorAnchors[index - 1] +
|
||||
(FactorAnchors[index] - FactorAnchors[index - 1]) * progress;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
public static double CalculatePlayRating(
|
||||
double chartConstant,
|
||||
double accuracyPercent,
|
||||
bool hardClear)
|
||||
{
|
||||
if (chartConstant <= 0.0)
|
||||
return 0.0;
|
||||
return chartConstant * AccuracyFactor(accuracyPercent) +
|
||||
(hardClear ? HardClearBonus : 0.0);
|
||||
}
|
||||
|
||||
public static RatingSummary CalculateBest30(
|
||||
string providerId,
|
||||
string providerDisplayName,
|
||||
int keyCount,
|
||||
bool hasScratch,
|
||||
IEnumerable<RatingContribution> contributions)
|
||||
{
|
||||
var bestByChart = new Dictionary<string, double>(StringComparer.Ordinal);
|
||||
foreach (var item in contributions ?? Array.Empty<RatingContribution>())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.ChartId) || item.PlayRating <= 0.0)
|
||||
continue;
|
||||
if (!bestByChart.TryGetValue(item.ChartId, out var current) || item.PlayRating > current)
|
||||
bestByChart[item.ChartId] = item.PlayRating;
|
||||
}
|
||||
|
||||
var best = bestByChart.Values
|
||||
.OrderByDescending(value => value)
|
||||
.Take(BestCount)
|
||||
.ToArray();
|
||||
return new RatingSummary(
|
||||
providerId,
|
||||
providerDisplayName,
|
||||
keyCount,
|
||||
hasScratch,
|
||||
best.Sum() / BestCount,
|
||||
best.Length);
|
||||
}
|
||||
|
||||
public static string KeyModeLabel(int keyCount, bool hasScratch) =>
|
||||
ChartKeyMode.FormatLabel(keyCount, hasScratch);
|
||||
|
||||
public static bool TryResolveChartConstant(
|
||||
string difficultyValue,
|
||||
string difficultyLabel,
|
||||
out double chartConstant)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(difficultyValue))
|
||||
return TryParseChartConstant(difficultyValue, out chartConstant);
|
||||
return TryParseChartConstant(difficultyLabel, out chartConstant);
|
||||
}
|
||||
|
||||
public static bool TryParseChartConstant(string difficultyText, out double chartConstant)
|
||||
{
|
||||
chartConstant = 0.0;
|
||||
var label = (difficultyText ?? string.Empty).Trim();
|
||||
if (label.Length == 0)
|
||||
return false;
|
||||
|
||||
var upper = label.ToUpperInvariant();
|
||||
if (upper.Contains("GIMMICK") || upper.Contains("SPECIAL") ||
|
||||
upper.Contains("DUMMY") || upper.Contains("OLD"))
|
||||
return false;
|
||||
|
||||
var numericEnd = label.Length;
|
||||
var plus = false;
|
||||
if (numericEnd > 0 && label[numericEnd - 1] == '+')
|
||||
{
|
||||
plus = true;
|
||||
numericEnd--;
|
||||
while (numericEnd > 0 && char.IsWhiteSpace(label[numericEnd - 1]))
|
||||
numericEnd--;
|
||||
}
|
||||
|
||||
var start = numericEnd;
|
||||
var dotSeen = false;
|
||||
var digitSeen = false;
|
||||
while (start > 0)
|
||||
{
|
||||
var character = label[start - 1];
|
||||
if (char.IsDigit(character))
|
||||
{
|
||||
digitSeen = true;
|
||||
start--;
|
||||
continue;
|
||||
}
|
||||
if (character == '.' && !dotSeen)
|
||||
{
|
||||
dotSeen = true;
|
||||
start--;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!digitSeen)
|
||||
return false;
|
||||
if (start > 0 && (label[start - 1] == '-' || label[start - 1] == '.'))
|
||||
return false;
|
||||
|
||||
if (!double.TryParse(
|
||||
label.Substring(start, numericEnd - start),
|
||||
NumberStyles.AllowDecimalPoint,
|
||||
CultureInfo.InvariantCulture,
|
||||
out chartConstant) ||
|
||||
chartConstant < 0.0 ||
|
||||
double.IsNaN(chartConstant) ||
|
||||
double.IsInfinity(chartConstant))
|
||||
{
|
||||
chartConstant = 0.0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some established tables start at level 0 (for example Satellite
|
||||
// sl0). It is a real lowest tier, not a missing difficulty value.
|
||||
// A positive half-step keeps it below level 1 while allowing it to
|
||||
// participate in the same rating pipeline.
|
||||
if (chartConstant <= 0.0)
|
||||
chartConstant = MinimumChartConstant;
|
||||
|
||||
if (plus &&
|
||||
Math.Abs(chartConstant - Math.Round(chartConstant)) < 0.000001)
|
||||
chartConstant += 0.5;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProfileLevelRules
|
||||
{
|
||||
public const int XpRulesVersion = 1;
|
||||
|
||||
public static long TotalXpForLevel(int level)
|
||||
{
|
||||
var normalized = Math.Max(1, level) - 1L;
|
||||
return 20L * normalized * normalized + 50L * normalized;
|
||||
}
|
||||
|
||||
public static int NextLevelXp(int level) => checked(40 * Math.Max(1, level) + 30);
|
||||
|
||||
public static int LevelForTotalXp(long totalXp)
|
||||
{
|
||||
var xp = Math.Max(0L, totalXp);
|
||||
var low = 1;
|
||||
var high = 2;
|
||||
while (TotalXpForLevel(high) <= xp && high < 1_000_000)
|
||||
high *= 2;
|
||||
while (low + 1 < high)
|
||||
{
|
||||
var middle = low + (high - low) / 2;
|
||||
if (TotalXpForLevel(middle) <= xp)
|
||||
low = middle;
|
||||
else
|
||||
high = middle;
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
public static int CalculateBaseXp(double activePlaySeconds, int judgedUnits)
|
||||
{
|
||||
var value = 20 +
|
||||
(int)Math.Floor(Math.Max(0.0, activePlaySeconds) / 10.0) * 4 +
|
||||
Math.Max(0, judgedUnits) / 100 * 2;
|
||||
return Math.Max(20, Math.Min(100, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 459ea391724314c1397e5b3531e642d3
|
||||
Reference in New Issue
Block a user