1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 06:17:23 +08:00

Merge branch 'master' into fix-replay-date

This commit is contained in:
Dan Balasescu 2021-07-19 21:58:37 +09:00 committed by GitHub
commit 5ef1fe6948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 226 additions and 97 deletions

View File

@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken)
{
var positionData = obj as IHasXPosition;
var xPositionData = obj as IHasXPosition;
var yPositionData = obj as IHasYPosition;
var comboData = obj as IHasCombo;
switch (obj)
@ -36,10 +37,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
Path = curveData.Path,
NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount,
X = positionData?.X ?? 0,
X = xPositionData?.X ?? 0,
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y
}.Yield();
case IHasDuration endTime:
@ -59,7 +61,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
Samples = obj.Samples,
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
X = positionData?.X ?? 0
X = xPositionData?.X ?? 0,
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y
}.Yield();
}
}

View File

@ -19,9 +19,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
{
get
{
float x = HitObject.OriginalX;
float y = HitObjectContainer.PositionAtTime(HitObject.StartTime);
return HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight));
Vector2 position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
return HitObjectContainer.ToScreenSpace(position + new Vector2(0, HitObjectContainer.DrawHeight));
}
}

View File

@ -1,14 +1,12 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
@ -28,10 +26,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
Colour = osuColour.Yellow;
}
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject, [CanBeNull] CatchHitObject parent = null)
public void UpdateFrom(CatchHitObject hitObject)
{
X = hitObject.EffectiveX - (parent?.OriginalX ?? 0);
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime, parent?.StartTime ?? hitObjectContainer.Time.Current);
Scale = new Vector2(hitObject.Scale);
}
}

View File

@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
Anchor = Anchor.BottomLeft;
}
public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject)
{
X = parentHitObject.OriginalX;
Y = hitObjectContainer.PositionAtTime(parentHitObject.StartTime);
}
public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject)
{
nestedHitObjects.Clear();
@ -43,7 +37,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
{
var hitObject = nestedHitObjects[i];
var outline = (FruitOutline)InternalChildren[i];
outline.UpdateFrom(hitObjectContainer, hitObject, parentHitObject);
outline.Position = CatchHitObjectUtils.GetStartPosition(hitObjectContainer, hitObject) - Position;
outline.UpdateFrom(hitObject);
outline.Scale *= hitObject is Droplet ? 0.5f : 1;
}
}

View File

@ -33,12 +33,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
};
}
public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject)
{
X = hitObject.OriginalX;
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime);
}
public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject)
{
double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity);

View File

@ -29,7 +29,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
{
base.Update();
outline.UpdateFrom(HitObjectContainer, HitObject);
outline.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
outline.UpdateFrom(HitObject);
}
protected override bool OnMouseDown(MouseDownEvent e)

View File

@ -20,8 +20,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
{
base.Update();
if (IsSelected)
outline.UpdateFrom(HitObjectContainer, HitObject);
if (!IsSelected) return;
outline.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
outline.UpdateFrom(HitObject);
}
}
}

View File

@ -49,8 +49,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
if (!IsSelected) return;
scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject);
nestedOutlineContainer.UpdatePositionFrom(HitObjectContainer, HitObject);
nestedOutlineContainer.Position = scrollingPath.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
if (pathCache.IsValid) return;

View File

@ -0,0 +1,24 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Catch.Edit
{
/// <summary>
/// Utility functions used by the editor.
/// </summary>
public static class CatchHitObjectUtils
{
/// <summary>
/// Get the position of the hit object in the playfield based on <see cref="CatchHitObject.OriginalX"/> and <see cref="HitObject.StartTime"/>.
/// </summary>
public static Vector2 GetStartPosition(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject)
{
return new Vector2(hitObject.OriginalX, hitObjectContainer.PositionAtTime(hitObject.StartTime));
}
}
}

View File

