diff --git a/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiDifficultyAttributes.cs b/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiDifficultyAttributes.cs new file mode 100644 index 000000000..5728d5d3c --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiDifficultyAttributes.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; + +namespace osu.Game.Rulesets.Sentakki.Difficulty +{ + public class SentakkiDifficultyAttributes : DifficultyAttributes + { + public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() + { + foreach (var attribute in base.ToDatabaseAttributes()) + yield return attribute; + + yield return (ATTRIB_ID_DIFFICULTY, StarRating); + } + + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) + { + base.FromDatabaseAttributes(values, onlineInfo); + + StarRating = values[ATTRIB_ID_DIFFICULTY]; + } + } +} diff --git a/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiDifficultyCalculator.cs b/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiDifficultyCalculator.cs index 95673ab91..619a7aa32 100644 --- a/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiDifficultyCalculator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; @@ -5,28 +6,31 @@ using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; -namespace osu.Game.Rulesets.Sentakki.Difficulty; - -public class SentakkiDifficultyCalculator : DifficultyCalculator +namespace osu.Game.Rulesets.Sentakki.Difficulty { - public SentakkiDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) - : base(ruleset, beatmap) + public class SentakkiDifficultyCalculator : DifficultyCalculator { - } - - protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) - { - int maxCombo = beatmap.GetMaxCombo(); + public SentakkiDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } - return new DifficultyAttributes + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - StarRating = beatmap.BeatmapInfo.StarRating * 1.25f, // Inflate SR of converts, to encourage players to try lower diffs, without hurting their fragile ego. - Mods = mods, - MaxCombo = maxCombo - }; - } + int maxCombo = beatmap.GetMaxCombo(); + + double baseSR = beatmap.BeatmapInfo.StarRating * clockRate; - protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => []; + return new SentakkiDifficultyAttributes + { + StarRating = baseSR * 1.25f, // Inflate SR of converts, to encourage players to try lower diffs, without hurting their fragile ego. + Mods = mods, + MaxCombo = maxCombo + }; + } - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => []; + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Array.Empty(); + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty(); + } } diff --git a/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiPerformanceAttributes.cs b/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiPerformanceAttributes.cs new file mode 100644 index 000000000..ee42fcca0 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiPerformanceAttributes.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Rulesets.Difficulty; + +namespace osu.Game.Rulesets.Sentakki.Difficulty +{ + public class SentakkiPerformanceAttributes : PerformanceAttributes + { + [JsonProperty("base_pp")] + public double Base_PP { get; set; } + + [JsonProperty("length_bonus")] + public double Length_Bonus { get; set; } + + public override IEnumerable GetAttributesForDisplay() + { + foreach (var attribute in base.GetAttributesForDisplay()) + { + yield return attribute; + } + + yield return new PerformanceDisplayAttribute(nameof(Base_PP), "Base PP", Base_PP); + yield return new PerformanceDisplayAttribute(nameof(Length_Bonus), "Length Bonus", Length_Bonus); + } + } +} diff --git a/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiPerformanceCalculator.cs b/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiPerformanceCalculator.cs new file mode 100644 index 000000000..8582a1fde --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Difficulty/SentakkiPerformanceCalculator.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + + +namespace osu.Game.Rulesets.Sentakki.Difficulty +{ + public class SentakkiPerformanceCalculator : PerformanceCalculator + { + public SentakkiPerformanceCalculator() + : base(new SentakkiRuleset()) { } + + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) + { + var sentakkiAttributes = (SentakkiDifficultyAttributes)attributes; + double accuracy = score.Accuracy; + int countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + + double baseValue = Math.Pow((5.0f * Math.Max(1.0f, sentakkiAttributes.StarRating / 0.0049f)) - 4.0f, 2.0f) / 100000.0f; + double value = baseValue; + double lengthBonus = 0.95 + ((0.3 * Math.Min(1.0, sentakkiAttributes.MaxCombo / 2500.0)) + (sentakkiAttributes.MaxCombo > 2500 ? Math.Log10(sentakkiAttributes.MaxCombo / 2500.0f) * 0.475f : 0.0f)); + value *= lengthBonus; + value *= Math.Pow(0.97, countMiss); + if (sentakkiAttributes.MaxCombo > 0) + value *= Math.Min(Math.Pow(score.MaxCombo, 0.35f) / Math.Pow(sentakkiAttributes.MaxCombo, 0.35f), 1.0f); + value *= Math.Pow(accuracy, 5.5); + //Until difficulty calculation is implemented, + //multiply combo progress instead of strain. + //(I think it is strange that the first combo gets 600pp.) + double comboProgress = (double)sentakkiAttributes.MaxCombo / score.GetMaximumAchievableCombo(); + double totalValue = value * comboProgress; + + return new SentakkiPerformanceAttributes + { + Base_PP = baseValue, + Length_Bonus = lengthBonus, + Total = totalValue + }; + } + } +} diff --git a/osu.Game.Rulesets.Sentakki/SentakkiPerformanceCalculator.cs b/osu.Game.Rulesets.Sentakki/SentakkiPerformanceCalculator.cs deleted file mode 100644 index e6dbcd96a..000000000 --- a/osu.Game.Rulesets.Sentakki/SentakkiPerformanceCalculator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using osu.Game.Rulesets.Difficulty; -using osu.Game.Scoring; - -namespace osu.Game.Rulesets.Sentakki; - -public class SentakkiPerformanceCalculator : PerformanceCalculator -{ - public SentakkiPerformanceCalculator(SentakkiRuleset ruleset) - : base(ruleset) - { - } - - // TODO: Create an actual performance calculator - protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) => new PerformanceAttributes { Total = 0 }; -} diff --git a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs index e5a7a89aa..20e4c0996 100644 --- a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs +++ b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Sentakki.Beatmaps; using osu.Game.Rulesets.Sentakki.Configuration; using osu.Game.Rulesets.Sentakki.Difficulty; -using osu.Game.Rulesets.Sentakki.Extensions; using osu.Game.Rulesets.Sentakki.Localisation; using osu.Game.Rulesets.Sentakki.Mods; using osu.Game.Rulesets.Sentakki.Objects; @@ -38,213 +37,221 @@ using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Sentakki; - -public partial class SentakkiRuleset : Ruleset +namespace osu.Game.Rulesets.Sentakki { - public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; - - private static readonly Lazy is_development_build - = new Lazy(() => typeof(SentakkiRuleset).Assembly.GetCustomAttributes(false).OfType().Any(da => da.IsJITTrackingEnabled)); + public partial class SentakkiRuleset : Ruleset + { + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; - public static bool IsDevelopmentBuild => is_development_build.Value; + private static readonly Lazy is_development_build + = new Lazy(() => typeof(SentakkiRuleset).Assembly.GetCustomAttributes(false).OfType().Any(da => da.IsJITTrackingEnabled)); - public override string Description => IsDevelopmentBuild ? "sentakki (Dev build)" : "sentakki"; - public override string PlayingVerb => "Washing laundry"; - public override string ShortName => "Sentakki"; + public static bool IsDevelopmentBuild => is_development_build.Value; - public override IEnumerable GetBeatmapAttributesForDisplay(IBeatmapInfo beatmapInfo, IReadOnlyCollection mods) => []; + public override string Description => IsDevelopmentBuild ? "sentakki (Dev build)" : "sentakki"; + public override string PlayingVerb => "Washing laundry"; + public override string ShortName => "Sentakki"; - public override ScoreProcessor CreateScoreProcessor() => new SentakkiScoreProcessor(this); - public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new SentakkiHealthProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new SentakkiScoreProcessor(this); + public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new SentakkiHealthProcessor(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => - new DrawableSentakkiRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => + new DrawableSentakkiRuleset(this, beatmap, mods); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => - new CompositeBeatmapConverter(beatmap, this); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => + new CompositeBeatmapConverter(beatmap, this); - public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => - new SentakkiBeatmapProcessor(beatmap); + public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => + new SentakkiBeatmapProcessor(beatmap); - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => - new SentakkiDifficultyCalculator(RulesetInfo, beatmap); + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => + new SentakkiDifficultyCalculator(RulesetInfo, beatmap); - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new SentakkiReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new SentakkiReplayFrame(); - public override PerformanceCalculator CreatePerformanceCalculator() => new SentakkiPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new SentakkiPerformanceCalculator(); - public override IEnumerable GetModsFor(ModType type) - { - switch (type) + public override IEnumerable GetModsFor(ModType type) { - case ModType.DifficultyReduction: - return - [ - new MultiMod(new SentakkiModHalfTime(), new SentakkiModDaycore()) - ]; - - case ModType.DifficultyIncrease: - return - [ - new SentakkiModHardRock(), - new MultiMod(new SentakkiModSuddenDeath(), new SentakkiModPerfect()), - new MultiMod(new SentakkiModChallenge(), new SentakkiModAccuracyChallenge()), - new MultiMod(new SentakkiModDoubleTime(), new SentakkiModNightcore()), - new SentakkiModHidden() - ]; - - case ModType.Automation: - return - [ - new SentakkiModAutoplay(), - new SentakkiModNoTouch() - ]; - - case ModType.Conversion: - return - [ - new SentakkiModExperimental(), - new SentakkiModClassic(), - new SentakkiModMirror(), - new SentakkiModDifficultyAdjust() - ]; - - case ModType.Fun: - return - [ - new MultiMod(new ModWindUp(), new ModWindDown()), - new SentakkiModBarrelRoll(), - new SentakkiModMuted(), - new ModAdaptiveSpeed(), - new SentakkiModSynesthesia() - ]; - - case ModType.System: - return - [ - new SentakkiModTouchDevice() - ]; - - default: - return []; + switch (type) + { + case ModType.DifficultyReduction: + return new Mod[] + { + new MultiMod(new SentakkiModHalfTime(), new SentakkiModDaycore()), + }; + + case ModType.DifficultyIncrease: + return new Mod[] + { + new SentakkiModHardRock(), + new MultiMod(new SentakkiModSuddenDeath(), new SentakkiModPerfect()), + new MultiMod(new SentakkiModChallenge(), new SentakkiModAccuracyChallenge()), + new MultiMod(new SentakkiModDoubleTime(), new SentakkiModNightcore()), + new SentakkiModHidden(), + }; + + case ModType.Automation: + return new Mod[] + { + new SentakkiModAutoplay(), + new SentakkiModAutoTouch() + }; + + case ModType.Conversion: + return new Mod[] + { + new SentakkiModExperimental(), + new SentakkiModClassic(), + new SentakkiModMirror(), + new SentakkiModDifficultyAdjust() + }; + + case ModType.Fun: + return new Mod[] + { + new MultiMod(new ModWindUp(), new ModWindDown()), + new SentakkiModBarrelRoll(), + new SentakkiModMuted(), + new ModAdaptiveSpeed(), + new SentakkiModSynesthesia(), + }; + + case ModType.System: + return new Mod[] + { + new SentakkiModTouchDevice() + }; + + default: + return []; + } } - } - public override RulesetSettingsSubsection CreateSettings() => new SentakkiSettingsSubsection(this); - - public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new SentakkiRulesetConfigManager(settings, RulesetInfo); - - public override IEnumerable GetDefaultKeyBindings(int variant = 0) => - [ - new KeyBinding(InputKey.Z, SentakkiAction.Button1), - new KeyBinding(InputKey.X, SentakkiAction.Button2), - new KeyBinding(InputKey.MouseLeft, SentakkiAction.Button1), - new KeyBinding(InputKey.MouseRight, SentakkiAction.Button2), - new KeyBinding(InputKey.Number1, SentakkiAction.Key1), - new KeyBinding(InputKey.Number2, SentakkiAction.Key2), - new KeyBinding(InputKey.Number3, SentakkiAction.Key3), - new KeyBinding(InputKey.Number4, SentakkiAction.Key4), - new KeyBinding(InputKey.Number5, SentakkiAction.Key5), - new KeyBinding(InputKey.Number6, SentakkiAction.Key6), - new KeyBinding(InputKey.Number7, SentakkiAction.Key7), - new KeyBinding(InputKey.Number8, SentakkiAction.Key8) - ]; - - public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => - [ - new StatisticItem(SentakkiStatisticsStrings.TimingDistribution, () => new HitEventTimingDistributionGraph(score.HitEvents) + public override RulesetSettingsSubsection CreateSettings() => new SentakkiSettingsSubsection(this); + + public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new SentakkiRulesetConfigManager(settings, RulesetInfo); + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - RelativeSizeAxes = Axes.X, - Height = 250 - }, true), + new KeyBinding(InputKey.Z, SentakkiAction.Button1), + new KeyBinding(InputKey.X, SentakkiAction.Button2), + new KeyBinding(InputKey.MouseLeft, SentakkiAction.Button1), + new KeyBinding(InputKey.MouseRight, SentakkiAction.Button2), + new KeyBinding(InputKey.Number1, SentakkiAction.Key1), + new KeyBinding(InputKey.Number2, SentakkiAction.Key2), + new KeyBinding(InputKey.Number3, SentakkiAction.Key3), + new KeyBinding(InputKey.Number4, SentakkiAction.Key4), + new KeyBinding(InputKey.Number5, SentakkiAction.Key5), + new KeyBinding(InputKey.Number6, SentakkiAction.Key6), + new KeyBinding(InputKey.Number7, SentakkiAction.Key7), + new KeyBinding(InputKey.Number8, SentakkiAction.Key8), + }; - new StatisticItem(SentakkiStatisticsStrings.JudgementChart, () => new JudgementChart([.. score.HitEvents.Where(e => e.HitObject is SentakkiHitObject)]) + public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { - RelativeSizeAxes = Axes.X, - Size = new Vector2(1, 250) - }, true), - - new StatisticItem(string.Empty, () => new SimpleStatisticTable(2, [ - new AverageHitError(score.HitEvents), - new UnstableRate(score.HitEvents) - ]), true) - ]; - - public override Drawable CreateIcon() => - new ConstrainedIconContainer + new StatisticItem(SentakkiStatisticsStrings.TimingDistribution, () => new HitEventTimingDistributionGraph(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), + + new StatisticItem(SentakkiStatisticsStrings.JudgementChart, () => new JudgementChart(score.HitEvents.Where(e => e.HitObject is SentakkiHitObject).ToList()) + { + RelativeSizeAxes = Axes.X, + Size = new Vector2(1, 250) + }, true), + + new StatisticItem(string.Empty, () => new SimpleStatisticTable(2, new SimpleStatisticItem[] + { + new AverageHitError(score.HitEvents), + new UnstableRate(score.HitEvents), + }), true) + }; + + public override Drawable CreateIcon() => + new ConstrainedIconContainer() { Icon = new SentakkiIcon(this), Size = new Vector2(100, 100) }; - protected override IEnumerable GetValidHitResults() - => [HitResult.Perfect, HitResult.Great, HitResult.Good, HitResult.Ok]; - - public override LocalisableString GetDisplayNameForHitResult(HitResult result) => result.GetDisplayNameForSentakkiResult(); - - public partial class SentakkiIcon : CompositeDrawable - { - private readonly Ruleset ruleset; - - public SentakkiIcon(Ruleset ruleset) + protected override IEnumerable GetValidHitResults() { - Anchor = Origin = Anchor.Centre; - this.ruleset = ruleset; - FillAspectRatio = 1; - FillMode = FillMode.Fit; - Size = new Vector2(100, 100); + return new[] + { + HitResult.Perfect, + HitResult.Great, + HitResult.Good, + HitResult.Ok, + }; } - // We don't want to generate a new texture store everytime this used, so we create a single texture store for all usages of this icon. - private static LargeTextureStore? textureStore; + public override LocalisableString GetDisplayNameForHitResult(HitResult result) => result.GetDisplayNameForSentakkiResult(); - [BackgroundDependencyLoader] - private void load(GameHost host) + public partial class SentakkiIcon : CompositeDrawable { - textureStore ??= new LargeTextureStore(host.Renderer, host.CreateTextureLoaderStore(ruleset.CreateResourceStore())); + private readonly Ruleset ruleset; - AddInternal(new Sprite + public SentakkiIcon(Ruleset ruleset) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Texture = textureStore.Get("Textures/SentakkiIcon.png"), - }); + Anchor = Origin = Anchor.Centre; + this.ruleset = ruleset; + FillAspectRatio = 1; + FillMode = FillMode.Fit; + Size = new Vector2(100, 100); + } + + // We don't want to generate a new texture store everytime this used, so we create a single texture store for all usages of this icon. + private static LargeTextureStore? textureStore = null!; - if (IsDevelopmentBuild) + [BackgroundDependencyLoader] + private void load(GameHost host) { - AddInternal(new Container + textureStore ??= new LargeTextureStore(host.Renderer, host.CreateTextureLoaderStore(ruleset.CreateResourceStore())); + + AddInternal(new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Texture = textureStore.Get("Textures/SentakkiIcon.png"), + }); + + if (IsDevelopmentBuild) { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Size = new Vector2(60, 35), - Children = - [ - // Used to offset the fonts being misaligned - new Container + AddInternal(new Container + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Size = new Vector2(60, 35), + Children = new Drawable[] { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(60, 32), - CornerRadius = 8f, - CornerExponent = 2.5f, - Masking = true, - Child = new Box + // Used to offset the fonts being misaligned + new Container { - RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(60, 32), + CornerRadius = 8f, + CornerExponent = 2.5f, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, }, - }, - new SpriteText - { - Text = "DEV", - Colour = Color4.Gray, - Font = OsuFont.Torus.With(size: 32, weight: FontWeight.Bold), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + new SpriteText + { + Text = "DEV", + Colour = Color4.Gray, + Font = OsuFont.Torus.With(size: 32, weight: FontWeight.Bold), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } - ] - }); + }); + } } } }