Merge branch 'master' into control-point-timeline-representation

This commit is contained in:
Dean Herbert 2020-10-02 19:53:25 +09:00
commit 2f15d558a3
29 changed files with 334 additions and 92 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.930.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.1001.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(239, 176), Position = new Vector2(239, 176),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
@ -185,22 +185,26 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5); private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5);
private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 0.5);
private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 15);
private Drawable createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) private const double time_offset = 1500;
private const float max_length = 200;
private Drawable createSlider(float circleSize = 2, float distance = max_length, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-(distance / 2), 0), Position = new Vector2(0, -(distance / 2)),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(distance, 0), new Vector2(0, distance),
}, distance), }, distance),
RepeatCount = repeats, RepeatCount = repeats,
StackHeight = stackHeight StackHeight = stackHeight
@ -213,14 +217,14 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-200, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(200, 200), new Vector2(max_length / 2, max_length / 2),
new Vector2(400, 0) new Vector2(max_length, 0)
}, 600), }, max_length * 1.5f),
RepeatCount = repeats, RepeatCount = repeats,
}; };
@ -233,16 +237,16 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-200, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.Linear, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 75), new Vector2(max_length * 0.375f, max_length * 0.18f),
new Vector2(200, 0), new Vector2(max_length / 2, 0),
new Vector2(300, -200), new Vector2(max_length * 0.75f, -max_length / 2),
new Vector2(400, 0), new Vector2(max_length * 0.95f, 0),
new Vector2(430, 0) new Vector2(max_length, 0)
}), }),
RepeatCount = repeats, RepeatCount = repeats,
}; };
@ -256,15 +260,15 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-200, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.Bezier, new[] Path = new SliderPath(PathType.Bezier, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 75), new Vector2(max_length * 0.375f, max_length * 0.18f),
new Vector2(200, 100), new Vector2(max_length / 2, max_length / 4),
new Vector2(300, -200), new Vector2(max_length * 0.75f, -max_length / 2),
new Vector2(430, 0) new Vector2(max_length, 0)
}), }),
RepeatCount = repeats, RepeatCount = repeats,
}; };
@ -278,16 +282,16 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.Linear, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(-200, 0), new Vector2(-max_length / 2, 0),
new Vector2(0, 0), new Vector2(0, 0),
new Vector2(0, -200), new Vector2(0, -max_length / 2),
new Vector2(-200, -200), new Vector2(-max_length / 2, -max_length / 2),
new Vector2(0, -200) new Vector2(0, -max_length / 2)
}), }),
RepeatCount = repeats, RepeatCount = repeats,
}; };
@ -305,14 +309,14 @@ namespace osu.Game.Rulesets.Osu.Tests
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-100, 0), Position = new Vector2(-max_length / 4, 0),
Path = new SliderPath(PathType.Catmull, new[] Path = new SliderPath(PathType.Catmull, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(50, -50), new Vector2(max_length * 0.125f, max_length * 0.125f),
new Vector2(150, 50), new Vector2(max_length * 0.375f, max_length * 0.125f),
new Vector2(200, 0) new Vector2(max_length / 2, 0)
}), }),
RepeatCount = repeats, RepeatCount = repeats,
NodeSamples = repeatSamples NodeSamples = repeatSamples

View File

@ -110,6 +110,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
public override void StopAllSamples()
{
base.StopAllSamples();
slidingSample?.Stop();
}
private void updateSlidingSample(ValueChangedEvent<bool> tracking) private void updateSlidingSample(ValueChangedEvent<bool> tracking)
{ {
if (tracking.NewValue) if (tracking.NewValue)

View File

@ -124,6 +124,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
public override void StopAllSamples()
{
base.StopAllSamples();
spinningSample?.Stop();
}
protected override void AddNestedHitObject(DrawableHitObject hitObject) protected override void AddNestedHitObject(DrawableHitObject hitObject)
{ {
base.AddNestedHitObject(hitObject); base.AddNestedHitObject(hitObject);

View File

@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
} }
protected override void Update() protected override void Update()
@ -124,6 +126,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
if (!(drawableHitObject is DrawableSpinner))
return;
centre.ScaleTo(0); centre.ScaleTo(0);
mainContainer.ScaleTo(0); mainContainer.ScaleTo(0);

View File

@ -72,10 +72,15 @@ namespace osu.Game.Rulesets.Osu.Skinning
this.FadeOut(); this.FadeOut();
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
} }
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
if (!(drawableHitObject is DrawableSpinner))
return;
var spinner = (Spinner)drawableSpinner.HitObject; var spinner = (Spinner)drawableSpinner.HitObject;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))

