1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-05 03:13:22 +08:00

Compare commits

...

63 Commits

Author SHA1 Message Date
Milo
6772bbc34e
Merge 3d0b15c875 into f09d8f097a 2024-12-03 13:03:21 +01:00
Dan Balasescu
f09d8f097a
Merge pull request #30953 from peppy/notification-while-chedcking-for-updates
Show an ongoing operation when checking for updates
2024-12-03 17:27:10 +09:00
Dan Balasescu
be05f2a1c2
Merge pull request #30929 from timschumi/rate-change-ready
Account for rate changing mods when disabling the "Ready" button
2024-12-03 16:30:16 +09:00
Dan Balasescu
6ff1dec7b2
Add tests 2024-12-03 15:45:58 +09:00
Dean Herbert
457957d3b8
Refactor check-update flow to better handle unobserved exceptions 2024-12-03 14:23:10 +09:00
Dean Herbert
2ceb3f6f85
Show an ongoing operation when checking for updates
Addresses https://github.com/ppy/osu/discussions/30950.
2024-12-03 13:43:20 +09:00
Dean Herbert
ce4aac4184
Merge pull request #30917 from bdach/fix-incorrect-taiko-legacy-combo
Fix strong drum rolls being counted for double the combo in legacy scoring attributes
2024-12-02 20:08:49 -08:00
Tim Schumacher
e920cfa187 Move rate-changing TODO to a common place in CalculateRateWithMods 2024-12-02 23:49:51 +01:00
Dean Herbert
ce8e4120b7
Merge pull request #30947 from bdach/undesirable-deselect-on-control-click
Do not deselect objects when control-clicking without hitting anything
2024-12-02 07:17:27 -08:00
Bartłomiej Dach
b505ecc7ba
Do not deselect objects when control-clicking without hitting anything
As per feedback in
https://discord.com/channels/90072389919997952/1259818301517725707/1310270647187935284.
2024-12-02 13:51:43 +01:00
Bartłomiej Dach
b14dde937d
Add failing test case 2024-12-02 13:51:41 +01:00
Bartłomiej Dach
6c0ccc5ebe
Merge pull request #30863 from frenzibyte/improve-back-button-display
Delay back button appearance when performing a quick restart
2024-12-02 11:49:45 +01:00
Bartłomiej Dach
52b8753a12
Merge pull request #30749 from Sheppsu/multi-spectator-settings-sidebar
Add player settings to multi spectator screen
2024-12-02 11:34:57 +01:00
Dean Herbert
5b2558cec2
Merge pull request #28473 from bdach/beatmap-info-purge
Move unnecessary properties from `BeatmapInfo` / realm to `IBeatmap`
2024-12-02 16:19:12 +09:00
Dean Herbert
23522b02d8
Use local instead of field for local only usage 2024-12-01 19:53:57 +09:00
Dean Herbert
6afe083ec9
Fix settings showing up during gameplay 2024-12-01 18:44:26 +09:00
Dean Herbert
ddac71628d
Merge branch 'master' into multi-spectator-settings-sidebar 2024-12-01 18:33:46 +09:00
Tim Schumacher
164b809c89 Document ready button enable state with some comments 2024-11-30 23:02:22 +01:00
Tim Schumacher
f4e155bfa6 Account for rate changing mods when disabling the "Ready" button 2024-11-30 16:01:32 +01:00
Bartłomiej Dach
3cfa455369
Fix strong drum rolls being counted for double the combo in legacy scoring attributes 2024-11-29 10:54:32 +01:00
Salman Alshamrani
932afcde01 Make editor make sense 2024-11-28 17:43:32 -05:00
Salman Alshamrani
f792b6de00 Fix comment 2024-11-27 06:07:10 -05:00
Salman Alshamrani
4ae3ccfe48 Make BackButtonVisibility in game class private 2024-11-27 06:05:02 -05:00
Dean Herbert
0f73941808
Combine new implementation back into the old one and use everywhere 2024-11-27 17:47:42 +09:00
Dean Herbert
7fdf13911b
Adjust the colour of non-pinned settings groups' headers to be more legible 2024-11-27 17:47:27 +09:00
Dean Herbert
782ce24ca6
Move player settings out of right flow 2024-11-27 17:09:15 +09:00
Dean Herbert
9c707ed341
Rename class and fix padding considerations 2024-11-27 16:47:54 +09:00
Dean Herbert
5ce55e9cb4
Merge branch 'master' into multi-spectator-settings-sidebar 2024-11-27 16:35:05 +09:00
Salman Alshamrani
3e1b4f4ac5 Rename AllowBackButton to AllowUserExit and rewrite visibility flow structure
Co-authored-by: Dean Herbert <pe@ppy.sh>
2024-11-26 16:52:39 -05:00
Bartłomiej Dach
46d1f00590
Fix Beatmap.Countdown not being copied on conversion 2024-11-26 11:39:03 +01:00
Bartłomiej Dach
cf905d0f5c
Merge branch 'master' into beatmap-info-purge 2024-11-26 10:21:16 +01:00
Salman Alshamrani
ae9119eef0 Hide back button when quick-restarting unless load time takes long 2024-11-24 05:40:06 -05:00
Salman Alshamrani
2420793466 Allow controlling back button visibility state from screens 2024-11-24 05:39:43 -05:00
Sheppsu
3713bb48b7 expand and contract settings from hover 2024-11-23 01:09:58 -05:00
Bartłomiej Dach
ead7e99c59
Fix incorrect comment 2024-11-22 11:06:36 +01:00
Dean Herbert
086a34f5c0
Merge branch 'master' into beatmap-info-purge 2024-11-22 18:47:32 +09:00
Sheppsu
7d4062d2ad remove redundant Scale attribute 2024-11-18 04:04:28 -05:00
Sheppsu
29e7adcd3b add player settings to multi spectator screen 2024-11-18 03:57:50 -05:00
Milo
3d0b15c875
format 2024-11-11 21:34:48 -04:00
Milo
50cbcd4a0c
remove unnecessary empty line 2024-11-11 21:27:06 -04:00
Milo
9cadca84bb lm 2024-11-12 02:07:48 +01:00
Milo
2fc4752999 lm 2024-11-12 02:06:42 +01:00
Milo
9a05edab7a implement proposed osu!mania mod, Full Perfect 2024-11-12 01:55:20 +01:00
Milo
5977fbfce7 implement proposed osu!mania mod, Full Perfect 2024-11-12 01:35:48 +01:00
Milo
5ae56df32b
implement proposed osu!mania mod, Full Perfect 2024-11-11 18:52:30 -04:00
Dean Herbert
58fe502af4
Merge branch 'master' into beatmap-info-purge 2024-09-15 04:53:35 +09:00
Bartłomiej Dach
1d4d806362
Fix WidescreenStoryboard breakage after moving out of BeatmapInfo 2024-07-23 12:19:45 +02:00
Dean Herbert
d707e29ff7
Merge branch 'master' into beatmap-info-purge 2024-07-23 12:09:32 +09:00
Bartłomiej Dach
04527f3c9d
Fix TestBeatmap not transferring newly migrated properties 2024-06-13 09:30:09 +02:00
Bartłomiej Dach
c67e2dc301
Bump schema version 2024-06-13 09:30:08 +02:00
Bartłomiej Dach
9fbf2872e1
Remove no longer applicable region marking 2024-06-12 14:27:40 +02:00
Bartłomiej Dach
dd50d6fa6e
Move CountdownOffset out of BeatmapInfo 2024-06-12 14:15:00 +02:00
Bartłomiej Dach
d373f752d6
Move Countdown out of BeatmapInfo 2024-06-12 14:15:00 +02:00
Bartłomiej Dach
7f2a6f6f5a
Move TimelineZoom out of BeatmapInfo 2024-06-12 14:15:00 +02:00
Bartłomiej Dach
6685c5ab74
Move GridSize out of BeatmapInfo 2024-06-12 14:15:00 +02:00
Bartłomiej Dach
3634307d7c
Move DistanceSpacing out of BeatmapInfo 2024-06-12 14:14:58 +02:00
Bartłomiej Dach
c216283bf4
Move SamplesMatchPlaybackRate out of BeatmapInfo 2024-06-12 13:32:23 +02:00
Bartłomiej Dach
f64a0624a5
Move EpilepsyWarning out of BeatmapInfo 2024-06-12 13:28:41 +02:00
Bartłomiej Dach
1ab86ebd24
Move WidescreenStoryboard out of BeatmapInfo 2024-06-12 13:23:53 +02:00
Bartłomiej Dach
a6b7600bf2
Move LetterboxInBreaks out of BeatmapInfo 2024-06-12 13:18:44 +02:00
Bartłomiej Dach
011c2e3651
Move SpecialStyle out of BeatmapInfo 2024-06-12 13:12:30 +02:00
Bartłomiej Dach
0a4560a03e
Move StackLeniency out of BeatmapInfo 2024-06-12 13:04:33 +02:00
Bartłomiej Dach
4339e2dc4a
Move AudioLeadIn out of BeatmapInfo 2024-06-12 13:04:28 +02:00
65 changed files with 746 additions and 257 deletions

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
{ {
Caption = "Use special (N+1) style", Caption = "Use special (N+1) style",
HintText = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.", HintText = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle } Current = { Value = Beatmap.SpecialStyle }
}, },
healthDrainSlider = new FormSliderBar<float> healthDrainSlider = new FormSliderBar<float>
{ {
@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
// for now, update these on commit rather than making BeatmapMetadata bindables. // for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction. // after switching database engines we can reconsider if switching to bindables is a good direction.
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value; Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value; Beatmap.SpecialStyle = specialStyle.Current.Value;
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value; Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value; Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;

View File

@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania
return new Mod[] return new Mod[]
{ {
new ManiaModHardRock(), new ManiaModHardRock(),
new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect()), new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect(), new ManiaModFullPerfect()),
new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()), new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden(), new ManiaModCover()), new MultiMod(new ManiaModFadeIn(), new ManiaModHidden(), new ManiaModCover()),
new ManiaModFlashlight(), new ManiaModFlashlight(),

View File

@ -0,0 +1,38 @@
// 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.4
using System;
using System.Linq;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModFullPerfect : ManiaModPerfect
{
public override string Name => "Full Perfect";
public override string Acronym => "FP";
public override LocalisableString Description => @"Placeholder";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
{
typeof(ManiaModPerfect),
}).ToArray();
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
{
if (!isRelevantResult(result.Judgement.MinResult) && !isRelevantResult(result.Judgement.MaxResult) && !isRelevantResult(result.Type))
return false;
if (result.Judgement.MaxResult == HitResult.Perfect)
return result.Type < HitResult.Perfect;
return result.Type != result.Judgement.MaxResult;
}
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();
}
}