@ -9,10 +9,11 @@ using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Catch.Objects
{
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation
{
public const float OBJECT_RADIUS = 64;
@ -31,8 +32,6 @@ namespace osu.Game.Rulesets.Catch.Objects
set => OriginalXBindable.Value = value;
}
float IHasXPosition.X => OriginalXBindable.Value;
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
/// <summary>
@ -131,5 +130,24 @@ namespace osu.Game.Rulesets.Catch.Objects
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
#region Hit object conversion
// The half of the height of the osu! playfield.
public const float DEFAULT_LEGACY_CONVERT_Y = 192;
/// <summary>
/// The Y position of the hit object is not used in the normal osu!catch gameplay.
/// It is preserved to maximize the backward compatibility with the legacy editor, in which the mappers use the Y position to organize the patterns.
/// </summary>
public float LegacyConvertedY { get; set; } = DEFAULT_LEGACY_CONVERT_Y;
float IHasXPosition.X => OriginalX;
float IHasYPosition.Y => LegacyConvertedY;
Vector2 IHasPosition.Position => new Vector2(OriginalX, LegacyConvertedY);
#endregion
}
}

View File

@ -13,8 +13,8 @@ namespace osu.Game.Tests.Visual.Editing
{
public TestSceneEditorComposeRadioButtons()
{
RadioButtonCollection collection;
Add(collection = new RadioButtonCollection
EditorRadioButtonCollection collection;
Add(collection = new EditorRadioButtonCollection
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,

View File

@ -2,17 +2,24 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Timing;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.RadioButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editing
@ -20,37 +27,89 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneHitObjectComposer : EditorClockTestScene
{
[BackgroundDependencyLoader]
private void load()
private OsuHitObjectComposer hitObjectComposer;
private EditorBeatmapContainer editorBeatmapContainer;
private EditorBeatmap editorBeatmap => editorBeatmapContainer.EditorBeatmap;
[SetUpSteps]
public void SetUpSteps()
{
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
AddStep("create beatmap", () =>
{
HitObjects = new List<HitObject>
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
{
new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f },
new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f },
new Slider
HitObjects = new List<HitObject>
{
Position = new Vector2(128, 256),
Path = new SliderPath(PathType.Linear, new[]
new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f },
new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f },
new Slider
{
Vector2.Zero,
new Vector2(216, 0),
}),
Scale = 0.5f,
}
},
Position = new Vector2(128, 256),
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
new Vector2(216, 0),
}),
Scale = 0.5f,
}
},
});
});
var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
AddStep("Create composer", () =>
{
Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value)
{
Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset())
};
});
}
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
Dependencies.CacheAs<IAdjustableClock>(clock);
Dependencies.CacheAs<IFrameBasedClock>(clock);
Dependencies.CacheAs(editorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
[Test]
public void TestPlacementOnlyWorksWithTiming()
{
AddStep("clear all control points", () => editorBeatmap.ControlPointInfo.Clear());
Child = new OsuHitObjectComposer(new OsuRuleset());
AddAssert("Tool is selection", () => hitObjectComposer.ChildrenOfType<ComposeBlueprintContainer>().First().CurrentTool is SelectTool);
AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").Enabled.Value);
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").Enabled.Value);
AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").Click());
AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType<ComposeBlueprintContainer>().First().CurrentTool is HitCircleCompositionTool);
}
public class EditorBeatmapContainer : Container
{
private readonly WorkingBeatmap working;
public EditorBeatmap EditorBeatmap { get; private set; }
public EditorBeatmapContainer(WorkingBeatmap working)
{
this.working = working;
RelativeSizeAxes = Axes.Both;
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
EditorBeatmap = new EditorBeatmap(working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
dependencies.CacheAs(EditorBeatmap);
dependencies.CacheAs<IBeatSnapProvider>(EditorBeatmap);
return dependencies;
}
protected override void LoadComplete()
{
base.LoadComplete();
Add(EditorBeatmap);
}
}
}
}

View File