View File

@ -24,12 +24,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
private Sprite metreSprite; private Sprite metreSprite;
private Container metre; private Container metre;
private bool spinnerBlink;
private const float sprite_scale = 1 / 1.6f; private const float sprite_scale = 1 / 1.6f;
private const float final_metre_height = 692 * sprite_scale; private const float final_metre_height = 692 * sprite_scale;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ISkinSource source, DrawableHitObject drawableObject) private void load(ISkinSource source, DrawableHitObject drawableObject)
{ {
spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true;
drawableSpinner = (DrawableSpinner)drawableObject; drawableSpinner = (DrawableSpinner)drawableObject;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -86,10 +90,15 @@ namespace osu.Game.Rulesets.Osu.Skinning
this.FadeOut(); this.FadeOut();
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
} }
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
if (!(drawableHitObject is DrawableSpinner))
return;
var spinner = drawableSpinner.HitObject; var spinner = drawableSpinner.HitObject;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
@ -116,12 +125,15 @@ namespace osu.Game.Rulesets.Osu.Skinning
private float getMetreHeight(float progress) private float getMetreHeight(float progress)
{ {
progress = Math.Min(99, progress * 100); progress *= 100;
// the spinner should still blink at 100% progress.
if (spinnerBlink)
progress = Math.Min(99, progress);
int barCount = (int)progress / 10; int barCount = (int)progress / 10;
// todo: add SpinnerNoBlink support if (spinnerBlink && RNG.NextBool(((int)progress % 10) / 10f))
if (RNG.NextBool(((int)progress % 10) / 10f))
barCount++; barCount++;
return (float)barCount / total_bars * final_metre_height; return (float)barCount / total_bars * final_metre_height;

View File

@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
CursorRotate, CursorRotate,
HitCircleOverlayAboveNumber, HitCircleOverlayAboveNumber,
HitCircleOverlayAboveNumer, // Some old skins will have this typo HitCircleOverlayAboveNumer, // Some old skins will have this typo
SpinnerFrequencyModulate SpinnerFrequencyModulate,
SpinnerNoBlink
} }
} }

View File

@ -139,6 +139,22 @@ namespace osu.Game.Tests.NonVisual
Assert.That(cpi.Groups.Count, Is.EqualTo(0)); Assert.That(cpi.Groups.Count, Is.EqualTo(0));
} }
[Test]
public void TestRemoveGroupAlsoRemovedControlPoints()
{
var cpi = new ControlPointInfo();
var group = cpi.GroupAt(1000, true);
group.Add(new SampleControlPoint());
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
cpi.RemoveGroup(group);
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(0));
}
[Test] [Test]
public void TestAddControlPointToGroup() public void TestAddControlPointToGroup()
{ {

View File

@ -0,0 +1,47 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorSamplePlayback : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
[Test]
public void TestSlidingSampleStopsOnSeek()
{
DrawableSlider slider = null;
DrawableSample[] samples = null;
AddStep("get first slider", () =>
{
slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
samples = slider.ChildrenOfType<DrawableSample>().ToArray();
});
AddStep("start playback", () => EditorClock.Start());
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
return false;
if (!samples.Any(s => s.Playing))
return false;
EditorClock.Seek(20000);
return true;
});
AddAssert("slider samples are not playing", () => samples.Length == 5 && samples.All(s => s.Played && !s.Playing));
}
}
}

View File

@ -28,7 +28,7 @@ namespace osu.Game.Tournament.Components
{ {
base.Bindable = new Bindable<string>(); base.Bindable = new Bindable<string>();
((OsuTextBox)Control).OnCommit = (sender, newText) => ((OsuTextBox)Control).OnCommit += (sender, newText) =>
{ {
try try
{ {

View File

@ -158,6 +158,9 @@ namespace osu.Game.Beatmaps.ControlPoints
public void RemoveGroup(ControlPointGroup group) public void RemoveGroup(ControlPointGroup group)
{ {
foreach (var item in group.ControlPoints.ToArray())
group.Remove(item);
group.ItemAdded -= groupItemAdded; group.ItemAdded -= groupItemAdded;
group.ItemRemoved -= groupItemRemoved; group.ItemRemoved -= groupItemRemoved;

View File

@ -38,6 +38,7 @@ namespace osu.Game.Online.API.Requests.Responses
Rank = Rank, Rank = Rank,
Ruleset = ruleset, Ruleset = ruleset,
Mods = mods, Mods = mods,
IsLegacyScore = true
}; };
if (Statistics != null) if (Statistics != null)

View File

@ -59,12 +59,13 @@ namespace osu.Game.Online.Chat
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = textbox_height, Height = textbox_height,
PlaceholderText = "type your message", PlaceholderText = "type your message",
OnCommit = postMessage,
ReleaseFocusOnCommit = false, ReleaseFocusOnCommit = false,
HoldFocus = true, HoldFocus = true,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
}); });
textbox.OnCommit += postMessage;
} }
Channel.BindValueChanged(channelChanged); Channel.BindValueChanged(channelChanged);

