1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-20 19:00:15 +08:00

Compare commits

...

35 Commits

20 changed files with 396 additions and 101 deletions
@@ -1,10 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
@@ -12,14 +13,68 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
public partial class OsuHealthProcessor : DrainingHealthProcessor
{
private ComboResult currentComboResult = ComboResult.Perfect;
public OsuHealthProcessor(double drainStartTime, double drainLenience = 0)
: base(drainStartTime, drainLenience)
{
}
protected override int? GetDensityGroup(HitObject hitObject) => (hitObject as IHasComboInformation)?.ComboIndex;
protected override double GetHealthIncreaseFor(JudgementResult result)
{
if (IsSimulating)
return getHealthIncreaseFor(result);
if (result.HitObject is not IHasComboInformation combo)
return getHealthIncreaseFor(result);
if (combo.NewCombo)
currentComboResult = ComboResult.Perfect;
switch (result.Type)
{
case HitResult.LargeTickMiss:
case HitResult.Ok:
setComboResult(ComboResult.Good);
break;
case HitResult.Meh:
case HitResult.Miss:
setComboResult(ComboResult.None);
break;
}
// The slider tail has a special judgement that can't accurately be described above.
if (result.HitObject is SliderTailCircle && !result.IsHit)
setComboResult(ComboResult.Good);
if (combo.LastInCombo && result.Type.IsHit())
{
switch (currentComboResult)
{
case ComboResult.Perfect:
return getHealthIncreaseFor(result) + 0.07;
case ComboResult.Good:
return getHealthIncreaseFor(result) + 0.05;
default:
return getHealthIncreaseFor(result) + 0.03;
}
}
return getHealthIncreaseFor(result);
void setComboResult(ComboResult comboResult) => currentComboResult = (ComboResult)Math.Min((int)currentComboResult, (int)comboResult);
}
protected override void Reset(bool storeResults)
{
base.Reset(storeResults);
currentComboResult = ComboResult.Perfect;
}
private double getHealthIncreaseFor(JudgementResult result)
{
switch (result.Type)
{
@@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
decimal? legacyVersion = skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value;
if (legacyVersion >= 2.0m)
if (legacyVersion > 1.0m)
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
hitCircleText.FadeOut(legacy_fade_duration / 4);
else
@@ -81,6 +81,21 @@ namespace osu.Game.Tests.Visual.Online
AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence());
}
[Test]
public void TestUserWasPlayingBeforeWatchingUserPresence()
{
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
AddStep("Begin watching user presence", () => metadataClient.BeginWatchingUserPresence());
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence());
}
internal partial class TestUserLookupCache : UserLookupCache
{
private static readonly string[] usernames =
@@ -3,6 +3,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@@ -14,6 +15,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Extensions;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
@@ -194,6 +196,36 @@ namespace osu.Game.Tests.Visual.SongSelect
});
}
[TestCase]
public void TestLengthUpdates()
{
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
double drain = beatmap.CalculateDrainLength();
beatmap.BeatmapInfo.Length = drain;
OsuModDoubleTime doubleTime = null;
selectBeatmap(beatmap);
checkDisplayedLength(drain);
AddStep("select DT", () => SelectedMods.Value = new[] { doubleTime = new OsuModDoubleTime() });
checkDisplayedLength(Math.Round(drain / 1.5f));
AddStep("change DT rate", () => doubleTime.SpeedChange.Value = 2);
checkDisplayedLength(Math.Round(drain / 2));
}
private void checkDisplayedLength(double drain)
{
var displayedLength = drain.ToFormattedDuration();
AddUntilStep($"check map drain ({displayedLength})", () =>
{
var label = infoWedge.DisplayedContent.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength));
return label.Statistic.Content == displayedLength.ToString();
});
}
private void setRuleset(RulesetInfo rulesetInfo)
{
Container containerBefore = null;
@@ -312,8 +312,12 @@ namespace osu.Game.Database
double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy;
// We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio.
double comboProportion =
((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore);
// Note that `maximumLegacyComboScore + maximumLegacyBonusScore` can actually be 0
// when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot).
// In such cases, just assume 0.
double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0
? ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore)
: 0;
// We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore.
long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore;
@@ -321,6 +325,8 @@ namespace osu.Game.Database
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
long convertedTotalScore;
switch (score.Ruleset.OnlineID)
{
case 0:
@@ -417,32 +423,42 @@ namespace osu.Game.Database
double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore;
return (long)Math.Round((
convertedTotalScore = (long)Math.Round((
500000 * newComboScoreProportion * score.Accuracy
+ 500000 * Math.Pow(score.Accuracy, 5)
+ bonusProportion) * modMultiplier);
break;
case 1:
return (long)Math.Round((
convertedTotalScore = (long)Math.Round((
250000 * comboProportion
+ 750000 * Math.Pow(score.Accuracy, 3.6)
+ bonusProportion) * modMultiplier);
break;
case 2:
return (long)Math.Round((
convertedTotalScore = (long)Math.Round((
600000 * comboProportion
+ 400000 * score.Accuracy
+ bonusProportion) * modMultiplier);
break;
case 3:
return (long)Math.Round((
convertedTotalScore = (long)Math.Round((
850000 * comboProportion
+ 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
+ bonusProportion) * modMultiplier);
break;
default:
return score.TotalScore;
convertedTotalScore = score.TotalScore;
break;
}
if (convertedTotalScore < 0)
throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScore}");
return convertedTotalScore;
}
public static double ComputeAccuracy(ScoreInfo scoreInfo)
@@ -160,6 +160,8 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Enter, GlobalAction.ToggleChatFocus),
new KeyBinding(InputKey.F1, GlobalAction.SaveReplay),
new KeyBinding(InputKey.F2, GlobalAction.ExportReplay),
new KeyBinding(InputKey.Plus, GlobalAction.IncreaseOffset),
new KeyBinding(InputKey.Minus, GlobalAction.DecreaseOffset),
};
private static IEnumerable<KeyBinding> replayKeyBindings => new[]
@@ -404,6 +406,12 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))]
EditorToggleRotateControl,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))]
IncreaseOffset,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseOffset))]
DecreaseOffset
}
public enum GlobalActionCategory
@@ -344,6 +344,16 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString ExportReplay => new TranslatableString(getKey(@"export_replay"), @"Export replay");
/// <summary>
/// "Increase offset"
/// </summary>
public static LocalisableString IncreaseOffset => new TranslatableString(getKey(@"increase_offset"), @"Increase offset");
/// <summary>
/// "Decrease offset"
/// </summary>
public static LocalisableString DecreaseOffset => new TranslatableString(getKey(@"decrease_offset"), @"Decrease offset");
/// <summary>
/// "Toggle rotate control"
/// </summary>
+9 -2
View File
@@ -527,14 +527,21 @@ namespace osu.Game
{
ManualResetEventSlim readyToRun = new ManualResetEventSlim();
bool success = false;
Scheduler.Add(() =>
{
realmBlocker = realm.BlockAllOperations("migration");
try
{
realmBlocker = realm.BlockAllOperations("migration");
success = true;
}
catch { }
readyToRun.Set();
}, false);
if (!readyToRun.Wait(30000))
if (!readyToRun.Wait(30000) || !success)
throw new TimeoutException("Attempting to block for migration took too long.");
bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -218,6 +219,7 @@ namespace osu.Game.Overlays.Dashboard
{
panel.Anchor = Anchor.TopCentre;
panel.Origin = Anchor.TopCentre;
panel.CanSpectate.Value = playingUsers.Contains(user.Id);
});
public partial class OnlineUserPanel : CompositeDrawable, IFilterable
@@ -232,6 +232,14 @@ namespace osu.Game.Overlays.Profile.Header
bool expanded = coverToggle.CoverExpanded.Value;
cover.ResizeHeightTo(expanded ? 250 : 0, transition_duration, Easing.OutQuint);
// Without this a very tiny slither of the cover will be visible even with a size of zero.
// Integer masking woes, no doubt.
if (expanded)
cover.FadeIn(transition_duration, Easing.OutQuint);
else
cover.FadeOut(transition_duration, Easing.InQuint);
avatar.ResizeTo(new Vector2(expanded ? 120 : content_height), transition_duration, Easing.OutQuint);
avatar.TransformTo(nameof(avatar.CornerRadius), expanded ? 40f : 20f, transition_duration, Easing.OutQuint);
flow.TransformTo(nameof(flow.Spacing), new Vector2(expanded ? 20f : 10f), transition_duration, Easing.OutQuint);
+14 -13
View File
@@ -157,6 +157,15 @@ namespace osu.Game.Overlays.Toolbar
};
}
[BackgroundDependencyLoader]
private void load()
{
if (Hotkey != null)
{
realm.SubscribeToPropertyChanged(r => r.All<RealmKeyBinding>().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value), kb => kb.KeyCombinationString, updateKeyBindingTooltip);
}
}
protected override bool OnMouseDown(MouseDownEvent e) => false;
protected override bool OnClick(ClickEvent e)
@@ -168,8 +177,6 @@ namespace osu.Game.Overlays.Toolbar
protected override bool OnHover(HoverEvent e)
{
updateKeyBindingTooltip();
HoverBackground.FadeIn(200);
tooltipContainer.FadeIn(100);
@@ -197,19 +204,13 @@ namespace osu.Game.Overlays.Toolbar
{
}
private void updateKeyBindingTooltip()
private void updateKeyBindingTooltip(string keyCombination)
{
if (Hotkey == null) return;
string keyBindingString = keyCombinationProvider.GetReadableString(keyCombination);
var realmKeyBinding = realm.Realm.All<RealmKeyBinding>().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value);
if (realmKeyBinding != null)
{
string keyBindingString = keyCombinationProvider.GetReadableString(realmKeyBinding.KeyCombination);
if (!string.IsNullOrEmpty(keyBindingString))
keyBindingTooltip.Text = $" ({keyBindingString})";
}
keyBindingTooltip.Text = !string.IsNullOrEmpty(keyBindingString)
? $" ({keyBindingString})"
: string.Empty;
}
}
+6 -2
View File
@@ -23,14 +23,14 @@ namespace osu.Game.Rulesets.Mods
}
}
public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer
public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer, IApplicableFailOverride
{
public override string Name => "Cinema";
public override string Acronym => "CN";
public override IconUsage? Icon => OsuIcon.ModCinema;
public override LocalisableString Description => "Watch the video without visual distractions.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAutoplay)).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail) }).ToArray();
public void ApplyToHUD(HUDOverlay overlay)
{
@@ -45,5 +45,9 @@ namespace osu.Game.Rulesets.Mods
player.BreakOverlay.Hide();
}
public bool PerformFail() => false;
public bool RestartOnFail => false;
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyReduction;
public override LocalisableString Description => "You can't fail, no matter what.";
public override double ScoreMultiplier => 0.5;
public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition) };
public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) };
private readonly Bindable<bool> showHealthBar = new Bindable<bool>();
@@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Scoring
protected readonly double DrainLenience;
private readonly List<HealthIncrease> healthIncreases = new List<HealthIncrease>();
private readonly Dictionary<int, double> densityMultiplierByGroup = new Dictionary<int, double>();
private double gameplayEndTime;
private double targetMinimumHealth;
@@ -139,29 +138,14 @@ namespace osu.Game.Rulesets.Scoring
{
healthIncreases.Add(new HealthIncrease(
result.HitObject.GetEndTime() + result.TimeOffset,
GetHealthIncreaseFor(result),
GetDensityGroup(result.HitObject)));
GetHealthIncreaseFor(result)));
}
}
protected override double GetHealthIncreaseFor(JudgementResult result) => base.GetHealthIncreaseFor(result) * getDensityMultiplier(GetDensityGroup(result.HitObject));
private double getDensityMultiplier(int? group)
{
if (group == null)
return 1;
return densityMultiplierByGroup.TryGetValue(group.Value, out double multiplier) ? multiplier : 1;
}
protected virtual int? GetDensityGroup(HitObject hitObject) => null;
protected override void Reset(bool storeResults)
{
base.Reset(storeResults);
densityMultiplierByGroup.Clear();
if (storeResults)
DrainRate = ComputeDrainRate();
@@ -173,24 +157,6 @@ namespace osu.Game.Rulesets.Scoring
if (healthIncreases.Count <= 1)
return 0;
// Normalise the health gain during sections with higher densities.
(int group, double avgIncrease)[] avgIncreasesByGroup = healthIncreases
.Where(i => i.Group != null)
.GroupBy(i => i.Group)
.Select(g => ((int)g.Key!, g.Sum(i => i.Amount) / (g.Max(i => i.Time) - g.Min(i => i.Time) + 1)))
.ToArray();
if (avgIncreasesByGroup.Length > 1)
{
double overallAverageIncrease = avgIncreasesByGroup.Average(g => g.avgIncrease);
foreach ((int group, double avgIncrease) in avgIncreasesByGroup)
{
// Reduce the health increase for groups that return more health than average.
densityMultiplierByGroup[group] = Math.Min(1, overallAverageIncrease / avgIncrease);
}
}
int adjustment = 1;
double result = 1;
@@ -216,12 +182,10 @@ namespace osu.Game.Rulesets.Scoring
currentBreak++;
}
double multiplier = getDensityMultiplier(healthIncreases[i].Group);
// Apply health adjustments
currentHealth -= (currentTime - lastTime) * result;
lowestHealth = Math.Min(lowestHealth, currentHealth);
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount * multiplier);
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount);
// Common scenario for when the drain rate is definitely too harsh
if (lowestHealth < 0)
@@ -240,6 +204,6 @@ namespace osu.Game.Rulesets.Scoring
return result;
}
private record struct HealthIncrease(double Time, double Amount, int? Group);
private record struct HealthIncrease(double Time, double Amount);
}
}
@@ -35,9 +35,10 @@ namespace osu.Game.Scoring.Legacy
/// <item><description>30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores.</description></item>
/// <item><description>30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores.</description></item>
/// <item><description>30000008: Add accuracy conversion. Reconvert all scores.</description></item>
/// <item><description>30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores.</description></item>
/// </list>
/// </remarks>
public const int LATEST_VERSION = 30000008;
public const int LATEST_VERSION = 30000009;
/// <summary>
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
@@ -0,0 +1,107 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
/// <summary>
/// This provides the ability to change the offset while in gameplay.
/// Eventually this should be replaced with all settings from PlayerLoader being accessible from the game.
/// </summary>
internal partial class GameplayOffsetControl : VisibilityContainer
{
protected override bool StartHidden => true;
public override bool PropagateNonPositionalInputSubTree => true;
// Disable interaction for now to avoid any funny business with slider bar dragging.
public override bool PropagatePositionalInputSubTree => false;
private BeatmapOffsetControl offsetControl = null!;
private OsuTextFlowContainer text = null!;
private ScheduledDelegate? hideOp;
public GameplayOffsetControl()
{
AutoSizeAxes = Axes.Y;
Width = SettingsToolboxGroup.CONTAINER_WIDTH;
Masking = true;
CornerRadius = 5;
// Allow BeatmapOffsetControl to handle keyboard input.
AlwaysPresent = true;
Anchor = Anchor.CentreRight;
Origin = Anchor.CentreRight;
X = 100;
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider? colourProvider)
{
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.8f,
Colour = colourProvider?.Background4 ?? Color4.Black,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(10),
Spacing = new Vector2(5),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
offsetControl = new BeatmapOffsetControl(),
text = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold))
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
}
}
},
};
offsetControl.Current.BindValueChanged(val =>
{
text.Text = BeatmapOffsetControl.GetOffsetExplanatoryText(val.NewValue);
Show();
hideOp?.Cancel();
hideOp = Scheduler.AddDelayed(Hide, 500);
});
}
protected override void PopIn()
{
this.FadeIn(500, Easing.OutQuint)
.MoveToX(0, 500, Easing.OutQuint);
}
protected override void PopOut()
{
this.FadeOut(500, Easing.InQuint)
.MoveToX(100, 500, Easing.InQuint);
}
}
}
+6
View File
@@ -461,6 +461,12 @@ namespace osu.Game.Screens.Play
OnRetry = () => Restart(),
OnQuit = () => PerformExit(true),
},
new GameplayOffsetControl
{
Margin = new MarginPadding(20),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
}
},
};
@@ -9,6 +9,8 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -16,6 +18,7 @@ using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
@@ -26,7 +29,7 @@ using osuTK;
namespace osu.Game.Screens.Play.PlayerSettings
{
public partial class BeatmapOffsetControl : CompositeDrawable
public partial class BeatmapOffsetControl : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{
public Bindable<ScoreInfo?> ReferenceScore { get; } = new Bindable<ScoreInfo?>();
@@ -48,6 +51,12 @@ namespace osu.Game.Screens.Play.PlayerSettings
[Resolved]
private OsuColour colours { get; set; } = null!;
[Resolved]
private Player? player { get; set; }
[Resolved]
private IGameplayClock? gameplayClock { get; set; }
private double lastPlayAverage;
private double lastPlayBeatmapOffset;
private HitEventTimingDistributionGraph? lastPlayGraph;
@@ -88,28 +97,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
};
}
public partial class OffsetSliderBar : PlayerSliderBar<double>
{
protected override Drawable CreateControl() => new CustomSliderBar();
protected partial class CustomSliderBar : SliderBar
{
public override LocalisableString TooltipText =>
Current.Value == 0
? LocalisableString.Interpolate($@"{base.TooltipText} ms")
: LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}");
private LocalisableString getEarlyLateText(double value)
{
Debug.Assert(value != 0);
return value > 0
? BeatmapOffsetControlStrings.HitObjectsAppearEarlier
: BeatmapOffsetControlStrings.HitObjectsAppearLater;
}
}
}
protected override void LoadComplete()
{
base.LoadComplete();
@@ -243,5 +230,68 @@ namespace osu.Game.Screens.Play.PlayerSettings
base.Dispose(isDisposing);
beatmapOffsetSubscription?.Dispose();
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
// General limitations to ensure players don't do anything too weird.
// These match stable for now.
if (player is SubmittingPlayer)
{
// TODO: the blocking conditions should probably display a message.
if (player?.IsBreakTime.Value == false && gameplayClock?.CurrentTime - gameplayClock?.StartTime > 10000)
return false;
if (gameplayClock?.IsPaused.Value == true)
return false;
}
// To match stable, this should adjust by 5 ms, or 1 ms when holding alt.
// But that is hard to make work with global actions due to the operating mode.
// Let's use the more precise as a default for now.
const double amount = 1;
switch (e.Action)
{
case GlobalAction.IncreaseOffset:
Current.Value += amount;
return true;
case GlobalAction.DecreaseOffset:
Current.Value -= amount;
return true;
}
return false;
}
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
}
public static LocalisableString GetOffsetExplanatoryText(double offset)
{
return offset == 0
? LocalisableString.Interpolate($@"{offset:0.0} ms")
: LocalisableString.Interpolate($@"{offset:0.0} ms {getEarlyLateText(offset)}");
LocalisableString getEarlyLateText(double value)
{
Debug.Assert(value != 0);
return value > 0
? BeatmapOffsetControlStrings.HitObjectsAppearEarlier
: BeatmapOffsetControlStrings.HitObjectsAppearLater;
}
}
public partial class OffsetSliderBar : PlayerSliderBar<double>
{
protected override Drawable CreateControl() => new CustomSliderBar();
protected partial class CustomSliderBar : SliderBar
{
public override LocalisableString TooltipText => GetOffsetExplanatoryText(Current.Value);
}
}
}
}
+17 -8
View File
@@ -161,6 +161,7 @@ namespace osu.Game.Screens.Select
private ILocalisedBindableString artistBinding;
private FillFlowContainer infoLabelContainer;
private Container bpmLabelContainer;
private Container lengthLabelContainer;
private readonly WorkingBeatmap working;
private readonly RulesetInfo ruleset;
@@ -341,10 +342,10 @@ namespace osu.Game.Screens.Select
{
settingChangeTracker?.Dispose();
refreshBPMLabel();
refreshBPMAndLengthLabel();
settingChangeTracker = new ModSettingChangeTracker(m.NewValue);
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();
settingChangeTracker.SettingChanged += _ => refreshBPMAndLengthLabel();
}, true);
}
@@ -370,12 +371,10 @@ namespace osu.Game.Screens.Select
infoLabelContainer.Children = new Drawable[]
{
new InfoLabel(new BeatmapStatistic
lengthLabelContainer = new Container
{
Name = BeatmapsetsStrings.ShowStatsTotalLength(playableBeatmap.CalculateDrainLength().ToFormattedDuration()),
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(),
}),
AutoSizeAxes = Axes.Both,
},
bpmLabelContainer = new Container
{
AutoSizeAxes = Axes.Both,
@@ -394,7 +393,7 @@ namespace osu.Game.Screens.Select
}
}
private void refreshBPMLabel()
private void refreshBPMAndLengthLabel()
{
var beatmap = working.Beatmap;
@@ -420,6 +419,16 @@ namespace osu.Game.Screens.Select
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
Content = labelText
});
double drainLength = Math.Round(beatmap.CalculateDrainLength() / rate);
double hitLength = Math.Round(beatmap.BeatmapInfo.Length / rate);
lengthLabelContainer.Child = new InfoLabel(new BeatmapStatistic
{
Name = BeatmapsetsStrings.ShowStatsTotalLength(drainLength.ToFormattedDuration()),
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = hitLength.ToFormattedDuration().ToString(),
});
}
private Drawable getMapper(BeatmapMetadata metadata)
+1 -1
View File
@@ -67,7 +67,7 @@ namespace osu.Game.Skinning
decimal? legacyVersion = skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value;
if (legacyVersion >= 2.0m)
if (legacyVersion > 1.0m)
{
this.MoveTo(new Vector2(0, -5));
this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In);