mirror of
https://github.com/osukey/osukey.git
synced 2025-06-20 18:58:05 +09:00
Merge branch 'master' into control-point-timeline-representation
This commit is contained in:
commit
2f15d558a3
@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
</Project>
|
||||
|
@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
var slider = new Slider
|
||||
{
|
||||
StartTime = Time.Current + 1000,
|
||||
StartTime = Time.Current + time_offset,
|
||||
Position = new Vector2(239, 176),
|
||||
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 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 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
|
||||
{
|
||||
StartTime = Time.Current + 1000,
|
||||
Position = new Vector2(-(distance / 2), 0),
|
||||
StartTime = Time.Current + time_offset,
|
||||
Position = new Vector2(0, -(distance / 2)),
|
||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(distance, 0),
|
||||
new Vector2(0, distance),
|
||||
}, distance),
|
||||
RepeatCount = repeats,
|
||||
StackHeight = stackHeight
|
||||
@ -213,14 +217,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
var slider = new Slider
|
||||
{
|
||||
StartTime = Time.Current + 1000,
|
||||
Position = new Vector2(-200, 0),
|
||||
StartTime = Time.Current + time_offset,
|
||||
Position = new Vector2(-max_length / 2, 0),
|
||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(200, 200),
|
||||
new Vector2(400, 0)
|
||||
}, 600),
|
||||
new Vector2(max_length / 2, max_length / 2),
|
||||
new Vector2(max_length, 0)
|
||||
}, max_length * 1.5f),
|
||||
RepeatCount = repeats,
|
||||
};
|
||||
|
||||
@ -233,16 +237,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
var slider = new Slider
|
||||
{
|
||||
StartTime = Time.Current + 1000,
|
||||
Position = new Vector2(-200, 0),
|
||||
StartTime = Time.Current + time_offset,
|
||||
Position = new Vector2(-max_length / 2, 0),
|
||||
Path = new SliderPath(PathType.Linear, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(150, 75),
|
||||
new Vector2(200, 0),
|
||||
new Vector2(300, -200),
|
||||
new Vector2(400, 0),
|
||||
new Vector2(430, 0)
|
||||
new Vector2(max_length * 0.375f, max_length * 0.18f),
|
||||
new Vector2(max_length / 2, 0),
|
||||
new Vector2(max_length * 0.75f, -max_length / 2),
|
||||
new Vector2(max_length * 0.95f, 0),
|
||||
new Vector2(max_length, 0)
|
||||
}),
|
||||
RepeatCount = repeats,
|
||||
};
|
||||
@ -256,15 +260,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
var slider = new Slider
|
||||
{
|
||||
StartTime = Time.Current + 1000,
|
||||
Position = new Vector2(-200, 0),
|
||||
StartTime = Time.Current + time_offset,
|
||||
Position = new Vector2(-max_length / 2, 0),
|
||||
Path = new SliderPath(PathType.Bezier, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(150, 75),
|
||||
new Vector2(200, 100),
|
||||
new Vector2(300, -200),
|
||||
new Vector2(430, 0)
|
||||
new Vector2(max_length * 0.375f, max_length * 0.18f),
|
||||
new Vector2(max_length / 2, max_length / 4),
|
||||
new Vector2(max_length * 0.75f, -max_length / 2),
|
||||
new Vector2(max_length, 0)
|
||||
}),
|
||||
RepeatCount = repeats,
|
||||
};
|
||||
@ -278,16 +282,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
var slider = new Slider
|
||||
{
|
||||
StartTime = Time.Current + 1000,
|
||||
StartTime = Time.Current + time_offset,
|
||||
Position = new Vector2(0, 0),
|
||||
Path = new SliderPath(PathType.Linear, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(-200, 0),
|
||||
new Vector2(-max_length / 2, 0),
|
||||
new Vector2(0, 0),
|
||||
new Vector2(0, -200),
|
||||
new Vector2(-200, -200),
|
||||
new Vector2(0, -200)
|
||||
new Vector2(0, -max_length / 2),
|
||||
new Vector2(-max_length / 2, -max_length / 2),
|
||||
new Vector2(0, -max_length / 2)
|
||||
}),
|
||||
RepeatCount = repeats,
|
||||
};
|
||||
@ -305,14 +309,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
var slider = new Slider
|
||||
{
|
||||
StartTime = Time.Current + 1000,
|
||||
Position = new Vector2(-100, 0),
|
||||
StartTime = Time.Current + time_offset,
|
||||
Position = new Vector2(-max_length / 4, 0),
|
||||
Path = new SliderPath(PathType.Catmull, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(50, -50),
|
||||
new Vector2(150, 50),
|
||||
new Vector2(200, 0)
|
||||
new Vector2(max_length * 0.125f, max_length * 0.125f),
|
||||
new Vector2(max_length * 0.375f, max_length * 0.125f),
|
||||
new Vector2(max_length / 2, 0)
|
||||
}),
|
||||
RepeatCount = repeats,
|
||||
NodeSamples = repeatSamples
|
||||
|
@ -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)
|
||||
{
|
||||
if (tracking.NewValue)
|
||||
|
@ -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)
|
||||
{
|
||||
base.AddNestedHitObject(hitObject);
|
||||
|
@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
|
||||
drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
|
||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||
|
||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -124,6 +126,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawableHitObject is DrawableSpinner))
|
||||
return;
|
||||
|
||||
centre.ScaleTo(0);
|
||||
mainContainer.ScaleTo(0);
|
||||
|
||||
|
@ -72,10 +72,15 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
this.FadeOut();
|
||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||
|
||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawableHitObject is DrawableSpinner))
|
||||
return;
|
||||
|
||||
var spinner = (Spinner)drawableSpinner.HitObject;
|
||||
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
||||
|
@ -24,12 +24,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
private Sprite metreSprite;
|
||||
private Container metre;
|
||||
|
||||
private bool spinnerBlink;
|
||||
|
||||
private const float sprite_scale = 1 / 1.6f;
|
||||
private const float final_metre_height = 692 * sprite_scale;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource source, DrawableHitObject drawableObject)
|
||||
{
|
||||
spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true;
|
||||
|
||||
drawableSpinner = (DrawableSpinner)drawableObject;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -86,10 +90,15 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
this.FadeOut();
|
||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||
|
||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawableHitObject is DrawableSpinner))
|
||||
return;
|
||||
|
||||
var spinner = drawableSpinner.HitObject;
|
||||
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
||||
@ -116,12 +125,15 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
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;
|
||||
|
||||
// todo: add SpinnerNoBlink support
|
||||
if (RNG.NextBool(((int)progress % 10) / 10f))
|
||||
if (spinnerBlink && RNG.NextBool(((int)progress % 10) / 10f))
|
||||
barCount++;
|
||||
|
||||
return (float)barCount / total_bars * final_metre_height;
|
||||
|
@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
CursorRotate,
|
||||
HitCircleOverlayAboveNumber,
|
||||
HitCircleOverlayAboveNumer, // Some old skins will have this typo
|
||||
SpinnerFrequencyModulate
|
||||
SpinnerFrequencyModulate,
|
||||
SpinnerNoBlink
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +139,22 @@ namespace osu.Game.Tests.NonVisual
|
||||
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]
|
||||
public void TestAddControlPointToGroup()
|
||||
{
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
base.Bindable = new Bindable<string>();
|
||||
|
||||
((OsuTextBox)Control).OnCommit = (sender, newText) =>
|
||||
((OsuTextBox)Control).OnCommit += (sender, newText) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -158,6 +158,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
|
||||
public void RemoveGroup(ControlPointGroup group)
|
||||
{
|
||||
foreach (var item in group.ControlPoints.ToArray())
|
||||
group.Remove(item);
|
||||
|
||||
group.ItemAdded -= groupItemAdded;
|
||||
group.ItemRemoved -= groupItemRemoved;
|
||||
|
||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
Rank = Rank,
|
||||
Ruleset = ruleset,
|
||||
Mods = mods,
|
||||
IsLegacyScore = true
|
||||
};
|
||||
|
||||
if (Statistics != null)
|
||||
|
@ -59,12 +59,13 @@ namespace osu.Game.Online.Chat
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = textbox_height,
|
||||
PlaceholderText = "type your message",
|
||||
OnCommit = postMessage,
|
||||
ReleaseFocusOnCommit = false,
|
||||
HoldFocus = true,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
});
|
||||
|
||||
textbox.OnCommit += postMessage;
|
||||
}
|
||||
|
||||
Channel.BindValueChanged(channelChanged);
|
||||
|
@ -146,7 +146,6 @@ namespace osu.Game.Overlays
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 1,
|
||||
PlaceholderText = "type your message",
|
||||
OnCommit = postMessage,
|
||||
ReleaseFocusOnCommit = false,
|
||||
HoldFocus = true,
|
||||
}
|
||||
@ -186,6 +185,8 @@ namespace osu.Game.Overlays
|
||||
},
|
||||
};
|
||||
|
||||
textbox.OnCommit += postMessage;
|
||||
|
||||
ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue;
|
||||
ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
|
||||
ChannelSelectionOverlay.State.ValueChanged += state =>
|
||||
|
@ -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();
|
||||
|
||||
|
@ -236,7 +236,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
PlaceholderText = "password",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
TabbableContentContainer = this,
|
||||
OnCommit = (sender, newText) => performLogin()
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
@ -276,6 +275,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
password.OnCommit += (sender, newText) => performLogin();
|
||||
}
|
||||
|
||||
public override bool AcceptsFocus => true;
|
||||
|
@ -124,19 +124,19 @@ namespace osu.Game.Rulesets.Judgements
|
||||
return -DEFAULT_MAX_HEALTH_INCREASE;
|
||||
|
||||
case HitResult.Meh:
|
||||
return -DEFAULT_MAX_HEALTH_INCREASE * 0.05;
|
||||
return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
|
||||
|
||||
case HitResult.Ok:
|
||||
return -DEFAULT_MAX_HEALTH_INCREASE * 0.01;
|
||||
return -DEFAULT_MAX_HEALTH_INCREASE * 0.3;
|
||||
|
||||
case HitResult.Good:
|
||||
return DEFAULT_MAX_HEALTH_INCREASE * 0.5;
|
||||
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
||||
|
||||
case HitResult.Great:
|
||||
return DEFAULT_MAX_HEALTH_INCREASE;
|
||||
return DEFAULT_MAX_HEALTH_INCREASE * 0.8;
|
||||
|
||||
case HitResult.Perfect:
|
||||
return DEFAULT_MAX_HEALTH_INCREASE * 1.05;
|
||||
return DEFAULT_MAX_HEALTH_INCREASE;
|
||||
|
||||
case HitResult.SmallBonus:
|
||||
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
||||
|
@ -51,12 +51,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
public override bool PropagateNonPositionalInputSubTree => HandleUserInput;
|
||||
|
||||
/// <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>
|
||||
public event Action<DrawableHitObject, JudgementResult> OnNewResult;
|
||||
|
||||
/// <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>
|
||||
public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
|
||||
|
||||
@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
#region State / Transform Management
|
||||
|
||||
/// <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>
|
||||
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()
|
||||
{
|
||||
base.Update();
|
||||
@ -452,6 +457,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
foreach (var nested in NestedHitObjects)
|
||||
nested.OnKilled();
|
||||
|
||||
StopAllSamples();
|
||||
|
||||
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>
|
||||
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);
|
||||
|
||||
if (!Result.HasResult)
|
||||
|
@ -185,6 +185,34 @@ namespace osu.Game.Scoring
|
||||
[JsonProperty("position")]
|
||||
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()
|
||||
{
|
||||
foreach (var key in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
||||
|
@ -10,6 +10,7 @@ using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -149,23 +150,38 @@ namespace osu.Game.Scoring
|
||||
return;
|
||||
}
|
||||
|
||||
int? beatmapMaxCombo = score.Beatmap.MaxCombo;
|
||||
int beatmapMaxCombo;
|
||||
|
||||
if (beatmapMaxCombo == null)
|
||||
if (score.IsLegacyScore)
|
||||
{
|
||||
if (score.Beatmap.ID == 0 || difficulties == null)
|
||||
// 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)
|
||||
{
|
||||
// We don't have enough information (max combo) to compute the score, so let's use the provided score.
|
||||
Value = score.TotalScore;
|
||||
if (score.Beatmap.ID == 0 || difficulties == null)
|
||||
{
|
||||
// We don't have enough information (max combo) to compute the score, so use the provided score.
|
||||
Value = score.TotalScore;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
|
||||
beatmapMaxCombo = score.Beatmap.MaxCombo.Value;
|
||||
}
|
||||
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)
|
||||
|
@ -2,27 +2,23 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
internal class DifficultySection : Section<DifficultyControlPoint>
|
||||
{
|
||||
private SettingsSlider<double> multiplier;
|
||||
private SliderWithTextBoxInput<double> multiplierSlider;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Flow.AddRange(new[]
|
||||
{
|
||||
multiplier = new SettingsSlider<double>
|
||||
multiplierSlider = new SliderWithTextBoxInput<double>("Speed Multiplier")
|
||||
{
|
||||
LabelText = "Speed Multiplier",
|
||||
Bindable = new DifficultyControlPoint().SpeedMultiplierBindable,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Current = new DifficultyControlPoint().SpeedMultiplierBindable
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -31,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
if (point.NewValue != null)
|
||||
{
|
||||
multiplier.Bindable = point.NewValue.SpeedMultiplierBindable;
|
||||
multiplierSlider.Current = point.NewValue.SpeedMultiplierBindable;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,18 +2,17 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
internal class SampleSection : Section<SampleControlPoint>
|
||||
{
|
||||
private LabelledTextBox bank;
|
||||
private SettingsSlider<int> volume;
|
||||
private SliderWithTextBoxInput<int> volume;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -24,10 +23,9 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
Label = "Bank Name",
|
||||
},
|
||||
volume = new SettingsSlider<int>
|
||||
volume = new SliderWithTextBoxInput<int>("Volume")
|
||||
{
|
||||
Bindable = new SampleControlPoint().SampleVolumeBindable,
|
||||
LabelText = "Volume",
|
||||
Current = new SampleControlPoint().SampleVolumeBindable,
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -37,7 +35,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
if (point.NewValue != null)
|
||||
{
|
||||
bank.Current = point.NewValue.SampleBankBindable;
|
||||
volume.Bindable = point.NewValue.SampleVolumeBindable;
|
||||
volume.Current = point.NewValue.SampleVolumeBindable;
|
||||
}
|
||||
}
|
||||
|
||||
|
77
osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs
Normal file
77
osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -65,18 +65,19 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
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);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// will restore the previous text value on failure.
|
||||
beatLengthBindable.TriggerChange();
|
||||
}
|
||||
}
|
||||
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.
|
||||
beatLengthBindable.TriggerChange();
|
||||
};
|
||||
|
||||
beatLengthBindable.BindValueChanged(val =>
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Skinning
|
||||
ComboPrefix,
|
||||
ComboOverlap,
|
||||
AnimationFramerate,
|
||||
LayeredHitSounds,
|
||||
LayeredHitSounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,14 @@ namespace osu.Game.Skinning
|
||||
// it's not easy to know if a sample has finished playing (to end).
|
||||
// to keep things simple only resume playing looping samples.
|
||||
else if (Looping)
|
||||
base.Play();
|
||||
{
|
||||
// schedule so we don't start playing a sample which is no longer alive.
|
||||
Schedule(() =>
|
||||
{
|
||||
if (RequestedPlaying)
|
||||
base.Play();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<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="Sentry" Version="2.1.6" />
|
||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||
|
@ -70,7 +70,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<!-- 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.Core" Version="2.2.6" />
|
||||
<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="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.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>
|
||||
</Project>
|
||||
|
Loading…
x
Reference in New Issue
Block a user