View File

@ -146,7 +146,6 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Height = 1, Height = 1,
PlaceholderText = "type your message", PlaceholderText = "type your message",
OnCommit = postMessage,
ReleaseFocusOnCommit = false, ReleaseFocusOnCommit = false,
HoldFocus = true, HoldFocus = true,
} }
@ -186,6 +185,8 @@ namespace osu.Game.Overlays
}, },
}; };
textbox.OnCommit += postMessage;
ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue;
ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
ChannelSelectionOverlay.State.ValueChanged += state => ChannelSelectionOverlay.State.ValueChanged += state =>

View File

@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music
}, },
}; };
filter.Search.OnCommit = (sender, newText) => filter.Search.OnCommit += (sender, newText) =>
{ {
BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();

View File

@ -236,7 +236,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
PlaceholderText = "password", PlaceholderText = "password",
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
TabbableContentContainer = this, TabbableContentContainer = this,
OnCommit = (sender, newText) => performLogin()
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
@ -276,6 +275,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
} }
} }
}; };
password.OnCommit += (sender, newText) => performLogin();
} }
public override bool AcceptsFocus => true; public override bool AcceptsFocus => true;

View File

@ -124,19 +124,19 @@ namespace osu.Game.Rulesets.Judgements
return -DEFAULT_MAX_HEALTH_INCREASE; return -DEFAULT_MAX_HEALTH_INCREASE;
case HitResult.Meh: case HitResult.Meh:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
case HitResult.Ok: case HitResult.Ok:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.01; return -DEFAULT_MAX_HEALTH_INCREASE * 0.3;
case HitResult.Good: case HitResult.Good:
return DEFAULT_MAX_HEALTH_INCREASE * 0.5; return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
case HitResult.Great: case HitResult.Great:
return DEFAULT_MAX_HEALTH_INCREASE; return DEFAULT_MAX_HEALTH_INCREASE * 0.8;
case HitResult.Perfect: case HitResult.Perfect:
return DEFAULT_MAX_HEALTH_INCREASE * 1.05; return DEFAULT_MAX_HEALTH_INCREASE;
case HitResult.SmallBonus: case HitResult.SmallBonus:
return DEFAULT_MAX_HEALTH_INCREASE * 0.1; return DEFAULT_MAX_HEALTH_INCREASE * 0.1;

View File

