mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 14:17:26 +08:00
Merge branch 'master' into fix-17143
This commit is contained in:
commit
6177df2a24
@ -15,6 +15,8 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen
|
|||||||
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
|
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
|
||||||
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
|
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
|
||||||
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
|
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
|
||||||
|
M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
|
||||||
|
M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
|
||||||
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
||||||
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
||||||
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
|
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
|
||||||
|
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1005.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1021.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1005.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1022.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
// 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.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneCatchModFlashlight : ModTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
|
||||||
|
[TestCase(1f)]
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(1.25f)]
|
||||||
|
[TestCase(1.5f)]
|
||||||
|
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new CatchModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new CatchModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
@ -12,6 +12,8 @@ using osu.Game.Rulesets.Catch.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -19,15 +21,28 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
public class TestSceneComboCounter : CatchSkinnableTestScene
|
public class TestSceneComboCounter : CatchSkinnableTestScene
|
||||||
{
|
{
|
||||||
private ScoreProcessor scoreProcessor;
|
private ScoreProcessor scoreProcessor = null!;
|
||||||
|
|
||||||
private Color4 judgedObjectColour = Color4.White;
|
private Color4 judgedObjectColour = Color4.White;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> showHud = new Bindable<bool>(true);
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Dependencies.CacheAs<Player>(new TestPlayer
|
||||||
|
{
|
||||||
|
ShowingOverlayComponents = { BindTarget = showHud },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
scoreProcessor = new ScoreProcessor(new CatchRuleset());
|
scoreProcessor = new ScoreProcessor(new CatchRuleset());
|
||||||
|
|
||||||
|
showHud.Value = true;
|
||||||
|
|
||||||
SetContents(_ => new CatchComboDisplay
|
SetContents(_ => new CatchComboDisplay
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -51,9 +66,15 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
1f
|
1f
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("set hud to never show", () => showHud.Value = false);
|
||||||
|
AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 5);
|
||||||
|
|
||||||
|
AddStep("set hud to show", () => showHud.Value = true);
|
||||||
|
AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performJudgement(HitResult type, Judgement judgement = null)
|
private void performJudgement(HitResult type, Judgement? judgement = null)
|
||||||
{
|
{
|
||||||
var judgedObject = new DrawableFruit(new Fruit()) { AccentColour = { Value = judgedObjectColour } };
|
var judgedObject = new DrawableFruit(new Fruit()) { AccentColour = { Value = judgedObjectColour } };
|
||||||
|
|
||||||
|
@ -36,5 +36,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
return base.CreateHitObjectBlueprintFor(hitObject);
|
return base.CreateHitObjectBlueprintFor(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
public class CatchEditorPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||||
|
{
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
public CatchEditorPlayfieldAdjustmentContainer()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
Size = new Vector2(0.8f, 0.9f);
|
||||||
|
|
||||||
|
InternalChild = new ScalingContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Child = content = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScalingContainer : Container
|
||||||
|
{
|
||||||
|
public ScalingContainer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
Width = CatchPlayfield.WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
Scale = new Vector2(Math.Min(Parent.ChildSize.X / CatchPlayfield.WIDTH, Parent.ChildSize.Y / CatchPlayfield.HEIGHT));
|
||||||
|
Height = 1 / Scale.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,10 @@ using osu.Framework.Extensions.EnumExtensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -37,6 +39,12 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
|
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 10,
|
||||||
|
};
|
||||||
|
|
||||||
public CatchHitObjectComposer(CatchRuleset ruleset)
|
public CatchHitObjectComposer(CatchRuleset ruleset)
|
||||||
: base(ruleset)
|
: base(ruleset)
|
||||||
{
|
{
|
||||||
@ -51,7 +59,10 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
LayerBelowRuleset.Add(new PlayfieldBorder
|
LayerBelowRuleset.Add(new PlayfieldBorder
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = CatchPlayfield.HEIGHT,
|
||||||
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,8 +88,30 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
updateDistanceSnapGrid();
|
updateDistanceSnapGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
// Note that right now these are hard to use as the default key bindings conflict with existing editor key bindings.
|
||||||
|
// In the future we will want to expose this via UI and potentially change the key bindings to be editor-specific.
|
||||||
|
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
|
||||||
|
case GlobalAction.IncreaseScrollSpeed:
|
||||||
|
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GlobalAction.DecreaseScrollSpeed:
|
||||||
|
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnPressed(e);
|
||||||
|
}
|
||||||
|
|
||||||
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
|
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
|
||||||
new DrawableCatchEditorRuleset(ruleset, beatmap, mods);
|
new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
|
||||||
|
{
|
||||||
|
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
|
||||||
|
};
|
||||||
|
|
||||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -13,11 +14,24 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
public class DrawableCatchEditorRuleset : DrawableCatchRuleset
|
public class DrawableCatchEditorRuleset : DrawableCatchRuleset
|
||||||
{
|
{
|
||||||
|
public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
|
||||||
|
|
||||||
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
double gamePlayTimeRange = GetTimeRange(Beatmap.Difficulty.ApproachRate);
|
||||||
|
float playfieldStretch = Playfield.DrawHeight / CatchPlayfield.HEIGHT;
|
||||||
|
TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch;
|
||||||
|
}
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
|
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
|
||||||
|
|
||||||
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
this.playfield = playfield;
|
this.playfield = playfield;
|
||||||
|
|
||||||
FlashlightSize = new Vector2(0, GetSizeFor(0));
|
FlashlightSize = new Vector2(0, GetSize());
|
||||||
FlashlightSmoothness = 1.4f;
|
FlashlightSmoothness = 1.4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,9 +66,9 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
|
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
protected override void UpdateFlashlightSize(float size)
|
||||||
{
|
{
|
||||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string FragmentShader => "CircularFlashlight";
|
protected override string FragmentShader => "CircularFlashlight";
|
||||||
|
@ -19,17 +19,20 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public override LocalisableString Description => @"Use the mouse to control the catcher.";
|
public override LocalisableString Description => @"Use the mouse to control the catcher.";
|
||||||
|
|
||||||
private DrawableRuleset<CatchHitObject> drawableRuleset = null!;
|
private DrawableCatchRuleset drawableRuleset = null!;
|
||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
this.drawableRuleset = drawableRuleset;
|
this.drawableRuleset = (DrawableCatchRuleset)drawableRuleset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToPlayer(Player player)
|
public void ApplyToPlayer(Player player)
|
||||||
{
|
{
|
||||||
if (!drawableRuleset.HasReplayLoaded.Value)
|
if (!drawableRuleset.HasReplayLoaded.Value)
|
||||||
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
|
{
|
||||||
|
var catchPlayfield = (CatchPlayfield)drawableRuleset.Playfield;
|
||||||
|
catchPlayfield.CatcherArea.Add(new MouseInputHelper(catchPlayfield.CatcherArea));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||||
@ -38,9 +41,10 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
public MouseInputHelper(CatchPlayfield playfield)
|
public MouseInputHelper(CatcherArea catcherArea)
|
||||||
{
|
{
|
||||||
catcherArea = playfield.CatcherArea;
|
this.catcherArea = catcherArea;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,10 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
public class CatchLegacySkinTransformer : LegacySkinTransformer
|
public class CatchLegacySkinTransformer : LegacySkinTransformer
|
||||||
{
|
{
|
||||||
|
public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasPear;
|
||||||
|
|
||||||
|
private bool hasPear => GetTexture("fruit-pear") != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default.
|
/// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -49,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
switch (catchSkinComponent.Component)
|
switch (catchSkinComponent.Component)
|
||||||
{
|
{
|
||||||
case CatchSkinComponents.Fruit:
|
case CatchSkinComponents.Fruit:
|
||||||
if (GetTexture("fruit-pear") != null)
|
if (hasPear)
|
||||||
return new LegacyFruitPiece();
|
return new LegacyFruitPiece();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using JetBrains.Annotations;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -19,14 +20,29 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
private int currentCombo;
|
private int currentCombo;
|
||||||
|
|
||||||
[CanBeNull]
|
public ICatchComboCounter? ComboCounter => Drawable as ICatchComboCounter;
|
||||||
public ICatchComboCounter ComboCounter => Drawable as ICatchComboCounter;
|
|
||||||
|
private readonly IBindable<bool> showCombo = new BindableBool(true);
|
||||||
|
|
||||||
public CatchComboDisplay()
|
public CatchComboDisplay()
|
||||||
: base(new CatchSkinComponent(CatchSkinComponents.CatchComboCounter), _ => Empty())
|
: base(new CatchSkinComponent(CatchSkinComponents.CatchComboCounter), _ => Empty())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private Player? player { get; set; }
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
showCombo.BindTo(player.ShowingOverlayComponents);
|
||||||
|
showCombo.BindValueChanged(s => this.FadeTo(s.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin)
|
protected override void SkinChanged(ISkinSource skin)
|
||||||
{
|
{
|
||||||
base.SkinChanged(skin);
|
base.SkinChanged(skin);
|
||||||
|
@ -23,6 +23,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const float WIDTH = 512;
|
public const float WIDTH = 512;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The height of the playfield.
|
||||||
|
/// This doesn't include the catcher area.
|
||||||
|
/// </summary>
|
||||||
|
public const float HEIGHT = 384;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The center position of the playfield.
|
/// The center position of the playfield.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
Direction.Value = ScrollingDirection.Down;
|
Direction.Value = ScrollingDirection.Down;
|
||||||
TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450);
|
TimeRange.Value = GetTimeRange(beatmap.Difficulty.ApproachRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
KeyBindingInputManager.Add(new CatchTouchInputMapper());
|
KeyBindingInputManager.Add(new CatchTouchInputMapper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected double GetTimeRange(float approachRate) => IBeatmapDifficultyInfo.DifficultyRange(approachRate, 1800, 1200, 450);
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReloadMappings()
|
protected override void ReloadMappings(IQueryable<RealmKeyBinding> realmKeyBindings)
|
||||||
{
|
{
|
||||||
KeyBindings = DefaultKeyBindings;
|
KeyBindings = DefaultKeyBindings;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
// 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.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneManiaModFlashlight : ModTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[TestCase(1f)]
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(1.5f)]
|
||||||
|
[TestCase(3f)]
|
||||||
|
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new ManiaModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new ManiaModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
scoreAccuracy = customAccuracy;
|
scoreAccuracy = calculateCustomAccuracy();
|
||||||
|
|
||||||
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
|
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
|
||||||
// The specific number has no intrinsic meaning and can be adjusted as needed.
|
// The specific number has no intrinsic meaning and can be adjusted as needed.
|
||||||
@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Accuracy used to weight judgements independently from the score's actual accuracy.
|
/// Accuracy used to weight judgements independently from the score's actual accuracy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
|
private double calculateCustomAccuracy()
|
||||||
|
{
|
||||||
|
if (totalHits == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,5 +33,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
|
||||||
|
|
||||||
|
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public ManiaFlashlight(ManiaModFlashlight modFlashlight)
|
public ManiaFlashlight(ManiaModFlashlight modFlashlight)
|
||||||
: base(modFlashlight)
|
: base(modFlashlight)
|
||||||
{
|
{
|
||||||
FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
|
FlashlightSize = new Vector2(DrawWidth, GetSize());
|
||||||
|
|
||||||
AddLayout(flashlightProperties);
|
AddLayout(flashlightProperties);
|
||||||
}
|
}
|
||||||
@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
protected override void UpdateFlashlightSize(float size)
|
||||||
{
|
{
|
||||||
this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, size), FLASHLIGHT_FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string FragmentShader => "RectangularFlashlight";
|
protected override string FragmentShader => "RectangularFlashlight";
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
private Container<DrawableHoldNoteTail> tailContainer;
|
private Container<DrawableHoldNoteTail> tailContainer;
|
||||||
private Container<DrawableHoldNoteTick> tickContainer;
|
private Container<DrawableHoldNoteTick> tickContainer;
|
||||||
|
|
||||||
|
private PausableSkinnableSound slidingSample;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
|
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -108,6 +112,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
},
|
},
|
||||||
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
|
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
|
||||||
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
|
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
|
||||||
|
slidingSample = new PausableSkinnableSound { Looping = true }
|
||||||
});
|
});
|
||||||
|
|
||||||
maskedContents.AddRange(new[]
|
maskedContents.AddRange(new[]
|
||||||
@ -118,6 +123,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
isHitting.BindValueChanged(updateSlidingSample, true);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnApply();
|
base.OnApply();
|
||||||
@ -322,5 +334,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
HoldStartTime = null;
|
HoldStartTime = null;
|
||||||
isHitting.Value = false;
|
isHitting.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadSamples()
|
||||||
|
{
|
||||||
|
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
||||||
|
|
||||||
|
if (HitObject.SampleControlPoint == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
||||||
|
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void StopAllSamples()
|
||||||
|
{
|
||||||
|
base.StopAllSamples();
|
||||||
|
slidingSample?.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSlidingSample(ValueChangedEvent<bool> tracking)
|
||||||
|
{
|
||||||
|
if (tracking.NewValue)
|
||||||
|
slidingSample?.Play();
|
||||||
|
else
|
||||||
|
slidingSample?.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
slidingSample.Samples = null;
|
||||||
|
base.OnFree();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -89,13 +90,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
Color4 colour;
|
Color4 colour;
|
||||||
|
|
||||||
|
const int total_colours = 7;
|
||||||
|
|
||||||
if (stage.IsSpecialColumn(column))
|
if (stage.IsSpecialColumn(column))
|
||||||
colour = new Color4(159, 101, 255, 255);
|
colour = new Color4(159, 101, 255, 255);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (column % 8)
|
switch (column % total_colours)
|
||||||
{
|
{
|
||||||
default:
|
case 0:
|
||||||
colour = new Color4(240, 216, 0, 255);
|
colour = new Color4(240, 216, 0, 255);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -112,20 +115,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
colour = new Color4(178, 0, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
colour = new Color4(0, 96, 240, 255);
|
colour = new Color4(0, 96, 240, 255);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6:
|
case 5:
|
||||||
colour = new Color4(0, 226, 240, 255);
|
colour = new Color4(0, 226, 240, 255);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 7:
|
case 6:
|
||||||
colour = new Color4(0, 240, 96, 255);
|
colour = new Color4(0, 240, 96, 255);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
public class ManiaLegacySkinTransformer : LegacySkinTransformer
|
public class ManiaLegacySkinTransformer : LegacySkinTransformer
|
||||||
{
|
{
|
||||||
|
public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasKeyTexture.Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mapping of <see cref="HitResult"/> to their corresponding
|
/// Mapping of <see cref="HitResult"/> to their corresponding
|
||||||
/// <see cref="LegacyManiaSkinConfigurationLookups"/> value.
|
/// <see cref="LegacyManiaSkinConfigurationLookups"/> value.
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
[Cached(typeof(IBeatSnapProvider))]
|
[Cached(typeof(IBeatSnapProvider))]
|
||||||
private readonly EditorBeatmap editorBeatmap;
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly EditorClock editorClock;
|
private readonly EditorClock editorClock;
|
||||||
|
|
||||||
|
@ -148,6 +148,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFloatEdgeCaseConversion()
|
||||||
|
{
|
||||||
|
Slider slider = null;
|
||||||
|
|
||||||
|
AddStep("select first slider", () =>
|
||||||
|
{
|
||||||
|
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
|
||||||
|
EditorClock.Seek(slider.StartTime);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change to these specific circumstances", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Difficulty.SliderMultiplier = 1;
|
||||||
|
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(slider.StartTime);
|
||||||
|
timingPoint.BeatLength = 352.941176470588;
|
||||||
|
slider.Path.ControlPoints[^1].Position = new Vector2(-110, 16);
|
||||||
|
slider.Path.ExpectedDistance.Value = 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
convertToStream();
|
||||||
|
|
||||||
|
AddAssert("stream created", () => streamCreatedFor(slider,
|
||||||
|
(time: 0, pathPosition: 0),
|
||||||
|
(time: 0.25, pathPosition: 0.25),
|
||||||
|
(time: 0.5, pathPosition: 0.5),
|
||||||
|
(time: 0.75, pathPosition: 0.75),
|
||||||
|
(time: 1, pathPosition: 1)));
|
||||||
|
}
|
||||||
|
|
||||||
private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles)
|
private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles)
|
||||||
{
|
{
|
||||||
if (EditorBeatmap.HitObjects.Contains(slider))
|
if (EditorBeatmap.HitObjects.Contains(slider))
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
// 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.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModFlashlight : OsuModTestScene
|
||||||
|
{
|
||||||
|
[TestCase(600)]
|
||||||
|
[TestCase(120)]
|
||||||
|
[TestCase(1200)]
|
||||||
|
public void TestFollowDelay(double followDelay) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { FollowDelay = { Value = followDelay } }, PassCondition = () => true });
|
||||||
|
|
||||||
|
[TestCase(1f)]
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(1.5f)]
|
||||||
|
[TestCase(2f)]
|
||||||
|
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
@ -145,6 +147,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
|
|
||||||
private bool isBreak() => Player.IsBreakTime.Value;
|
private bool isBreak() => Player.IsBreakTime.Value;
|
||||||
|
|
||||||
private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f);
|
private OsuPlayfield playfield => (OsuPlayfield)Player.DrawableRuleset.Playfield;
|
||||||
|
|
||||||
|
private bool cursorAlphaAlmostEquals(float alpha) =>
|
||||||
|
Precision.AlmostEquals(playfield.Cursor.AsNonNull().Alpha, alpha, 0.1f) &&
|
||||||
|
Precision.AlmostEquals(playfield.Smoke.Alpha, alpha, 0.1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
|
||||||
{
|
|
||||||
public class TestSceneOsuFlashlight : TestSceneOsuPlayer
|
|
||||||
{
|
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset)
|
|
||||||
{
|
|
||||||
SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
|
|
||||||
|
|
||||||
return base.CreatePlayer(ruleset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -68,10 +69,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
AddStep("create slider", () =>
|
AddStep("create slider", () =>
|
||||||
{
|
{
|
||||||
var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
|
var skin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
|
||||||
tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
|
var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(skin, Beatmap.Value.Beatmap);
|
||||||
|
|
||||||
var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(tintingSkin, Beatmap.Value.Beatmap);
|
|
||||||
|
|
||||||
Child = new SkinProvidingContainer(provider)
|
Child = new SkinProvidingContainer(provider)
|
||||||
{
|
{
|
||||||
@ -92,10 +91,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
|
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
|
||||||
AddAssert("ball is white", () => dho.ChildrenOfType<DrawableSliderBall>().Single().AccentColour == Color4.White);
|
AddAssert("ball is white", () => dho.ChildrenOfType<LegacySliderBall>().Single().BallColour == Color4.White);
|
||||||
|
|
||||||
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
|
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
|
||||||
AddAssert("ball is red", () => dho.ChildrenOfType<DrawableSliderBall>().Single().AccentColour == Color4.Red);
|
AddAssert("ball is red", () => dho.ChildrenOfType<LegacySliderBall>().Single().BallColour == Color4.Red);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Slider prepareObject(Slider slider)
|
private Slider prepareObject(Slider slider)
|
||||||
|
@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount();
|
double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount();
|
||||||
double pathPosition = positionWithRepeats - (int)positionWithRepeats;
|
double pathPosition = positionWithRepeats - (int)positionWithRepeats;
|
||||||
// every second span is in the reverse direction - need to reverse the path position.
|
// every second span is in the reverse direction - need to reverse the path position.
|
||||||
if (Precision.AlmostBigger(positionWithRepeats % 2, 1))
|
if (positionWithRepeats % 2 >= 1)
|
||||||
pathPosition = 1 - pathPosition;
|
pathPosition = 1 - pathPosition;
|
||||||
|
|
||||||
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
|
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
|
||||||
|
@ -60,6 +60,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
// Give a bit of breathing room around the playfield content.
|
||||||
|
PlayfieldContentContainer.Padding = new MarginPadding(10);
|
||||||
|
|
||||||
LayerBelowRuleset.AddRange(new Drawable[]
|
LayerBelowRuleset.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
distanceSnapGridContainer = new Container
|
distanceSnapGridContainer = new Container
|
||||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
followDelay = modFlashlight.FollowDelay.Value;
|
followDelay = modFlashlight.FollowDelay.Value;
|
||||||
|
|
||||||
FlashlightSize = new Vector2(0, GetSizeFor(0));
|
FlashlightSize = new Vector2(0, GetSize());
|
||||||
FlashlightSmoothness = 1.4f;
|
FlashlightSmoothness = 1.4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +83,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
protected override void UpdateFlashlightSize(float size)
|
||||||
{
|
{
|
||||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string FragmentShader => "CircularFlashlight";
|
protected override string FragmentShader => "CircularFlashlight";
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void Update(Playfield playfield)
|
public void Update(Playfield playfield)
|
||||||
{
|
{
|
||||||
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
|
var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition;
|
||||||
|
|
||||||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -9,6 +10,7 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
@ -33,9 +35,15 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void Update(Playfield playfield)
|
public void Update(Playfield playfield)
|
||||||
{
|
{
|
||||||
bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime);
|
var osuPlayfield = (OsuPlayfield)playfield;
|
||||||
|
Debug.Assert(osuPlayfield.Cursor != null);
|
||||||
|
|
||||||
|
bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(osuPlayfield.Clock.CurrentTime);
|
||||||
float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha;
|
float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha;
|
||||||
playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
float currentAlpha = (float)Interpolation.Lerp(osuPlayfield.Cursor.Alpha, targetAlpha, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
||||||
|
|
||||||
|
osuPlayfield.Cursor.Alpha = currentAlpha;
|
||||||
|
osuPlayfield.Smoke.Alpha = currentAlpha;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||||
{
|
{
|
||||||
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
|
||||||
|
public override Type[] IncompatibleMods =>
|
||||||
|
base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How early before a hitobject's start time to trigger a hit.
|
/// How early before a hitobject's start time to trigger a hit.
|
||||||
@ -51,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
osuInputManager.AllowUserPresses = false;
|
osuInputManager.AllowGameplayInputs = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(Playfield playfield)
|
public void Update(Playfield playfield)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void Update(Playfield playfield)
|
public void Update(Playfield playfield)
|
||||||
{
|
{
|
||||||
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
|
var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition;
|
||||||
|
|
||||||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||||
{
|
{
|
||||||
|
@ -14,12 +14,10 @@ using osu.Game.Audio;
|
|||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Skinning;
|
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -106,7 +104,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
foreach (var drawableHitObject in NestedHitObjects)
|
foreach (var drawableHitObject in NestedHitObjects)
|
||||||
drawableHitObject.AccentColour.Value = colour.NewValue;
|
drawableHitObject.AccentColour.Value = colour.NewValue;
|
||||||
updateBallTint();
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Tracking.BindValueChanged(updateSlidingSample);
|
Tracking.BindValueChanged(updateSlidingSample);
|
||||||
@ -257,22 +254,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
SliderBody?.RecyclePath();
|
SliderBody?.RecyclePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
|
|
||||||
{
|
|
||||||
base.ApplySkin(skin, allowFallback);
|
|
||||||
|
|
||||||
updateBallTint();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBallTint()
|
|
||||||
{
|
|
||||||
if (CurrentSkin == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool allowBallTint = CurrentSkin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
|
||||||
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (userTriggered || Time.Current < HitObject.EndTime)
|
if (userTriggered || Time.Current < HitObject.EndTime)
|
||||||
@ -331,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateHitStateTransforms(state);
|
base.UpdateHitStateTransforms(state);
|
||||||
|
|
||||||
const float fade_out_time = 450;
|
const float fade_out_time = 240;
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
@ -341,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
this.FadeOut(fade_out_time).Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
@ -11,28 +11,20 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
|
public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
|
||||||
{
|
{
|
||||||
public const float FOLLOW_AREA = 2.4f;
|
public const float FOLLOW_AREA = 2.4f;
|
||||||
|
|
||||||
public Func<OsuAction?> GetInitialHitAction;
|
public Func<OsuAction?> GetInitialHitAction;
|
||||||
|
|
||||||
public Color4 AccentColour
|
|
||||||
{
|
|
||||||
get => ball.Colour;
|
|
||||||
set => ball.Colour = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Drawable followCircleReceptor;
|
private Drawable followCircleReceptor;
|
||||||
private DrawableSlider drawableSlider;
|
private DrawableSlider drawableSlider;
|
||||||
private Drawable ball;
|
private Drawable ball;
|
||||||
@ -186,17 +178,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private Vector2? lastPosition;
|
private Vector2? lastPosition;
|
||||||
|
|
||||||
|
private bool rewinding;
|
||||||
|
|
||||||
public void UpdateProgress(double completionProgress)
|
public void UpdateProgress(double completionProgress)
|
||||||
{
|
{
|
||||||
Position = drawableSlider.HitObject.CurvePositionAt(completionProgress);
|
Position = drawableSlider.HitObject.CurvePositionAt(completionProgress);
|
||||||
|
|
||||||
var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f);
|
var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f);
|
||||||
|
|
||||||
|
if (Clock.ElapsedFrameTime != 0)
|
||||||
|
rewinding = Clock.ElapsedFrameTime < 0;
|
||||||
|
|
||||||
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
|
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
|
||||||
if (diff.LengthFast < 0.01f)
|
if (diff.LengthFast < 0.01f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
|
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0);
|
||||||
lastPosition = Position;
|
lastPosition = Position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly IBindable<double> SpinsPerMinute = new BindableDouble();
|
public readonly IBindable<double> SpinsPerMinute = new BindableDouble();
|
||||||
|
|
||||||
private const double fade_out_duration = 160;
|
private const double fade_out_duration = 240;
|
||||||
|
|
||||||
public DrawableSpinner()
|
public DrawableSpinner()
|
||||||
: this(null)
|
: this(null)
|
||||||
|
@ -34,21 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
public override IList<HitSampleInfo> AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray();
|
public override IList<HitSampleInfo> AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray();
|
||||||
|
|
||||||
public IList<HitSampleInfo> CreateSlidingSamples()
|
|
||||||
{
|
|
||||||
var slidingSamples = new List<HitSampleInfo>();
|
|
||||||
|
|
||||||
var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
|
|
||||||
if (normalSample != null)
|
|
||||||
slidingSamples.Add(normalSample.With("sliderslide"));
|
|
||||||
|
|
||||||
var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE);
|
|
||||||
if (whistleSample != null)
|
|
||||||
slidingSamples.Add(whistleSample.With("sliderwhistle"));
|
|
||||||
|
|
||||||
return slidingSamples;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
|
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
|
||||||
|
|
||||||
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
|
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
|
||||||
|
@ -5,10 +5,12 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Input.StateChanges.Events;
|
using osu.Framework.Input.StateChanges.Events;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu
|
namespace osu.Game.Rulesets.Osu
|
||||||
@ -17,9 +19,16 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||||
|
|
||||||
public bool AllowUserPresses
|
/// <summary>
|
||||||
|
/// Whether gameplay input buttons should be allowed.
|
||||||
|
/// Defaults to <c>true</c>, generally used for mods like Relax which turn off main inputs.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Of note, auxiliary inputs like the "smoke" key are left usable.
|
||||||
|
/// </remarks>
|
||||||
|
public bool AllowGameplayInputs
|
||||||
{
|
{
|
||||||
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value;
|
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowGameplayInputs = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -58,18 +67,36 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
|
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
|
||||||
{
|
{
|
||||||
public bool AllowUserPresses = true;
|
private bool allowGameplayInputs = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether gameplay input buttons should be allowed.
|
||||||
|
/// Defaults to <c>true</c>, generally used for mods like Relax which turn off main inputs.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Of note, auxiliary inputs like the "smoke" key are left usable.
|
||||||
|
/// </remarks>
|
||||||
|
public bool AllowGameplayInputs
|
||||||
|
{
|
||||||
|
get => allowGameplayInputs;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
allowGameplayInputs = value;
|
||||||
|
ReloadMappings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||||
: base(ruleset, variant, unique)
|
: base(ruleset, variant, unique)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Handle(UIEvent e)
|
protected override void ReloadMappings(IQueryable<RealmKeyBinding> realmKeyBindings)
|
||||||
{
|
{
|
||||||
if (!AllowUserPresses) return false;
|
base.ReloadMappings(realmKeyBindings);
|
||||||
|
|
||||||
return base.Handle(e);
|
if (!AllowGameplayInputs)
|
||||||
|
KeyBindings = KeyBindings.Where(b => b.GetAction<OsuAction>() == OsuAction.Smoke).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
JudgementText
|
JudgementText
|
||||||
|
.FadeInFromZero(300, Easing.OutQuint)
|
||||||
.ScaleTo(Vector2.One)
|
.ScaleTo(Vector2.One)
|
||||||
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
||||||
break;
|
break;
|
||||||
@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
ringExplosion?.PlayAnimation();
|
ringExplosion?.PlayAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable? GetAboveHitObjectsProxiedContent() => null;
|
public Drawable? GetAboveHitObjectsProxiedContent() => JudgementText.CreateProxy();
|
||||||
|
|
||||||
private class RingExplosion : CompositeDrawable
|
private class RingExplosion : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
@ -108,18 +108,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
accentColour.BindValueChanged(colour =>
|
|
||||||
{
|
|
||||||
outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
|
|
||||||
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
|
|
||||||
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
|
|
||||||
flash.Colour = colour.NewValue;
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
||||||
|
|
||||||
|
accentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
// A colour transform is applied.
|
||||||
|
// Without removing transforms first, when it is rewound it may apply an old colour.
|
||||||
|
outerGradient.ClearTransforms(targetMember: nameof(Colour));
|
||||||
|
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
|
||||||
|
|
||||||
|
outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
|
||||||
|
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
|
||||||
|
flash.Colour = colour.NewValue;
|
||||||
|
|
||||||
|
updateStateTransforms(drawableObject, drawableObject.State.Value);
|
||||||
|
}, true);
|
||||||
|
|
||||||
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
updateStateTransforms(drawableObject, drawableObject.State.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
@ -173,11 +178,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
.FadeOut(flash_in_duration);
|
.FadeOut(flash_in_duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The flash layer starts white to give the wanted brightness, but is almost immediately
|
|
||||||
// recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash)
|
|
||||||
// but works well enough with the colour fade.
|
|
||||||
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
|
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
|
||||||
flash.FlashColour(accentColour.Value, fade_out_time, Easing.OutQuint);
|
|
||||||
|
|
||||||
this.FadeOut(fade_out_time, Easing.OutQuad);
|
this.FadeOut(fade_out_time, Easing.OutQuad);
|
||||||
break;
|
break;
|
||||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
CircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
|
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Child = OverlaySprite = new KiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d))
|
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d))
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -134,10 +134,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
CircleSprite.FadeOut(legacy_fade_duration, Easing.Out);
|
CircleSprite.FadeOut(legacy_fade_duration);
|
||||||
CircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
CircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||||
|
|
||||||
OverlaySprite.FadeOut(legacy_fade_duration, Easing.Out);
|
OverlaySprite.FadeOut(legacy_fade_duration);
|
||||||
OverlaySprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
OverlaySprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||||
|
|
||||||
if (hasNumber)
|
if (hasNumber)
|
||||||
@ -146,11 +146,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
if (legacyVersion >= 2.0m)
|
if (legacyVersion >= 2.0m)
|
||||||
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
|
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
|
||||||
hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out);
|
hitCircleText.FadeOut(legacy_fade_duration / 4);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// old skins scale and fade it normally along other pieces.
|
// old skins scale and fade it normally along other pieces.
|
||||||
hitCircleText.FadeOut(legacy_fade_duration, Easing.Out);
|
hitCircleText.FadeOut(legacy_fade_duration);
|
||||||
hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
this.FadeOut();
|
this.FadeOut();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
|
||||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn);
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private DrawableHitObject? parentObject { get; set; }
|
private DrawableHitObject? parentObject { get; set; }
|
||||||
|
|
||||||
|
public Color4 BallColour => animationContent.Colour;
|
||||||
|
|
||||||
private Sprite layerNd = null!;
|
private Sprite layerNd = null!;
|
||||||
private Sprite layerSpec = null!;
|
private Sprite layerSpec = null!;
|
||||||
|
|
||||||
@ -61,6 +64,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -69,6 +74,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
updateStateTransforms(parentObject, parentObject.State.Value);
|
updateStateTransforms(parentObject, parentObject.State.Value);
|
||||||
|
|
||||||
|
if (skin.GetConfig<SkinConfiguration.LegacySetting, bool>(SkinConfiguration.LegacySetting.AllowSliderBallTint)?.Value == true)
|
||||||
|
{
|
||||||
|
accentColour.BindTo(parentObject.AccentColour);
|
||||||
|
accentColour.BindValueChanged(a => animationContent.Colour = a.NewValue, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
spin = new Sprite
|
spin = new Sprite
|
||||||
{
|
{
|
||||||
|
Alpha = 0,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Texture = source.GetTexture("spinner-spin"),
|
Texture = source.GetTexture("spinner-spin"),
|
||||||
@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
},
|
},
|
||||||
bonusCounter = new LegacySpriteText(LegacyFont.Score)
|
bonusCounter = new LegacySpriteText(LegacyFont.Score)
|
||||||
{
|
{
|
||||||
Alpha = 0f,
|
Alpha = 0,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Scale = new Vector2(SPRITE_SCALE),
|
Scale = new Vector2(SPRITE_SCALE),
|
||||||
@ -179,6 +180,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
|
spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(d.HitObject.StartTime - d.HitObject.TimeFadeIn / 2))
|
||||||
|
spin.FadeInFromZero(d.HitObject.TimeFadeIn / 2);
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(d.HitObject.StartTime))
|
using (BeginAbsoluteSequence(d.HitObject.StartTime))
|
||||||
ApproachCircle?.ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration);
|
ApproachCircle?.ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration);
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
public class OsuLegacySkinTransformer : LegacySkinTransformer
|
public class OsuLegacySkinTransformer : LegacySkinTransformer
|
||||||
{
|
{
|
||||||
|
public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasHitCircle.Value;
|
||||||
|
|
||||||
private readonly Lazy<bool> hasHitCircle;
|
private readonly Lazy<bool> hasHitCircle;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -9,7 +9,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
SliderBorderSize,
|
SliderBorderSize,
|
||||||
SliderPathRadius,
|
SliderPathRadius,
|
||||||
AllowSliderBallTint,
|
|
||||||
CursorCentre,
|
CursorCentre,
|
||||||
CursorExpand,
|
CursorExpand,
|
||||||
CursorRotate,
|
CursorRotate,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -14,6 +15,7 @@ using osu.Framework.Graphics.Rendering.Vertices;
|
|||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -21,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable
|
public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable
|
||||||
{
|
{
|
||||||
private const int max_point_count = 18_000;
|
|
||||||
|
|
||||||
// fade anim values
|
// fade anim values
|
||||||
private const double initial_fade_out_duration = 4000;
|
private const double initial_fade_out_duration = 4000;
|
||||||
|
|
||||||
@ -84,12 +84,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
totalDistance = pointInterval;
|
totalDistance = pointInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 nextPointDirection()
|
|
||||||
{
|
|
||||||
float angle = RNG.NextSingle(0, 2 * MathF.PI);
|
|
||||||
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddPosition(Vector2 position, double time)
|
public void AddPosition(Vector2 position, double time)
|
||||||
{
|
{
|
||||||
lastPosition ??= position;
|
lastPosition ??= position;
|
||||||
@ -106,33 +100,27 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
|
Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
|
||||||
increment *= pointInterval;
|
increment *= pointInterval;
|
||||||
|
|
||||||
if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time)
|
|
||||||
{
|
|
||||||
int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer());
|
|
||||||
SmokePoints.RemoveRange(index, SmokePoints.Count - index);
|
|
||||||
}
|
|
||||||
|
|
||||||
totalDistance %= pointInterval;
|
totalDistance %= pointInterval;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
if (SmokePoints.Count == 0 || SmokePoints[^1].Time <= time)
|
||||||
{
|
{
|
||||||
SmokePoints.Add(new SmokePoint
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
Position = pointPos,
|
SmokePoints.Add(new SmokePoint
|
||||||
Time = time,
|
{
|
||||||
Direction = nextPointDirection(),
|
Position = pointPos,
|
||||||
});
|
Time = time,
|
||||||
|
Angle = RNG.NextSingle(0, 2 * MathF.PI),
|
||||||
|
});
|
||||||
|
|
||||||
pointPos += increment;
|
pointPos += increment;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Invalidate(Invalidation.DrawNode);
|
Invalidate(Invalidation.DrawNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPosition = position;
|
lastPosition = position;
|
||||||
|
|
||||||
if (SmokePoints.Count >= max_point_count)
|
|
||||||
FinishDrawing(time);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FinishDrawing(double time)
|
public void FinishDrawing(double time)
|
||||||
@ -156,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
public Vector2 Position;
|
public Vector2 Position;
|
||||||
public double Time;
|
public double Time;
|
||||||
public Vector2 Direction;
|
public float Angle;
|
||||||
|
|
||||||
public struct UpperBoundComparer : IComparer<SmokePoint>
|
public struct UpperBoundComparer : IComparer<SmokePoint>
|
||||||
{
|
{
|
||||||
@ -170,6 +158,17 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
return x.Time > target.Time ? 1 : -1;
|
return x.Time > target.Time ? 1 : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct LowerBoundComparer : IComparer<SmokePoint>
|
||||||
|
{
|
||||||
|
public int Compare(SmokePoint x, SmokePoint target)
|
||||||
|
{
|
||||||
|
// Similar logic as UpperBoundComparer, except returned index will always be
|
||||||
|
// the first element larger or equal
|
||||||
|
|
||||||
|
return x.Time < target.Time ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class SmokeDrawNode : TexturedShaderDrawNode
|
protected class SmokeDrawNode : TexturedShaderDrawNode
|
||||||
@ -185,17 +184,17 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
private float radius;
|
private float radius;
|
||||||
private Vector2 drawSize;
|
private Vector2 drawSize;
|
||||||
private Texture? texture;
|
private Texture? texture;
|
||||||
|
private int rotationSeed;
|
||||||
|
private int firstVisiblePointIndex;
|
||||||
|
|
||||||
// anim calculation vars (color, scale, direction)
|
// anim calculation vars (color, scale, direction)
|
||||||
private double initialFadeOutDurationTrunc;
|
private double initialFadeOutDurationTrunc;
|
||||||
private double firstVisiblePointTime;
|
private double firstVisiblePointTimeAfterSmokeEnded;
|
||||||
|
|
||||||
private double initialFadeOutTime;
|
private double initialFadeOutTime;
|
||||||
private double reFadeInTime;
|
private double reFadeInTime;
|
||||||
private double finalFadeOutTime;
|
private double finalFadeOutTime;
|
||||||
|
|
||||||
private Random rotationRNG = new Random();
|
|
||||||
|
|
||||||
public SmokeDrawNode(ITexturedShaderDrawable source)
|
public SmokeDrawNode(ITexturedShaderDrawable source)
|
||||||
: base(source)
|
: base(source)
|
||||||
{
|
{
|
||||||
@ -205,9 +204,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
base.ApplyState();
|
base.ApplyState();
|
||||||
|
|
||||||
points.Clear();
|
|
||||||
points.AddRange(Source.SmokePoints);
|
|
||||||
|
|
||||||
radius = Source.radius;
|
radius = Source.radius;
|
||||||
drawSize = Source.DrawSize;
|
drawSize = Source.DrawSize;
|
||||||
texture = Source.Texture;
|
texture = Source.Texture;
|
||||||
@ -216,14 +212,21 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
SmokeEndTime = Source.smokeEndTime;
|
SmokeEndTime = Source.smokeEndTime;
|
||||||
CurrentTime = Source.Clock.CurrentTime;
|
CurrentTime = Source.Clock.CurrentTime;
|
||||||
|
|
||||||
rotationRNG = new Random(Source.rotationSeed);
|
rotationSeed = Source.rotationSeed;
|
||||||
|
|
||||||
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
|
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
|
||||||
firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc;
|
firstVisiblePointTimeAfterSmokeEnded = SmokeEndTime - initialFadeOutDurationTrunc;
|
||||||
|
|
||||||
initialFadeOutTime = CurrentTime;
|
initialFadeOutTime = Math.Min(CurrentTime, SmokeEndTime);
|
||||||
reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed);
|
reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTimeAfterSmokeEnded * (1 - 1 / re_fade_in_speed);
|
||||||
finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed);
|
finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTimeAfterSmokeEnded * (1 - 1 / final_fade_out_speed);
|
||||||
|
|
||||||
|
double firstVisiblePointTime = Math.Min(SmokeEndTime, CurrentTime) - initialFadeOutDurationTrunc;
|
||||||
|
firstVisiblePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = firstVisiblePointTime }, new SmokePoint.LowerBoundComparer());
|
||||||
|
int futurePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = CurrentTime }, new SmokePoint.UpperBoundComparer());
|
||||||
|
|
||||||
|
points.Clear();
|
||||||
|
points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed override void Draw(IRenderer renderer)
|
public sealed override void Draw(IRenderer renderer)
|
||||||
@ -233,7 +236,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
if (points.Count == 0)
|
if (points.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(max_point_count / 10, 10);
|
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(200, 4);
|
||||||
|
|
||||||
|
if (points.Count > quadBatch.Size && quadBatch.Size != IRenderer.MAX_QUADS)
|
||||||
|
{
|
||||||
|
int batchSize = Math.Min(quadBatch.Size * 2, IRenderer.MAX_QUADS);
|
||||||
|
quadBatch = renderer.CreateQuadBatch<TexturedVertex2D>(batchSize, 4);
|
||||||
|
}
|
||||||
|
|
||||||
texture ??= renderer.WhitePixel;
|
texture ??= renderer.WhitePixel;
|
||||||
RectangleF textureRect = texture.GetTextureRect();
|
RectangleF textureRect = texture.GetTextureRect();
|
||||||
|
|
||||||
@ -245,8 +255,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
shader.Bind();
|
shader.Bind();
|
||||||
texture.Bind();
|
texture.Bind();
|
||||||
|
|
||||||
foreach (var point in points)
|
for (int i = 0; i < points.Count; i++)
|
||||||
drawPointQuad(point, textureRect);
|
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
||||||
|
|
||||||
shader.Unbind();
|
shader.Unbind();
|
||||||
renderer.PopLocalMatrix();
|
renderer.PopLocalMatrix();
|
||||||
@ -260,30 +270,34 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
var color = Color4.White;
|
var color = Color4.White;
|
||||||
|
|
||||||
double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time;
|
double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
|
||||||
|
|
||||||
if (timeDoingInitialFadeOut > 0)
|
if (timeDoingFinalFadeOut > 0 && point.Time >= firstVisiblePointTimeAfterSmokeEnded)
|
||||||
{
|
{
|
||||||
float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
|
float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
|
||||||
color.A = (1 - fraction) * initial_alpha;
|
fraction = MathF.Pow(fraction, 5);
|
||||||
|
color.A = (1 - fraction) * re_fade_in_alpha;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (color.A > 0)
|
|
||||||
{
|
{
|
||||||
double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
|
double timeDoingInitialFadeOut = initialFadeOutTime - point.Time;
|
||||||
double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
|
|
||||||
|
|
||||||
if (timeDoingFinalFadeOut > 0)
|
if (timeDoingInitialFadeOut > 0)
|
||||||
{
|
{
|
||||||
float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
|
float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
|
||||||
fraction = MathF.Pow(fraction, 5);
|
color.A = (1 - fraction) * initial_alpha;
|
||||||
color.A = (1 - fraction) * re_fade_in_alpha;
|
|
||||||
}
|
}
|
||||||
else if (timeDoingReFadeIn > 0)
|
|
||||||
|
if (point.Time > firstVisiblePointTimeAfterSmokeEnded)
|
||||||
{
|
{
|
||||||
float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
|
double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
|
||||||
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
|
||||||
color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
|
if (timeDoingReFadeIn > 0)
|
||||||
|
{
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
|
||||||
|
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
||||||
|
color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,33 +312,33 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
return fraction * (final_scale - initial_scale) + initial_scale;
|
return fraction * (final_scale - initial_scale) + initial_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Vector2 PointDirection(SmokePoint point)
|
protected virtual Vector2 PointDirection(SmokePoint point, int index)
|
||||||
{
|
{
|
||||||
float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X);
|
|
||||||
float finalAngle = initialAngle + nextRotation();
|
|
||||||
|
|
||||||
double timeDoingRotation = CurrentTime - point.Time;
|
double timeDoingRotation = CurrentTime - point.Time;
|
||||||
float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1);
|
float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1);
|
||||||
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
||||||
float angle = fraction * (finalAngle - initialAngle) + initialAngle;
|
float angle = fraction * getRotation(index) + point.Angle;
|
||||||
|
|
||||||
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
|
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
|
||||||
}
|
}
|
||||||
|
|
||||||
private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1);
|
private float getRotation(int index) => max_rotation * (StatelessRNG.NextSingle(rotationSeed, index) * 2 - 1);
|
||||||
|
|
||||||
private void drawPointQuad(SmokePoint point, RectangleF textureRect)
|
private void drawPointQuad(SmokePoint point, RectangleF textureRect, int index)
|
||||||
{
|
{
|
||||||
Debug.Assert(quadBatch != null);
|
Debug.Assert(quadBatch != null);
|
||||||
|
|
||||||
var colour = PointColour(point);
|
var colour = PointColour(point);
|
||||||
float scale = PointScale(point);
|
if (colour.A == 0)
|
||||||
var dir = PointDirection(point);
|
|
||||||
var ortho = dir.PerpendicularLeft;
|
|
||||||
|
|
||||||
if (colour.A == 0 || scale == 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
float scale = PointScale(point);
|
||||||
|
if (scale == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dir = PointDirection(point, index);
|
||||||
|
var ortho = dir.PerpendicularLeft;
|
||||||
|
|
||||||
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
|
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
|
||||||
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
|
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
|
||||||
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
|
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
|
||||||
|
@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
private readonly ProxyContainer spinnerProxies;
|
private readonly ProxyContainer spinnerProxies;
|
||||||
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
||||||
|
|
||||||
|
public SmokeContainer Smoke { get; }
|
||||||
public FollowPointRenderer FollowPoints { get; }
|
public FollowPointRenderer FollowPoints { get; }
|
||||||
|
|
||||||
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
||||||
@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||||
new SmokeContainer { RelativeSizeAxes = Axes.Both },
|
Smoke = new SmokeContainer { RelativeSizeAxes = Axes.Both },
|
||||||
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
||||||
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||||
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
// 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.Rulesets.Taiko.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneTaikoModFlashlight : TaikoModTestScene
|
||||||
|
{
|
||||||
|
[TestCase(1f)]
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(1.25f)]
|
||||||
|
[TestCase(1.5f)]
|
||||||
|
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new TaikoModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new TaikoModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||||
|
}
|
||||||
|
}
|
@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
TimeRange = { Value = 5000 },
|
TimeRange = { Value = 5000 },
|
||||||
};
|
};
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[Test]
|
||||||
private void load()
|
public void DrumrollTest()
|
||||||
{
|
{
|
||||||
AddStep("Drum roll", () => SetContents(_ =>
|
AddStep("Drum roll", () => SetContents(_ =>
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
// 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.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneDrawableDrumRollKiai : TestSceneDrawableDrumRoll
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
// track needs to be playing for BeatSyncedContainer to work.
|
||||||
|
Beatmap.Value.Track.Start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -16,8 +15,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[Test]
|
||||||
private void load()
|
public void TestHits()
|
||||||
{
|
{
|
||||||
AddStep("Centre hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime())
|
AddStep("Centre hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime())
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
// 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.Beatmaps.ControlPoints;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneDrawableHitKiai : TestSceneDrawableHit
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
// track needs to be playing for BeatSyncedContainer to work.
|
||||||
|
Beatmap.Value.Track.Start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
87
osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs
Normal file
87
osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneBarLineGeneration : OsuTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestCloseBarLineGeneration()
|
||||||
|
{
|
||||||
|
const double start_time = 1000;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<TaikoHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
Type = HitType.Centre,
|
||||||
|
StartTime = start_time
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new TaikoRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint());
|
||||||
|
beatmap.ControlPointInfo.Add(start_time + 1, new TimingControlPoint());
|
||||||
|
|
||||||
|
var barlines = new BarLineGenerator<BarLine>(beatmap).BarLines;
|
||||||
|
|
||||||
|
AddAssert("first barline generated", () => barlines.Any(b => b.StartTime == start_time));
|
||||||
|
AddAssert("second barline generated", () => barlines.Any(b => b.StartTime == start_time + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOmitBarLineEffectPoint()
|
||||||
|
{
|
||||||
|
const double start_time = 1000;
|
||||||
|
const double beat_length = 500;
|
||||||
|
|
||||||
|
const int time_signature_numerator = 4;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<TaikoHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
Type = HitType.Centre,
|
||||||
|
StartTime = start_time
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new TaikoRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint
|
||||||
|
{
|
||||||
|
BeatLength = beat_length,
|
||||||
|
TimeSignature = new TimeSignature(time_signature_numerator)
|
||||||
|
});
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(start_time, new EffectControlPoint { OmitFirstBarLine = true });
|
||||||
|
|
||||||
|
var barlines = new BarLineGenerator<BarLine>(beatmap).BarLines;
|
||||||
|
|
||||||
|
AddAssert("first barline ommited", () => barlines.All(b => b.StartTime != start_time));
|
||||||
|
AddAssert("second barline generated", () => barlines.Any(b => b.StartTime == start_time + (beat_length * time_signature_numerator)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
BeginPlacement();
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
switch (e.Button)
|
switch (e.Button)
|
||||||
|
@ -52,6 +52,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
private double originalStartTime;
|
private double originalStartTime;
|
||||||
private Vector2 originalPosition;
|
private Vector2 originalPosition;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
BeginPlacement();
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Button != MouseButton.Left)
|
if (e.Button != MouseButton.Left)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
@ -47,21 +47,21 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
this.taikoPlayfield = taikoPlayfield;
|
this.taikoPlayfield = taikoPlayfield;
|
||||||
|
|
||||||
FlashlightSize = getSizeFor(0);
|
FlashlightSize = adjustSize(GetSize());
|
||||||
FlashlightSmoothness = 1.4f;
|
FlashlightSmoothness = 1.4f;
|
||||||
|
|
||||||
AddLayout(flashlightProperties);
|
AddLayout(flashlightProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 getSizeFor(int combo)
|
private Vector2 adjustSize(float size)
|
||||||
{
|
{
|
||||||
// Preserve flashlight size through the playfield's aspect adjustment.
|
// Preserve flashlight size through the playfield's aspect adjustment.
|
||||||
return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
|
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
protected override void UpdateFlashlightSize(float size)
|
||||||
{
|
{
|
||||||
this.TransformTo(nameof(FlashlightSize), getSizeFor(e.NewValue), FLASHLIGHT_FADE_DURATION);
|
this.TransformTo(nameof(FlashlightSize), adjustSize(size), FLASHLIGHT_FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string FragmentShader => "CircularFlashlight";
|
protected override string FragmentShader => "CircularFlashlight";
|
||||||
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
|
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
|
||||||
|
|
||||||
ClearTransforms(targetMember: nameof(FlashlightSize));
|
ClearTransforms(targetMember: nameof(FlashlightSize));
|
||||||
FlashlightSize = getSizeFor(Combo.Value);
|
FlashlightSize = adjustSize(Combo.Value);
|
||||||
|
|
||||||
flashlightProperties.Validate();
|
flashlightProperties.Validate();
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -13,6 +14,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -32,6 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
|
|
||||||
private const double pre_beat_transition_time = 80;
|
private const double pre_beat_transition_time = 80;
|
||||||
|
|
||||||
|
private const float flash_opacity = 0.3f;
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -152,11 +156,22 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject drawableHitObject { get; set; }
|
||||||
|
|
||||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||||
{
|
{
|
||||||
if (!effectPoint.KiaiMode)
|
if (!effectPoint.KiaiMode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (drawableHitObject.State.Value == ArmedState.Idle)
|
||||||
|
{
|
||||||
|
FlashBox
|
||||||
|
.FadeTo(flash_opacity)
|
||||||
|
.Then()
|
||||||
|
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
||||||
|
}
|
||||||
|
|
||||||
if (beatIndex % timingPoint.TimeSignature.Numerator != 0)
|
if (beatIndex % timingPoint.TimeSignature.Numerator != 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
||||||
AddInternal(backgroundLayer = getDrawableFor("circle"));
|
AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle")));
|
||||||
|
|
||||||
var foregroundLayer = getDrawableFor("circleoverlay");
|
var foregroundLayer = getDrawableFor("circleoverlay");
|
||||||
if (foregroundLayer != null)
|
if (foregroundLayer != null)
|
||||||
|
@ -14,8 +14,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
public class TaikoLegacySkinTransformer : LegacySkinTransformer
|
public class TaikoLegacySkinTransformer : LegacySkinTransformer
|
||||||
{
|
{
|
||||||
|
public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasHitCircle || hasBarLeft;
|
||||||
|
|
||||||
private readonly Lazy<bool> hasExplosion;
|
private readonly Lazy<bool> hasExplosion;
|
||||||
|
|
||||||
|
private bool hasHitCircle => GetTexture("taikohitcircle") != null;
|
||||||
|
private bool hasBarLeft => GetTexture("taiko-bar-left") != null;
|
||||||
|
|
||||||
public TaikoLegacySkinTransformer(ISkin skin)
|
public TaikoLegacySkinTransformer(ISkin skin)
|
||||||
: base(skin)
|
: base(skin)
|
||||||
{
|
{
|
||||||
@ -42,14 +47,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.InputDrum:
|
case TaikoSkinComponents.InputDrum:
|
||||||
if (GetTexture("taiko-bar-left") != null)
|
if (hasBarLeft)
|
||||||
return new LegacyInputDrum();
|
return new LegacyInputDrum();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.CentreHit:
|
case TaikoSkinComponents.CentreHit:
|
||||||
case TaikoSkinComponents.RimHit:
|
case TaikoSkinComponents.RimHit:
|
||||||
if (GetTexture("taikohitcircle") != null)
|
if (hasHitCircle)
|
||||||
return new LegacyHit(taikoComponent.Component);
|
return new LegacyHit(taikoComponent.Component);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -9,8 +9,10 @@ using osu.Framework.Extensions;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
@ -96,5 +98,41 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
var second = beatmaps.GetWorkingBeatmap(beatmap, true);
|
var second = beatmaps.GetWorkingBeatmap(beatmap, true);
|
||||||
Assert.That(first, Is.Not.SameAs(second));
|
Assert.That(first, Is.Not.SameAs(second));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSavePreservesCollections() => AddStep("run test", () =>
|
||||||
|
{
|
||||||
|
var beatmap = Realm.Run(r => r.Find<BeatmapInfo>(importedSet.Beatmaps.First().ID).Detach());
|
||||||
|
|
||||||
|
var working = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
|
Assert.That(working.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||||
|
|
||||||
|
string initialHash = working.BeatmapInfo.MD5Hash;
|
||||||
|
|
||||||
|
var preserveCollection = new BeatmapCollection("test contained");
|
||||||
|
preserveCollection.BeatmapMD5Hashes.Add(initialHash);
|
||||||
|
|
||||||
|
var noNewCollection = new BeatmapCollection("test not contained");
|
||||||
|
|
||||||
|
Realm.Write(r =>
|
||||||
|
{
|
||||||
|
r.Add(preserveCollection);
|
||||||
|
r.Add(noNewCollection);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Contain(initialHash));
|
||||||
|
Assert.That(noNewCollection.BeatmapMD5Hashes, Does.Not.Contain(initialHash));
|
||||||
|
|
||||||
|
beatmaps.Save(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
|
string finalHash = working.BeatmapInfo.MD5Hash;
|
||||||
|
|
||||||
|
Assert.That(finalHash, Is.Not.SameAs(initialHash));
|
||||||
|
|
||||||
|
Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Not.Contain(initialHash));
|
||||||
|
Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Contain(finalHash));
|
||||||
|
Assert.That(noNewCollection.BeatmapMD5Hashes, Does.Not.Contain(finalHash));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,9 +71,9 @@ namespace osu.Game.Tests.Editing
|
|||||||
|
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
public void TestSpeedMultiplier(float multiplier)
|
public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier)
|
||||||
{
|
{
|
||||||
assertSnapDistance(100 * multiplier, new HitObject
|
assertSnapDistance(100, new HitObject
|
||||||
{
|
{
|
||||||
DifficultyControlPoint = new DifficultyControlPoint
|
DifficultyControlPoint = new DifficultyControlPoint
|
||||||
{
|
{
|
||||||
|
BIN
osu.Game.Tests/Resources/Archives/modified-default-20221012.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-default-20221012.osk
Normal file
Binary file not shown.
@ -38,7 +38,9 @@ namespace osu.Game.Tests.Skins
|
|||||||
// Covers legacy song progress, UR counter, colour hit error metre.
|
// Covers legacy song progress, UR counter, colour hit error metre.
|
||||||
"Archives/modified-classic-20220801.osk",
|
"Archives/modified-classic-20220801.osk",
|
||||||
// Covers clicks/s counter
|
// Covers clicks/s counter
|
||||||
"Archives/modified-default-20220818.osk"
|
"Archives/modified-default-20220818.osk",
|
||||||
|
// Covers longest combo counter
|
||||||
|
"Archives/modified-default-20221012.osk"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -244,7 +244,10 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
public void TestResumeFromPlayer()
|
public void TestResumeFromPlayer()
|
||||||
{
|
{
|
||||||
performFullSetup();
|
performFullSetup();
|
||||||
AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
|
AddStep("Move mouse to Visual Settings location", () => InputManager.MoveMouseTo(playerLoader.ScreenSpaceDrawQuad.TopRight
|
||||||
|
+ new Vector2(-playerLoader.VisualSettingsPos.ScreenSpaceDrawQuad.Width,
|
||||||
|
playerLoader.VisualSettingsPos.ScreenSpaceDrawQuad.Height / 2
|
||||||
|
)));
|
||||||
AddStep("Resume PlayerLoader", () => player.Restart());
|
AddStep("Resume PlayerLoader", () => player.Restart());
|
||||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||||
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
|
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneEditorComposeRadioButtons : OsuTestScene
|
public class TestSceneEditorComposeRadioButtons : OsuTestScene
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
public TestSceneEditorComposeRadioButtons()
|
public TestSceneEditorComposeRadioButtons()
|
||||||
{
|
{
|
||||||
EditorRadioButtonCollection collection;
|
EditorRadioButtonCollection collection;
|
||||||
|
@ -148,10 +148,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0);
|
AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0);
|
||||||
|
|
||||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
|
||||||
|
|
||||||
AddAssert("circle placed", () => editorBeatmap.HitObjects.Count == 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -29,16 +29,18 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
private TimelineBlueprintContainer blueprintContainer
|
private TimelineBlueprintContainer blueprintContainer
|
||||||
=> Editor.ChildrenOfType<TimelineBlueprintContainer>().First();
|
=> Editor.ChildrenOfType<TimelineBlueprintContainer>().First();
|
||||||
|
|
||||||
|
private Vector2 getPosition(HitObject hitObject) =>
|
||||||
|
blueprintContainer.SelectionBlueprints.First(s => s.Item == hitObject).ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
|
private Vector2 getMiddlePosition(HitObject hitObject1, HitObject hitObject2) =>
|
||||||
|
(getPosition(hitObject1) + getPosition(hitObject2)) / 2;
|
||||||
|
|
||||||
private void moveMouseToObject(Func<HitObject> targetFunc)
|
private void moveMouseToObject(Func<HitObject> targetFunc)
|
||||||
{
|
{
|
||||||
AddStep("move mouse to object", () =>
|
AddStep("move mouse to object", () =>
|
||||||
{
|
{
|
||||||
var pos = blueprintContainer.SelectionBlueprints
|
var hitObject = targetFunc();
|
||||||
.First(s => s.Item == targetFunc())
|
InputManager.MoveMouseTo(getPosition(hitObject));
|
||||||
.ChildrenOfType<TimelineHitObjectBlueprint>()
|
|
||||||
.First().ScreenSpaceDrawQuad.Centre;
|
|
||||||
|
|
||||||
InputManager.MoveMouseTo(pos);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,6 +264,56 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicDragSelection()
|
||||||
|
{
|
||||||
|
var addedObjects = new[]
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0 },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(100) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(200) },
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(300) },
|
||||||
|
};
|
||||||
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||||
|
|
||||||
|
AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1])));
|
||||||
|
AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("drag to select", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[2], addedObjects[3])));
|
||||||
|
assertSelectionIs(new[] { addedObjects[1], addedObjects[2] });
|
||||||
|
|
||||||
|
AddStep("drag to deselect", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[1], addedObjects[2])));
|
||||||
|
assertSelectionIs(new[] { addedObjects[1] });
|
||||||
|
|
||||||
|
AddStep("mouse up", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
assertSelectionIs(new[] { addedObjects[1] });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFastDragSelection()
|
||||||
|
{
|
||||||
|
var addedObjects = new[]
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0 },
|
||||||
|
new HitCircle { StartTime = 500 },
|
||||||
|
new HitCircle { StartTime = 20000, Position = new Vector2(100) },
|
||||||
|
new HitCircle { StartTime = 31000, Position = new Vector2(200) },
|
||||||
|
new HitCircle { StartTime = 60000, Position = new Vector2(300) },
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||||
|
|
||||||
|
AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1])));
|
||||||
|
AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
AddStep("start drag", () => InputManager.MoveMouseTo(getPosition(addedObjects[1])));
|
||||||
|
|
||||||
|
AddStep("jump editor clock", () => EditorClock.Seek(30000));
|
||||||
|
AddStep("jump editor clock", () => EditorClock.Seek(60000));
|
||||||
|
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
assertSelectionIs(addedObjects.Skip(1));
|
||||||
|
AddAssert("all blueprints are present", () => blueprintContainer.SelectionBlueprints.Count == EditorBeatmap.SelectedHitObjects.Count);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSelectionIs(IEnumerable<HitObject> hitObjects)
|
private void assertSelectionIs(IEnumerable<HitObject> hitObjects)
|
||||||
=> AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects));
|
=> AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects));
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new MenuCursor()
|
new MenuCursorContainer()
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollContainer.Add(innerBox = new Box
|
scrollContainer.Add(innerBox = new Box
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Gameplay;
|
using osu.Game.Tests.Gameplay;
|
||||||
@ -148,6 +149,42 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputDoesntWorkWhenHUDHidden()
|
||||||
|
{
|
||||||
|
SongProgressBar getSongProgress() => hudOverlay.ChildrenOfType<SongProgressBar>().Single();
|
||||||
|
|
||||||
|
bool seeked = false;
|
||||||
|
|
||||||
|
createNew();
|
||||||
|
|
||||||
|
AddStep("bind seek", () =>
|
||||||
|
{
|
||||||
|
seeked = false;
|
||||||
|
|
||||||
|
var progress = getSongProgress();
|
||||||
|
|
||||||
|
progress.ShowHandle = true;
|
||||||
|
progress.OnSeek += _ => seeked = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||||
|
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
||||||
|
|
||||||
|
AddStep("attempt seek", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(getSongProgress());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("seek not performed", () => !seeked);
|
||||||
|
|
||||||
|
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||||
|
|
||||||
|
AddStep("attempt seek", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("seek performed", () => seeked);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHiddenHUDDoesntBlockComponentUpdates()
|
public void TestHiddenHUDDoesntBlockComponentUpdates()
|
||||||
{
|
{
|
||||||
|
498
osu.Game.Tests/Visual/Gameplay/TestSceneScoring.cs
Normal file
498
osu.Game.Tests/Visual/Gameplay/TestSceneScoring.cs
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneScoring : OsuTestScene
|
||||||
|
{
|
||||||
|
private GraphContainer graphs = null!;
|
||||||
|
private SettingsSlider<int> sliderMaxCombo = null!;
|
||||||
|
|
||||||
|
private FillFlowContainer legend = null!;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
AddStep("setup tests", () =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
graphs = new GraphContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
legend = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
Direction = FillDirection.Full,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Full,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
sliderMaxCombo = new SettingsSlider<int>
|
||||||
|
{
|
||||||
|
Width = 0.5f,
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
Current = new BindableInt(1024)
|
||||||
|
{
|
||||||
|
MinValue = 96,
|
||||||
|
MaxValue = 8192,
|
||||||
|
},
|
||||||
|
LabelText = "max combo",
|
||||||
|
},
|
||||||
|
new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.5f,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Text = $"Left click to add miss\nRight click to add OK/{base_ok}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sliderMaxCombo.Current.BindValueChanged(_ => rerun());
|
||||||
|
|
||||||
|
graphs.MissLocations.BindCollectionChanged((_, __) => rerun());
|
||||||
|
graphs.NonPerfectLocations.BindCollectionChanged((_, __) => rerun());
|
||||||
|
|
||||||
|
graphs.MaxCombo.BindTo(sliderMaxCombo.Current);
|
||||||
|
|
||||||
|
rerun();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int base_great = 300;
|
||||||
|
private const int base_ok = 100;
|
||||||
|
|
||||||
|
private void rerun()
|
||||||
|
{
|
||||||
|
graphs.Clear();
|
||||||
|
legend.Clear();
|
||||||
|
|
||||||
|
runForProcessor("lazer-standardised", Color4.YellowGreen, new ScoreProcessor(new OsuRuleset()) { Mode = { Value = ScoringMode.Standardised } });
|
||||||
|
runForProcessor("lazer-classic", Color4.MediumPurple, new ScoreProcessor(new OsuRuleset()) { Mode = { Value = ScoringMode.Classic } });
|
||||||
|
|
||||||
|
runScoreV1();
|
||||||
|
runScoreV2();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runScoreV1()
|
||||||
|
{
|
||||||
|
int totalScore = 0;
|
||||||
|
int currentCombo = 0;
|
||||||
|
|
||||||
|
void applyHitV1(int baseScore)
|
||||||
|
{
|
||||||
|
if (baseScore == 0)
|
||||||
|
{
|
||||||
|
currentCombo = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float score_multiplier = 1;
|
||||||
|
|
||||||
|
totalScore += baseScore;
|
||||||
|
|
||||||
|
// combo multiplier
|
||||||
|
// ReSharper disable once PossibleLossOfFraction
|
||||||
|
totalScore += (int)(Math.Max(0, currentCombo - 1) * (baseScore / 25 * score_multiplier));
|
||||||
|
|
||||||
|
currentCombo++;
|
||||||
|
}
|
||||||
|
|
||||||
|
runForAlgorithm("ScoreV1 (classic)", Color4.Purple,
|
||||||
|
() => applyHitV1(base_great),
|
||||||
|
() => applyHitV1(base_ok),
|
||||||
|
() => applyHitV1(0),
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// Arbitrary value chosen towards the upper range.
|
||||||
|
const double score_multiplier = 4;
|
||||||
|
|
||||||
|
return (int)(totalScore * score_multiplier);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runScoreV2()
|
||||||
|
{
|
||||||
|
int maxCombo = sliderMaxCombo.Current.Value;
|
||||||
|
|
||||||
|
int currentCombo = 0;
|
||||||
|
double comboPortion = 0;
|
||||||
|
double currentBaseScore = 0;
|
||||||
|
double maxBaseScore = 0;
|
||||||
|
int currentHits = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < maxCombo; i++)
|
||||||
|
applyHitV2(base_great);
|
||||||
|
|
||||||
|
double comboPortionMax = comboPortion;
|
||||||
|
|
||||||
|
currentCombo = 0;
|
||||||
|
comboPortion = 0;
|
||||||
|
currentBaseScore = 0;
|
||||||
|
maxBaseScore = 0;
|
||||||
|
currentHits = 0;
|
||||||
|
|
||||||
|
void applyHitV2(int baseScore)
|
||||||
|
{
|
||||||
|
maxBaseScore += base_great;
|
||||||
|
currentBaseScore += baseScore;
|
||||||
|
comboPortion += baseScore * (1 + ++currentCombo / 10.0);
|
||||||
|
|
||||||
|
currentHits++;
|
||||||
|
}
|
||||||
|
|
||||||
|
runForAlgorithm("ScoreV2", Color4.OrangeRed,
|
||||||
|
() => applyHitV2(base_great),
|
||||||
|
() => applyHitV2(base_ok),
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
currentHits++;
|
||||||
|
maxBaseScore += base_great;
|
||||||
|
currentCombo = 0;
|
||||||
|
}, () =>
|
||||||
|
{
|
||||||
|
double accuracy = currentBaseScore / maxBaseScore;
|
||||||
|
|
||||||
|
return (int)Math.Round
|
||||||
|
(
|
||||||
|
700000 * comboPortion / comboPortionMax +
|
||||||
|
300000 * Math.Pow(accuracy, 10) * ((double)currentHits / maxCombo)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runForProcessor(string name, Color4 colour, ScoreProcessor processor)
|
||||||
|
{
|
||||||
|
int maxCombo = sliderMaxCombo.Current.Value;
|
||||||
|
|
||||||
|
var beatmap = new OsuBeatmap();
|
||||||
|
for (int i = 0; i < maxCombo; i++)
|
||||||
|
beatmap.HitObjects.Add(new HitCircle());
|
||||||
|
|
||||||
|
processor.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
|
runForAlgorithm(name, colour,
|
||||||
|
() => processor.ApplyResult(new OsuJudgementResult(new HitCircle(), new OsuJudgement()) { Type = HitResult.Great }),
|
||||||
|
() => processor.ApplyResult(new OsuJudgementResult(new HitCircle(), new OsuJudgement()) { Type = HitResult.Ok }),
|
||||||
|
() => processor.ApplyResult(new OsuJudgementResult(new HitCircle(), new OsuJudgement()) { Type = HitResult.Miss }),
|
||||||
|
() => (int)processor.TotalScore.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runForAlgorithm(string name, Color4 colour, Action applyHit, Action applyNonPerfect, Action applyMiss, Func<int> getTotalScore)
|
||||||
|
{
|
||||||
|
int maxCombo = sliderMaxCombo.Current.Value;
|
||||||
|
|
||||||
|
List<float> results = new List<float>();
|
||||||
|
|
||||||
|
for (int i = 0; i < maxCombo; i++)
|
||||||
|
{
|
||||||
|
if (graphs.MissLocations.Contains(i))
|
||||||
|
applyMiss();
|
||||||
|
else if (graphs.NonPerfectLocations.Contains(i))
|
||||||
|
applyNonPerfect();
|
||||||
|
else
|
||||||
|
applyHit();
|
||||||
|
|
||||||
|
results.Add(getTotalScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
graphs.Add(new LineGraph
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
LineColour = colour,
|
||||||
|
Values = results
|
||||||
|
});
|
||||||
|
|
||||||
|
legend.Add(new OsuSpriteText
|
||||||
|
{
|
||||||
|
Colour = colour,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.5f,
|
||||||
|
Text = $"{FontAwesome.Solid.Circle.Icon} {name}"
|
||||||
|
});
|
||||||
|
|
||||||
|
legend.Add(new OsuSpriteText
|
||||||
|
{
|
||||||
|
Colour = colour,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.5f,
|
||||||
|
Text = $"final score {getTotalScore():#,0}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GraphContainer : Container, IHasCustomTooltip<IEnumerable<LineGraph>>
|
||||||
|
{
|
||||||
|
public readonly BindableList<double> MissLocations = new BindableList<double>();
|
||||||
|
public readonly BindableList<double> NonPerfectLocations = new BindableList<double>();
|
||||||
|
|
||||||
|
public Bindable<int> MaxCombo = new Bindable<int>();
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
|
private readonly Box hoverLine;
|
||||||
|
|
||||||
|
private readonly Container missLines;
|
||||||
|
private readonly Container verticalGridLines;
|
||||||
|
|
||||||
|
public int CurrentHoverCombo { get; private set; }
|
||||||
|
|
||||||
|
public GraphContainer()
|
||||||
|
{
|
||||||
|
InternalChild = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = OsuColour.Gray(0.1f),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
verticalGridLines = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
hoverLine = new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Yellow,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Alpha = 0,
|
||||||
|
Width = 1,
|
||||||
|
},
|
||||||
|
missLines = new Container
|
||||||
|
{
|
||||||
|
Alpha = 0.6f,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
Content,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MissLocations.BindCollectionChanged((_, _) => updateMissLocations());
|
||||||
|
NonPerfectLocations.BindCollectionChanged((_, _) => updateMissLocations());
|
||||||
|
|
||||||
|
MaxCombo.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateMissLocations();
|
||||||
|
updateVerticalGridLines();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateVerticalGridLines()
|
||||||
|
{
|
||||||
|
verticalGridLines.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < MaxCombo.Value; i++)
|
||||||
|
{
|
||||||
|
if (i % 100 == 0)
|
||||||
|
{
|
||||||
|
verticalGridLines.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = OsuColour.Gray(0.2f),
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Width = 1,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = (float)i / MaxCombo.Value,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = (float)i / MaxCombo.Value,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = $"{i:#,0}",
|
||||||
|
Rotation = -30,
|
||||||
|
Y = -20,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMissLocations()
|
||||||
|
{
|
||||||
|
missLines.Clear();
|
||||||
|
|
||||||
|
foreach (int miss in MissLocations)
|
||||||
|
{
|
||||||
|
missLines.Add(new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Red,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Width = 1,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = (float)miss / MaxCombo.Value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (int miss in NonPerfectLocations)
|
||||||
|
{
|
||||||
|
missLines.Add(new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Orange,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Width = 1,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = (float)miss / MaxCombo.Value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
hoverLine.Show();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
hoverLine.Hide();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
CurrentHoverCombo = (int)(e.MousePosition.X / DrawWidth * MaxCombo.Value);
|
||||||
|
|
||||||
|
hoverLine.X = e.MousePosition.X;
|
||||||
|
return base.OnMouseMove(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button == MouseButton.Left)
|
||||||
|
MissLocations.Add(CurrentHoverCombo);
|
||||||
|
else
|
||||||
|
NonPerfectLocations.Add(CurrentHoverCombo);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GraphTooltip? tooltip;
|
||||||
|
|
||||||
|
public ITooltip<IEnumerable<LineGraph>> GetCustomTooltip() => tooltip ??= new GraphTooltip(this);
|
||||||
|
|
||||||
|
public IEnumerable<LineGraph> TooltipContent => Content.OfType<LineGraph>();
|
||||||
|
|
||||||
|
public class GraphTooltip : CompositeDrawable, ITooltip<IEnumerable<LineGraph>>
|
||||||
|
{
|
||||||
|
private readonly GraphContainer graphContainer;
|
||||||
|
|
||||||
|
private readonly OsuTextFlowContainer textFlow;
|
||||||
|
|
||||||
|
public GraphTooltip(GraphContainer graphContainer)
|
||||||
|
{
|
||||||
|
this.graphContainer = graphContainer;
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 10;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = OsuColour.Gray(0.15f),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
textFlow = new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
Colour = Color4.White,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? lastContentCombo;
|
||||||
|
|
||||||
|
public void SetContent(IEnumerable<LineGraph> content)
|
||||||
|
{
|
||||||
|
int relevantCombo = graphContainer.CurrentHoverCombo;
|
||||||
|
|
||||||
|
if (lastContentCombo == relevantCombo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lastContentCombo = relevantCombo;
|
||||||
|
textFlow.Clear();
|
||||||
|
|
||||||
|
textFlow.AddParagraph($"At combo {relevantCombo}:");
|
||||||
|
|
||||||
|
foreach (var graph in content)
|
||||||
|
{
|
||||||
|
float valueAtHover = graph.Values.ElementAt(relevantCombo);
|
||||||
|
float ofTotal = valueAtHover / graph.Values.Last();
|
||||||
|
|
||||||
|
textFlow.AddParagraph($"{graph.Name}: {valueAtHover:#,0} ({ofTotal * 100:N0}% of final)\n", st => st.Colour = graph.LineColour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Move(Vector2 pos) => this.MoveTo(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void addControlPoints(IList<MultiplierControlPoint> controlPoints, double sequenceStartTime)
|
private void addControlPoints(IList<MultiplierControlPoint> controlPoints, double sequenceStartTime)
|
||||||
{
|
{
|
||||||
controlPoints.ForEach(point => point.StartTime += sequenceStartTime);
|
controlPoints.ForEach(point => point.Time += sequenceStartTime);
|
||||||
|
|
||||||
scrollContainers.ForEach(container =>
|
scrollContainers.ForEach(container =>
|
||||||
{
|
{
|
||||||
@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
foreach (var playfield in playfields)
|
foreach (var playfield in playfields)
|
||||||
{
|
{
|
||||||
foreach (var controlPoint in controlPoints)
|
foreach (var controlPoint in controlPoints)
|
||||||
playfield.Add(createDrawablePoint(playfield, controlPoint.StartTime));
|
playfield.Add(createDrawablePoint(playfield, controlPoint.Time));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,14 +97,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCurrentItemDoesNotHaveDeleteButton()
|
public void TestSingleItemDoesNotHaveDeleteButton()
|
||||||
|
{
|
||||||
|
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
|
||||||
|
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||||
|
|
||||||
|
assertDeleteButtonVisibility(0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCurrentItemHasDeleteButtonIfNotSingle()
|
||||||
{
|
{
|
||||||
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
|
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
|
||||||
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||||
|
|
||||||
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
|
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
|
||||||
|
|
||||||
assertDeleteButtonVisibility(0, false);
|
assertDeleteButtonVisibility(0, true);
|
||||||
assertDeleteButtonVisibility(1, true);
|
assertDeleteButtonVisibility(1, true);
|
||||||
|
|
||||||
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());
|
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());
|
||||||
|
@ -25,6 +25,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Navigation
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
{
|
{
|
||||||
@ -79,6 +80,16 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase gameBase { get; set; }
|
private OsuGameBase gameBase { get; set; }
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCursorHidesWhenIdle()
|
||||||
|
{
|
||||||
|
AddStep("click mouse", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddUntilStep("wait until idle", () => Game.IsIdle.Value);
|
||||||
|
AddUntilStep("menu cursor hidden", () => Game.GlobalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0);
|
||||||
|
AddStep("click mouse", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddUntilStep("menu cursor shown", () => Game.GlobalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNullRulesetHandled()
|
public void TestNullRulesetHandled()
|
||||||
{
|
{
|
||||||
|
257
osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs
Normal file
257
osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Comments;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneCommentActions : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private Container<Drawable> content = null!;
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
|
[Cached(typeof(IDialogOverlay))]
|
||||||
|
private readonly DialogOverlay dialogOverlay = new DialogOverlay();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
private CommentsContainer commentsContainer = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
base.Content.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
content = new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
dialogOverlay
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
API.Login("test", "test");
|
||||||
|
Child = commentsContainer = new CommentsContainer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNonOwnCommentCantBeDeleted()
|
||||||
|
{
|
||||||
|
addTestComments();
|
||||||
|
|
||||||
|
AddUntilStep("First comment has button", () =>
|
||||||
|
{
|
||||||
|
var comments = this.ChildrenOfType<DrawableComment>();
|
||||||
|
var ourComment = comments.SingleOrDefault(x => x.Comment.Id == 1);
|
||||||
|
return ourComment != null && ourComment.ChildrenOfType<OsuSpriteText>().Any(x => x.Text == "Delete");
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Second doesn't", () =>
|
||||||
|
{
|
||||||
|
var comments = this.ChildrenOfType<DrawableComment>();
|
||||||
|
var ourComment = comments.Single(x => x.Comment.Id == 2);
|
||||||
|
return ourComment.ChildrenOfType<OsuSpriteText>().All(x => x.Text != "Delete");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ManualResetEventSlim deletionPerformed = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeletion()
|
||||||
|
{
|
||||||
|
DrawableComment? ourComment = null;
|
||||||
|
|
||||||
|
addTestComments();
|
||||||
|
AddUntilStep("Comment exists", () =>
|
||||||
|
{
|
||||||
|
var comments = this.ChildrenOfType<DrawableComment>();
|
||||||
|
ourComment = comments.SingleOrDefault(x => x.Comment.Id == 1);
|
||||||
|
return ourComment != null;
|
||||||
|
});
|
||||||
|
AddStep("It has delete button", () =>
|
||||||
|
{
|
||||||
|
var btn = ourComment.ChildrenOfType<OsuSpriteText>().Single(x => x.Text == "Delete");
|
||||||
|
InputManager.MoveMouseTo(btn);
|
||||||
|
});
|
||||||
|
AddStep("Click delete button", () =>
|
||||||
|
{
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddStep("Setup request handling", () =>
|
||||||
|
{
|
||||||
|
deletionPerformed.Reset();
|
||||||
|
|
||||||
|
dummyAPI.HandleRequest = request =>
|
||||||
|
{
|
||||||
|
if (!(request is CommentDeleteRequest req))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (req.CommentId != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CommentBundle cb = new CommentBundle
|
||||||
|
{
|
||||||
|
Comments = new List<Comment>
|
||||||
|
{
|
||||||
|
new Comment
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Message = "This is a comment by another user",
|
||||||
|
UserId = API.LocalUser.Value.Id + 1,
|
||||||
|
CreatedAt = DateTimeOffset.Now,
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Id = API.LocalUser.Value.Id + 1,
|
||||||
|
Username = "Another user"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IncludedComments = new List<Comment>(),
|
||||||
|
PinnedComments = new List<Comment>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
deletionPerformed.Wait(10000);
|
||||||
|
req.TriggerSuccess(cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddStep("Confirm dialog", () => InputManager.Key(Key.Number1));
|
||||||
|
|
||||||
|
AddAssert("Loading spinner shown", () => commentsContainer.ChildrenOfType<LoadingSpinner>().Any(d => d.IsPresent));
|
||||||
|
|
||||||
|
AddStep("Complete request", () => deletionPerformed.Set());
|
||||||
|
|
||||||
|
AddUntilStep("Comment is deleted locally", () => this.ChildrenOfType<DrawableComment>().Single(x => x.Comment.Id == 1).WasDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeletionFail()
|
||||||
|
{
|
||||||
|
DrawableComment? ourComment = null;
|
||||||
|
bool delete = false;
|
||||||
|
|
||||||
|
addTestComments();
|
||||||
|
AddUntilStep("Comment exists", () =>
|
||||||
|
{
|
||||||
|
var comments = this.ChildrenOfType<DrawableComment>();
|
||||||
|
ourComment = comments.SingleOrDefault(x => x.Comment.Id == 1);
|
||||||
|
return ourComment != null;
|
||||||
|
});
|
||||||
|
AddStep("It has delete button", () =>
|
||||||
|
{
|
||||||
|
var btn = ourComment.ChildrenOfType<OsuSpriteText>().Single(x => x.Text == "Delete");
|
||||||
|
InputManager.MoveMouseTo(btn);
|
||||||
|
});
|
||||||
|
AddStep("Click delete button", () =>
|
||||||
|
{
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddStep("Setup request handling", () =>
|
||||||
|
{
|
||||||
|
dummyAPI.HandleRequest = request =>
|
||||||
|
{
|
||||||
|
if (request is not CommentDeleteRequest req)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
req.TriggerFailure(new Exception());
|
||||||
|
delete = true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddStep("Confirm dialog", () => InputManager.Key(Key.Number1));
|
||||||
|
AddUntilStep("Deletion requested", () => delete);
|
||||||
|
AddUntilStep("Comment is available", () =>
|
||||||
|
{
|
||||||
|
return !this.ChildrenOfType<DrawableComment>().Single(x => x.Comment.Id == 1).WasDeleted;
|
||||||
|
});
|
||||||
|
AddAssert("Loading spinner hidden", () =>
|
||||||
|
{
|
||||||
|
return ourComment.ChildrenOfType<LoadingSpinner>().All(d => !d.IsPresent);
|
||||||
|
});
|
||||||
|
AddAssert("Actions available", () =>
|
||||||
|
{
|
||||||
|
return ourComment.ChildrenOfType<LinkFlowContainer>().Single(x => x.Name == @"Actions buttons").IsPresent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTestComments()
|
||||||
|
{
|
||||||
|
AddStep("set up response", () =>
|
||||||
|
{
|
||||||
|
CommentBundle cb = new CommentBundle
|
||||||
|
{
|
||||||
|
Comments = new List<Comment>
|
||||||
|
{
|
||||||
|
new Comment
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Message = "This is our comment",
|
||||||
|
UserId = API.LocalUser.Value.Id,
|
||||||
|
CreatedAt = DateTimeOffset.Now,
|
||||||
|
User = API.LocalUser.Value,
|
||||||
|
},
|
||||||
|
new Comment
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Message = "This is a comment by another user",
|
||||||
|
UserId = API.LocalUser.Value.Id + 1,
|
||||||
|
CreatedAt = DateTimeOffset.Now,
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Id = API.LocalUser.Value.Id + 1,
|
||||||
|
Username = "Another user"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IncludedComments = new List<Comment>(),
|
||||||
|
PinnedComments = new List<Comment>(),
|
||||||
|
};
|
||||||
|
setUpCommentsResponse(cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpCommentsResponse(CommentBundle commentBundle)
|
||||||
|
{
|
||||||
|
dummyAPI.HandleRequest = request =>
|
||||||
|
{
|
||||||
|
if (!(request is GetCommentsRequest getCommentsRequest))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
getCommentsRequest.TriggerSuccess(commentBundle);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -189,6 +189,16 @@ Line after image";
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFlag()
|
||||||
|
{
|
||||||
|
AddStep("Add flag", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.CurrentPath = @"https://dev.ppy.sh";
|
||||||
|
markdownContainer.Text = "::{flag=\"AU\"}:: ::{flag=\"ZZ\"}::";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private class TestMarkdownContainer : WikiMarkdownContainer
|
private class TestMarkdownContainer : WikiMarkdownContainer
|
||||||
{
|
{
|
||||||
public LinkInline Link;
|
public LinkInline Link;
|
||||||
|
File diff suppressed because one or more lines are too long
@ -3,18 +3,17 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Tests.Visual.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Settings
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
{
|
{
|
||||||
public class TestSceneDirectorySelector : OsuTestScene
|
public class TestSceneDirectorySelector : ThemeComparisonTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
protected override Drawable CreateContent() => new OsuDirectorySelector
|
||||||
private void load()
|
|
||||||
{
|
{
|
||||||
Add(new OsuDirectorySelector { RelativeSizeAxes = Axes.Both });
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,43 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Tests.Visual.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Settings
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
{
|
{
|
||||||
public class TestSceneFileSelector : OsuTestScene
|
public class TestSceneFileSelector : ThemeComparisonTestScene
|
||||||
{
|
{
|
||||||
[Test]
|
[Resolved]
|
||||||
public void TestAllFiles()
|
private OsuColour colours { get; set; }
|
||||||
{
|
|
||||||
AddStep("create", () => Child = new OsuFileSelector { RelativeSizeAxes = Axes.Both });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestJpgFilesOnly()
|
public void TestJpgFilesOnly()
|
||||||
{
|
{
|
||||||
AddStep("create", () => Child = new OsuFileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both });
|
AddStep("create", () =>
|
||||||
|
{
|
||||||
|
Cell(0, 0).Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.GreySeaFoam
|
||||||
|
},
|
||||||
|
new OsuFileSelector(validFileExtensions: new[] { ".jpg" })
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateContent() => new OsuFileSelector
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,18 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Carousel;
|
using osu.Game.Screens.Select.Carousel;
|
||||||
using osu.Game.Tests.Online;
|
using osu.Game.Tests.Online;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
{
|
{
|
||||||
@ -41,17 +45,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("create carousel", () =>
|
AddStep("create carousel", () => Child = createCarousel());
|
||||||
{
|
|
||||||
Child = carousel = new BeatmapCarousel
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
BeatmapSets = new List<BeatmapSetInfo>
|
|
||||||
{
|
|
||||||
(testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo()),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => carousel.BeatmapSetsLoaded);
|
AddUntilStep("wait for load", () => carousel.BeatmapSetsLoaded);
|
||||||
|
|
||||||
@ -152,5 +146,62 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("wait for button enabled", () => getUpdateButton()?.Enabled.Value == true);
|
AddUntilStep("wait for button enabled", () => getUpdateButton()?.Enabled.Value == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUpdateLocalBeatmap()
|
||||||
|
{
|
||||||
|
DialogOverlay dialogOverlay = null!;
|
||||||
|
UpdateBeatmapSetButton? updateButton = null;
|
||||||
|
|
||||||
|
AddStep("create carousel with dialog overlay", () =>
|
||||||
|
{
|
||||||
|
dialogOverlay = new DialogOverlay();
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[] { (typeof(IDialogOverlay), dialogOverlay), },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
createCarousel(),
|
||||||
|
dialogOverlay,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("setup beatmap state", () =>
|
||||||
|
{
|
||||||
|
testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash";
|
||||||
|
testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now;
|
||||||
|
testBeatmapSetInfo.Status = BeatmapOnlineStatus.LocallyModified;
|
||||||
|
|
||||||
|
carousel.UpdateBeatmapSet(testBeatmapSetInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for update button", () => (updateButton = getUpdateButton()) != null);
|
||||||
|
AddStep("click button", () => updateButton.AsNonNull().TriggerClick());
|
||||||
|
|
||||||
|
AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is UpdateLocalConfirmationDialog);
|
||||||
|
AddStep("click confirmation", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().First());
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("update started", () => beatmapDownloader.GetExistingDownload(testBeatmapSetInfo) != null);
|
||||||
|
AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapCarousel createCarousel()
|
||||||
|
{
|
||||||
|
return carousel = new BeatmapCarousel
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
BeatmapSets = new List<BeatmapSetInfo>
|
||||||
|
{
|
||||||
|
(testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Graphics.Cursor;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
@ -81,25 +82,24 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
};
|
};
|
||||||
|
|
||||||
AddToggleStep("Smooth transitions", b => cursorBoxes.ForEach(box => box.SmoothTransition = b));
|
AddToggleStep("Smooth transitions", b => cursorBoxes.ForEach(box => box.SmoothTransition = b));
|
||||||
|
|
||||||
testUserCursor();
|
|
||||||
testLocalCursor();
|
|
||||||
testUserCursorOverride();
|
|
||||||
testMultipleLocalCursors();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(moveOut);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -- Green Box --
|
/// -- Green Box --
|
||||||
/// Tests whether hovering in and out of a drawable that provides the user cursor (green)
|
/// Tests whether hovering in and out of a drawable that provides the user cursor (green)
|
||||||
/// results in the correct visibility state for that cursor.
|
/// results in the correct visibility state for that cursor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void testUserCursor()
|
[Test]
|
||||||
|
public void TestUserCursor()
|
||||||
{
|
{
|
||||||
AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
|
AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
|
||||||
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].MenuCursor));
|
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor));
|
||||||
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].MenuCursor));
|
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
|
||||||
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,15 +108,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
/// Tests whether hovering in and out of a drawable that provides a local cursor (purple)
|
/// Tests whether hovering in and out of a drawable that provides a local cursor (purple)
|
||||||
/// results in the correct visibility and state for that cursor.
|
/// results in the correct visibility and state for that cursor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void testLocalCursor()
|
[Test]
|
||||||
|
public void TestLocalCursor()
|
||||||
{
|
{
|
||||||
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
|
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
|
||||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor));
|
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
|
||||||
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||||
AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor));
|
AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||||
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,47 +126,98 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
/// Tests whether overriding a user cursor (green) with another user cursor (blue)
|
/// Tests whether overriding a user cursor (green) with another user cursor (blue)
|
||||||
/// results in the correct visibility and states for the cursors.
|
/// results in the correct visibility and states for the cursors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void testUserCursorOverride()
|
[Test]
|
||||||
|
public void TestUserCursorOverride()
|
||||||
{
|
{
|
||||||
AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
||||||
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor));
|
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
|
||||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
|
||||||
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor));
|
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].MenuCursor));
|
AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor));
|
||||||
AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -- Yellow-Purple Box Boundary --
|
/// -- Yellow-Purple Box Boundary --
|
||||||
/// Tests whether multiple local cursors (purple + yellow) may be visible and at the mouse position at the same time.
|
/// Tests whether multiple local cursors (purple + yellow) may be visible and at the mouse position at the same time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void testMultipleLocalCursors()
|
[Test]
|
||||||
|
public void TestMultipleLocalCursors()
|
||||||
{
|
{
|
||||||
AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
||||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor));
|
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
|
||||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
||||||
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor));
|
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -- Yellow-Blue Box Boundary --
|
/// -- Yellow-Blue Box Boundary --
|
||||||
/// Tests whether a local cursor (yellow) may be displayed along with a user cursor override (blue).
|
/// Tests whether a local cursor (yellow) may be displayed along with a user cursor override (blue).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void testUserOverrideWithLocal()
|
[Test]
|
||||||
|
public void TestUserOverrideWithLocal()
|
||||||
{
|
{
|
||||||
AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10)));
|
AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10, 0)));
|
||||||
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor));
|
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
|
||||||
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor));
|
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
|
||||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
||||||
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor));
|
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].MenuCursor));
|
AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor));
|
||||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures non-mouse input hides global cursor on a "local cursor" area (which doesn't hide global cursor).
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestKeyboardLocalCursor([Values] bool clickToShow)
|
||||||
|
{
|
||||||
|
AddStep("Enable cursor hiding", () => globalCursorDisplay.MenuCursor.HideCursorOnNonMouseInput = true);
|
||||||
|
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3].ScreenSpaceDrawQuad.Centre + new Vector2(10, 0)));
|
||||||
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||||
|
AddAssert("Check global cursor alpha is 1", () => globalCursorDisplay.MenuCursor.Alpha == 1);
|
||||||
|
|
||||||
|
AddStep("Press key", () => InputManager.Key(Key.A));
|
||||||
|
AddAssert("Check purple cursor still visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||||
|
AddUntilStep("Check global cursor alpha is 0", () => globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0);
|
||||||
|
|
||||||
|
if (clickToShow)
|
||||||
|
AddStep("Click mouse", () => InputManager.Click(MouseButton.Left));
|
||||||
|
else
|
||||||
|
AddStep("Move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + Vector2.One));
|
||||||
|
|
||||||
|
AddAssert("Check purple cursor still visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||||
|
AddUntilStep("Check global cursor alpha is 1", () => globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures mouse input after non-mouse input doesn't show global cursor on a "user cursor" area (which hides global cursor).
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestKeyboardUserCursor([Values] bool clickToShow)
|
||||||
|
{
|
||||||
|
AddStep("Enable cursor hiding", () => globalCursorDisplay.MenuCursor.HideCursorOnNonMouseInput = true);
|
||||||
|
AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
|
||||||
|
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor));
|
||||||
|
AddAssert("Check global cursor alpha is 0", () => !checkVisible(globalCursorDisplay.MenuCursor) && globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0);
|
||||||
|
|
||||||
|
AddStep("Press key", () => InputManager.Key(Key.A));
|
||||||
|
AddAssert("Check green cursor still visible", () => checkVisible(cursorBoxes[0].Cursor));
|
||||||
|
AddAssert("Check global cursor alpha is still 0", () => !checkVisible(globalCursorDisplay.MenuCursor) && globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0);
|
||||||
|
|
||||||
|
if (clickToShow)
|
||||||
|
AddStep("Click mouse", () => InputManager.Click(MouseButton.Left));
|
||||||
|
else
|
||||||
|
AddStep("Move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + Vector2.One));
|
||||||
|
|
||||||
|
AddAssert("Check green cursor still visible", () => checkVisible(cursorBoxes[0].Cursor));
|
||||||
|
AddAssert("Check global cursor alpha is still 0", () => !checkVisible(globalCursorDisplay.MenuCursor) && globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -191,7 +243,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
public bool SmoothTransition;
|
public bool SmoothTransition;
|
||||||
|
|
||||||
public CursorContainer MenuCursor { get; }
|
public CursorContainer Cursor { get; }
|
||||||
public bool ProvidingUserCursor { get; }
|
public bool ProvidingUserCursor { get; }
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor);
|
||||||
@ -218,7 +270,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = providesUserCursor ? "User cursor" : "Local cursor"
|
Text = providesUserCursor ? "User cursor" : "Local cursor"
|
||||||
},
|
},
|
||||||
MenuCursor = new TestCursorContainer
|
Cursor = new TestCursorContainer
|
||||||
{
|
{
|
||||||
State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible },
|
State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible },
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,14 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
@ -20,11 +22,17 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
private SettingsToolboxGroup group;
|
private SettingsToolboxGroup group;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
Child = group = new SettingsToolboxGroup("example")
|
Child = group = new SettingsToolboxGroup("example")
|
||||||
{
|
{
|
||||||
|
Scale = new Vector2(3),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new RoundedButton
|
new RoundedButton
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace osu.Game.Tournament
|
namespace osu.Game.Tournament
|
||||||
@ -31,7 +32,9 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
Debug.Assert(str != null);
|
Debug.Assert(str != null);
|
||||||
|
|
||||||
return new PointConverter().ConvertFromString(str) as Point? ?? new Point();
|
// Null check suppression is required due to .NET standard expecting a non-null context.
|
||||||
|
// Seems to work fine at a runtime level (and the parameter is nullable in .NET 6+).
|
||||||
|
return new PointConverter().ConvertFromString(null!, CultureInfo.InvariantCulture, str) as Point? ?? new Point();
|
||||||
}
|
}
|
||||||
|
|
||||||
var point = new Point();
|
var point = new Point();
|
||||||
|
@ -239,17 +239,17 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
|
|
||||||
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = Model.ID });
|
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = Model.ID });
|
||||||
|
|
||||||
req.Success += res =>
|
req.Success += res => Schedule(() =>
|
||||||
{
|
{
|
||||||
Model.Beatmap = new TournamentBeatmap(res);
|
Model.Beatmap = new TournamentBeatmap(res);
|
||||||
updatePanel();
|
updatePanel();
|
||||||
};
|
});
|
||||||
|
|
||||||
req.Failure += _ =>
|
req.Failure += _ => Schedule(() =>
|
||||||
{
|
{
|
||||||
Model.Beatmap = null;
|
Model.Beatmap = null;
|
||||||
updatePanel();
|
updatePanel();
|
||||||
};
|
});
|
||||||
|
|
||||||
API.Queue(req);
|
API.Queue(req);
|
||||||
}, true);
|
}, true);
|
||||||
|
@ -141,18 +141,9 @@ namespace osu.Game.Beatmaps
|
|||||||
// Handle collections using permissive difficulty name to track difficulties.
|
// Handle collections using permissive difficulty name to track difficulties.
|
||||||
foreach (var originalBeatmap in original.Beatmaps)
|
foreach (var originalBeatmap in original.Beatmaps)
|
||||||
{
|
{
|
||||||
var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.DifficultyName == originalBeatmap.DifficultyName);
|
updated.Beatmaps
|
||||||
|
.FirstOrDefault(b => b.DifficultyName == originalBeatmap.DifficultyName)?
|
||||||
if (updatedBeatmap == null)
|
.TransferCollectionReferences(realm, originalBeatmap.MD5Hash);
|
||||||
continue;
|
|
||||||
|
|
||||||
var collections = realm.All<BeatmapCollection>().AsEnumerable().Where(c => c.BeatmapMD5Hashes.Contains(originalBeatmap.MD5Hash));
|
|
||||||
|
|
||||||
foreach (var c in collections)
|
|
||||||
{
|
|
||||||
c.BeatmapMD5Hashes.Remove(originalBeatmap.MD5Hash);
|
|
||||||
c.BeatmapMD5Hashes.Add(updatedBeatmap.MD5Hash);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ using JetBrains.Annotations;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -213,6 +214,23 @@ namespace osu.Game.Beatmaps
|
|||||||
return fileHashX == fileHashY;
|
return fileHashX == fileHashY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When updating a beatmap, its hashes will change. Collections currently track beatmaps by hash, so they need to be updated.
|
||||||
|
/// This method will handle updating
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="realm">A realm instance in an active write transaction.</param>
|
||||||
|
/// <param name="previousMD5Hash">The previous MD5 hash of the beatmap before update.</param>
|
||||||
|
public void TransferCollectionReferences(Realm realm, string previousMD5Hash)
|
||||||
|
{
|
||||||
|
var collections = realm.All<BeatmapCollection>().AsEnumerable().Where(c => c.BeatmapMD5Hashes.Contains(previousMD5Hash));
|
||||||
|
|
||||||
|
foreach (var c in collections)
|
||||||
|
{
|
||||||
|
c.BeatmapMD5Hashes.Remove(previousMD5Hash);
|
||||||
|
c.BeatmapMD5Hashes.Add(MD5Hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
|
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
|
||||||
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;
|
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;
|
||||||
IRulesetInfo IBeatmapInfo.Ruleset => Ruleset;
|
IRulesetInfo IBeatmapInfo.Ruleset => Ruleset;
|
||||||
|
@ -311,6 +311,8 @@ namespace osu.Game.Beatmaps
|
|||||||
if (existingFileInfo != null)
|
if (existingFileInfo != null)
|
||||||
DeleteFile(setInfo, existingFileInfo);
|
DeleteFile(setInfo, existingFileInfo);
|
||||||
|
|
||||||
|
string oldMd5Hash = beatmapInfo.MD5Hash;
|
||||||
|
|
||||||
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
||||||
beatmapInfo.Hash = stream.ComputeSHA2Hash();
|
beatmapInfo.Hash = stream.ComputeSHA2Hash();
|
||||||
|
|
||||||
@ -327,6 +329,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
setInfo.CopyChangesToRealm(liveBeatmapSet);
|
setInfo.CopyChangesToRealm(liveBeatmapSet);
|
||||||
|
|
||||||
|
beatmapInfo.TransferCollectionReferences(r, oldMd5Hash);
|
||||||
|
|
||||||
ProcessBeatmap?.Invoke((liveBeatmapSet, false));
|
ProcessBeatmap?.Invoke((liveBeatmapSet, false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,8 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps.ControlPoints
|
namespace osu.Game.Beatmaps.ControlPoints
|
||||||
{
|
{
|
||||||
public abstract class ControlPoint : IComparable<ControlPoint>, IDeepCloneable<ControlPoint>, IEquatable<ControlPoint>
|
public abstract class ControlPoint : IComparable<ControlPoint>, IDeepCloneable<ControlPoint>, IEquatable<ControlPoint>, IControlPoint
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The time at which the control point takes effect.
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double Time { get; set; }
|
public double Time { get; set; }
|
||||||
|
|
||||||
|
@ -196,8 +196,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// <param name="time">The time to find the control point at.</param>
|
/// <param name="time">The time to find the control point at.</param>
|
||||||
/// <param name="fallback">The control point to use when <paramref name="time"/> is before any control points.</param>
|
/// <param name="fallback">The control point to use when <paramref name="time"/> is before any control points.</param>
|
||||||
/// <returns>The active control point at <paramref name="time"/>, or a fallback <see cref="ControlPoint"/> if none found.</returns>
|
/// <returns>The active control point at <paramref name="time"/>, or a fallback <see cref="ControlPoint"/> if none found.</returns>
|
||||||
protected T BinarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T fallback)
|
public static T BinarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T fallback)
|
||||||
where T : ControlPoint
|
where T : class, IControlPoint
|
||||||
{
|
{
|
||||||
return BinarySearch(list, time) ?? fallback;
|
return BinarySearch(list, time) ?? fallback;
|
||||||
}
|
}
|
||||||
@ -207,9 +207,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="list">The list to search.</param>
|
/// <param name="list">The list to search.</param>
|
||||||
/// <param name="time">The time to find the control point at.</param>
|
/// <param name="time">The time to find the control point at.</param>
|
||||||
/// <returns>The active control point at <paramref name="time"/>.</returns>
|
/// <returns>The active control point at <paramref name="time"/>. Will return <c>null</c> if there are no control points, or if the time is before the first control point.</returns>
|
||||||
protected virtual T BinarySearch<T>(IReadOnlyList<T> list, double time)
|
public static T BinarySearch<T>(IReadOnlyList<T> list, double time)
|
||||||
where T : ControlPoint
|
where T : class, IControlPoint
|
||||||
{
|
{
|
||||||
if (list == null)
|
if (list == null)
|
||||||
throw new ArgumentNullException(nameof(list));
|
throw new ArgumentNullException(nameof(list));
|
||||||
|
13
osu.Game/Beatmaps/ControlPoints/IControlPoint.cs
Normal file
13
osu.Game/Beatmaps/ControlPoints/IControlPoint.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.ControlPoints
|
||||||
|
{
|
||||||
|
public interface IControlPoint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which the control point takes effect.
|
||||||
|
/// </summary>
|
||||||
|
double Time { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -355,6 +355,14 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
|
case LegacyEventType.Sprite:
|
||||||
|
// Generally, the background is the first thing defined in a beatmap file.
|
||||||
|
// In some older beatmaps, it is not present and replaced by a storyboard-level background instead.
|
||||||
|
// Allow the first sprite (by file order) to act as the background in such cases.
|
||||||
|
if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
|
||||||
|
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]);
|
||||||
|
break;
|
||||||
|
|
||||||
case LegacyEventType.Background:
|
case LegacyEventType.Background:
|
||||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
|
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
|
||||||
break;
|
break;
|
||||||
|
@ -134,6 +134,6 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
|
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void PrepareTrackForPreview(bool looping);
|
void PrepareTrackForPreview(bool looping, double offsetFromPreviewPoint = 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public Track LoadTrack() => track = GetBeatmapTrack() ?? GetVirtualTrack(1000);
|
public Track LoadTrack() => track = GetBeatmapTrack() ?? GetVirtualTrack(1000);
|
||||||
|
|
||||||
public void PrepareTrackForPreview(bool looping)
|
public void PrepareTrackForPreview(bool looping, double offsetFromPreviewPoint = 0)
|
||||||
{
|
{
|
||||||
Track.Looping = looping;
|
Track.Looping = looping;
|
||||||
Track.RestartPoint = Metadata.PreviewTime;
|
Track.RestartPoint = Metadata.PreviewTime;
|
||||||
@ -125,6 +125,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
Track.RestartPoint = 0.4f * Track.Length;
|
Track.RestartPoint = 0.4f * Track.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Track.RestartPoint += offsetFromPreviewPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
public bool Download(T model, bool minimiseDownloadSize = false) => Download(model, minimiseDownloadSize, null);
|
public bool Download(T model, bool minimiseDownloadSize = false) => Download(model, minimiseDownloadSize, null);
|
||||||
|
|
||||||
public void DownloadAsUpdate(TModel originalModel) => Download(originalModel, false, originalModel);
|
public void DownloadAsUpdate(TModel originalModel, bool minimiseDownloadSize) => Download(originalModel, minimiseDownloadSize, originalModel);
|
||||||
|
|
||||||
protected bool Download(T model, bool minimiseDownloadSize, TModel? originalModel)
|
protected bool Download(T model, bool minimiseDownloadSize, TModel? originalModel)
|
||||||
{
|
{
|
||||||
|
@ -294,15 +294,38 @@ namespace osu.Game.Database
|
|||||||
// Log output here will be missing a valid hash in non-batch imports.
|
// Log output here will be missing a valid hash in non-batch imports.
|
||||||
LogForModel(item, $@"Beginning import from {archive?.Name ?? "unknown"}...");
|
LogForModel(item, $@"Beginning import from {archive?.Name ?? "unknown"}...");
|
||||||
|
|
||||||
|
List<RealmNamedFileUsage> files = new List<RealmNamedFileUsage>();
|
||||||
|
|
||||||
|
if (archive != null)
|
||||||
|
{
|
||||||
|
// Import files to the disk store.
|
||||||
|
// We intentionally delay adding to realm to avoid blocking on a write during disk operations.
|
||||||
|
foreach (var filenames in getShortenedFilenames(archive))
|
||||||
|
{
|
||||||
|
using (Stream s = archive.GetStream(filenames.original))
|
||||||
|
files.Add(new RealmNamedFileUsage(Files.Add(s, realm, false), filenames.shortened));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var transaction = realm.BeginWrite())
|
||||||
|
{
|
||||||
|
// Add all files to realm in one go.
|
||||||
|
// This is done ahead of the main transaction to ensure we can correctly cleanup the files, even if the import fails.
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
if (!file.File.IsManaged)
|
||||||
|
realm.Add(file.File, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Files.AddRange(files);
|
||||||
|
item.Hash = ComputeHash(item);
|
||||||
|
|
||||||
// TODO: do we want to make the transaction this local? not 100% sure, will need further investigation.
|
// TODO: do we want to make the transaction this local? not 100% sure, will need further investigation.
|
||||||
using (var transaction = realm.BeginWrite())
|
using (var transaction = realm.BeginWrite())
|
||||||
{
|
{
|
||||||
if (archive != null)
|
|
||||||
// TODO: look into rollback of file additions (or delayed commit).
|
|
||||||
item.Files.AddRange(createFileInfos(archive, Files, realm));
|
|
||||||
|
|
||||||
item.Hash = ComputeHash(item);
|
|
||||||
|
|
||||||
// TODO: we may want to run this outside of the transaction.
|
// TODO: we may want to run this outside of the transaction.
|
||||||
Populate(item, archive, realm, cancellationToken);
|
Populate(item, archive, realm, cancellationToken);
|
||||||
|
|
||||||
@ -425,16 +448,6 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
var fileInfos = new List<RealmNamedFileUsage>();
|
var fileInfos = new List<RealmNamedFileUsage>();
|
||||||
|
|
||||||
// import files to manager
|
|
||||||
foreach (var filenames in getShortenedFilenames(reader))
|
|
||||||
{
|
|
||||||
using (Stream s = reader.GetStream(filenames.original))
|
|
||||||
{
|
|
||||||
var item = new RealmNamedFileUsage(files.Add(s, realm), filenames.shortened);
|
|
||||||
fileInfos.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileInfos;
|
return fileInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,8 +40,8 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">The file data stream.</param>
|
/// <param name="data">The file data stream.</param>
|
||||||
/// <param name="realm">The realm instance to add to. Should already be in a transaction.</param>
|
/// <param name="realm">The realm instance to add to. Should already be in a transaction.</param>
|
||||||
/// <returns></returns>
|
/// <param name="addToRealm">Whether the <see cref="RealmFile"/> should immediately be added to the underlying realm. If <c>false</c> is provided here, the instance must be manually added.</param>
|
||||||
public RealmFile Add(Stream data, Realm realm)
|
public RealmFile Add(Stream data, Realm realm, bool addToRealm = true)
|
||||||
{
|
{
|
||||||
string hash = data.ComputeSHA2Hash();
|
string hash = data.ComputeSHA2Hash();
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ namespace osu.Game.Database
|
|||||||
if (!checkFileExistsAndMatchesHash(file))
|
if (!checkFileExistsAndMatchesHash(file))
|
||||||
copyToStore(file, data);
|
copyToStore(file, data);
|
||||||
|
|
||||||
if (!file.IsManaged)
|
if (addToRealm && !file.IsManaged)
|
||||||
realm.Add(file);
|
realm.Add(file);
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user