mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 22:33:05 +08:00
Merge branch 'master' into game-overlay-activation-mode
This commit is contained in:
commit
84b3604192
@ -5,6 +5,6 @@
|
||||
"version": "3.1.100"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.Traversal": "2.0.52"
|
||||
"Microsoft.Build.Traversal": "2.1.1"
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
@ -61,7 +62,12 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
switch (lookup)
|
||||
{
|
||||
case CatchSkinColour colour:
|
||||
return Source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
||||
var result = (Bindable<Color4>)Source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
||||
if (result == null)
|
||||
return null;
|
||||
|
||||
result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value);
|
||||
return (IBindable<TValue>)result;
|
||||
}
|
||||
|
||||
return Source.GetConfig<TLookup, TValue>(lookup);
|
||||
|
@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
colouredSprite = new Sprite
|
||||
{
|
||||
Texture = skin.GetTexture(lookupName),
|
||||
Colour = drawableObject.AccentColour.Value,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue, true);
|
||||
accentColour.BindValueChanged(colour => colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
|
||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })]
|
||||
[TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })]
|
||||
[TestCase(LegacyMods.Flashlight | LegacyMods.Mirror, new[] { typeof(ManiaModFlashlight), typeof(ManiaModMirror) })]
|
||||
public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
|
@ -22,18 +22,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[Cached]
|
||||
private readonly Column column;
|
||||
|
||||
public ColumnTestContainer(int column, ManiaAction action)
|
||||
public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false)
|
||||
{
|
||||
this.column = new Column(column)
|
||||
InternalChildren = new[]
|
||||
{
|
||||
Action = { Value = action },
|
||||
AccentColour = Color4.Orange,
|
||||
ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd
|
||||
};
|
||||
|
||||
InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
this.column = new Column(column)
|
||||
{
|
||||
Action = { Value = action },
|
||||
AccentColour = Color4.Orange,
|
||||
ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd,
|
||||
Alpha = showColumn ? 1 : 0
|
||||
},
|
||||
content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
this.column.TopLevelContainer.CreateProxy()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ColumnTestContainer(0, ManiaAction.Key1)
|
||||
new ColumnTestContainer(0, ManiaAction.Key1, true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
}));
|
||||
})
|
||||
},
|
||||
new ColumnTestContainer(1, ManiaAction.Key2)
|
||||
new ColumnTestContainer(1, ManiaAction.Key2, true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
117
osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
Normal file
117
osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
Normal file
@ -0,0 +1,117 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestPreviousHitWindowDoesNotExtendPastNextObject()
|
||||
{
|
||||
var objects = new List<ManiaHitObject>();
|
||||
var frames = new List<ReplayFrame>();
|
||||
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
double time = 1000 + i * 100;
|
||||
|
||||
objects.Add(new Note { StartTime = time });
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1));
|
||||
frames.Add(new ManiaReplayFrame(time + 11));
|
||||
}
|
||||
}
|
||||
|
||||
performTest(objects, frames);
|
||||
|
||||
addJudgementAssert(objects[0], HitResult.Miss);
|
||||
|
||||
for (int i = 1; i < 7; i++)
|
||||
{
|
||||
addJudgementAssert(objects[i], HitResult.Perfect);
|
||||
addJudgementOffsetAssert(objects[i], 10);
|
||||
}
|
||||
}
|
||||
|
||||
private void addJudgementAssert(ManiaHitObject hitObject, HitResult result)
|
||||
{
|
||||
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
||||
() => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
|
||||
}
|
||||
|
||||
private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset)
|
||||
{
|
||||
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",
|
||||
() => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100));
|
||||
}
|
||||
|
||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||
private List<JudgementResult> judgementResults;
|
||||
|
||||
private void performTest(List<ManiaHitObject> hitObjects, List<ReplayFrame> frames)
|
||||
{
|
||||
AddStep("load player", () =>
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
|
||||
{
|
||||
HitObjects = hitObjects,
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
});
|
||||
|
||||
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
||||
|
||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
{
|
||||
p.ScoreProcessor.NewJudgement += result =>
|
||||
{
|
||||
if (currentPlayer == p) judgementResults.Add(result);
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
judgementResults = new List<JudgementResult>();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, false, false)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -126,6 +126,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Random))
|
||||
yield return new ManiaModRandom();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Mirror))
|
||||
yield return new ManiaModMirror();
|
||||
}
|
||||
|
||||
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
|
||||
@ -175,6 +178,10 @@ namespace osu.Game.Rulesets.Mania
|
||||
case ManiaModFadeIn _:
|
||||
value |= LegacyMods.FadeIn;
|
||||
break;
|
||||
|
||||
case ManiaModMirror _:
|
||||
value |= LegacyMods.Mirror;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,6 +333,16 @@ namespace osu.Game.Rulesets.Mania
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new UnstableRate(score.HitEvents)
|
||||
}))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -238,7 +238,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (Tail.AllJudged)
|
||||
{
|
||||
ApplyResult(r => r.Type = HitResult.Perfect);
|
||||
endHold();
|
||||
}
|
||||
|
||||
if (Tail.Result.Type == HitResult.Miss)
|
||||
HasBroken = true;
|
||||
@ -252,6 +255,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (action != Action.Value)
|
||||
return false;
|
||||
|
||||
if (CheckHittable?.Invoke(this, Time.Current) == false)
|
||||
return false;
|
||||
|
||||
// The tail has a lenience applied to it which is factored into the miss window (i.e. the miss judgement will be delayed).
|
||||
// But the hold cannot ever be started within the late-lenience window, so we should skip trying to begin the hold during that time.
|
||||
// Note: Unlike below, we use the tail's start time to determine the time offset.
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -8,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
@ -34,6 +36,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="DrawableManiaHitObject"/> can be hit, given a time value.
|
||||
/// If non-null, judgements will be ignored whilst the function returns false.
|
||||
/// </summary>
|
||||
public Func<DrawableHitObject, double, bool> CheckHittable;
|
||||
|
||||
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
@ -124,6 +132,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||
/// </summary>
|
||||
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
|
||||
}
|
||||
|
||||
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
||||
|
@ -64,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (action != Action.Value)
|
||||
return false;
|
||||
|
||||
if (CheckHittable?.Invoke(this, Time.Current) == false)
|
||||
return false;
|
||||
|
||||
return UpdateResult(true);
|
||||
}
|
||||
|
||||
|
46
osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
Normal file
46
osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning
|
||||
{
|
||||
public class HitTargetInsetContainer : Container
|
||||
{
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
private float hitPosition;
|
||||
|
||||
public HitTargetInsetContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
||||
{
|
||||
hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
content.Padding = direction.NewValue == ScrollingDirection.Up
|
||||
? new MarginPadding { Top = hitPosition }
|
||||
: new MarginPadding { Bottom = hitPosition };
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -19,7 +21,14 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
||||
|
||||
private Drawable sprite;
|
||||
[CanBeNull]
|
||||
private Drawable bodySprite;
|
||||
|
||||
[CanBeNull]
|
||||
private Drawable lightContainer;
|
||||
|
||||
[CanBeNull]
|
||||
private Drawable light;
|
||||
|
||||
public LegacyBodyPiece()
|
||||
{
|
||||
@ -32,7 +41,39 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
||||
?? $"mania-note{FallbackColumnIndex}L";
|
||||
|
||||
sprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
|
||||
string lightImage = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightImage)?.Value
|
||||
?? "lightingL";
|
||||
|
||||
float lightScale = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value
|
||||
?? 1;
|
||||
|
||||
// Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
|
||||
// This animation is discarded and re-queried with the appropriate frame length afterwards.
|
||||
var tmp = skin.GetAnimation(lightImage, true, false);
|
||||
double frameLength = 0;
|
||||
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
||||
|
||||
light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
|
||||
d.Origin = Anchor.Centre;
|
||||
d.Blending = BlendingParameters.Additive;
|
||||
d.Scale = new Vector2(lightScale);
|
||||
});
|
||||
|
||||
if (light != null)
|
||||
{
|
||||
lightContainer = new HitTargetInsetContainer
|
||||
{
|
||||
Alpha = 0,
|
||||
Child = light
|
||||
};
|
||||
}
|
||||
|
||||
bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
@ -47,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
// Todo: Wrap
|
||||
});
|
||||
|
||||
if (sprite != null)
|
||||
InternalChild = sprite;
|
||||
if (bodySprite != null)
|
||||
InternalChild = bodySprite;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
@ -60,28 +101,68 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
|
||||
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
||||
{
|
||||
if (!(sprite is TextureAnimation animation))
|
||||
if (bodySprite is TextureAnimation bodyAnimation)
|
||||
{
|
||||
bodyAnimation.GotoFrame(0);
|
||||
bodyAnimation.IsPlaying = isHitting.NewValue;
|
||||
}
|
||||
|
||||
if (lightContainer == null)
|
||||
return;
|
||||
|
||||
animation.GotoFrame(0);
|
||||
animation.IsPlaying = isHitting.NewValue;
|
||||
if (isHitting.NewValue)
|
||||
{
|
||||
// Clear the fade out and, more importantly, the removal.
|
||||
lightContainer.ClearTransforms();
|
||||
|
||||
// Only add the container if the removal has taken place.
|
||||
if (lightContainer.Parent == null)
|
||||
Column.TopLevelContainer.Add(lightContainer);
|
||||
|
||||
// The light must be seeked only after being loaded, otherwise a nullref occurs (https://github.com/ppy/osu-framework/issues/3847).
|
||||
if (light is TextureAnimation lightAnimation)
|
||||
lightAnimation.GotoFrame(0);
|
||||
|
||||
lightContainer.FadeIn(80);
|
||||
}
|
||||
else
|
||||
{
|
||||
lightContainer.FadeOut(120)
|
||||
.OnComplete(d => Column.TopLevelContainer.Remove(d));
|
||||
}
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
if (sprite == null)
|
||||
return;
|
||||
|
||||
if (direction.NewValue == ScrollingDirection.Up)
|
||||
{
|
||||
sprite.Origin = Anchor.BottomCentre;
|
||||
sprite.Scale = new Vector2(1, -1);
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.BottomCentre;
|
||||
bodySprite.Scale = new Vector2(1, -1);
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
light.Anchor = Anchor.TopCentre;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.Origin = Anchor.TopCentre;
|
||||
sprite.Scale = Vector2.One;
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Scale = Vector2.One;
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
light.Anchor = Anchor.BottomCentre;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
lightContainer?.Expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Colour = lightColour,
|
||||
Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour),
|
||||
Texture = skin.GetTexture(lightImage),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 1,
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
Anchor = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 1,
|
||||
Colour = lineColour,
|
||||
Colour = LegacyColourCompatibility.DisallowZeroAlpha(lineColour),
|
||||
Alpha = showJudgementLine ? 0.9f : 0
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
|
||||
if (GetColumnSkinConfig<bool>(skin, LegacyManiaSkinConfigurationLookups.KeysUnderNotes)?.Value ?? false)
|
||||
Column.UnderlayElements.Add(CreateProxy());
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
|
@ -2,14 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -108,75 +106,43 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = backgroundColour
|
||||
},
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}, backgroundColour),
|
||||
new HitTargetInsetContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = leftLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
Colour = lineColour,
|
||||
Alpha = hasLeftLine ? 1 : 0
|
||||
Alpha = hasLeftLine ? 1 : 0,
|
||||
Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}, lineColour)
|
||||
},
|
||||
new Box
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = rightLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
Colour = lineColour,
|
||||
Alpha = hasRightLine ? 1 : 0
|
||||
Alpha = hasRightLine ? 1 : 0,
|
||||
Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}, lineColour)
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class HitTargetInsetContainer : Container
|
||||
{
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
private float hitPosition;
|
||||
|
||||
public HitTargetInsetContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
||||
{
|
||||
hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
content.Padding = direction.NewValue == ScrollingDirection.Up
|
||||
? new MarginPadding { Top = hitPosition }
|
||||
: new MarginPadding { Bottom = hitPosition };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
public readonly ColumnHitObjectArea HitObjectArea;
|
||||
internal readonly Container TopLevelContainer;
|
||||
private readonly DrawablePool<PoolableHitExplosion> hitExplosionPool;
|
||||
private readonly OrderedHitPolicy hitPolicy;
|
||||
|
||||
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
||||
|
||||
@ -65,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
|
||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||
|
||||
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||
}
|
||||
|
||||
@ -90,6 +94,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
hitObject.AccentColour.Value = AccentColour;
|
||||
hitObject.OnNewResult += OnNewResult;
|
||||
|
||||
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
|
||||
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
HitObjectContainer.Add(hitObject);
|
||||
}
|
||||
|
||||
@ -104,6 +111,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
if (result.IsHit)
|
||||
hitPolicy.HandleHit(judgedObject);
|
||||
|
||||
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
|
78
osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs
Normal file
78
osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs
Normal file
@ -0,0 +1,78 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that only the most recent <see cref="HitObject"/> is hittable, affectionately known as "note lock".
|
||||
/// </summary>
|
||||
public class OrderedHitPolicy
|
||||
{
|
||||
private readonly HitObjectContainer hitObjectContainer;
|
||||
|
||||
public OrderedHitPolicy(HitObjectContainer hitObjectContainer)
|
||||
{
|
||||
this.hitObjectContainer = hitObjectContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a <see cref="DrawableHitObject"/> can be hit at a point in time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only the most recent <see cref="DrawableHitObject"/> can be hit, a previous hitobject's window cannot extend past the next one.
|
||||
/// </remarks>
|
||||
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to check.</param>
|
||||
/// <param name="time">The time to check.</param>
|
||||
/// <returns>Whether <paramref name="hitObject"/> can be hit at the given <paramref name="time"/>.</returns>
|
||||
public bool IsHittable(DrawableHitObject hitObject, double time)
|
||||
{
|
||||
var nextObject = hitObjectContainer.AliveObjects.GetNext(hitObject);
|
||||
return nextObject == null || time < nextObject.HitObject.StartTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a <see cref="HitObject"/> being hit to potentially miss all earlier <see cref="HitObject"/>s.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
|
||||
public void HandleHit(DrawableHitObject hitObject)
|
||||
{
|
||||
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
||||
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
||||
|
||||
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||
{
|
||||
if (obj.Judged)
|
||||
continue;
|
||||
|
||||
((DrawableManiaHitObject)obj).MissForcefully();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
||||
{
|
||||
foreach (var obj in hitObjectContainer.AliveObjects)
|
||||
{
|
||||
if (obj.HitObject.GetEndTime() >= targetTime)
|
||||
yield break;
|
||||
|
||||
yield return obj;
|
||||
|
||||
foreach (var nestedObj in obj.NestedHitObjects)
|
||||
{
|
||||
if (nestedObj.HitObject.GetEndTime() >= targetTime)
|
||||
break;
|
||||
|
||||
yield return nestedObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -79,7 +79,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
columnFlow = new ColumnFlow<Column>(definition)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING },
|
||||
},
|
||||
new Container
|
||||
{
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -22,7 +22,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Storyboards;
|
||||
using osuTK;
|
||||
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@ -32,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
|
||||
private TrackVirtualManual track;
|
||||
|
||||
protected override bool Autoplay => autoplay;
|
||||
private bool autoplay;
|
||||
|
||||
@ -44,11 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private const double fade_in_modifier = -1200;
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
track = (TrackVirtualManual)working.Track;
|
||||
return working;
|
||||
}
|
||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetConfigCache configCache)
|
||||
@ -72,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
AddStep("enable autoplay", () => autoplay = true);
|
||||
base.SetUpSteps();
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
||||
double startTime = hitObjects[sliderIndex].StartTime;
|
||||
retrieveDrawableSlider(sliderIndex);
|
||||
@ -97,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
AddStep("have autoplay", () => autoplay = true);
|
||||
base.SetUpSteps();
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
||||
double startTime = hitObjects[sliderIndex].StartTime;
|
||||
retrieveDrawableSlider(sliderIndex);
|
||||
@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => track.Seek(time));
|
||||
AddStep($"seek to {time}", () => MusicController.SeekTo(time));
|
||||
|
||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@ -62,7 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
drawableSpinner = new TestDrawableSpinner(spinner, auto)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Depth = depthIndex++
|
||||
Depth = depthIndex++,
|
||||
Scale = new Vector2(0.75f)
|
||||
};
|
||||
|
||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
||||
|
@ -25,7 +25,6 @@ using osu.Game.Scoring;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@ -34,18 +33,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
|
||||
private TrackVirtualManual track;
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
track = (TrackVirtualManual)working.Track;
|
||||
return working;
|
||||
}
|
||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
private DrawableSpinner drawableSpinner;
|
||||
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
||||
@ -55,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First());
|
||||
}
|
||||
|
||||
@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
addSeekStep(0);
|
||||
|
||||
AddStep("adjust track rate", () => track.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate)));
|
||||
AddStep("adjust track rate", () => MusicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate)));
|
||||
// autoplay replay frames use track time;
|
||||
// if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time.
|
||||
// therefore we need to apply the rate adjustment to the replay itself to change from track time to real time,
|
||||
@ -230,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => track.Seek(time));
|
||||
AddStep($"seek to {time}", () => MusicController.SeekTo(time));
|
||||
|
||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected override float SamplePlaybackPosition => HitObject.X / OsuPlayfield.BASE_SIZE.X;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="DrawableOsuHitObject"/> can be hit.
|
||||
/// Whether this <see cref="DrawableOsuHitObject"/> can be hit, given a time value.
|
||||
/// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false.
|
||||
/// </summary>
|
||||
public Func<DrawableHitObject, double, bool> CheckHittable;
|
||||
|
@ -193,30 +193,46 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
||||
|
||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||
{
|
||||
new StatisticRow
|
||||
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
|
||||
|
||||
return new[]
|
||||
{
|
||||
Columns = new[]
|
||||
new StatisticRow
|
||||
{
|
||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList())
|
||||
Columns = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
Columns = new[]
|
||||
new StatisticItem("Timing Distribution",
|
||||
new HitEventTimingDistributionGraph(timedHitEvents)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
||||
Columns = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new UnstableRate(timedHitEvents)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,8 +154,12 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
// The startPosition for the slider should not be its .Position, but the point on the circle whose tangent crosses the current cursor position
|
||||
// We also modify spinnerDirection so it spins in the direction it enters the spin circle, to make a smooth transition.
|
||||
// TODO: Shouldn't the spinner always spin in the same direction?
|
||||
if (h is Spinner)
|
||||
if (h is Spinner spinner)
|
||||
{
|
||||
// spinners with 0 spins required will auto-complete - don't bother
|
||||
if (spinner.SpinsRequired == 0)
|
||||
return;
|
||||
|
||||
calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection);
|
||||
|
||||
Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position;
|
||||
|
@ -59,7 +59,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
hitCircleSprite = new Sprite
|
||||
{
|
||||
Texture = getTextureWithFallback(string.Empty),
|
||||
Colour = drawableObject.AccentColour.Value,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
@ -107,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
base.LoadComplete();
|
||||
|
||||
state.BindValueChanged(updateState, true);
|
||||
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true);
|
||||
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
|
||||
}
|
||||
|
||||
|
@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
private DrawableSpinner drawableSpinner;
|
||||
private Sprite disc;
|
||||
private Sprite metreSprite;
|
||||
private Container metre;
|
||||
|
||||
private const float background_y_offset = 20;
|
||||
|
||||
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)
|
||||
@ -35,50 +35,58 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChild = new Container
|
||||
{
|
||||
new Sprite
|
||||
// the old-style spinner relied heavily on absolute screen-space coordinate values.
|
||||
// wrap everything in a container simulating absolute coords to preserve alignment
|
||||
// as there are skins that depend on it.
|
||||
Width = 640,
|
||||
Height = 480,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Texture = source.GetTexture("spinner-background"),
|
||||
Y = background_y_offset,
|
||||
Scale = new Vector2(sprite_scale)
|
||||
},
|
||||
disc = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-circle"),
|
||||
Scale = new Vector2(sprite_scale)
|
||||
},
|
||||
metre = new Container
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Y = background_y_offset,
|
||||
Masking = true,
|
||||
Child = new Sprite
|
||||
new Sprite
|
||||
{
|
||||
Texture = source.GetTexture("spinner-metre"),
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-background"),
|
||||
Scale = new Vector2(sprite_scale)
|
||||
},
|
||||
Scale = new Vector2(0.625f)
|
||||
disc = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-circle"),
|
||||
Scale = new Vector2(sprite_scale)
|
||||
},
|
||||
metre = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
// this anchor makes no sense, but that's what stable uses.
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
// adjustment for stable (metre has additional offset)
|
||||
Margin = new MarginPadding { Top = 20 },
|
||||
Masking = true,
|
||||
Child = metreSprite = new Sprite
|
||||
{
|
||||
Texture = source.GetTexture("spinner-metre"),
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Scale = new Vector2(0.625f)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Vector2 metreFinalSize;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
this.FadeOut();
|
||||
drawableSpinner.State.BindValueChanged(updateStateTransforms, true);
|
||||
|
||||
metreFinalSize = metre.Size = metre.Child.Size;
|
||||
}
|
||||
|
||||
private void updateStateTransforms(ValueChangedEvent<ArmedState> state)
|
||||
@ -93,7 +101,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
base.Update();
|
||||
disc.Rotation = drawableSpinner.RotationTracker.Rotation;
|
||||
metre.Height = getMetreHeight(drawableSpinner.Progress);
|
||||
|
||||
// careful: need to call this exactly once for all calculations in a frame
|
||||
// as the function has a random factor in it
|
||||
var metreHeight = getMetreHeight(drawableSpinner.Progress);
|
||||
|
||||
// hack to make the metre blink up from below than down from above.
|
||||
// move down the container to be able to apply masking for the metre,
|
||||
// and then move the sprite back up the same amount to keep its position absolute.
|
||||
metre.Y = final_metre_height - metreHeight;
|
||||
metreSprite.Y = -metre.Y;
|
||||
}
|
||||
|
||||
private const int total_bars = 10;
|
||||
@ -108,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
if (RNG.NextBool(((int)progress % 10) / 10f))
|
||||
barCount++;
|
||||
|
||||
return (float)barCount / total_bars * metreFinalSize.Y;
|
||||
return (float)barCount / total_bars * final_metre_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, DrawableHitObject drawableObject)
|
||||
{
|
||||
animationContent.Colour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
|
||||
var ballColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
@ -39,11 +39,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
Texture = skin.GetTexture("sliderb-nd"),
|
||||
Colour = new Color4(5, 5, 5, 255),
|
||||
},
|
||||
animationContent.With(d =>
|
||||
LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.Origin = Anchor.Centre;
|
||||
}),
|
||||
}), ballColour),
|
||||
layerSpec = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
|
||||
private void updateAccentColour()
|
||||
{
|
||||
backgroundLayer.Colour = accentColour;
|
||||
backgroundLayer.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,9 +76,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
|
||||
private void updateAccentColour()
|
||||
{
|
||||
headCircle.AccentColour = accentColour;
|
||||
body.Colour = accentColour;
|
||||
end.Colour = accentColour;
|
||||
var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
|
||||
|
||||
headCircle.AccentColour = colour;
|
||||
body.Colour = colour;
|
||||
end.Colour = colour;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
@ -18,9 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AccentColour = component == TaikoSkinComponents.CentreHit
|
||||
? new Color4(235, 69, 44, 255)
|
||||
: new Color4(67, 142, 172, 255);
|
||||
AccentColour = LegacyColourCompatibility.DisallowZeroAlpha(
|
||||
component == TaikoSkinComponents.CentreHit
|
||||
? new Color4(235, 69, 44, 255)
|
||||
: new Color4(67, 142, 172, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,19 +161,34 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
||||
|
||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||
{
|
||||
new StatisticRow
|
||||
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
|
||||
|
||||
return new[]
|
||||
{
|
||||
Columns = new[]
|
||||
new StatisticRow
|
||||
{
|
||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList())
|
||||
Columns = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new UnstableRate(timedHitEvents)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs
Normal file
32
osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
[TestFixture]
|
||||
public class BeatmapDifficultyManagerTest
|
||||
{
|
||||
[Test]
|
||||
public void TestKeyEqualsWithDifferentModInstances()
|
||||
{
|
||||
var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||
var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||
|
||||
Assert.That(key1, Is.EqualTo(key2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyEqualsWithDifferentModOrder()
|
||||
{
|
||||
var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||
var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
|
||||
|
||||
Assert.That(key1, Is.EqualTo(key2));
|
||||
}
|
||||
}
|
||||
}
|
@ -106,7 +106,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
protected override Texture GetBackground() => throw new NotImplementedException();
|
||||
|
||||
protected override Track GetTrack() => throw new NotImplementedException();
|
||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
70
osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
Normal file
70
osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
Normal file
@ -0,0 +1,70 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
[TestFixture]
|
||||
public class LegacyScoreDecoderTest
|
||||
{
|
||||
[Test]
|
||||
public void TestDecodeManiaReplay()
|
||||
{
|
||||
var decoder = new TestLegacyScoreDecoder();
|
||||
|
||||
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
||||
{
|
||||
var score = decoder.Parse(resourceStream);
|
||||
|
||||
Assert.AreEqual(3, score.ScoreInfo.Ruleset.ID);
|
||||
|
||||
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
|
||||
Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
|
||||
|
||||
Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
|
||||
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
||||
Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
|
||||
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
||||
|
||||
Assert.That(score.Replay.Frames, Is.Not.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestLegacyScoreDecoder : LegacyScoreDecoder
|
||||
{
|
||||
private static readonly Dictionary<int, Ruleset> rulesets = new Ruleset[]
|
||||
{
|
||||
new OsuRuleset(),
|
||||
new TaikoRuleset(),
|
||||
new CatchRuleset(),
|
||||
new ManiaRuleset()
|
||||
}.ToDictionary(ruleset => ((ILegacyRuleset)ruleset).LegacyID);
|
||||
|
||||
protected override Ruleset GetRuleset(int rulesetId) => rulesets[rulesetId];
|
||||
|
||||
protected override WorkingBeatmap GetBeatmap(string md5Hash) => new TestWorkingBeatmap(new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
MD5Hash = md5Hash,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BaseDifficulty = new BeatmapDifficulty()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
@ -19,7 +17,14 @@ namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
GameplayClockContainer gcc = null;
|
||||
|
||||
AddStep("create container", () => Add(gcc = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty<Mod>(), 0)));
|
||||
AddStep("create container", () =>
|
||||
{
|
||||
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
working.LoadTrack();
|
||||
|
||||
Add(gcc = new GameplayClockContainer(working, 0));
|
||||
});
|
||||
|
||||
AddStep("start track", () => gcc.Start());
|
||||
AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
|
||||
}
|
||||
|
@ -59,7 +59,10 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
AddStep("create container", () =>
|
||||
{
|
||||
Add(gameplayContainer = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty<Mod>(), 0));
|
||||
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
working.LoadTrack();
|
||||
|
||||
Add(gameplayContainer = new GameplayClockContainer(working, 0));
|
||||
|
||||
gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
|
||||
{
|
||||
@ -103,7 +106,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
|
||||
SelectedMods.Value = new[] { testedMod };
|
||||
|
||||
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0));
|
||||
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0));
|
||||
|
||||
gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
|
||||
{
|
||||
|
43
osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs
Normal file
43
osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual.Ranking
|
||||
{
|
||||
[TestFixture]
|
||||
public class UnstableRateTest
|
||||
{
|
||||
[Test]
|
||||
public void TestDistributedHits()
|
||||
{
|
||||
var events = Enumerable.Range(-5, 11)
|
||||
.Select(t => new HitEvent(t - 5, HitResult.Great, new HitObject(), null, null));
|
||||
|
||||
var unstableRate = new UnstableRate(events);
|
||||
|
||||
Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value, 10 * Math.Sqrt(10)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissesAndEmptyWindows()
|
||||
{
|
||||
var events = new[]
|
||||
{
|
||||
new HitEvent(-100, HitResult.Miss, new HitObject(), null, null),
|
||||
new HitEvent(0, HitResult.Great, new HitObject(), null, null),
|
||||
new HitEvent(200, HitResult.Meh, new HitObject { HitWindows = HitWindows.Empty }, null, null),
|
||||
};
|
||||
|
||||
var unstableRate = new UnstableRate(events);
|
||||
|
||||
Assert.AreEqual(0, unstableRate.Value);
|
||||
}
|
||||
}
|
||||
}
|
BIN
osu.Game.Tests/Resources/Replays/mania-replay.osr
Normal file
BIN
osu.Game.Tests/Resources/Replays/mania-replay.osr
Normal file
Binary file not shown.
@ -1,5 +0,0 @@
|
||||
[General]
|
||||
Version: latest
|
||||
|
||||
[Colours]
|
||||
Combo1: 255,255,255,0
|
@ -108,15 +108,5 @@ namespace osu.Game.Tests.Skins
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeColourWithZeroAlpha()
|
||||
{
|
||||
var decoder = new LegacySkinDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("skin-zero-alpha-colour.ini"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
Assert.That(decoder.Decode(stream).ComboColours[0].A, Is.EqualTo(1.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ namespace osu.Game.Tests.Skins
|
||||
{
|
||||
var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result;
|
||||
beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]);
|
||||
beatmap.LoadTrack();
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -4,7 +4,6 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
@ -18,8 +17,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneCompletionCancellation : OsuPlayerTestScene
|
||||
{
|
||||
private Track track;
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audio { get; set; }
|
||||
|
||||
@ -34,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
base.SetUpSteps();
|
||||
|
||||
// Ensure track has actually running before attempting to seek
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -73,13 +70,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void complete()
|
||||
{
|
||||
AddStep("seek to completion", () => track.Seek(5000));
|
||||
AddStep("seek to completion", () => Beatmap.Value.Track.Seek(5000));
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private void cancel()
|
||||
{
|
||||
AddStep("rewind to cancel", () => track.Seek(4000));
|
||||
AddStep("rewind to cancel", () => Beatmap.Value.Track.Seek(4000));
|
||||
AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
@ -91,11 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
|
||||
track = working.Track;
|
||||
return working;
|
||||
}
|
||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -21,19 +20,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
|
||||
private Track track;
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
track = working.Track;
|
||||
return working;
|
||||
}
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
|
||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
[Test]
|
||||
public void TestNoJudgementsOnRewind()
|
||||
{
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
addSeekStep(3000);
|
||||
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
|
||||
@ -46,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => track.Seek(time));
|
||||
AddStep($"seek to {time}", () => Beatmap.Value.Track.Seek(time));
|
||||
|
||||
// Allow a few frames of lenience
|
||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||
|
@ -1,11 +1,9 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
@ -32,7 +30,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
requestCount = 0;
|
||||
increment = skip_time;
|
||||
|
||||
Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty<Mod>(), 0)
|
||||
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
|
||||
working.LoadTrack();
|
||||
|
||||
Child = gameplayClockContainer = new GameplayClockContainer(working, 0)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
|
@ -22,19 +22,32 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[TestFixture]
|
||||
public class TestSceneStoryboard : OsuTestScene
|
||||
{
|
||||
private readonly Container<DrawableStoryboard> storyboardContainer;
|
||||
private Container<DrawableStoryboard> storyboardContainer;
|
||||
private DrawableStoryboard storyboard;
|
||||
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
[Test]
|
||||
public void TestStoryboard()
|
||||
{
|
||||
AddStep("Restart", restart);
|
||||
AddToggleStep("Passing", passing =>
|
||||
{
|
||||
if (storyboard != null) storyboard.Passing = passing;
|
||||
});
|
||||
}
|
||||
|
||||
public TestSceneStoryboard()
|
||||
[Test]
|
||||
public void TestStoryboardMissingVideo()
|
||||
{
|
||||
AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Clock = new FramedClock();
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
musicController,
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -58,32 +71,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
State = { Value = Visibility.Visible },
|
||||
}
|
||||
});
|
||||
|
||||
Beatmap.BindValueChanged(beatmapChanged, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboard()
|
||||
{
|
||||
AddStep("Restart", restart);
|
||||
AddToggleStep("Passing", passing =>
|
||||
{
|
||||
if (storyboard != null) storyboard.Passing = passing;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardMissingVideo()
|
||||
{
|
||||
AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.ValueChanged += beatmapChanged;
|
||||
}
|
||||
|
||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e)
|
||||
=> loadStoryboard(e.NewValue);
|
||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e) => loadStoryboard(e.NewValue);
|
||||
|
||||
private void restart()
|
||||
{
|
||||
|
@ -2,8 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Screens.Menu;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
@ -15,11 +15,9 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
|
||||
public TestSceneIntroWelcome()
|
||||
{
|
||||
AddUntilStep("wait for load", () => getTrack() != null);
|
||||
|
||||
AddAssert("check if menu music loops", () => getTrack().Looping);
|
||||
AddUntilStep("wait for load", () => MusicController.TrackLoaded);
|
||||
AddAssert("correct track", () => Precision.AlmostEquals(MusicController.CurrentTrack.Length, 48000, 1));
|
||||
AddAssert("check if menu music loops", () => MusicController.CurrentTrack.Looping);
|
||||
}
|
||||
|
||||
private Track getTrack() => (IntroStack?.CurrentScreen as MainMenu)?.Track;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
@ -11,14 +10,10 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
public class TestSceneSongTicker : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
|
||||
public TestSceneSongTicker()
|
||||
{
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
musicController,
|
||||
new SongTicker
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -46,7 +45,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
Player player = null;
|
||||
|
||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
Track track() => beatmap().Track;
|
||||
|
||||
PushAndConfirm(() => new TestSongSelect());
|
||||
|
||||
@ -62,30 +60,27 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
|
||||
AddUntilStep("wait for fail", () => player.HasFailed);
|
||||
|
||||
AddUntilStep("wait for track stop", () => !track().IsRunning);
|
||||
AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
|
||||
AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying);
|
||||
AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime);
|
||||
|
||||
pushEscape();
|
||||
|
||||
AddUntilStep("wait for track playing", () => track().IsRunning);
|
||||
AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
|
||||
AddUntilStep("wait for track playing", () => Game.MusicController.IsPlaying);
|
||||
AddAssert("Ensure time wasn't reset to preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMenuMakesMusic()
|
||||
{
|
||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
Track track() => beatmap().Track;
|
||||
|
||||
TestSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
|
||||
AddUntilStep("wait for no track", () => track() is TrackVirtual);
|
||||
AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
|
||||
|
||||
AddStep("return to menu", () => songSelect.Exit());
|
||||
|
||||
AddUntilStep("wait for track", () => !(track() is TrackVirtual) && track().IsRunning);
|
||||
AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -140,12 +135,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded);
|
||||
AddStep("Seek close to end", () =>
|
||||
{
|
||||
Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000);
|
||||
Game.Beatmap.Value.Track.Completed += () => trackCompleted = true;
|
||||
Game.MusicController.SeekTo(Game.MusicController.CurrentTrack.Length - 1000);
|
||||
Game.MusicController.CurrentTrack.Completed += () => trackCompleted = true;
|
||||
});
|
||||
|
||||
AddUntilStep("Track was completed", () => trackCompleted);
|
||||
AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning);
|
||||
AddUntilStep("Track was restarted", () => Game.MusicController.IsPlaying);
|
||||
}
|
||||
|
||||
private void pushEscape() =>
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
|
||||
|
||||
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null)
|
||||
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)
|
||||
{
|
||||
BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null }
|
||||
});
|
||||
|
@ -35,6 +35,18 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
createTest(new List<HitEvent>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissesDontShow()
|
||||
{
|
||||
createTest(Enumerable.Range(0, 100).Select(i =>
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
return new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null);
|
||||
|
||||
return new HitEvent(30, HitResult.Miss, new HitCircle(), new HitCircle(), null);
|
||||
}).ToList());
|
||||
}
|
||||
|
||||
private void createTest(List<HitEvent> events) => AddStep("create test", () =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
@ -212,6 +213,25 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddAssert("expanded panel still on screen", () => this.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded).ScreenSpaceDrawQuad.TopLeft.X > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDownloadButtonInitiallyDisabled()
|
||||
{
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
|
||||
AddAssert("download button is disabled", () => !screen.ChildrenOfType<DownloadButton>().Single().Enabled.Value);
|
||||
|
||||
AddStep("click contracted panel", () =>
|
||||
{
|
||||
var contractedPanel = this.ChildrenOfType<ScorePanel>().First(p => p.State == PanelState.Contracted && p.ScreenSpaceDrawQuad.TopLeft.X > screen.ScreenSpaceDrawQuad.TopLeft.X);
|
||||
InputManager.MoveMouseTo(contractedPanel);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Single().Enabled.Value);
|
||||
}
|
||||
|
||||
private class TestResultsContainer : Container
|
||||
{
|
||||
[Cached(typeof(Player))]
|
||||
@ -255,6 +275,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
score.TotalScore += 10 - i;
|
||||
score.Hash = $"test{i}";
|
||||
scores.Add(score);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ using osu.Game.Screens.Ranking.Statistics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public class TestSceneSimpleStatisticRow : OsuTestScene
|
||||
public class TestSceneSimpleStatisticTable : OsuTestScene
|
||||
{
|
||||
private Container container;
|
||||
|
||||
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
public void TestEmpty()
|
||||
{
|
||||
AddStep("create with no items",
|
||||
() => container.Add(new SimpleStatisticRow(2, Enumerable.Empty<SimpleStatisticItem>())));
|
||||
() => container.Add(new SimpleStatisticTable(2, Enumerable.Empty<SimpleStatisticItem>())));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Value = RNG.Next(100)
|
||||
});
|
||||
|
||||
container.Add(new SimpleStatisticRow(columnCount, items));
|
||||
container.Add(new SimpleStatisticTable(columnCount, items));
|
||||
});
|
||||
}
|
||||
}
|
@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
private readonly NowPlayingOverlay np;
|
||||
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
|
||||
public TestSceneBeatSyncedContainer()
|
||||
{
|
||||
Clock = new FramedClock();
|
||||
@ -36,7 +33,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
musicController,
|
||||
new BeatContainer
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
@ -71,6 +67,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
private readonly Box flashLayer;
|
||||
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; }
|
||||
|
||||
public BeatContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -165,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
if (timingPoints.Count == 0) return 0;
|
||||
|
||||
if (timingPoints[^1] == current)
|
||||
return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength);
|
||||
return (int)Math.Ceiling((musicController.CurrentTrack.Length - current.Time) / current.BeatLength);
|
||||
|
||||
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
@ -79,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, Audio, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
||||
|
||||
beatmap = beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result.Beatmaps[0];
|
||||
|
@ -2,6 +2,7 @@
|
||||
// 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.Audio;
|
||||
@ -11,6 +12,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
@ -20,8 +22,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
|
||||
private WorkingBeatmap currentBeatmap;
|
||||
|
||||
private NowPlayingOverlay nowPlayingOverlay;
|
||||
|
||||
private RulesetStore rulesets;
|
||||
@ -76,16 +76,21 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
}).Wait(), 5);
|
||||
|
||||
AddStep(@"Next track", () => musicController.NextTrack());
|
||||
AddStep("Store track", () => currentBeatmap = Beatmap.Value);
|
||||
WorkingBeatmap currentBeatmap = null;
|
||||
|
||||
AddStep("import beatmap with track", () =>
|
||||
{
|
||||
var setWithTrack = manager.Import(TestResources.GetTestBeatmapForImport()).Result;
|
||||
Beatmap.Value = currentBeatmap = manager.GetWorkingBeatmap(setWithTrack.Beatmaps.First());
|
||||
});
|
||||
|
||||
AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000));
|
||||
AddUntilStep(@"Wait for current time to update", () => currentBeatmap.Track.CurrentTime > 5000);
|
||||
AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrack.CurrentTime > 5000);
|
||||
|
||||
AddStep(@"Set previous", () => musicController.PreviousTrack());
|
||||
|
||||
AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value);
|
||||
AddUntilStep("Wait for current time to update", () => currentBeatmap.Track.CurrentTime < 5000);
|
||||
AddUntilStep("Wait for current time to update", () => musicController.CurrentTrack.CurrentTime < 5000);
|
||||
|
||||
AddStep(@"Set previous", () => musicController.PreviousTrack());
|
||||
AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value);
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Tests
|
||||
|
||||
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
||||
|
||||
protected override Track GetTrack() => trackStore.Get(firstAudioFile);
|
||||
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
|
||||
|
||||
private string firstAudioFile
|
||||
{
|
||||
|
@ -3,7 +3,7 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
41
osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs
Normal file
41
osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osu.Game.Tournament.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tournament.Tests.Components
|
||||
{
|
||||
public class TestSceneDateTextBox : OsuManualInputManagerTestScene
|
||||
{
|
||||
private DateTextBox textBox;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Child = textBox = new DateTextBox
|
||||
{
|
||||
Width = 0.3f
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestCommitWithoutSettingBindable()
|
||||
{
|
||||
AddStep("click textbox", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(textBox);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("unfocus", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(Vector2.Zero);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
</ItemGroup>
|
||||
|
@ -22,11 +22,12 @@ namespace osu.Game.Tournament.Components
|
||||
}
|
||||
|
||||
// hold a reference to the provided bindable so we don't have to in every settings section.
|
||||
private Bindable<DateTimeOffset> bindable;
|
||||
private Bindable<DateTimeOffset> bindable = new Bindable<DateTimeOffset>();
|
||||
|
||||
public DateTextBox()
|
||||
{
|
||||
base.Bindable = new Bindable<string>();
|
||||
|
||||
((OsuTextBox)Control).OnCommit = (sender, newText) =>
|
||||
{
|
||||
try
|
||||
|
@ -26,6 +26,8 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
[Cached]
|
||||
private LadderEditorInfo editorInfo = new LadderEditorInfo();
|
||||
|
||||
private WarningBox rightClickMessage;
|
||||
|
||||
protected override bool DrawLoserPaths => true;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -37,6 +39,16 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Origin = Anchor.TopRight,
|
||||
Margin = new MarginPadding(5)
|
||||
});
|
||||
|
||||
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));
|
||||
|
||||
LadderInfo.Matches.CollectionChanged += (_, __) => updateMessage();
|
||||
updateMessage();
|
||||
}
|
||||
|
||||
private void updateMessage()
|
||||
{
|
||||
rightClickMessage.Alpha = LadderInfo.Matches.Count > 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
public void BeginJoin(TournamentMatch match, bool losers)
|
||||
|
@ -87,30 +87,7 @@ namespace osu.Game.Tournament
|
||||
},
|
||||
}
|
||||
},
|
||||
heightWarning = new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Depth = float.MinValue,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Red,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new TournamentSpriteText
|
||||
{
|
||||
Text = "Please make the window wider",
|
||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
|
||||
Colour = Color4.White,
|
||||
Padding = new MarginPadding(20)
|
||||
}
|
||||
}
|
||||
},
|
||||
heightWarning = new WarningBox("Please make the window wider"),
|
||||
new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
40
osu.Game.Tournament/WarningBox.cs
Normal file
40
osu.Game.Tournament/WarningBox.cs
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tournament
|
||||
{
|
||||
internal class WarningBox : Container
|
||||
{
|
||||
public WarningBox(string text)
|
||||
{
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
Depth = float.MinValue;
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Red,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new TournamentSpriteText
|
||||
{
|
||||
Text = text,
|
||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
|
||||
Colour = Color4.White,
|
||||
Padding = new MarginPadding(20)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -89,8 +89,14 @@ namespace osu.Game.Beatmaps
|
||||
if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key))
|
||||
return existing;
|
||||
|
||||
return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken,
|
||||
TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
// Computation may have finished in a previous task.
|
||||
if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out existing, out _))
|
||||
return existing;
|
||||
|
||||
return computeDifficulty(key, beatmapInfo, rulesetInfo);
|
||||
}, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -245,7 +251,7 @@ namespace osu.Game.Beatmaps
|
||||
updateScheduler?.Dispose();
|
||||
}
|
||||
|
||||
private readonly struct DifficultyCacheLookup : IEquatable<DifficultyCacheLookup>
|
||||
public readonly struct DifficultyCacheLookup : IEquatable<DifficultyCacheLookup>
|
||||
{
|
||||
public readonly int BeatmapId;
|
||||
public readonly int RulesetId;
|
||||
@ -261,7 +267,7 @@ namespace osu.Game.Beatmaps
|
||||
public bool Equals(DifficultyCacheLookup other)
|
||||
=> BeatmapId == other.BeatmapId
|
||||
&& RulesetId == other.RulesetId
|
||||
&& Mods.SequenceEqual(other.Mods);
|
||||
&& Mods.Select(m => m.Acronym).SequenceEqual(other.Mods.Select(m => m.Acronym));
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
@ -63,16 +64,16 @@ namespace osu.Game.Beatmaps
|
||||
private readonly RulesetStore rulesets;
|
||||
private readonly BeatmapStore beatmaps;
|
||||
private readonly AudioManager audioManager;
|
||||
private readonly GameHost host;
|
||||
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
|
||||
private readonly TextureStore textureStore;
|
||||
private readonly ITrackStore trackStore;
|
||||
|
||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
|
||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null,
|
||||
WorkingBeatmap defaultBeatmap = null)
|
||||
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
||||
{
|
||||
this.rulesets = rulesets;
|
||||
this.audioManager = audioManager;
|
||||
this.host = host;
|
||||
|
||||
DefaultBeatmap = defaultBeatmap;
|
||||
|
||||
@ -83,6 +84,9 @@ namespace osu.Game.Beatmaps
|
||||
beatmaps.ItemUpdated += removeWorkingCache;
|
||||
|
||||
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
||||
|
||||
textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store));
|
||||
trackStore = audioManager.GetTrackStore(Files.Store);
|
||||
}
|
||||
|
||||
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
||||
@ -218,7 +222,7 @@ namespace osu.Game.Beatmaps
|
||||
removeWorkingCache(info);
|
||||
}
|
||||
|
||||
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
|
||||
private readonly WeakList<BeatmapManagerWorkingBeatmap> workingCache = new WeakList<BeatmapManagerWorkingBeatmap>();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
|
||||
@ -246,16 +250,13 @@ namespace osu.Game.Beatmaps
|
||||
lock (workingCache)
|
||||
{
|
||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
||||
if (working != null)
|
||||
return working;
|
||||
|
||||
if (working == null)
|
||||
{
|
||||
beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
|
||||
beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
|
||||
|
||||
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store,
|
||||
new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager));
|
||||
}
|
||||
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager));
|
||||
|
||||
previous?.TransferTo(working);
|
||||
return working;
|
||||
}
|
||||
}
|
||||
@ -459,7 +460,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
protected override IBeatmap GetBeatmap() => beatmap;
|
||||
protected override Texture GetBackground() => null;
|
||||
protected override Track GetTrack() => null;
|
||||
protected override Track GetBeatmapTrack() => null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,15 +17,18 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public partial class BeatmapManager
|
||||
{
|
||||
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
|
||||
private class BeatmapManagerWorkingBeatmap : WorkingBeatmap
|
||||
{
|
||||
private readonly IResourceStore<byte[]> store;
|
||||
private readonly TextureStore textureStore;
|
||||
private readonly ITrackStore trackStore;
|
||||
|
||||
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, TextureStore textureStore, BeatmapInfo beatmapInfo, AudioManager audioManager)
|
||||
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager)
|
||||
: base(beatmapInfo, audioManager)
|
||||
{
|
||||
this.store = store;
|
||||
this.textureStore = textureStore;
|
||||
this.trackStore = trackStore;
|
||||
}
|
||||
|
||||
protected override IBeatmap GetBeatmap()
|
||||
@ -44,10 +47,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||
|
||||
private TextureStore textureStore;
|
||||
|
||||
private ITrackStore trackStore;
|
||||
|
||||
protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes.
|
||||
|
||||
protected override Texture GetBackground()
|
||||
@ -66,11 +65,11 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
protected override Track GetTrack()
|
||||
protected override Track GetBeatmapTrack()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile));
|
||||
return trackStore.Get(getPathForFile(Metadata.AudioFile));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -79,22 +78,6 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
public override void RecycleTrack()
|
||||
{
|
||||
base.RecycleTrack();
|
||||
|
||||
trackStore?.Dispose();
|
||||
trackStore = null;
|
||||
}
|
||||
|
||||
public override void TransferTo(WorkingBeatmap other)
|
||||
{
|
||||
base.TransferTo(other);
|
||||
|
||||
if (other is BeatmapManagerWorkingBeatmap owb && textureStore != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo))
|
||||
owb.textureStore = textureStore;
|
||||
}
|
||||
|
||||
protected override Waveform GetWaveform()
|
||||
{
|
||||
try
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
@ -19,7 +20,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
private readonly TextureStore textures;
|
||||
|
||||
public DummyWorkingBeatmap(AudioManager audio, TextureStore textures)
|
||||
public DummyWorkingBeatmap([NotNull] AudioManager audio, TextureStore textures)
|
||||
: base(new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
@ -44,7 +45,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
|
||||
|
||||
protected override Track GetTrack() => GetVirtualTrack();
|
||||
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
||||
|
||||
private class DummyRulesetInfo : RulesetInfo
|
||||
{
|
||||
|
@ -104,10 +104,6 @@ namespace osu.Game.Beatmaps.Formats
|
||||
try
|
||||
{
|
||||
byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255;
|
||||
|
||||
if (alpha == 0)
|
||||
alpha = 255;
|
||||
|
||||
colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha);
|
||||
}
|
||||
catch
|
||||
|
@ -26,11 +26,6 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
Texture Background { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the audio track for this <see cref="WorkingBeatmap"/>.
|
||||
/// </summary>
|
||||
Track Track { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="WorkingBeatmap"/>.
|
||||
/// </summary>
|
||||
@ -59,5 +54,18 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>The converted <see cref="IBeatmap"/>.</returns>
|
||||
/// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
|
||||
IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null);
|
||||
|
||||
/// <summary>
|
||||
/// Load a new audio track instance for this beatmap. This should be called once before accessing <see cref="Track"/>.
|
||||
/// The caller of this method is responsible for the lifetime of the track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In a standard game context, the loading of the track is managed solely by MusicController, which will
|
||||
/// automatically load the track of the current global IBindable WorkingBeatmap.
|
||||
/// As such, this method should only be called in very special scenarios, such as external tests or apps which are
|
||||
/// outside of the game context.
|
||||
/// </remarks>
|
||||
/// <returns>A fresh track instance, which will also be available via <see cref="Track"/>.</returns>
|
||||
Track LoadTrack();
|
||||
}
|
||||
}
|
||||
|
@ -38,5 +38,6 @@ namespace osu.Game.Beatmaps.Legacy
|
||||
Key1 = 1 << 26,
|
||||
Key3 = 1 << 27,
|
||||
Key2 = 1 << 28,
|
||||
Mirror = 1 << 30,
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
@ -40,7 +41,6 @@ namespace osu.Game.Beatmaps
|
||||
BeatmapSetInfo = beatmapInfo.BeatmapSet;
|
||||
Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
|
||||
|
||||
track = new RecyclableLazy<Track>(() => GetTrack() ?? GetVirtualTrack(1000));
|
||||
background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid);
|
||||
waveform = new RecyclableLazy<Waveform>(GetWaveform);
|
||||
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
|
||||
@ -259,10 +259,39 @@ namespace osu.Game.Beatmaps
|
||||
protected abstract Texture GetBackground();
|
||||
private readonly RecyclableLazy<Texture> background;
|
||||
|
||||
public virtual bool TrackLoaded => track.IsResultAvailable;
|
||||
public Track Track => track.Value;
|
||||
protected abstract Track GetTrack();
|
||||
private RecyclableLazy<Track> track;
|
||||
private Track loadedTrack;
|
||||
|
||||
[NotNull]
|
||||
public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000);
|
||||
|
||||
/// <summary>
|
||||
/// Transfer a valid audio track into this working beatmap. Used as an optimisation to avoid reload / track swap
|
||||
/// across difficulties in the same beatmap set.
|
||||
/// </summary>
|
||||
/// <param name="track">The track to transfer.</param>
|
||||
public void TransferTrack([NotNull] Track track) => loadedTrack = track ?? throw new ArgumentNullException(nameof(track));
|
||||
|
||||
/// <summary>
|
||||
/// Whether this beatmap's track has been loaded via <see cref="LoadTrack"/>.
|
||||
/// </summary>
|
||||
public virtual bool TrackLoaded => loadedTrack != null;
|
||||
|
||||
/// <summary>
|
||||
/// Get the loaded audio track instance. <see cref="LoadTrack"/> must have first been called.
|
||||
/// This generally happens via MusicController when changing the global beatmap.
|
||||
/// </summary>
|
||||
public Track Track
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!TrackLoaded)
|
||||
throw new InvalidOperationException($"Cannot access {nameof(Track)} without first calling {nameof(LoadTrack)}.");
|
||||
|
||||
return loadedTrack;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Track GetBeatmapTrack();
|
||||
|
||||
public bool WaveformLoaded => waveform.IsResultAvailable;
|
||||
public Waveform Waveform => waveform.Value;
|
||||
@ -280,22 +309,6 @@ namespace osu.Game.Beatmaps
|
||||
protected virtual ISkin GetSkin() => new DefaultSkin();
|
||||
private readonly RecyclableLazy<ISkin> skin;
|
||||
|
||||
/// <summary>
|
||||
/// Transfer pieces of a beatmap to a new one, where possible, to save on loading.
|
||||
/// </summary>
|
||||
/// <param name="other">The new beatmap which is being switched to.</param>
|
||||
public virtual void TransferTo(WorkingBeatmap other)
|
||||
{
|
||||
if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo))
|
||||
other.track = track;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eagerly dispose of the audio track associated with this <see cref="WorkingBeatmap"/> (if any).
|
||||
/// Accessing track again will load a fresh instance.
|
||||
/// </summary>
|
||||
public virtual void RecycleTrack() => track.Recycle();
|
||||
|
||||
~WorkingBeatmap()
|
||||
{
|
||||
total_count.Value--;
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
Track track = null;
|
||||
ITrack track = null;
|
||||
IBeatmap beatmap = null;
|
||||
|
||||
double currentTrackTime = 0;
|
||||
|
@ -40,10 +40,5 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText()
|
||||
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f, fixedWidth: true));
|
||||
|
||||
public override void Increment(double amount)
|
||||
{
|
||||
Current.Value += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,20 +57,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void Increment(T amount);
|
||||
|
||||
/// <summary>
|
||||
/// Skeleton of a numeric counter which value rolls over time.
|
||||
/// </summary>
|
||||
protected RollingCounter()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Current.ValueChanged += val =>
|
||||
{
|
||||
if (IsLoaded)
|
||||
TransformCount(DisplayedCount, val.NewValue);
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -81,6 +73,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Child = displayedCountSpriteText;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(val => TransformCount(DisplayedCount, val.NewValue), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets count value, bypassing rollover animation.
|
||||
/// </summary>
|
||||
|
@ -51,10 +51,5 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText()
|
||||
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));
|
||||
|
||||
public override void Increment(double amount)
|
||||
{
|
||||
Current.Value += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,11 +33,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f;
|
||||
}
|
||||
|
||||
public override void Increment(int amount)
|
||||
{
|
||||
Current.Value += amount;
|
||||
}
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText()
|
||||
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f));
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Online.Chat
|
||||
case "http":
|
||||
case "https":
|
||||
// length > 3 since all these links need another argument to work
|
||||
if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh"))
|
||||
if (args.Length > 3 && args[1] == "osu.ppy.sh")
|
||||
{
|
||||
switch (args[2])
|
||||
{
|
||||
|
@ -428,23 +428,7 @@ namespace osu.Game
|
||||
|
||||
updateModDefaults();
|
||||
|
||||
var newBeatmap = beatmap.NewValue;
|
||||
|
||||
if (newBeatmap != null)
|
||||
{
|
||||
newBeatmap.Track.Completed += () => Scheduler.AddOnce(() => trackCompleted(newBeatmap));
|
||||
newBeatmap.BeginAsyncLoad();
|
||||
}
|
||||
|
||||
void trackCompleted(WorkingBeatmap b)
|
||||
{
|
||||
// the source of track completion is the audio thread, so the beatmap may have changed before firing.
|
||||
if (Beatmap.Value != b)
|
||||
return;
|
||||
|
||||
if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled)
|
||||
MusicController.NextTrack();
|
||||
}
|
||||
beatmap.NewValue?.BeginAsyncLoad();
|
||||
}
|
||||
|
||||
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||
@ -618,8 +602,6 @@ namespace osu.Game
|
||||
|
||||
loadComponentSingleFile(new OnScreenDisplay(), Add, true);
|
||||
|
||||
loadComponentSingleFile(MusicController = new MusicController(), Add, true);
|
||||
|
||||
loadComponentSingleFile(notifications.With(d =>
|
||||
{
|
||||
d.GetToolbarHeight = () => ToolbarOffset;
|
||||
@ -924,8 +906,6 @@ namespace osu.Game
|
||||
|
||||
private ScalingContainer screenContainer;
|
||||
|
||||
protected MusicController MusicController { get; private set; }
|
||||
|
||||
protected override bool OnExiting()
|
||||
{
|
||||
if (ScreenStack.CurrentScreen is Loader)
|
||||
|
@ -30,6 +30,7 @@ using osu.Game.Database;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Resources;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -73,6 +74,8 @@ namespace osu.Game
|
||||
|
||||
protected MenuCursorContainer MenuCursorContainer;
|
||||
|
||||
protected MusicController MusicController;
|
||||
|
||||
private Container content;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
@ -238,16 +241,6 @@ namespace osu.Game
|
||||
|
||||
Beatmap = new NonNullableBindable<WorkingBeatmap>(defaultBeatmap);
|
||||
|
||||
// ScheduleAfterChildren is safety against something in the current frame accessing the previous beatmap's track
|
||||
// and potentially causing a reload of it after just unloading.
|
||||
// Note that the reason for this being added *has* been resolved, so it may be feasible to removed this if required.
|
||||
Beatmap.BindValueChanged(b => ScheduleAfterChildren(() =>
|
||||
{
|
||||
// compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo)
|
||||
if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track)
|
||||
b.OldValue.RecycleTrack();
|
||||
}));
|
||||
|
||||
dependencies.CacheAs<IBindable<WorkingBeatmap>>(Beatmap);
|
||||
dependencies.CacheAs(Beatmap);
|
||||
|
||||
@ -275,6 +268,9 @@ namespace osu.Game
|
||||
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
|
||||
Add(previewTrackManager);
|
||||
|
||||
AddInternal(MusicController = new MusicController());
|
||||
dependencies.CacheAs(MusicController);
|
||||
|
||||
Ruleset.BindValueChanged(onRulesetChanged);
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Music
|
||||
{
|
||||
if (set.ID == (beatmap.Value?.BeatmapSetInfo?.ID ?? -1))
|
||||
{
|
||||
beatmap.Value?.Track?.Seek(0);
|
||||
beatmap.Value?.Track.Seek(0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Threading;
|
||||
@ -21,7 +25,7 @@ namespace osu.Game.Overlays
|
||||
/// <summary>
|
||||
/// Handles playback of the global music track.
|
||||
/// </summary>
|
||||
public class MusicController : Component, IKeyBindingHandler<GlobalAction>
|
||||
public class MusicController : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
@ -61,6 +65,9 @@ namespace osu.Game.Overlays
|
||||
[Resolved(canBeNull: true)]
|
||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||
|
||||
[NotNull]
|
||||
public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000));
|
||||
|
||||
private IBindable<WeakReference<BeatmapSetInfo>> managerUpdated;
|
||||
private IBindable<WeakReference<BeatmapSetInfo>> managerRemoved;
|
||||
|
||||
@ -73,12 +80,9 @@ namespace osu.Game.Overlays
|
||||
managerRemoved.BindValueChanged(beatmapRemoved);
|
||||
|
||||
beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next()));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// Todo: These binds really shouldn't be here, but are unlikely to cause any issues for now.
|
||||
// They are placed here for now since some tests rely on setting the beatmap _and_ their hierarchies inside their load(), which runs before the MusicController's load().
|
||||
beatmap.BindValueChanged(beatmapChanged, true);
|
||||
mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
|
||||
}
|
||||
@ -95,9 +99,14 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the current beatmap track is playing.
|
||||
/// Returns whether the beatmap track is playing.
|
||||
/// </summary>
|
||||
public bool IsPlaying => current?.Track.IsRunning ?? false;
|
||||
public bool IsPlaying => CurrentTrack.IsRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the beatmap track is loaded.
|
||||
/// </summary>
|
||||
public bool TrackLoaded => CurrentTrack.TrackLoaded;
|
||||
|
||||
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
|
||||
{
|
||||
@ -130,7 +139,7 @@ namespace osu.Game.Overlays
|
||||
seekDelegate = Schedule(() =>
|
||||
{
|
||||
if (!beatmap.Disabled)
|
||||
current?.Track.Seek(position);
|
||||
CurrentTrack.Seek(position);
|
||||
});
|
||||
}
|
||||
|
||||
@ -142,9 +151,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
if (IsUserPaused) return;
|
||||
|
||||
var track = current?.Track;
|
||||
|
||||
if (track == null || track is TrackVirtual)
|
||||
if (CurrentTrack.IsDummyDevice)
|
||||
{
|
||||
if (beatmap.Disabled)
|
||||
return;
|
||||
@ -163,17 +170,12 @@ namespace osu.Game.Overlays
|
||||
/// <returns>Whether the operation was successful.</returns>
|
||||
public bool Play(bool restart = false)
|
||||
{
|
||||
var track = current?.Track;
|
||||
|
||||
IsUserPaused = false;
|
||||
|
||||
if (track == null)
|
||||
return false;
|
||||
|
||||
if (restart)
|
||||
track.Restart();
|
||||
CurrentTrack.Restart();
|
||||
else if (!IsPlaying)
|
||||
track.Start();
|
||||
CurrentTrack.Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -183,11 +185,9 @@ namespace osu.Game.Overlays
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
var track = current?.Track;
|
||||
|
||||
IsUserPaused = true;
|
||||
if (track?.IsRunning == true)
|
||||
track.Stop();
|
||||
if (CurrentTrack.IsRunning)
|
||||
CurrentTrack.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -196,9 +196,7 @@ namespace osu.Game.Overlays
|
||||
/// <returns>Whether the operation was successful.</returns>
|
||||
public bool TogglePause()
|
||||
{
|
||||
var track = current?.Track;
|
||||
|
||||
if (track?.IsRunning == true)
|
||||
if (CurrentTrack.IsRunning)
|
||||
Stop();
|
||||
else
|
||||
Play();
|
||||
@ -220,7 +218,7 @@ namespace osu.Game.Overlays
|
||||
if (beatmap.Disabled)
|
||||
return PreviousTrackResult.None;
|
||||
|
||||
var currentTrackPosition = current?.Track.CurrentTime;
|
||||
var currentTrackPosition = CurrentTrack.CurrentTime;
|
||||
|
||||
if (currentTrackPosition >= restart_cutoff_point)
|
||||
{
|
||||
@ -234,9 +232,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
if (playable != null)
|
||||
{
|
||||
if (beatmap is Bindable<WorkingBeatmap> working)
|
||||
working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
|
||||
|
||||
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value));
|
||||
restartTrack();
|
||||
return PreviousTrackResult.Previous;
|
||||
}
|
||||
@ -260,9 +256,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
if (playable != null)
|
||||
{
|
||||
if (beatmap is Bindable<WorkingBeatmap> working)
|
||||
working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
|
||||
|
||||
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value));
|
||||
restartTrack();
|
||||
return true;
|
||||
}
|
||||
@ -274,21 +268,25 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
// if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase).
|
||||
// we probably want to move this to a central method for switching to a new working beatmap in the future.
|
||||
Schedule(() => beatmap.Value.Track.Restart());
|
||||
Schedule(() => CurrentTrack.Restart());
|
||||
}
|
||||
|
||||
private WorkingBeatmap current;
|
||||
|
||||
private TrackChangeDirection? queuedDirection;
|
||||
|
||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
|
||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap) => changeBeatmap(beatmap.NewValue);
|
||||
|
||||
private void changeBeatmap(WorkingBeatmap newWorking)
|
||||
{
|
||||
var lastWorking = current;
|
||||
|
||||
TrackChangeDirection direction = TrackChangeDirection.None;
|
||||
|
||||
bool audioEquals = newWorking?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false;
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false;
|
||||
|
||||
if (audioEquals)
|
||||
direction = TrackChangeDirection.None;
|
||||
else if (queuedDirection.HasValue)
|
||||
@ -300,18 +298,74 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
// figure out the best direction based on order in playlist.
|
||||
var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
|
||||
var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
|
||||
var next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != newWorking.BeatmapSetInfo?.ID).Count();
|
||||
|
||||
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
|
||||
}
|
||||
}
|
||||
|
||||
current = beatmap.NewValue;
|
||||
current = newWorking;
|
||||
|
||||
if (!audioEquals || CurrentTrack.IsDummyDevice)
|
||||
{
|
||||
changeTrack();
|
||||
}
|
||||
else
|
||||
{
|
||||
// transfer still valid track to new working beatmap
|
||||
current.TransferTrack(lastWorking.Track);
|
||||
}
|
||||
|
||||
TrackChanged?.Invoke(current, direction);
|
||||
|
||||
ResetTrackAdjustments();
|
||||
|
||||
queuedDirection = null;
|
||||
|
||||
// this will be a noop if coming from the beatmapChanged event.
|
||||
// the exception is local operations like next/prev, where we want to complete loading the track before sending out a change.
|
||||
if (beatmap.Value != current && beatmap is Bindable<WorkingBeatmap> working)
|
||||
working.Value = current;
|
||||
}
|
||||
|
||||
private void changeTrack()
|
||||
{
|
||||
var lastTrack = CurrentTrack;
|
||||
|
||||
var queuedTrack = new DrawableTrack(current.LoadTrack());
|
||||
queuedTrack.Completed += () => onTrackCompleted(current);
|
||||
|
||||
CurrentTrack = queuedTrack;
|
||||
|
||||
// At this point we may potentially be in an async context from tests. This is extremely dangerous but we have to make do for now.
|
||||
// CurrentTrack is immediately updated above for situations where a immediate knowledge about the new track is required,
|
||||
// but the mutation of the hierarchy is scheduled to avoid exceptions.
|
||||
Schedule(() =>
|
||||
{
|
||||
lastTrack.VolumeTo(0, 500, Easing.Out).Expire();
|
||||
|
||||
if (queuedTrack == CurrentTrack)
|
||||
{
|
||||
AddInternal(queuedTrack);
|
||||
queuedTrack.VolumeTo(0).Then().VolumeTo(1, 300, Easing.Out);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the track has changed since the call to changeTrack, it is safe to dispose the
|
||||
// queued track rather than consume it.
|
||||
queuedTrack.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onTrackCompleted(WorkingBeatmap workingBeatmap)
|
||||
{
|
||||
// the source of track completion is the audio thread, so the beatmap may have changed before firing.
|
||||
if (current != workingBeatmap)
|
||||
return;
|
||||
|
||||
if (!CurrentTrack.Looping && !beatmap.Disabled)
|
||||
NextTrack();
|
||||
}
|
||||
|
||||
private bool allowRateAdjustments;
|
||||
@ -332,18 +386,20 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the speed adjustments currently applied on <see cref="CurrentTrack"/> and applies the mod adjustments if <see cref="AllowRateAdjustments"/> is <c>true</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not reset speed adjustments applied directly to the beatmap track.
|
||||
/// </remarks>
|
||||
public void ResetTrackAdjustments()
|
||||
{
|
||||
var track = current?.Track;
|
||||
if (track == null)
|
||||
return;
|
||||
|
||||
track.ResetSpeedAdjustments();
|
||||
CurrentTrack.ResetSpeedAdjustments();
|
||||
|
||||
if (allowRateAdjustments)
|
||||
{
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToTrack>())
|
||||
mod.ApplyToTrack(track);
|
||||
mod.ApplyToTrack(CurrentTrack);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,9 +234,9 @@ namespace osu.Game.Overlays
|
||||
pendingBeatmapSwitch = null;
|
||||
}
|
||||
|
||||
var track = beatmap.Value?.TrackLoaded ?? false ? beatmap.Value.Track : null;
|
||||
var track = musicController.CurrentTrack;
|
||||
|
||||
if (track?.IsDummyDevice == false)
|
||||
if (!track.IsDummyDevice)
|
||||
{
|
||||
progressBar.EndTime = track.Length;
|
||||
progressBar.CurrentTime = track.CurrentTime;
|
||||
|
@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// </summary>
|
||||
public interface IApplicableToTrack : IApplicableMod
|
||||
{
|
||||
void ApplyToTrack(Track track);
|
||||
void ApplyToTrack(ITrack track);
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
}, true);
|
||||
}
|
||||
|
||||
public override void ApplyToTrack(Track track)
|
||||
public override void ApplyToTrack(ITrack track)
|
||||
{
|
||||
// base.ApplyToTrack() intentionally not called (different tempo adjustment is applied)
|
||||
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
}, true);
|
||||
}
|
||||
|
||||
public override void ApplyToTrack(Track track)
|
||||
public override void ApplyToTrack(ITrack track)
|
||||
{
|
||||
// base.ApplyToTrack() intentionally not called (different tempo adjustment is applied)
|
||||
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract BindableNumber<double> SpeedChange { get; }
|
||||
|
||||
public virtual void ApplyToTrack(Track track)
|
||||
public virtual void ApplyToTrack(ITrack track)
|
||||
{
|
||||
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
private Track track;
|
||||
private ITrack track;
|
||||
|
||||
protected ModTimeRamp()
|
||||
{
|
||||
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
AdjustPitch.BindValueChanged(applyPitchAdjustment);
|
||||
}
|
||||
|
||||
public void ApplyToTrack(Track track)
|
||||
public void ApplyToTrack(ITrack track)
|
||||
{
|
||||
this.track = track;
|
||||
|
||||
|
@ -13,7 +13,6 @@ using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Users;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
|
||||
@ -123,12 +122,12 @@ namespace osu.Game.Scoring.Legacy
|
||||
|
||||
protected void CalculateAccuracy(ScoreInfo score)
|
||||
{
|
||||
score.Statistics.TryGetValue(HitResult.Miss, out int countMiss);
|
||||
score.Statistics.TryGetValue(HitResult.Meh, out int count50);
|
||||
score.Statistics.TryGetValue(HitResult.Good, out int count100);
|
||||
score.Statistics.TryGetValue(HitResult.Great, out int count300);
|
||||
score.Statistics.TryGetValue(HitResult.Perfect, out int countGeki);
|
||||
score.Statistics.TryGetValue(HitResult.Ok, out int countKatu);
|
||||
int countMiss = score.GetCountMiss() ?? 0;
|
||||
int count50 = score.GetCount50() ?? 0;
|
||||
int count100 = score.GetCount100() ?? 0;
|
||||
int count300 = score.GetCount300() ?? 0;
|
||||
int countGeki = score.GetCountGeki() ?? 0;
|
||||
int countKatu = score.GetCountKatu() ?? 0;
|
||||
|
||||
switch (score.Ruleset.ID)
|
||||
{
|
||||
@ -241,12 +240,15 @@ namespace osu.Game.Scoring.Legacy
|
||||
}
|
||||
|
||||
var diff = Parsing.ParseFloat(split[0]);
|
||||
var mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE);
|
||||
var mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE);
|
||||
|
||||
lastTime += diff;
|
||||
|
||||
if (i == 0 && diff == 0)
|
||||
// osu-stable adds a zero-time frame before potentially valid negative user frames.
|
||||
// we need to ignore this.
|
||||
if (i < 2 && mouseX == 256 && mouseY == -500)
|
||||
// at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively.
|
||||
// both frames use a position of (256, -500).
|
||||
// ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania)
|
||||
continue;
|
||||
|
||||
// Todo: At some point we probably want to rewind and play back the negative-time frames
|
||||
@ -255,8 +257,8 @@ namespace osu.Game.Scoring.Legacy
|
||||
continue;
|
||||
|
||||
currentFrame = convertFrame(new LegacyReplayFrame(lastTime,
|
||||
Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE),
|
||||
Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE),
|
||||
mouseX,
|
||||
mouseY,
|
||||
(ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame);
|
||||
|
||||
replay.Frames.Add(currentFrame);
|
||||
|
@ -26,6 +26,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timeline's scroll position in the last frame.
|
||||
/// </summary>
|
||||
private float lastScrollPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The track time in the last frame.
|
||||
/// </summary>
|
||||
private double lastTrackTime;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user is currently dragging the timeline.
|
||||
/// </summary>
|
||||
private bool handlingDragInput;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the track was playing before a user drag event.
|
||||
/// </summary>
|
||||
private bool trackWasPlaying;
|
||||
|
||||
private Track track;
|
||||
|
||||
public Timeline()
|
||||
{
|
||||
ZoomDuration = 200;
|
||||
@ -59,6 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
waveform.Waveform = b.NewValue.Waveform;
|
||||
track = b.NewValue.Track;
|
||||
|
||||
// todo: i don't think this is safe, the track may not be loaded yet.
|
||||
if (track.Length > 0)
|
||||
{
|
||||
MaxZoom = getZoomLevelForVisibleMilliseconds(500);
|
||||
@ -68,29 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}, true);
|
||||
}
|
||||
|
||||
private float getZoomLevelForVisibleMilliseconds(double milliseconds) => (float)(track.Length / milliseconds);
|
||||
|
||||
/// <summary>
|
||||
/// The timeline's scroll position in the last frame.
|
||||
/// </summary>
|
||||
private float lastScrollPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The track time in the last frame.
|
||||
/// </summary>
|
||||
private double lastTrackTime;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user is currently dragging the timeline.
|
||||
/// </summary>
|
||||
private bool handlingDragInput;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the track was playing before a user drag event.
|
||||
/// </summary>
|
||||
private bool trackWasPlaying;
|
||||
|
||||
private Track track;
|
||||
private float getZoomLevelForVisibleMilliseconds(double milliseconds) => Math.Max(1, (float)(track.Length / milliseconds));
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
|
@ -284,7 +284,7 @@ namespace osu.Game.Screens.Edit
|
||||
// this is a special case to handle the "pivot" scenario.
|
||||
// if we are precise scrolling in one direction then change our mind and scroll backwards,
|
||||
// the existing accumulation should be applied in the inverse direction to maintain responsiveness.
|
||||
if (Math.Sign(scrollAccumulation) != scrollDirection)
|
||||
if (scrollAccumulation != 0 && Math.Sign(scrollAccumulation) != scrollDirection)
|
||||
scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation));
|
||||
|
||||
scrollAccumulation += scrollComponent * (e.IsPrecise ? 0.1 : 1);
|
||||
|
@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected override Texture GetBackground() => throw new NotImplementedException();
|
||||
|
||||
protected override Track GetTrack() => throw new NotImplementedException();
|
||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user