@ -51,12 +51,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
public override bool PropagateNonPositionalInputSubTree => HandleUserInput; public override bool PropagateNonPositionalInputSubTree => HandleUserInput;
/// <summary> /// <summary>
/// Invoked when a <see cref="JudgementResult"/> has been applied by this <see cref="DrawableHitObject"/> or a nested <see cref="DrawableHitObject"/>. /// Invoked by this or a nested <see cref="DrawableHitObject"/> after a <see cref="JudgementResult"/> has been applied.
/// </summary> /// </summary>
public event Action<DrawableHitObject, JudgementResult> OnNewResult; public event Action<DrawableHitObject, JudgementResult> OnNewResult;
/// <summary> /// <summary>
/// Invoked when a <see cref="JudgementResult"/> is being reverted by this <see cref="DrawableHitObject"/> or a nested <see cref="DrawableHitObject"/>. /// Invoked by this or a nested <see cref="DrawableHitObject"/> prior to a <see cref="JudgementResult"/> being reverted.
/// </summary> /// </summary>
public event Action<DrawableHitObject, JudgementResult> OnRevertResult; public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
#region State / Transform Management #region State / Transform Management
/// <summary> /// <summary>
/// Bind to apply a custom state which can override the default implementation. /// Invoked by this or a nested <see cref="DrawableHitObject"/> to apply a custom state that can override the default implementation.
/// </summary> /// </summary>
public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState; public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState;
@ -384,6 +384,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
} }
} }
/// <summary>
/// Stops playback of all samples. Automatically called when <see cref="DrawableHitObject{TObject}"/>'s lifetime has been exceeded.
/// </summary>
public virtual void StopAllSamples() => Samples?.Stop();
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
@ -452,6 +457,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
foreach (var nested in NestedHitObjects) foreach (var nested in NestedHitObjects)
nested.OnKilled(); nested.OnKilled();
StopAllSamples();
UpdateResult(false); UpdateResult(false);
} }
@ -462,6 +469,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param> /// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param>
protected void ApplyResult(Action<JudgementResult> application) protected void ApplyResult(Action<JudgementResult> application)
{ {
if (Result.HasResult)
throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result.");
application?.Invoke(Result); application?.Invoke(Result);
if (!Result.HasResult) if (!Result.HasResult)

View File

@ -185,6 +185,34 @@ namespace osu.Game.Scoring
[JsonProperty("position")] [JsonProperty("position")]
public int? Position { get; set; } public int? Position { get; set; }
private bool isLegacyScore;
/// <summary>
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
/// </summary>
[JsonIgnore]
[NotMapped]
public bool IsLegacyScore
{
get
{
if (isLegacyScore)
return true;
// The above check will catch legacy online scores that have an appropriate UserString + UserId.
// For non-online scores such as those imported in, a heuristic is used based on the following table:
//
// Mode | UserString | UserId
// --------------- | ---------- | ---------
// stable | <username> | 1
// lazer | <username> | <userid>
// lazer (offline) | Guest | 1
return ID > 0 && UserID == 1 && UserString != "Guest";
}
set => isLegacyScore = value;
}
public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay() public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay()
{ {
foreach (var key in OrderAttributeUtils.GetValuesInOrder<HitResult>()) foreach (var key in OrderAttributeUtils.GetValuesInOrder<HitResult>())

View File

@ -10,6 +10,7 @@ using System.Threading;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -149,13 +150,17 @@ namespace osu.Game.Scoring
return; return;
} }
int? beatmapMaxCombo = score.Beatmap.MaxCombo; int beatmapMaxCombo;
if (beatmapMaxCombo == null) if (score.IsLegacyScore)
{
// This score is guaranteed to be an osu!stable score.
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
if (score.Beatmap.MaxCombo == null)
{ {
if (score.Beatmap.ID == 0 || difficulties == null) if (score.Beatmap.ID == 0 || difficulties == null)
{ {
// We don't have enough information (max combo) to compute the score, so let's use the provided score. // We don't have enough information (max combo) to compute the score, so use the provided score.
Value = score.TotalScore; Value = score.TotalScore;
return; return;
} }
@ -163,9 +168,20 @@ namespace osu.Game.Scoring
// We can compute the max combo locally after the async beatmap difficulty computation. // We can compute the max combo locally after the async beatmap difficulty computation.
difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token);
difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
return;
}
beatmapMaxCombo = score.Beatmap.MaxCombo.Value;
} }
else else
updateScore(beatmapMaxCombo.Value); {
// This score is guaranteed to be an osu!lazer score.
// The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum();
}
updateScore(beatmapMaxCombo);
} }
private void updateScore(int beatmapMaxCombo) private void updateScore(int beatmapMaxCombo)

View File

@ -2,27 +2,23 @@
// 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.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Edit.Timing namespace osu.Game.Screens.Edit.Timing
{ {
internal class DifficultySection : Section<DifficultyControlPoint> internal class DifficultySection : Section<DifficultyControlPoint>
{ {
private SettingsSlider<double> multiplier; private SliderWithTextBoxInput<double> multiplierSlider;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Flow.AddRange(new[] Flow.AddRange(new[]
{ {
multiplier = new SettingsSlider<double> multiplierSlider = new SliderWithTextBoxInput<double>("Speed Multiplier")
{ {
LabelText = "Speed Multiplier", Current = new DifficultyControlPoint().SpeedMultiplierBindable
Bindable = new DifficultyControlPoint().SpeedMultiplierBindable,
RelativeSizeAxes = Axes.X,
} }
}); });
} }
@ -31,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing
{ {
if (point.NewValue != null) if (point.NewValue != null)
{ {
multiplier.Bindable = point.NewValue.SpeedMultiplierBindable; multiplierSlider.Current = point.NewValue.SpeedMultiplierBindable;
} }
} }

View File

@ -2,18 +2,17 @@
// 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.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Edit.Timing namespace osu.Game.Screens.Edit.Timing
{ {
internal class SampleSection : Section<SampleControlPoint> internal class SampleSection : Section<SampleControlPoint>
{ {
private LabelledTextBox bank; private LabelledTextBox bank;
private SettingsSlider<int> volume; private SliderWithTextBoxInput<int> volume;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
@ -24,10 +23,9 @@ namespace osu.Game.Screens.Edit.Timing
{ {
Label = "Bank Name", Label = "Bank Name",
}, },
volume = new SettingsSlider<int> volume = new SliderWithTextBoxInput<int>("Volume")
{ {
Bindable = new SampleControlPoint().SampleVolumeBindable, Current = new SampleControlPoint().SampleVolumeBindable,
LabelText = "Volume",
} }
}); });
} }
@ -37,7 +35,7 @@ namespace osu.Game.Screens.Edit.Timing
if (point.NewValue != null) if (point.NewValue != null)
{ {
bank.Current = point.NewValue.SampleBankBindable; bank.Current = point.NewValue.SampleBankBindable;
volume.Bindable = point.NewValue.SampleVolumeBindable; volume.Current = point.NewValue.SampleVolumeBindable;
} }
} }