@ -251,11 +251,8 @@ namespace osu.Game.Beatmaps.Formats
switch (beatmap.BeatmapInfo.RulesetID)
{
case 0:
position = ((IHasPosition)hitObject).Position;
break;
case 2:
position.X = ((IHasXPosition)hitObject).X;
position = ((IHasPosition)hitObject).Position;
break;
case 3:

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
@ -63,10 +64,12 @@ namespace osu.Game.Rulesets.Edit
private InputManager inputManager;
private RadioButtonCollection toolboxCollection;
private EditorRadioButtonCollection toolboxCollection;
private FillFlowContainer togglesCollection;
private IBindable<bool> hasTiming;
protected HitObjectComposer(Ruleset ruleset)
{
Ruleset = ruleset;
@ -126,7 +129,7 @@ namespace osu.Game.Rulesets.Edit
{
new ToolboxGroup("toolbox (1-9)")
{
Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X }
Child = toolboxCollection = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X }
},
new ToolboxGroup("toggles (Q~P)")
{
@ -160,6 +163,14 @@ namespace osu.Game.Rulesets.Edit
base.LoadComplete();
inputManager = GetContainingInputManager();
hasTiming = EditorBeatmap.HasTiming.GetBoundCopy();
hasTiming.BindValueChanged(timing =>
{
// it's important this is performed before the similar code in EditorRadioButton disables the button.
if (!timing.NewValue)
setSelectTool();
});
}
public override Playfield Playfield => drawableRulesetWrapper.Playfield;
@ -219,7 +230,8 @@ namespace osu.Game.Rulesets.Edit
if (item != null)
{
item.Select();
if (!item.Selected.Disabled)
item.Select();
return true;
}
}

View File