View File

@ -231,6 +231,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("slider still has 2 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(2)); AddAssert("slider still has 2 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(2));
} }
[Test]
public void TestControlClickDoesNotDiscardExistingSelectionEvenIfNothingHit()
{
var firstSlider = new Slider
{
StartTime = 0,
Position = new Vector2(0, 0),
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(100))
}
}
};
AddStep("add object", () => EditorBeatmap.AddRange([firstSlider]));
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange([firstSlider]));
AddStep("move mouse to middle of playfield", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre));
AddStep("control-click left mouse", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1));
}
private ComposeBlueprintContainer blueprintContainer private ComposeBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<ComposeBlueprintContainer>().First(); => Editor.ChildrenOfType<ComposeBlueprintContainer>().First();

View File

@ -191,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private void gridSizeIs(int size) private void gridSizeIs(int size)
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Single().Spacing.Value == new Vector2(size) => AddAssert($"grid size is {size}", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Single().Spacing.Value == new Vector2(size)
&& EditorBeatmap.BeatmapInfo.GridSize == size); && EditorBeatmap.GridSize == size);
[Test] [Test]
public void TestGridTypeToggling() public void TestGridTypeToggling()

View File

@ -83,10 +83,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
}) })
} }
}, },
BeatmapInfo = StackLeniency = 0,
{
StackLeniency = 0,
}
}, },
ReplayFrames = new List<ReplayFrame> ReplayFrames = new List<ReplayFrame>
{ {

View File

@ -74,12 +74,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{ {
BeatmapInfo = new BeatmapInfo BeatmapInfo = new BeatmapInfo
{ {
StackLeniency = 0,
Difficulty = new BeatmapDifficulty Difficulty = new BeatmapDifficulty
{ {
ApproachRate = 8.5f ApproachRate = 8.5f
} }
}, },
StackLeniency = 0,
ControlPointInfo = controlPointInfo ControlPointInfo = controlPointInfo
}; };

View File

@ -465,7 +465,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void performTest(List<ReplayFrame> frames, Beatmap<OsuHitObject> beatmap) private void performTest(List<ReplayFrame> frames, Beatmap<OsuHitObject> beatmap)
{ {
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
beatmap.BeatmapInfo.StackLeniency = 0; beatmap.StackLeniency = 0;
beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty
{ {
SliderMultiplier = 4, SliderMultiplier = 4,

View File

@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
h.StackHeight = 0; h.StackHeight = 0;
if (beatmap.BeatmapInfo.BeatmapVersion >= 6) if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1); applyStacking(beatmap, hitObjects, 0, hitObjects.Count - 1);
else else
applyStackingOld(beatmap.BeatmapInfo, hitObjects); applyStackingOld(beatmap, hitObjects);
} }
} }
private static void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex) private static void applyStacking(IBeatmap beatmap, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
{ {
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex); ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
ArgumentOutOfRangeException.ThrowIfNegative(startIndex); ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
continue; continue;
double endTime = stackBaseObject.GetEndTime(); double endTime = stackBaseObject.GetEndTime();
double stackThreshold = objectN.TimePreempt * beatmapInfo.StackLeniency; double stackThreshold = objectN.TimePreempt * beatmap.StackLeniency;
if (objectN.StartTime - endTime > stackThreshold) if (objectN.StartTime - endTime > stackThreshold)
// We are no longer within stacking range of the next object. // We are no longer within stacking range of the next object.
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectI = hitObjects[i]; OsuHitObject objectI = hitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue; if (objectI.StackHeight != 0 || objectI is Spinner) continue;
double stackThreshold = objectI.TimePreempt * beatmapInfo.StackLeniency; double stackThreshold = objectI.TimePreempt * beatmap.StackLeniency;
/* If this object is a hitcircle, then we enter this "special" case. /* If this object is a hitcircle, then we enter this "special" case.
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
} }
} }
private static void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects) private static void applyStackingOld(IBeatmap beatmap, List<OsuHitObject> hitObjects)
{ {
for (int i = 0; i < hitObjects.Count; i++) for (int i = 0; i < hitObjects.Count; i++)
{ {
@ -228,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
for (int j = i + 1; j < hitObjects.Count; j++) for (int j = i + 1; j < hitObjects.Count; j++)
{ {
double stackThreshold = hitObjects[i].TimePreempt * beatmapInfo.StackLeniency; double stackThreshold = hitObjects[i].TimePreempt * beatmap.StackLeniency;
if (hitObjects[j].StartTime - stackThreshold > startTime) if (hitObjects[j].StartTime - stackThreshold > startTime)
break; break;

View File

@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Edit
}, },
}; };
Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; Spacing.Value = editorBeatmap.GridSize;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Edit
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}"; spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}";
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}";
SpacingVector.Value = new Vector2(spacing.NewValue); SpacingVector.Value = new Vector2(spacing.NewValue);
editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue; editorBeatmap.GridSize = (int)spacing.NewValue;
}, true); }, true);
GridLinesRotation.BindValueChanged(rotation => GridLinesRotation.BindValueChanged(rotation =>

View File

@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
{ {
Caption = "Stack Leniency", Caption = "Stack Leniency",
HintText = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.", HintText = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.",
Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency) Current = new BindableFloat(Beatmap.StackLeniency)
{ {
Default = 0.7f, Default = 0.7f,
MinValue = 0, MinValue = 0,
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value; Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value; Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value; Beatmap.StackLeniency = stackLeniency.Current.Value;
Beatmap.UpdateAllHitObjects(); Beatmap.UpdateAllHitObjects();
Beatmap.SaveState(); Beatmap.SaveState();

View File

@ -144,6 +144,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
foreach (var nested in hitObject.NestedHitObjects) foreach (var nested in hitObject.NestedHitObjects)
simulateHit(nested, ref attributes); simulateHit(nested, ref attributes);
return; return;
case StrongNestedHitObject:
// we never need to deal with these directly.
// the only thing strong hits do in terms of scoring is double their object's score increase,
// which is already handled at the parent object level via the `strongable.IsStrong` check lower down in this method.
// not handling these here can lead to them falsely being counted as combo-increasing when handling strong drum rolls!
return;
} }
if (hitObject is DrumRollTick tick) if (hitObject is DrumRollTick tick)

View File

@ -80,16 +80,16 @@ namespace osu.Game.Tests.Beatmaps.Formats
var metadata = beatmap.Metadata; var metadata = beatmap.Metadata;
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile);
Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(0, beatmap.AudioLeadIn);
Assert.AreEqual(164471, metadata.PreviewTime); Assert.AreEqual(164471, metadata.PreviewTime);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(0.7f, beatmap.StackLeniency);
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmap.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle); Assert.IsFalse(beatmap.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard); Assert.IsFalse(beatmap.WidescreenStoryboard);
Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); Assert.IsFalse(beatmap.SamplesMatchPlaybackRate);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(CountdownType.None, beatmap.Countdown);
Assert.AreEqual(0, beatmapInfo.CountdownOffset); Assert.AreEqual(0, beatmap.CountdownOffset);
} }
} }
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new LineBufferedReader(resStream)) using (var stream = new LineBufferedReader(resStream))
{ {
var beatmapInfo = decoder.Decode(stream).BeatmapInfo; var beatmap = decoder.Decode(stream);
int[] expectedBookmarks = int[] expectedBookmarks =
{ {
@ -109,13 +109,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
95901, 106450, 116999, 119637, 130186, 140735, 151285, 95901, 106450, 116999, 119637, 130186, 140735, 151285,
161834, 164471, 175020, 185570, 196119, 206669, 209306 161834, 164471, 175020, 185570, 196119, 206669, 209306
}; };
Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); Assert.AreEqual(expectedBookmarks.Length, beatmap.BeatmapInfo.Bookmarks.Length);
for (int i = 0; i < expectedBookmarks.Length; i++) for (int i = 0; i < expectedBookmarks.Length; i++)
Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); Assert.AreEqual(expectedBookmarks[i], beatmap.BeatmapInfo.Bookmarks[i]);
Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); Assert.AreEqual(1.8, beatmap.DistanceSpacing);
Assert.AreEqual(4, beatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor);
Assert.AreEqual(4, beatmapInfo.GridSize); Assert.AreEqual(4, beatmap.GridSize);
Assert.AreEqual(2, beatmapInfo.TimelineZoom); Assert.AreEqual(2, beatmap.TimelineZoom);
} }
} }
@ -993,15 +993,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0)); Assert.That(decoded.AudioLeadIn, Is.EqualTo(0));
Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f));
Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); Assert.That(decoded.SpecialStyle, Is.False);
Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); Assert.That(decoded.LetterboxInBreaks, Is.False);
Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); Assert.That(decoded.WidescreenStoryboard, Is.False);
Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); Assert.That(decoded.EpilepsyWarning, Is.False);
Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.SamplesMatchPlaybackRate, Is.False);
Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.None)); Assert.That(decoded.Countdown, Is.EqualTo(CountdownType.None));
Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); Assert.That(decoded.CountdownOffset, Is.EqualTo(0));
Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1));
Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0));
}); });

View File

@ -51,14 +51,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
{ {
var beatmap = decodeAsJson(normal); var beatmap = decodeAsJson(normal);
var beatmapInfo = beatmap.BeatmapInfo; var beatmapInfo = beatmap.BeatmapInfo;
Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(0, beatmap.AudioLeadIn);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(0.7f, beatmap.StackLeniency);
Assert.AreEqual(false, beatmapInfo.SpecialStyle); Assert.AreEqual(false, beatmap.SpecialStyle);
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); Assert.AreEqual(false, beatmap.LetterboxInBreaks);
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); Assert.AreEqual(false, beatmap.WidescreenStoryboard);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(CountdownType.None, beatmap.Countdown);
Assert.AreEqual(0, beatmapInfo.CountdownOffset); Assert.AreEqual(0, beatmap.CountdownOffset);
} }
[Test] [Test]
@ -76,10 +76,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length);
for (int i = 0; i < expectedBookmarks.Length; i++) for (int i = 0; i < expectedBookmarks.Length; i++)
Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]);
Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); Assert.AreEqual(1.8, beatmap.DistanceSpacing);
Assert.AreEqual(4, beatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmapInfo.BeatDivisor);
Assert.AreEqual(4, beatmapInfo.GridSize); Assert.AreEqual(4, beatmap.GridSize);
Assert.AreEqual(2, beatmapInfo.TimelineZoom); Assert.AreEqual(2, beatmap.TimelineZoom);
} }
[Test] [Test]

View File

@ -41,7 +41,7 @@ namespace osu.Game.Tests.Database
Assert.That(lastChanges?.ModifiedIndices, Is.Empty); Assert.That(lastChanges?.ModifiedIndices, Is.Empty);
Assert.That(lastChanges?.NewModifiedIndices, Is.Empty); Assert.That(lastChanges?.NewModifiedIndices, Is.Empty);
realm.Write(r => r.All<BeatmapSetInfo>().First().Beatmaps.First().CountdownOffset = 5); realm.Write(r => r.All<BeatmapSetInfo>().First().Beatmaps.First().EditorTimestamp = 5);
realm.Run(r => r.Refresh()); realm.Run(r => r.Refresh());
Assert.That(collectionChanges, Is.EqualTo(1)); Assert.That(collectionChanges, Is.EqualTo(1));

View File

@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Editing
{ {
AddStep("turn countdown off", () => designSection.EnableCountdown.Current.Value = false); AddStep("turn countdown off", () => designSection.EnableCountdown.Current.Value = false);
AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.None); AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.None);
AddUntilStep("other controls hidden", () => !designSection.CountdownSettings.IsPresent); AddUntilStep("other controls hidden", () => !designSection.CountdownSettings.IsPresent);
} }
@ -65,12 +65,12 @@ namespace osu.Game.Tests.Visual.Editing
{ {
AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true);
AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.Normal);
AddUntilStep("other controls shown", () => designSection.CountdownSettings.IsPresent); AddUntilStep("other controls shown", () => designSection.CountdownSettings.IsPresent);
AddStep("change countdown speed", () => designSection.CountdownSpeed.Current.Value = CountdownType.DoubleSpeed); AddStep("change countdown speed", () => designSection.CountdownSpeed.Current.Value = CountdownType.DoubleSpeed);
AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.DoubleSpeed); AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.DoubleSpeed);
AddUntilStep("other controls still shown", () => designSection.CountdownSettings.IsPresent); AddUntilStep("other controls still shown", () => designSection.CountdownSettings.IsPresent);
} }
@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Editing
{ {
AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true);
AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.Normal);
checkOffsetAfter("1", 1); checkOffsetAfter("1", 1);
checkOffsetAfter(string.Empty, 0); checkOffsetAfter(string.Empty, 0);
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("commit text", () => InputManager.Key(Key.Enter)); AddStep("commit text", () => InputManager.Key(Key.Enter));
AddAssert($"displayed value is {expectedFinalValue}", () => designSection.CountdownOffset.Current.Value == expectedFinalValue.ToString(CultureInfo.InvariantCulture)); AddAssert($"displayed value is {expectedFinalValue}", () => designSection.CountdownOffset.Current.Value == expectedFinalValue.ToString(CultureInfo.InvariantCulture));
AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.BeatmapInfo.CountdownOffset == expectedFinalValue); AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.CountdownOffset == expectedFinalValue);
} }
private partial class TestDesignSection : DesignSection private partial class TestDesignSection : DesignSection

View File

@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Set beat divisor", () => Editor.Dependencies.Get<BindableBeatDivisor>().Value = 16); AddStep("Set beat divisor", () => Editor.Dependencies.Get<BindableBeatDivisor>().Value = 16);
AddStep("Set timeline zoom", () => AddStep("Set timeline zoom", () =>
{ {
originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; originalTimelineZoom = EditorBeatmap.TimelineZoom;
var timeline = Editor.ChildrenOfType<Timeline>().Single(); var timeline = Editor.ChildrenOfType<Timeline>().Single();
InputManager.MoveMouseTo(timeline); InputManager.MoveMouseTo(timeline);
@ -81,19 +81,19 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Ensure timeline zoom changed", () => AddAssert("Ensure timeline zoom changed", () =>
{ {
changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; changedTimelineZoom = EditorBeatmap.TimelineZoom;
return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom); return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom);
}); });
SaveEditor(); SaveEditor();
AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.TimelineZoom == changedTimelineZoom);
ReloadEditorToSameBeatmap(); ReloadEditorToSameBeatmap();
AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.TimelineZoom == changedTimelineZoom);
} }
[Test] [Test]

View File

@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.Editing
{ {
double originalSpacing = 0; double originalSpacing = 0;
AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.BeatmapInfo.DistanceSpacing); AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.DistanceSpacing);
AddStep("hold ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("hold ctrl", () => InputManager.PressKey(Key.LControl));
AddStep("hold alt", () => InputManager.PressKey(Key.LAlt)); AddStep("hold alt", () => InputManager.PressKey(Key.LAlt));
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt)); AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt));
AddStep("release ctrl", () => InputManager.ReleaseKey(Key.LControl)); AddStep("release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("distance spacing increased by 0.5", () => editorBeatmap.BeatmapInfo.DistanceSpacing == originalSpacing + 0.5); AddAssert("distance spacing increased by 0.5", () => editorBeatmap.DistanceSpacing == originalSpacing + 0.5);
} }
public partial class EditorBeatmapContainer : PopoverContainer public partial class EditorBeatmapContainer : PopoverContainer

View File

@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
{ {
BeatmapInfo = { AudioLeadIn = leadIn } AudioLeadIn = leadIn
}); });
checkFirstFrameTime(expectedStartTime); checkFirstFrameTime(expectedStartTime);
@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} " Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
+ $"FirstHitObjectTime: {FirstHitObjectTime} " + $"FirstHitObjectTime: {FirstHitObjectTime} "
+ $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} " + $"LeadInTime: {Beatmap.Value.Beatmap.AudioLeadIn} "
+ $"FirstFrameClockTime: {FirstFrameClockTime}" + $"FirstFrameClockTime: {FirstFrameClockTime}"
}); });
} }

View File

@ -136,10 +136,10 @@ namespace osu.Game.Tests.Visual.Gameplay
var workingBeatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); var workingBeatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
// Add intro time to test quick retry skipping (TestQuickRetry). // Add intro time to test quick retry skipping (TestQuickRetry).
workingBeatmap.BeatmapInfo.AudioLeadIn = 60000; workingBeatmap.Beatmap.AudioLeadIn = 60000;
// Set up data for testing disclaimer display. // Set up data for testing disclaimer display.
workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning ?? false; workingBeatmap.Beatmap.EpilepsyWarning = epilepsyWarning ?? false;
workingBeatmap.BeatmapInfo.Status = onlineStatus ?? BeatmapOnlineStatus.Ranked; workingBeatmap.BeatmapInfo.Status = onlineStatus ?? BeatmapOnlineStatus.Ranked;
Beatmap.Value = workingBeatmap; Beatmap.Value = workingBeatmap;

View File

@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
{ {
BeatmapInfo = { AudioLeadIn = 60000 } AudioLeadIn = 60000
}); });
AddUntilStep("wait for skip overlay", () => Player.ChildrenOfType<SkipOverlay>().First().IsButtonVisible); AddUntilStep("wait for skip overlay", () => Player.ChildrenOfType<SkipOverlay>().First().IsButtonVisible);

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("load storyboard with only video", () => AddStep("load storyboard with only video", () =>
{ {
// LegacyStoryboardDecoder doesn't parse WidescreenStoryboard, so it is set manually // LegacyStoryboardDecoder doesn't parse WidescreenStoryboard, so it is set manually
loadStoryboard("storyboard_only_video.osu", s => s.BeatmapInfo.WidescreenStoryboard = false); loadStoryboard("storyboard_only_video.osu", s => s.Beatmap.WidescreenStoryboard = false);
}); });
AddAssert("storyboard is correct width", () => Precision.AlmostEquals(storyboard?.Width ?? 0f, 480 * 16 / 9f)); AddAssert("storyboard is correct width", () => Precision.AlmostEquals(storyboard?.Width ?? 0f, 480 * 16 / 9f));

View File

@ -406,13 +406,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
/// <summary> /// <summary>
/// Tests spectating with a beatmap that has a high <see cref="BeatmapInfo.AudioLeadIn"/> value. /// Tests spectating with a beatmap that has a high <see cref="IBeatmap.AudioLeadIn"/> value.
/// ///
/// This test is not intended not to check the correct initial time value, but only to guard against /// This test is not intended not to check the correct initial time value, but only to guard against
/// gameplay potentially getting stuck in a stopped state due to lead in time being present. /// gameplay potentially getting stuck in a stopped state due to lead in time being present.
/// </summary> /// </summary>
[Test] [Test]
public void TestAudioLeadIn() => testLeadIn(b => b.BeatmapInfo.AudioLeadIn = 2000); public void TestAudioLeadIn() => testLeadIn(b => b.Beatmap.AudioLeadIn = 2000);
/// <summary> /// <summary>
/// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element). /// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element).

View File

@ -5,25 +5,64 @@ using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Playlists namespace osu.Game.Tests.Visual.Playlists
{ {
public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
{ {
private const double track_length = 10000;
[Resolved] [Resolved]
private IAPIProvider api { get; set; } = null!; private IAPIProvider api { get; set; } = null!;
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private BeatmapManager beatmaps = null!;
private RulesetStore rulesets = null!;
private BeatmapSetInfo? importedSet;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, API));
Dependencies.Cache(Realm);
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
Realm.Write(r =>
{
foreach (var set in r.All<BeatmapSetInfo>())
{
foreach (var b in set.Beatmaps)
{
// These will all have a virtual track length of 1000, see WorkingBeatmap.GetVirtualTrack().
b.Length = track_length - 1000;
}
}
});
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
}
[Test] [Test]
public void TestStatusUpdateOnEnter() public void TestStatusUpdateOnEnter()
{ {
@ -69,5 +108,42 @@ namespace osu.Game.Tests.Visual.Playlists
AddAssert("close button present", () => roomScreen.ChildrenOfType<DangerousRoundedButton>().Any()); AddAssert("close button present", () => roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType<DangerousRoundedButton>().Any()); AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
} }
[TestCase(120_000, true)] // Definitely enough time.
[TestCase(45_000, true)] // Enough time.
[TestCase(35_000, false)] // Not enough time to complete beatmap after lenience.
[TestCase(20_000, false)] // Not enough time.
[TestCase(5_000, false)] // Not enough time to complete beatmap before lenience.
[TestCase(37_500, true, 2)] // Enough time to complete beatmap after mods are applied.
public void TestReadyButtonEnablementPeriod(int offsetMs, bool enabled, double rate = 1)
{
Room room = null!;
PlaylistsRoomSubScreen roomScreen = null!;
AddStep("create room", () =>
{
RoomManager.AddRoom(room = new Room
{
Name = @"Test Room",
Host = api.LocalUser.Value,
Category = RoomCategory.Normal,
StartDate = DateTimeOffset.Now,
EndDate = DateTimeOffset.Now.AddMilliseconds(offsetMs),
Playlist =
[
new PlaylistItem(importedSet!.Beatmaps[0])
{
RequiredMods = rate == 1
? []
: [new APIMod(new OsuModDoubleTime { SpeedChange = { Value = rate } })]
}
]
});
});
AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
AddUntilStep("ready button enabled", () => roomScreen.ChildrenOfType<PlaylistsReadyButton>().SingleOrDefault()?.Enabled.Value, () => Is.EqualTo(enabled));
}
} }
} }

View File

@ -115,6 +115,30 @@ namespace osu.Game.Beatmaps
return mostCommon.beatLength; return mostCommon.beatLength;
} }
public double AudioLeadIn { get; set; }
public float StackLeniency { get; set; } = 0.7f;
public bool SpecialStyle { get; set; }
public bool LetterboxInBreaks { get; set; }
public bool WidescreenStoryboard { get; set; } = true;
public bool EpilepsyWarning { get; set; }
public bool SamplesMatchPlaybackRate { get; set; }
public double DistanceSpacing { get; set; } = 1.0;
public int GridSize { get; set; }
public double TimelineZoom { get; set; } = 1.0;
public CountdownType Countdown { get; set; } = CountdownType.None;
public int CountdownOffset { get; set; }
IBeatmap IBeatmap.Clone() => Clone(); IBeatmap IBeatmap.Clone() => Clone();
public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone(); public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();

View File

@ -73,6 +73,18 @@ namespace osu.Game.Beatmaps
beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList(); beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList();
beatmap.Breaks = original.Breaks; beatmap.Breaks = original.Breaks;
beatmap.UnhandledEventLines = original.UnhandledEventLines; beatmap.UnhandledEventLines = original.UnhandledEventLines;
beatmap.AudioLeadIn = original.AudioLeadIn;
beatmap.StackLeniency = original.StackLeniency;
beatmap.SpecialStyle = original.SpecialStyle;
beatmap.LetterboxInBreaks = original.LetterboxInBreaks;
beatmap.WidescreenStoryboard = original.WidescreenStoryboard;
beatmap.EpilepsyWarning = original.EpilepsyWarning;
beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate;
beatmap.DistanceSpacing = original.DistanceSpacing;
beatmap.GridSize = original.GridSize;
beatmap.TimelineZoom = original.TimelineZoom;
beatmap.Countdown = original.Countdown;
beatmap.CountdownOffset = original.CountdownOffset;
return beatmap; return beatmap;
} }

View File

@ -428,17 +428,7 @@ namespace osu.Game.Beatmaps
Hash = hash, Hash = hash,
DifficultyName = decodedInfo.DifficultyName, DifficultyName = decodedInfo.DifficultyName,
OnlineID = decodedInfo.OnlineID, OnlineID = decodedInfo.OnlineID,
AudioLeadIn = decodedInfo.AudioLeadIn,
StackLeniency = decodedInfo.StackLeniency,
SpecialStyle = decodedInfo.SpecialStyle,
LetterboxInBreaks = decodedInfo.LetterboxInBreaks,
WidescreenStoryboard = decodedInfo.WidescreenStoryboard,
EpilepsyWarning = decodedInfo.EpilepsyWarning,
SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate,
DistanceSpacing = decodedInfo.DistanceSpacing,
BeatDivisor = decodedInfo.BeatDivisor, BeatDivisor = decodedInfo.BeatDivisor,
GridSize = decodedInfo.GridSize,
TimelineZoom = decodedInfo.TimelineZoom,
MD5Hash = memoryStream.ComputeMD5Hash(), MD5Hash = memoryStream.ComputeMD5Hash(),
EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration), EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration),
TotalObjectCount = decoded.HitObjects.Count TotalObjectCount = decoded.HitObjects.Count

View File

@ -6,14 +6,12 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Collections; using osu.Game.Collections;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.BeatmapSet.Scores;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Scoring; using osu.Game.Scoring;
using Realms; using Realms;
@ -136,60 +134,18 @@ namespace osu.Game.Beatmaps
Status = BeatmapOnlineStatus.None; Status = BeatmapOnlineStatus.None;
} }
#region Properties we may not want persisted (but also maybe no harm?)
public double AudioLeadIn { get; set; }
public float StackLeniency { get; set; } = 0.7f;
public bool SpecialStyle { get; set; }
public bool LetterboxInBreaks { get; set; }
public bool WidescreenStoryboard { get; set; } = true;
public bool EpilepsyWarning { get; set; }
public bool SamplesMatchPlaybackRate { get; set; }
/// <summary> /// <summary>
/// The time at which this beatmap was last played by the local user. /// The time at which this beatmap was last played by the local user.
/// </summary> /// </summary>
public DateTimeOffset? LastPlayed { get; set; } public DateTimeOffset? LastPlayed { get; set; }
/// <summary>
/// The ratio of distance travelled per time unit.
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
/// </summary>
/// <remarks>
/// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap
/// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider.
///
/// This is only a hint property, used by the editor in <see cref="IDistanceSnapProvider"/> implementations. It does not directly affect the beatmap or gameplay.
/// </remarks>
public double DistanceSpacing { get; set; } = 1.0;
public int BeatDivisor { get; set; } = 4; public int BeatDivisor { get; set; } = 4;
public int GridSize { get; set; }
public double TimelineZoom { get; set; } = 1.0;
/// <summary> /// <summary>
/// The time in milliseconds when last exiting the editor with this beatmap loaded. /// The time in milliseconds when last exiting the editor with this beatmap loaded.
/// </summary> /// </summary>
public double? EditorTimestamp { get; set; } public double? EditorTimestamp { get; set; }
[Ignored]
public CountdownType Countdown { get; set; } = CountdownType.None;
/// <summary>
/// The number of beats to move the countdown backwards (compared to its default location).
/// </summary>
public int CountdownOffset { get; set; }
#endregion
public bool Equals(BeatmapInfo? other) public bool Equals(BeatmapInfo? other)
{ {
if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(this, other)) return true;

View File

@ -82,7 +82,7 @@ namespace osu.Game.Beatmaps.Formats
this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion;
parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion); parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion);
applyLegacyDefaults(this.beatmap.BeatmapInfo); ApplyLegacyDefaults(this.beatmap);
base.ParseStreamInto(stream, beatmap); base.ParseStreamInto(stream, beatmap);
@ -190,9 +190,9 @@ namespace osu.Game.Beatmaps.Formats
/// This method's intention is to restore those legacy defaults. /// This method's intention is to restore those legacy defaults.
/// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29 /// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29
/// </summary> /// </summary>
private static void applyLegacyDefaults(BeatmapInfo beatmapInfo) internal static void ApplyLegacyDefaults(Beatmap beatmap)
{ {
beatmapInfo.WidescreenStoryboard = false; beatmap.WidescreenStoryboard = false;
} }
protected override void ParseLine(Beatmap beatmap, Section section, string line) protected override void ParseLine(Beatmap beatmap, Section section, string line)
@ -244,7 +244,7 @@ namespace osu.Game.Beatmaps.Formats
break; break;
case @"AudioLeadIn": case @"AudioLeadIn":
beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); beatmap.AudioLeadIn = Parsing.ParseInt(pair.Value);
break; break;
case @"PreviewTime": case @"PreviewTime":
@ -261,7 +261,7 @@ namespace osu.Game.Beatmaps.Formats
break; break;
case @"StackLeniency": case @"StackLeniency":
beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); beatmap.StackLeniency = Parsing.ParseFloat(pair.Value);
break; break;
case @"Mode": case @"Mode":
@ -269,31 +269,31 @@ namespace osu.Game.Beatmaps.Formats
break; break;
case @"LetterboxInBreaks": case @"LetterboxInBreaks":
beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; beatmap.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1;
break; break;
case @"SpecialStyle": case @"SpecialStyle":
beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; beatmap.SpecialStyle = Parsing.ParseInt(pair.Value) == 1;
break; break;
case @"WidescreenStoryboard": case @"WidescreenStoryboard":
beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; beatmap.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1;
break; break;
case @"EpilepsyWarning": case @"EpilepsyWarning":
beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; beatmap.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
break; break;
case @"SamplesMatchPlaybackRate": case @"SamplesMatchPlaybackRate":
beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1; beatmap.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1;
break; break;
case @"Countdown": case @"Countdown":
beatmap.BeatmapInfo.Countdown = Enum.Parse<CountdownType>(pair.Value); beatmap.Countdown = Enum.Parse<CountdownType>(pair.Value);
break; break;
case @"CountdownOffset": case @"CountdownOffset":
beatmap.BeatmapInfo.CountdownOffset = Parsing.ParseInt(pair.Value); beatmap.CountdownOffset = Parsing.ParseInt(pair.Value);
break; break;
} }
} }
@ -313,7 +313,7 @@ namespace osu.Game.Beatmaps.Formats
break; break;
case @"DistanceSpacing": case @"DistanceSpacing":
beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); beatmap.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value));
break; break;
case @"BeatDivisor": case @"BeatDivisor":
@ -321,11 +321,11 @@ namespace osu.Game.Beatmaps.Formats
break; break;
case @"GridSize": case @"GridSize":
beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); beatmap.GridSize = Parsing.ParseInt(pair.Value);
break; break;
case @"TimelineZoom": case @"TimelineZoom":
beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); beatmap.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value));
break; break;
} }
} }

View File

@ -79,14 +79,14 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine("[General]"); writer.WriteLine("[General]");
if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.AudioLeadIn}"));
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.Countdown}"));
writer.WriteLine(FormattableString.Invariant( writer.WriteLine(FormattableString.Invariant(
$"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}"));
writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.StackLeniency}"));
writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}"));
writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.LetterboxInBreaks ? '1' : '0')}"));
// if (beatmap.BeatmapInfo.UseSkinSprites) // if (beatmap.BeatmapInfo.UseSkinSprites)
// writer.WriteLine(@"UseSkinSprites: 1"); // writer.WriteLine(@"UseSkinSprites: 1");
// if (b.AlwaysShowPlayfield) // if (b.AlwaysShowPlayfield)
@ -95,14 +95,14 @@ namespace osu.Game.Beatmaps.Formats
// writer.WriteLine(@"OverlayPosition: " + b.OverlayPosition); // writer.WriteLine(@"OverlayPosition: " + b.OverlayPosition);
// if (!string.IsNullOrEmpty(b.SkinPreference)) // if (!string.IsNullOrEmpty(b.SkinPreference))
// writer.WriteLine(@"SkinPreference:" + b.SkinPreference); // writer.WriteLine(@"SkinPreference:" + b.SkinPreference);
if (beatmap.BeatmapInfo.EpilepsyWarning) if (beatmap.EpilepsyWarning)
writer.WriteLine(@"EpilepsyWarning: 1"); writer.WriteLine(@"EpilepsyWarning: 1");
if (beatmap.BeatmapInfo.CountdownOffset > 0) if (beatmap.CountdownOffset > 0)
writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.CountdownOffset}"));
if (onlineRulesetID == 3) if (onlineRulesetID == 3)
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}"));
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.WidescreenStoryboard ? '1' : '0')}"));
if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) if (beatmap.SamplesMatchPlaybackRate)
writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
} }
@ -112,10 +112,10 @@ namespace osu.Game.Beatmaps.Formats
if (beatmap.BeatmapInfo.Bookmarks.Length > 0) if (beatmap.BeatmapInfo.Bookmarks.Length > 0)
writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}")); writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}"));
writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.BeatmapInfo.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}"));
writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}"));
writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.BeatmapInfo.GridSize}")); writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.GridSize}"));
writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}")); writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.TimelineZoom}"));
} }
private void handleMetadata(TextWriter writer) private void handleMetadata(TextWriter writer)

View File

@ -38,6 +38,17 @@ namespace osu.Game.Beatmaps.Formats
SetFallbackDecoder<Storyboard>(() => new LegacyStoryboardDecoder()); SetFallbackDecoder<Storyboard>(() => new LegacyStoryboardDecoder());
} }
protected override Storyboard CreateTemplateObject()
{
var sb = base.CreateTemplateObject();
var beatmap = new Beatmap();
LegacyBeatmapDecoder.ApplyLegacyDefaults(beatmap);
sb.Beatmap = beatmap;
return sb;
}
protected override void ParseStreamInto(LineBufferedReader stream, Storyboard storyboard) protected override void ParseStreamInto(LineBufferedReader stream, Storyboard storyboard)
{ {
this.storyboard = storyboard; this.storyboard = storyboard;
@ -73,6 +84,10 @@ namespace osu.Game.Beatmaps.Formats
case "UseSkinSprites": case "UseSkinSprites":
storyboard.UseSkinSprites = pair.Value == "1"; storyboard.UseSkinSprites = pair.Value == "1";
break; break;
case @"WidescreenStoryboard":
storyboard.Beatmap.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1;
break;
} }
} }

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -69,6 +70,43 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
double GetMostCommonBeatLength(); double GetMostCommonBeatLength();
double AudioLeadIn { get; internal set; }
float StackLeniency { get; internal set; }
bool SpecialStyle { get; internal set; }
bool LetterboxInBreaks { get; internal set; }
bool WidescreenStoryboard { get; internal set; }
bool EpilepsyWarning { get; internal set; }
bool SamplesMatchPlaybackRate { get; internal set; }
/// <summary>
/// The ratio of distance travelled per time unit.
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
/// </summary>
/// <remarks>
/// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap
/// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider.
///
/// This is only a hint property, used by the editor in <see cref="IDistanceSnapProvider"/> implementations. It does not directly affect the beatmap or gameplay.
/// </remarks>
double DistanceSpacing { get; internal set; }
int GridSize { get; internal set; }
double TimelineZoom { get; internal set; }
CountdownType Countdown { get; internal set; }
/// <summary>
/// The number of beats to move the countdown backwards (compared to its default location).
/// </summary>
int CountdownOffset { get; internal set; }
/// <summary> /// <summary>
/// Creates a shallow-clone of this beatmap and returns it. /// Creates a shallow-clone of this beatmap and returns it.
/// </summary> /// </summary>

View File

@ -62,7 +62,12 @@ namespace osu.Game.Beatmaps
#region Resource getters #region Resource getters
protected virtual Waveform GetWaveform() => new Waveform(null); protected virtual Waveform GetWaveform() => new Waveform(null);
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
protected virtual Storyboard GetStoryboard() => new Storyboard
{
BeatmapInfo = BeatmapInfo,
Beatmap = Beatmap,
};
protected abstract IBeatmap GetBeatmap(); protected abstract IBeatmap GetBeatmap();
public abstract Texture GetBackground(); public abstract Texture GetBackground();

View File

@ -94,8 +94,9 @@ namespace osu.Game.Database
/// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances.
/// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction /// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction
/// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user. /// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user.
/// 44 2024-11-22 Removed several properties from BeatmapInfo which did not need to be persisted to realm.
/// </summary> /// </summary>
private const int schema_version = 43; private const int schema_version = 44;
/// <summary> /// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods. /// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.

View File

@ -44,6 +44,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates"); public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates");
/// <summary>
/// "Checking for updates"
/// </summary>
public static LocalisableString CheckingForUpdates => new TranslatableString(getKey(@"checking_for_updates"), @"Checking for updates");
/// <summary> /// <summary>
/// "Open osu! folder" /// "Open osu! folder"
/// </summary> /// </summary>

View File

@ -174,6 +174,11 @@ namespace osu.Game
/// </summary> /// </summary>
public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(); public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>();
/// <summary>
/// Whether the back button is currently displayed.
/// </summary>
private readonly IBindable<bool> backButtonVisibility = new Bindable<bool>();
IBindable<LocalUserPlayingState> ILocalUserPlayInfo.PlayingState => playingState; IBindable<LocalUserPlayingState> ILocalUserPlayInfo.PlayingState => playingState;
private readonly Bindable<LocalUserPlayingState> playingState = new Bindable<LocalUserPlayingState>(); private readonly Bindable<LocalUserPlayingState> playingState = new Bindable<LocalUserPlayingState>();
@ -1019,7 +1024,7 @@ namespace osu.Game
if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen)) if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen))
return; return;
if (!((Drawable)currentScreen).IsLoaded || (currentScreen.AllowBackButton && !currentScreen.OnBackButton())) if (!((Drawable)currentScreen).IsLoaded || (currentScreen.AllowUserExit && !currentScreen.OnBackButton()))
ScreenStack.Exit(); ScreenStack.Exit();
} }
}, },
@ -1189,6 +1194,14 @@ namespace osu.Game
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
}; };
backButtonVisibility.ValueChanged += visible =>
{
if (visible.NewValue)
BackButton.Show();
else
BackButton.Hide();
};
// Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup. // Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup.
handleStartupImport(); handleStartupImport();
} }
@ -1581,12 +1594,14 @@ namespace osu.Game
if (current is IOsuScreen currentOsuScreen) if (current is IOsuScreen currentOsuScreen)
{ {
backButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility);
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);
API.Activity.UnbindFrom(currentOsuScreen.Activity); API.Activity.UnbindFrom(currentOsuScreen.Activity);
} }
if (newScreen is IOsuScreen newOsuScreen) if (newScreen is IOsuScreen newOsuScreen)
{ {
backButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility);
OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode);
API.Activity.BindTo(newOsuScreen.Activity); API.Activity.BindTo(newOsuScreen.Activity);
@ -1597,11 +1612,6 @@ namespace osu.Game
else else
Toolbar.Show(); Toolbar.Show();
if (newOsuScreen.AllowBackButton)
BackButton.Show();
else
BackButton.Hide();
if (newOsuScreen.ShowFooter) if (newOsuScreen.ShowFooter)
{ {
BackButton.Hide(); BackButton.Hide();

View File

@ -4,7 +4,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Logging; using osu.Framework.Logging;
@ -13,6 +12,7 @@ using osu.Framework.Screens;
using osu.Framework.Statistics; using osu.Framework.Statistics;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Overlays.Settings.Sections.Maintenance;
using osu.Game.Updater; using osu.Game.Updater;
@ -36,8 +36,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
[Resolved] [Resolved]
private Storage storage { get; set; } = null!; private Storage storage { get; set; } = null!;
[Resolved]
private OsuGame? game { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config, OsuGame? game) private void load(OsuConfigManager config)
{ {
Add(new SettingsEnumDropdown<ReleaseStream> Add(new SettingsEnumDropdown<ReleaseStream>
{ {
@ -50,23 +53,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
Add(checkForUpdatesButton = new SettingsButton Add(checkForUpdatesButton = new SettingsButton
{ {
Text = GeneralSettingsStrings.CheckUpdate, Text = GeneralSettingsStrings.CheckUpdate,
Action = () => Action = () => checkForUpdates().FireAndForget()
{
checkForUpdatesButton.Enabled.Value = false;
Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() =>
{
if (!task.GetResultSafely())
{
notifications?.Post(new SimpleNotification
{
Text = GeneralSettingsStrings.RunningLatestRelease(game!.Version),
Icon = FontAwesome.Solid.CheckCircle,
});
}
checkForUpdatesButton.Enabled.Value = true;
}));
}
}); });
} }
@ -94,6 +81,44 @@ namespace osu.Game.Overlays.Settings.Sections.General
} }
} }
private async Task checkForUpdates()
{
if (updateManager == null || game == null)
return;
checkForUpdatesButton.Enabled.Value = false;
var checkingNotification = new ProgressNotification
{
Text = GeneralSettingsStrings.CheckingForUpdates,
};
notifications?.Post(checkingNotification);
try
{
bool foundUpdate = await updateManager.CheckForUpdateAsync().ConfigureAwait(true);
if (!foundUpdate)
{
notifications?.Post(new SimpleNotification
{
Text = GeneralSettingsStrings.RunningLatestRelease(game.Version),
Icon = FontAwesome.Solid.CheckCircle,
});
}
}
catch
{
}
finally
{
// This sequence allows the notification to be immediately dismissed.
checkingNotification.State = ProgressNotificationState.Cancelled;
checkingNotification.Close(false);
checkForUpdatesButton.Enabled.Value = true;
}
}
private void exportLogs() private void exportLogs()
{ {
ProgressNotification notification = new ProgressNotification ProgressNotification notification = new ProgressNotification

View File

@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private OsuGame game { get; set; } private OsuGame game { get; set; }
public override bool AllowBackButton => false; public override bool AllowUserExit => false;
public override bool AllowExternalScreenChange => false; public override bool AllowExternalScreenChange => false;

View File

@ -184,7 +184,7 @@ namespace osu.Game.Overlays
content.ResizeHeightTo(0, animate ? transition_duration : 0, Easing.OutQuint); content.ResizeHeightTo(0, animate ? transition_duration : 0, Easing.OutQuint);
} }
headerContent.FadeColour(Expanded.Value ? Color4.White : OsuColour.Gray(0.5f), 200, Easing.OutQuint); headerContent.FadeColour(Expanded.Value ? Color4.White : OsuColour.Gray(0.7f), 200, Easing.OutQuint);
} }
private void updateFadeState() private void updateFadeState()

View File

@ -341,6 +341,78 @@ namespace osu.Game.Rulesets.Difficulty
public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength(); public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength();
public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone()); public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone());
public double AudioLeadIn
{
get => baseBeatmap.AudioLeadIn;
set => baseBeatmap.AudioLeadIn = value;
}
public float StackLeniency
{
get => baseBeatmap.StackLeniency;
set => baseBeatmap.StackLeniency = value;
}
public bool SpecialStyle
{
get => baseBeatmap.SpecialStyle;
set => baseBeatmap.SpecialStyle = value;
}
public bool LetterboxInBreaks
{
get => baseBeatmap.LetterboxInBreaks;
set => baseBeatmap.LetterboxInBreaks = value;
}
public bool WidescreenStoryboard
{
get => baseBeatmap.WidescreenStoryboard;
set => baseBeatmap.WidescreenStoryboard = value;
}
public bool EpilepsyWarning
{
get => baseBeatmap.EpilepsyWarning;
set => baseBeatmap.EpilepsyWarning = value;
}
public bool SamplesMatchPlaybackRate
{
get => baseBeatmap.SamplesMatchPlaybackRate;
set => baseBeatmap.SamplesMatchPlaybackRate = value;
}
public double DistanceSpacing
{
get => baseBeatmap.DistanceSpacing;
set => baseBeatmap.DistanceSpacing = value;
}
public int GridSize
{
get => baseBeatmap.GridSize;
set => baseBeatmap.GridSize = value;
}
public double TimelineZoom
{
get => baseBeatmap.TimelineZoom;
set => baseBeatmap.TimelineZoom = value;
}
public CountdownType Countdown
{
get => baseBeatmap.Countdown;
set => baseBeatmap.Countdown = value;
}
public int CountdownOffset
{
get => baseBeatmap.CountdownOffset;
set => baseBeatmap.CountdownOffset = value;
}
#endregion #endregion
} }
} }

View File

@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Edit
} }
}); });
DistanceSpacingMultiplier.Value = editorBeatmap.BeatmapInfo.DistanceSpacing; DistanceSpacingMultiplier.Value = editorBeatmap.DistanceSpacing;
DistanceSpacingMultiplier.BindValueChanged(multiplier => DistanceSpacingMultiplier.BindValueChanged(multiplier =>
{ {
distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})"; distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})";
@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Edit
if (multiplier.NewValue != multiplier.OldValue) if (multiplier.NewValue != multiplier.OldValue)
onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier)); onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier));
editorBeatmap.BeatmapInfo.DistanceSpacing = multiplier.NewValue; editorBeatmap.DistanceSpacing = multiplier.NewValue;
}, true); }, true);
DistanceSpacingMultiplier.BindDisabledChanged(disabled => distanceSpacingSlider.Alpha = disabled ? 0 : 1, true); DistanceSpacingMultiplier.BindDisabledChanged(disabled => distanceSpacingSlider.Alpha = disabled ? 0 : 1, true);

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Edit
/// A multiplier which changes the ratio of distance travelled per time unit. /// A multiplier which changes the ratio of distance travelled per time unit.
/// Importantly, this is provided for manual usage, and not multiplied into any of the methods exposed by this interface. /// Importantly, this is provided for manual usage, and not multiplied into any of the methods exposed by this interface.
/// </summary> /// </summary>
/// <seealso cref="BeatmapInfo.DistanceSpacing"/> /// <seealso cref="IBeatmap.DistanceSpacing"/>
Bindable<double> DistanceSpacingMultiplier { get; } Bindable<double> DistanceSpacingMultiplier { get; }
/// <summary> /// <summary>

View File

@ -433,7 +433,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// Finishes the current blueprint selection. /// Finishes the current blueprint selection.
/// </summary> /// </summary>
/// <param name="e">The mouse event which triggered end of selection.</param> /// <param name="e">The mouse event which triggered end of selection.</param>
/// <returns>Whether a click selection was active.</returns> /// <returns>
/// Whether the mouse event is considered to be fully handled.
/// If the return value is <see langword="false"/>, the standard click / mouse up action will follow.
/// </returns>
private bool endClickSelection(MouseButtonEvent e) private bool endClickSelection(MouseButtonEvent e)
{ {
// If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action. // If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action.
@ -443,14 +446,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (e.ControlPressed) if (e.ControlPressed)
{ {
// if a selection didn't occur, we may want to trigger a deselection.
// Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Iterate from the top of the input stack (blueprints closest to the front of the screen first).
// Priority is given to already-selected blueprints. // Priority is given to already-selected blueprints.
foreach (SelectionBlueprint<T> blueprint in SelectionBlueprints.AliveChildren.Where(b => b.IsHovered).OrderByDescending(b => b.IsSelected)) foreach (SelectionBlueprint<T> blueprint in SelectionBlueprints.AliveChildren.Where(b => b.IsHovered).OrderByDescending(b => b.IsSelected))
return clickSelectionHandled = SelectionHandler.MouseUpSelectionRequested(blueprint, e); return clickSelectionHandled = SelectionHandler.MouseUpSelectionRequested(blueprint, e);
return false; // can only be reached if there are no hovered blueprints.
// in that case, we still want to suppress mouse up / click handling, because when control is pressed,
// it is presumed we want to add to existing selection, not remove from it
// (unless explicitly control-clicking a selected object, which is handled above).
return true;
} }
if (selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1) if (selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1)

View File

@ -157,7 +157,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Scheduler.AddOnce(applyVisualOffset, beatmap); Scheduler.AddOnce(applyVisualOffset, beatmap);
}, true); }, true);
Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); Zoom = (float)(defaultTimelineZoom * editorBeatmap.TimelineZoom);
} }
private void applyVisualOffset(IBindable<WorkingBeatmap> beatmap) private void applyVisualOffset(IBindable<WorkingBeatmap> beatmap)
@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
float minimumZoom = getZoomLevelForVisibleMilliseconds(10000); float minimumZoom = getZoomLevelForVisibleMilliseconds(10000);
float maximumZoom = getZoomLevelForVisibleMilliseconds(500); float maximumZoom = getZoomLevelForVisibleMilliseconds(500);
float initialZoom = (float)Math.Clamp(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom == 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom), minimumZoom, maximumZoom); float initialZoom = (float)Math.Clamp(defaultTimelineZoom * (editorBeatmap.TimelineZoom == 0 ? 1 : editorBeatmap.TimelineZoom), minimumZoom, maximumZoom);
SetupZoom(initialZoom, minimumZoom, maximumZoom); SetupZoom(initialZoom, minimumZoom, maximumZoom);
@ -234,7 +234,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override void OnZoomChanged() protected override void OnZoomChanged()
{ {
base.OnZoomChanged(); base.OnZoomChanged();
editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom; editorBeatmap.TimelineZoom = Zoom / defaultTimelineZoom;
} }
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()

View File

@ -80,8 +80,6 @@ namespace osu.Game.Screens.Edit
public override float BackgroundParallaxAmount => 0.1f; public override float BackgroundParallaxAmount => 0.1f;
public override bool AllowBackButton => false;
public override bool HideOverlaysOnEnter => true; public override bool HideOverlaysOnEnter => true;
public override bool DisallowExternalBeatmapRulesetChanges => true; public override bool DisallowExternalBeatmapRulesetChanges => true;
@ -194,6 +192,8 @@ namespace osu.Game.Screens.Edit
} }
} }
protected override bool InitialBackButtonVisibility => false;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
@ -760,11 +760,6 @@ namespace osu.Game.Screens.Edit
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Back:
// as we don't want to display the back button, manual handling of exit action is required.
this.Exit();
return true;
case GlobalAction.EditorCloneSelection: case GlobalAction.EditorCloneSelection:
Clone(); Clone();
return true; return true;

View File

@ -198,6 +198,78 @@ namespace osu.Game.Screens.Edit
public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength();
public double AudioLeadIn
{
get => PlayableBeatmap.AudioLeadIn;
set => PlayableBeatmap.AudioLeadIn = value;
}
public float StackLeniency
{
get => PlayableBeatmap.StackLeniency;
set => PlayableBeatmap.StackLeniency = value;
}
public bool SpecialStyle
{
get => PlayableBeatmap.SpecialStyle;
set => PlayableBeatmap.SpecialStyle = value;
}
public bool LetterboxInBreaks
{
get => PlayableBeatmap.LetterboxInBreaks;
set => PlayableBeatmap.LetterboxInBreaks = value;
}
public bool WidescreenStoryboard
{
get => PlayableBeatmap.WidescreenStoryboard;
set => PlayableBeatmap.WidescreenStoryboard = value;
}
public bool EpilepsyWarning
{
get => PlayableBeatmap.EpilepsyWarning;
set => PlayableBeatmap.EpilepsyWarning = value;
}
public bool SamplesMatchPlaybackRate
{
get => PlayableBeatmap.SamplesMatchPlaybackRate;
set => PlayableBeatmap.SamplesMatchPlaybackRate = value;
}
public double DistanceSpacing
{
get => PlayableBeatmap.DistanceSpacing;
set => PlayableBeatmap.DistanceSpacing = value;
}
public int GridSize
{
get => PlayableBeatmap.GridSize;
set => PlayableBeatmap.GridSize = value;
}
public double TimelineZoom
{
get => PlayableBeatmap.TimelineZoom;
set => PlayableBeatmap.TimelineZoom = value;
}
public CountdownType Countdown
{
get => PlayableBeatmap.Countdown;
set => PlayableBeatmap.Countdown = value;
}
public int CountdownOffset
{
get => PlayableBeatmap.CountdownOffset;
set => PlayableBeatmap.CountdownOffset = value;
}
public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone();
private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects;

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit
public override float BackgroundParallaxAmount => 0.1f; public override float BackgroundParallaxAmount => 0.1f;
public override bool AllowBackButton => false; public override bool AllowUserExit => false;
public override bool HideOverlaysOnEnter => true; public override bool HideOverlaysOnEnter => true;

View File

@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Setup
{ {
Caption = EditorSetupStrings.EnableCountdown, Caption = EditorSetupStrings.EnableCountdown,
HintText = EditorSetupStrings.CountdownDescription, HintText = EditorSetupStrings.CountdownDescription,
Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None }, Current = { Value = Beatmap.Countdown != CountdownType.None },
}, },
CountdownSettings = new FillFlowContainer CountdownSettings = new FillFlowContainer
{ {
@ -52,14 +52,14 @@ namespace osu.Game.Screens.Edit.Setup
CountdownSpeed = new FormEnumDropdown<CountdownType> CountdownSpeed = new FormEnumDropdown<CountdownType>
{ {
Caption = EditorSetupStrings.CountdownSpeed, Caption = EditorSetupStrings.CountdownSpeed,
Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None ? Beatmap.BeatmapInfo.Countdown : CountdownType.Normal }, Current = { Value = Beatmap.Countdown != CountdownType.None ? Beatmap.Countdown : CountdownType.Normal },
Items = Enum.GetValues<CountdownType>().Where(type => type != CountdownType.None) Items = Enum.GetValues<CountdownType>().Where(type => type != CountdownType.None)
}, },
CountdownOffset = new FormNumberBox CountdownOffset = new FormNumberBox
{ {
Caption = EditorSetupStrings.CountdownOffset, Caption = EditorSetupStrings.CountdownOffset,
HintText = EditorSetupStrings.CountdownOffsetDescription, HintText = EditorSetupStrings.CountdownOffsetDescription,
Current = { Value = Beatmap.BeatmapInfo.CountdownOffset.ToString() }, Current = { Value = Beatmap.CountdownOffset.ToString() },
TabbableContentContainer = this, TabbableContentContainer = this,
} }
} }
@ -68,25 +68,25 @@ namespace osu.Game.Screens.Edit.Setup
{ {
Caption = EditorSetupStrings.WidescreenSupport, Caption = EditorSetupStrings.WidescreenSupport,
HintText = EditorSetupStrings.WidescreenSupportDescription, HintText = EditorSetupStrings.WidescreenSupportDescription,
Current = { Value = Beatmap.BeatmapInfo.WidescreenStoryboard } Current = { Value = Beatmap.WidescreenStoryboard }
}, },
epilepsyWarning = new FormCheckBox epilepsyWarning = new FormCheckBox
{ {
Caption = EditorSetupStrings.EpilepsyWarning, Caption = EditorSetupStrings.EpilepsyWarning,
HintText = EditorSetupStrings.EpilepsyWarningDescription, HintText = EditorSetupStrings.EpilepsyWarningDescription,
Current = { Value = Beatmap.BeatmapInfo.EpilepsyWarning } Current = { Value = Beatmap.EpilepsyWarning }
}, },
letterboxDuringBreaks = new FormCheckBox letterboxDuringBreaks = new FormCheckBox
{ {
Caption = EditorSetupStrings.LetterboxDuringBreaks, Caption = EditorSetupStrings.LetterboxDuringBreaks,
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks } Current = { Value = Beatmap.LetterboxInBreaks }
}, },
samplesMatchPlaybackRate = new FormCheckBox samplesMatchPlaybackRate = new FormCheckBox
{ {
Caption = EditorSetupStrings.SamplesMatchPlaybackRate, Caption = EditorSetupStrings.SamplesMatchPlaybackRate,
HintText = EditorSetupStrings.SamplesMatchPlaybackRateDescription, HintText = EditorSetupStrings.SamplesMatchPlaybackRateDescription,
Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate } Current = { Value = Beatmap.SamplesMatchPlaybackRate }
} }
}; };
} }
@ -113,18 +113,18 @@ namespace osu.Game.Screens.Edit.Setup
{ {
updateBeatmap(); updateBeatmap();
// update displayed text to ensure parsed value matches display (i.e. if empty string was provided). // update displayed text to ensure parsed value matches display (i.e. if empty string was provided).
CountdownOffset.Current.Value = Beatmap.BeatmapInfo.CountdownOffset.ToString(CultureInfo.InvariantCulture); CountdownOffset.Current.Value = Beatmap.CountdownOffset.ToString(CultureInfo.InvariantCulture);
} }
private void updateBeatmap() private void updateBeatmap()
{ {
Beatmap.BeatmapInfo.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; Beatmap.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None;
Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; Beatmap.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0;
Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value;
Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.EpilepsyWarning = epilepsyWarning.Current.Value;
Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value;
Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; Beatmap.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value;
Beatmap.SaveState(); Beatmap.SaveState();
} }

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Footer; using osu.Game.Screens.Footer;
@ -21,15 +22,21 @@ namespace osu.Game.Screens
bool DisallowExternalBeatmapRulesetChanges { get; } bool DisallowExternalBeatmapRulesetChanges { get; }
/// <summary> /// <summary>
/// Whether the user can exit this <see cref="IOsuScreen"/> by pressing the back button. /// Whether the user can exit this <see cref="IOsuScreen"/>.
/// </summary> /// </summary>
bool AllowBackButton { get; } /// <remarks>
/// When overriden to <c>false</c>,
/// the user is blocked from exiting the screen via the <see cref="GlobalAction.Back"/> action,
/// and the back button is hidden from this screen by the initial state of <see cref="BackButtonVisibility"/> being set to hidden.
/// </remarks>
bool AllowUserExit { get; }
/// <summary> /// <summary>
/// Whether a footer (and a back button) should be displayed underneath the screen. /// Whether a footer (and a back button) should be displayed underneath the screen.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Temporarily, the back button is shown regardless of whether <see cref="AllowBackButton"/> is true. /// Temporarily, the footer's own back button is shown regardless of whether <see cref="BackButtonVisibility"/> is set to hidden.
/// This will be corrected as the footer becomes used more commonly.
/// </remarks> /// </remarks>
bool ShowFooter { get; } bool ShowFooter { get; }
@ -59,6 +66,11 @@ namespace osu.Game.Screens
/// </summary> /// </summary>
IBindable<OverlayActivation> OverlayActivationMode { get; } IBindable<OverlayActivation> OverlayActivationMode { get; }
/// <summary>
/// Whether the back button should be displayed in this screen.
/// </summary>
IBindable<bool> BackButtonVisibility { get; }
/// <summary> /// <summary>
/// The current <see cref="UserActivity"/> for this screen. /// The current <see cref="UserActivity"/> for this screen.
/// </summary> /// </summary>

View File

@ -48,7 +48,7 @@ namespace osu.Game.Screens.Menu
public override bool HideOverlaysOnEnter => Buttons == null || Buttons.State == ButtonSystemState.Initial; public override bool HideOverlaysOnEnter => Buttons == null || Buttons.State == ButtonSystemState.Initial;
public override bool AllowBackButton => false; public override bool AllowUserExit => false;
public override bool AllowExternalScreenChange => true; public override bool AllowExternalScreenChange => true;

View File

@ -126,7 +126,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
syncManager = new SpectatorSyncManager(masterClockContainer) syncManager = new SpectatorSyncManager(masterClockContainer)
{ {
ReadyToStart = performInitialSeek, ReadyToStart = performInitialSeek,
} },
new PlayerSettingsOverlay()
}; };
for (int i = 0; i < Users.Count; i++) for (int i = 0; i < Users.Count; i++)

View File

@ -180,7 +180,7 @@ namespace osu.Game.Screens.OnlinePlay
if (!(screenStack.CurrentScreen is IOnlinePlaySubScreen onlineSubScreen)) if (!(screenStack.CurrentScreen is IOnlinePlaySubScreen onlineSubScreen))
return false; return false;
if (((Drawable)onlineSubScreen).IsLoaded && onlineSubScreen.AllowBackButton && onlineSubScreen.OnBackButton()) if (((Drawable)onlineSubScreen).IsLoaded && onlineSubScreen.AllowUserExit && onlineSubScreen.OnBackButton())
return true; return true;
if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen))

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -10,7 +11,9 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Utils;
namespace osu.Game.Screens.OnlinePlay.Playlists namespace osu.Game.Screens.OnlinePlay.Playlists
{ {
@ -19,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
[Resolved] [Resolved]
private IBindable<WorkingBeatmap> gameBeatmap { get; set; } = null!; private IBindable<WorkingBeatmap> gameBeatmap { get; set; } = null!;
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
private readonly Room room; private readonly Room room;
public PlaylistsReadyButton(Room room) public PlaylistsReadyButton(Room room)
@ -63,14 +69,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{ {
base.Update(); base.Update();
Enabled.Value = hasRemainingAttempts && enoughTimeLeft; Enabled.Value = hasRemainingAttempts && enoughTimeLeft();
} }
public override LocalisableString TooltipText public override LocalisableString TooltipText
{ {
get get
{ {
if (!enoughTimeLeft) if (!enoughTimeLeft())
return "No time left!"; return "No time left!";
if (!hasRemainingAttempts) if (!hasRemainingAttempts)
@ -80,9 +86,17 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
} }
} }
private bool enoughTimeLeft => private bool enoughTimeLeft()
// This should probably consider the length of the currently selected item, rather than a constant 30 seconds. {
room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < room.EndDate; double rate = ModUtils.CalculateRateWithMods(mods.Value);
// We want to avoid users not being able to submit scores if they chose to not skip,
// so track length is chosen over playable length.
double trackLength = Math.Round(gameBeatmap.Value.Track.Length / rate);
// Additional 30 second delay added to account for load and/or submit time.
return room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(trackLength) < room.EndDate;
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {

View File

@ -37,7 +37,7 @@ namespace osu.Game.Screens
public string Description => Title; public string Description => Title;
public virtual bool AllowBackButton => true; public virtual bool AllowUserExit => true;
public virtual bool ShowFooter => false; public virtual bool ShowFooter => false;
@ -56,6 +56,15 @@ namespace osu.Game.Screens
IBindable<OverlayActivation> IOsuScreen.OverlayActivationMode => OverlayActivationMode; IBindable<OverlayActivation> IOsuScreen.OverlayActivationMode => OverlayActivationMode;
/// <summary>
/// The initial visibility state of the back button when this screen is entered for the first time.
/// </summary>
protected virtual bool InitialBackButtonVisibility => AllowUserExit;
public readonly Bindable<bool> BackButtonVisibility;
IBindable<bool> IOsuScreen.BackButtonVisibility => BackButtonVisibility;
public virtual bool CursorVisible => true; public virtual bool CursorVisible => true;
protected new OsuGameBase Game => base.Game as OsuGameBase; protected new OsuGameBase Game => base.Game as OsuGameBase;
@ -154,6 +163,7 @@ namespace osu.Game.Screens
Origin = Anchor.Centre; Origin = Anchor.Centre;
OverlayActivationMode = new Bindable<OverlayActivation>(InitialOverlayActivationMode); OverlayActivationMode = new Bindable<OverlayActivation>(InitialOverlayActivationMode);
BackButtonVisibility = new Bindable<bool>(InitialBackButtonVisibility);
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]

View File

@ -1,46 +1,102 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osuTK; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
public partial class PlayerSettingsOverlay : VisibilityContainer public partial class PlayerSettingsOverlay : ExpandingContainer
{ {
public VisualSettings VisualSettings { get; private set; }
private const float padding = 10;
public const float EXPANDED_WIDTH = player_settings_width + padding * 2;
private const float player_settings_width = 270;
private const int fade_duration = 200; private const int fade_duration = 200;
public readonly VisualSettings VisualSettings; public override void Show() => this.FadeIn(fade_duration);
public override void Hide() => this.FadeOut(fade_duration);
// we'll handle this ourselves because we have slightly custom logic.
protected override bool ExpandOnHover => false;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly FillFlowContainer content; private readonly FillFlowContainer content;
public PlayerSettingsOverlay() private readonly IconButton button;
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
AutoSizeAxes = Axes.Both;
InternalChild = content = new FillFlowContainer private InputManager inputManager = null!;
public PlayerSettingsOverlay()
: base(0, EXPANDED_WIDTH)
{
Origin = Anchor.TopRight;
Anchor = Anchor.TopRight;
base.Content.Add(content = new FillFlowContainer
{ {
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20), Spacing = new Vector2(0, 20),
Margin = new MarginPadding(padding),
Children = new PlayerSettingsGroup[] Children = new PlayerSettingsGroup[]
{ {
VisualSettings = new VisualSettings { Expanded = { Value = false } }, VisualSettings = new VisualSettings { Expanded = { Value = false } },
new AudioSettings { Expanded = { Value = false } } new AudioSettings { Expanded = { Value = false } }
} }
}; });
AddInternal(button = new IconButton
{
Icon = FontAwesome.Solid.Cog,
Origin = Anchor.TopRight,
Anchor = Anchor.TopLeft,
Margin = new MarginPadding(5),
Action = () => Expanded.Toggle()
});
AddInternal(new Box
{
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), Color4.Black.Opacity(0.8f)),
Depth = float.MaxValue,
RelativeSizeAxes = Axes.Both,
});
} }
protected override void PopIn() => this.FadeIn(fade_duration); protected override void LoadComplete()
protected override void PopOut() => this.FadeOut(fade_duration); {
base.LoadComplete();
inputManager = GetContainingInputManager()!;
}
protected override void Update()
{
base.Update();
Expanded.Value = inputManager.CurrentState.Mouse.Position.X >= button.ScreenSpaceDrawQuad.TopLeft.X;
}
protected override void OnHoverLost(HoverLostEvent e)
{
// handle un-expanding manually because our children do weird hover blocking stuff.
}
public void AddAtStart(PlayerSettingsGroup drawable) => content.Insert(-1, drawable); public void AddAtStart(PlayerSettingsGroup drawable) => content.Insert(-1, drawable);
} }

View File

@ -115,6 +115,8 @@ namespace osu.Game.Screens.Play
public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods, bool alwaysShowLeaderboard = true) public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods, bool alwaysShowLeaderboard = true)
{ {
Container rightSettings;
this.drawableRuleset = drawableRuleset; this.drawableRuleset = drawableRuleset;
this.mods = mods; this.mods = mods;
@ -146,7 +148,6 @@ namespace osu.Game.Screens.Play
Children = new Drawable[] Children = new Drawable[]
{ {
ModDisplay = CreateModsContainer(), ModDisplay = CreateModsContainer(),
PlayerSettingsOverlay = CreatePlayerSettingsOverlay(),
} }
}, },
bottomRightElements = new FillFlowContainer bottomRightElements = new FillFlowContainer
@ -164,6 +165,14 @@ namespace osu.Game.Screens.Play
HoldToQuit = CreateHoldForMenuButton(), HoldToQuit = CreateHoldForMenuButton(),
} }
}, },
rightSettings = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
PlayerSettingsOverlay = new PlayerSettingsOverlay(),
}
},
LeaderboardFlow = new FillFlowContainer LeaderboardFlow = new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
@ -173,7 +182,7 @@ namespace osu.Game.Screens.Play
}, },
}; };
hideTargets = new List<Drawable> { mainComponents, topRightElements }; hideTargets = new List<Drawable> { mainComponents, topRightElements, rightSettings };
if (rulesetComponents != null) if (rulesetComponents != null)
hideTargets.Add(rulesetComponents); hideTargets.Add(rulesetComponents);
@ -389,8 +398,6 @@ namespace osu.Game.Screens.Play
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
}; };
protected PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat) if (e.Repeat)

View File

@ -95,8 +95,8 @@ namespace osu.Game.Screens.Play
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing. // this is not available as an option in the live editor but can still be applied via .osu editing.
double firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; double firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime;
if (beatmap.BeatmapInfo.AudioLeadIn > 0) if (beatmap.Beatmap.AudioLeadIn > 0)
time = Math.Min(time, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); time = Math.Min(time, firstHitObjectTime - beatmap.Beatmap.AudioLeadIn);
return time; return time;
} }

View File

@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
public event Action OnGameplayStarted; public event Action OnGameplayStarted;
public override bool AllowBackButton => false; // handled by HoldForMenuButton public override bool AllowUserExit => false; // handled by HoldForMenuButton
protected override bool PlayExitSound => !isRestarting; protected override bool PlayExitSound => !isRestarting;
@ -457,7 +457,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre Origin = Anchor.Centre
}, },
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) BreakOverlay = new BreakOverlay(working.Beatmap.LetterboxInBreaks, ScoreProcessor)
{ {
Clock = DrawableRuleset.FrameStableClock, Clock = DrawableRuleset.FrameStableClock,
ProcessCustomClock = false, ProcessCustomClock = false,

View File

@ -242,7 +242,7 @@ namespace osu.Game.Screens.Play
sampleRestart = new SkinnableSound(new SampleInfo(@"Gameplay/restart", @"pause-retry-click")) sampleRestart = new SkinnableSound(new SampleInfo(@"Gameplay/restart", @"pause-retry-click"))
}; };
if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) if (Beatmap.Value.Beatmap.EpilepsyWarning)
{ {
disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer(PlayerLoaderStrings.EpilepsyWarningTitle, PlayerLoaderStrings.EpilepsyWarningContent)); disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer(PlayerLoaderStrings.EpilepsyWarningTitle, PlayerLoaderStrings.EpilepsyWarningContent));
} }
@ -485,6 +485,8 @@ namespace osu.Game.Screens.Play
if (quickRestart) if (quickRestart)
{ {
BackButtonVisibility.Value = false;
// A quick restart starts by triggering a fade to black // A quick restart starts by triggering a fade to black
AddInternal(quickRestartBlackLayer = new Box AddInternal(quickRestartBlackLayer = new Box
{ {
@ -503,6 +505,8 @@ namespace osu.Game.Screens.Play
.Delay(quick_restart_initial_delay) .Delay(quick_restart_initial_delay)
.ScaleTo(1) .ScaleTo(1)
.FadeInFromZero(500, Easing.OutQuint); .FadeInFromZero(500, Easing.OutQuint);
this.Delay(quick_restart_initial_delay).Schedule(() => BackButtonVisibility.Value = true);
} }
else else
{ {

View File

@ -401,7 +401,6 @@ namespace osu.Game.Screens.Select
if (beatmap == null || bpmLabelContainer == null) if (beatmap == null || bpmLabelContainer == null)
return; return;
// this doesn't consider mods which apply variable rates, yet.
double rate = ModUtils.CalculateRateWithMods(mods.Value); double rate = ModUtils.CalculateRateWithMods(mods.Value);
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate); int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);

View File

@ -10,7 +10,7 @@ namespace osu.Game.Screens
/// </summary> /// </summary>
public abstract partial class StartupScreen : OsuScreen public abstract partial class StartupScreen : OsuScreen
{ {
public override bool AllowBackButton => false; public override bool AllowUserExit => false;
public override bool HideOverlaysOnEnter => true; public override bool HideOverlaysOnEnter => true;

View File

@ -67,7 +67,7 @@ namespace osu.Game.Storyboards.Drawables
bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).All(e => e is StoryboardVideo); bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).All(e => e is StoryboardVideo);
Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f); Width = Height * (storyboard.Beatmap.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f);
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;

View File

@ -17,6 +17,7 @@ namespace osu.Game.Storyboards
public IEnumerable<StoryboardLayer> Layers => layers.Values; public IEnumerable<StoryboardLayer> Layers => layers.Values;
public BeatmapInfo BeatmapInfo = new BeatmapInfo(); public BeatmapInfo BeatmapInfo = new BeatmapInfo();
public IBeatmap Beatmap { get; set; } = new Beatmap();
/// <summary> /// <summary>
/// Whether the storyboard should prefer textures from the current skin before using local storyboard textures. /// Whether the storyboard should prefer textures from the current skin before using local storyboard textures.

View File

@ -27,6 +27,17 @@ namespace osu.Game.Tests.Beatmaps
BeatmapInfo = baseBeatmap.BeatmapInfo; BeatmapInfo = baseBeatmap.BeatmapInfo;
ControlPointInfo = baseBeatmap.ControlPointInfo; ControlPointInfo = baseBeatmap.ControlPointInfo;
UnhandledEventLines = baseBeatmap.UnhandledEventLines; UnhandledEventLines = baseBeatmap.UnhandledEventLines;
AudioLeadIn = baseBeatmap.AudioLeadIn;
StackLeniency = baseBeatmap.StackLeniency;
SpecialStyle = baseBeatmap.SpecialStyle;
LetterboxInBreaks = baseBeatmap.LetterboxInBreaks;
WidescreenStoryboard = baseBeatmap.WidescreenStoryboard;
EpilepsyWarning = baseBeatmap.EpilepsyWarning;
SamplesMatchPlaybackRate = baseBeatmap.SamplesMatchPlaybackRate;
DistanceSpacing = baseBeatmap.DistanceSpacing;
GridSize = baseBeatmap.GridSize;
TimelineZoom = baseBeatmap.TimelineZoom;
CountdownOffset = baseBeatmap.CountdownOffset;
if (withHitObjects) if (withHitObjects)
{ {

View File

@ -286,6 +286,7 @@ namespace osu.Game.Utils
{ {
double rate = 1; double rate = 1;
// TODO: This doesn't consider mods which apply variable rates, yet.
foreach (var mod in mods.OfType<IApplicableToRate>()) foreach (var mod in mods.OfType<IApplicableToRate>())
rate = mod.ApplyToRate(0, rate); rate = mod.ApplyToRate(0, rate);