View File

@ -0,0 +1,77 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Edit.Timing
{
internal class SliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T>
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
{
private readonly SettingsSlider<T> slider;
public SliderWithTextBoxInput(string labelText)
{
LabelledTextBox textbox;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
textbox = new LabelledTextBox
{
Label = labelText,
},
slider = new SettingsSlider<T>
{
RelativeSizeAxes = Axes.X,
}
}
},
};
textbox.OnCommit += (t, isNew) =>
{
if (!isNew) return;
try
{
slider.Bindable.Parse(t.Text);
}
catch
{
// TriggerChange below will restore the previous text value on failure.
}
// This is run regardless of parsing success as the parsed number may not actually trigger a change
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
Current.TriggerChange();
};
Current.BindValueChanged(val =>
{
textbox.Text = val.NewValue.ToString();
}, true);
}
public Bindable<T> Current
{
get => slider.Bindable;
set => slider.Bindable = value;
}
}
}

View File

@ -65,18 +65,19 @@ namespace osu.Game.Screens.Edit.Timing
{ {
if (!isNew) return; if (!isNew) return;
if (double.TryParse(Current.Value, out double doubleVal))
{
try try
{ {
if (double.TryParse(Current.Value, out double doubleVal) && doubleVal > 0)
beatLengthBindable.Value = beatLengthToBpm(doubleVal); beatLengthBindable.Value = beatLengthToBpm(doubleVal);
} }
catch catch
{ {
// will restore the previous text value on failure. // TriggerChange below will restore the previous text value on failure.
}
// This is run regardless of parsing success as the parsed number may not actually trigger a change
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
beatLengthBindable.TriggerChange(); beatLengthBindable.TriggerChange();
}
}
}; };
beatLengthBindable.BindValueChanged(val => beatLengthBindable.BindValueChanged(val =>

View File

@ -18,7 +18,7 @@ namespace osu.Game.Skinning
ComboPrefix, ComboPrefix,
ComboOverlap, ComboOverlap,
AnimationFramerate, AnimationFramerate,
LayeredHitSounds, LayeredHitSounds
} }
} }
} }

View File

@ -41,7 +41,14 @@ namespace osu.Game.Skinning
// it's not easy to know if a sample has finished playing (to end). // it's not easy to know if a sample has finished playing (to end).
// to keep things simple only resume playing looping samples. // to keep things simple only resume playing looping samples.
else if (Looping) else if (Looping)
{
// schedule so we don't start playing a sample which is no longer alive.
Schedule(() =>
{
if (RequestedPlaying)
base.Play(); base.Play();
});
}
} }
}); });
} }

View File

@ -24,7 +24,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.930.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.1001.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
<PackageReference Include="Sentry" Version="2.1.6" /> <PackageReference Include="Sentry" Version="2.1.6" />
<PackageReference Include="SharpCompress" Version="0.26.0" /> <PackageReference Include="SharpCompress" Version="0.26.0" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.930.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1001.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
</ItemGroup> </ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. --> <!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
@ -80,11 +80,11 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.930.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.1001.0" />
<PackageReference Include="SharpCompress" Version="0.26.0" /> <PackageReference Include="SharpCompress" Version="0.26.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2020.213.0" ExcludeAssets="all" /> <PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2020.923.0" ExcludeAssets="all" />
</ItemGroup> </ItemGroup>
</Project> </Project>