@ -2,15 +2,20 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Catch
{
/// <summary>
/// Legacy osu!catch Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertHit : ConvertHitObject, IHasCombo, IHasXPosition
internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo
{
public float X { get; set; }
public float X => Position.X;
public float Y => Position.Y;
public Vector2 Position { get; set; }
public bool NewCombo { get; set; }

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
return new ConvertHit
{
X = position.X,
Position = position,
NewCombo = newCombo,
ComboOffset = comboOffset
};
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
return new ConvertSlider
{
X = position.X,
Position = position,
NewCombo = FirstObject || newCombo,
ComboOffset = comboOffset,
Path = new SliderPath(controlPoints, length),

View File

@ -2,15 +2,20 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Catch
{
/// <summary>
/// Legacy osu!catch Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo
{
public float X { get; set; }
public float X => Position.X;
public float Y => Position.Y;
public Vector2 Position { get; set; }
public bool NewCombo { get; set; }

View File

@ -5,9 +5,11 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -16,26 +18,30 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Components.RadioButtons
{
public class DrawableRadioButton : OsuButton
public class EditorRadioButton : OsuButton, IHasTooltip
{
/// <summary>
/// Invoked when this <see cref="DrawableRadioButton"/> has been selected.
/// Invoked when this <see cref="EditorRadioButton"/> has been selected.
/// </summary>
public Action<RadioButton> Selected;
public readonly RadioButton Button;
private Color4 defaultBackgroundColour;
private Color4 defaultBubbleColour;
private Color4 selectedBackgroundColour;
private Color4 selectedBubbleColour;
private Drawable icon;
private readonly RadioButton button;
public DrawableRadioButton(RadioButton button)
[Resolved(canBeNull: true)]
private EditorBeatmap editorBeatmap { get; set; }
public EditorRadioButton(RadioButton button)
{
this.button = button;
Button = button;
Text = button.Item.ToString();
Text = button.Label;
Action = button.Select;
RelativeSizeAxes = Axes.X;
@ -57,7 +63,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
Colour = Color4.Black.Opacity(0.5f)
};
Add(icon = (button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
{
b.Blending = BlendingParameters.Additive;
b.Anchor = Anchor.CentreLeft;
@ -71,13 +77,16 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
{
base.LoadComplete();
button.Selected.ValueChanged += selected =>
Button.Selected.ValueChanged += selected =>
{
updateSelectionState();
if (selected.NewValue)
Selected?.Invoke(button);
Selected?.Invoke(Button);
};
editorBeatmap?.HasTiming.BindValueChanged(hasTiming => Button.Selected.Disabled = !hasTiming.NewValue, true);
Button.Selected.BindDisabledChanged(disabled => Enabled.Value = !disabled, true);
updateSelectionState();
}
@ -86,8 +95,8 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
if (!IsLoaded)
return;
BackgroundColour = button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour;
icon.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour;
BackgroundColour = Button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour;
icon.Colour = Button.Selected.Value ? selectedBubbleColour : defaultBubbleColour;
}
protected override SpriteText CreateText() => new OsuSpriteText
@ -97,5 +106,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
Anchor = Anchor.CentreLeft,
X = 40f
};
public LocalisableString TooltipText => Enabled.Value ? string.Empty : "Add at least one timing point first!";
}
}

View File

@ -9,7 +9,7 @@ using osuTK;
namespace osu.Game.Screens.Edit.Components.RadioButtons
{
public class RadioButtonCollection : CompositeDrawable
public class EditorRadioButtonCollection : CompositeDrawable
{
private IReadOnlyList<RadioButton> items;
@ -28,13 +28,13 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
}
}
private readonly FlowContainer<DrawableRadioButton> buttonContainer;
private readonly FlowContainer<EditorRadioButton> buttonContainer;
public RadioButtonCollection()
public EditorRadioButtonCollection()
{
AutoSizeAxes = Axes.Y;
InternalChild = buttonContainer = new FillFlowContainer<DrawableRadioButton>
InternalChild = buttonContainer = new FillFlowContainer<EditorRadioButton>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
@ -58,7 +58,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
currentlySelected = null;
};
buttonContainer.Add(new DrawableRadioButton(button));
buttonContainer.Add(new EditorRadioButton(button));
}
}
}

View File

@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
/// <summary>
/// The item related to this button.
/// </summary>
public object Item;
public string Label;
/// <summary>
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
@ -26,21 +26,14 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
private readonly Action action;
public RadioButton(object item, Action action, Func<Drawable> createIcon = null)
public RadioButton(string label, Action action, Func<Drawable> createIcon = null)
{
Item = item;
Label = label;
CreateIcon = createIcon;
this.action = action;
Selected = new BindableBool();
}
public RadioButton(string item)
: this(item, null)
{
Item = item;
action = null;
}
/// <summary>
/// Selects this <see cref="RadioButton"/>.
/// </summary>

View File

@ -46,12 +46,22 @@ namespace osu.Game.Screens.Edit
public readonly IBeatmap PlayableBeatmap;
/// <summary>
/// Whether at least one timing control point is present and providing timing information.
/// </summary>
public IBindable<bool> HasTiming => hasTiming;
private readonly Bindable<bool> hasTiming = new Bindable<bool>();
[CanBeNull]
public readonly ISkin BeatmapSkin;
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
[Resolved]
private EditorClock editorClock { get; set; }
private readonly IBeatmapProcessor beatmapProcessor;
private readonly Dictionary<HitObject, Bindable<double>> startTimeBindables = new Dictionary<HitObject, Bindable<double>>();
@ -238,6 +248,8 @@ namespace osu.Game.Screens.Edit
if (batchPendingUpdates.Count > 0)
UpdateState();
hasTiming.Value = !ReferenceEquals(ControlPointInfo.TimingPointAt(editorClock.CurrentTime), TimingControlPoint.DEFAULT);
}
protected override void UpdateState()

View File

@ -955,7 +955,11 @@ namespace osu.Game.Screens.Play
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
if (prepareScoreForDisplayTask == null)
{
Score.ScoreInfo.Passed = false;
// potentially should be ScoreRank.F instead? this is the best alternative for now.
Score.ScoreInfo.Rank = ScoreRank.D;
}
// EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.
// To resolve test failures, forcefully end playing synchronously when this screen exits.