mirror of
https://github.com/ppy/osu.git
synced 2024-11-06 09:07:25 +08:00
Merge branch 'master' into sv_to_scrollspeed
This commit is contained in:
commit
73a9fac6d2
@ -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.1008.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1021.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1011.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@ -10,10 +11,11 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
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;
|
||||||
@ -21,7 +23,6 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -33,10 +34,14 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
private CatchDistanceSnapGrid distanceSnapGrid;
|
private CatchDistanceSnapGrid distanceSnapGrid;
|
||||||
|
|
||||||
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
|
||||||
|
|
||||||
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 +56,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 }
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -70,6 +78,19 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||||
|
{
|
||||||
|
// osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
|
||||||
|
// Therefore this functionality is not currently used.
|
||||||
|
//
|
||||||
|
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
||||||
|
|
||||||
|
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||||
|
float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
|
||||||
|
|
||||||
|
return actualDistance / expectedDistance;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -77,8 +98,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[]
|
||||||
{
|
{
|
||||||
@ -87,11 +130,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
new BananaShowerCompositionTool()
|
new BananaShowerCompositionTool()
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
|
||||||
{
|
|
||||||
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
|
|
||||||
});
|
|
||||||
|
|
||||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||||
{
|
{
|
||||||
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||||
@ -163,7 +201,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
private void updateDistanceSnapGrid()
|
private void updateDistanceSnapGrid()
|
||||||
{
|
{
|
||||||
if (distanceSnapToggle.Value != TernaryState.True)
|
if (DistanceSnapToggle.Value != TernaryState.True)
|
||||||
{
|
{
|
||||||
distanceSnapGrid.Hide();
|
distanceSnapGrid.Hide();
|
||||||
return;
|
return;
|
||||||
|
@ -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);
|
||||||
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.LargeTickMiss);
|
assertTickJudgement(HitResult.LargeTickMiss);
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
assertNoteJudgement(HitResult.IgnoreHit);
|
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
performTest(objects, new List<ReplayFrame>());
|
performTest(objects, new List<ReplayFrame>());
|
||||||
|
|
||||||
addJudgementAssert(objects[0], HitResult.IgnoreHit);
|
addJudgementAssert(objects[0], HitResult.IgnoreMiss);
|
||||||
addJudgementAssert(objects[1], HitResult.IgnoreHit);
|
addJudgementAssert(objects[1], HitResult.IgnoreMiss);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -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";
|
||||||
|
@ -69,6 +69,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private double? releaseTime;
|
private double? releaseTime;
|
||||||
|
|
||||||
|
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||||
|
|
||||||
public DrawableHoldNote()
|
public DrawableHoldNote()
|
||||||
: this(null)
|
: this(null)
|
||||||
{
|
{
|
||||||
@ -260,7 +262,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
tick.MissForcefully();
|
tick.MissForcefully();
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||||
endHold();
|
endHold();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -4,14 +4,18 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Testing;
|
||||||
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 +37,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;
|
||||||
|
|
||||||
@ -48,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
};
|
};
|
||||||
|
|
||||||
private OsuDistanceSnapGrid grid;
|
private OsuDistanceSnapGrid grid;
|
||||||
|
private SnappingCursorContainer cursor;
|
||||||
|
|
||||||
public TestSceneOsuDistanceSnapGrid()
|
public TestSceneOsuDistanceSnapGrid()
|
||||||
{
|
{
|
||||||
@ -84,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.SlateGray
|
Colour = Color4.SlateGray
|
||||||
},
|
},
|
||||||
|
cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
|
||||||
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
|
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
|
||||||
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -150,6 +158,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertSnappedDistance(expectedDistance);
|
assertSnappedDistance(expectedDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReferenceObjectNotOnSnapGrid()
|
||||||
|
{
|
||||||
|
AddStep("create grid", () =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.SlateGray
|
||||||
|
},
|
||||||
|
cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
|
||||||
|
grid = new OsuDistanceSnapGrid(new HitCircle
|
||||||
|
{
|
||||||
|
Position = grid_position,
|
||||||
|
// This is important. It sets the reference object to a point in time that isn't on the current snap divisor's grid.
|
||||||
|
// We are testing that the grid's display is offset correctly.
|
||||||
|
StartTime = 40,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse to point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 2)));
|
||||||
|
|
||||||
|
AddAssert("Ensure cursor is on a grid line", () =>
|
||||||
|
{
|
||||||
|
return grid.ChildrenOfType<CircularProgress>().Any(p => Precision.AlmostEquals(p.ScreenSpaceDrawQuad.TopRight.X, grid.ToScreenSpace(cursor.LastSnappedPosition).X));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLimitedDistance()
|
public void TestLimitedDistance()
|
||||||
{
|
{
|
||||||
@ -162,8 +201,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.SlateGray
|
Colour = Color4.SlateGray
|
||||||
},
|
},
|
||||||
|
cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
|
||||||
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
|
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
|
||||||
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -182,6 +221,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
public Func<Vector2, Vector2> GetSnapPosition;
|
public Func<Vector2, Vector2> GetSnapPosition;
|
||||||
|
|
||||||
|
public Vector2 LastSnappedPosition { get; private set; }
|
||||||
|
|
||||||
private readonly Drawable cursor;
|
private readonly Drawable cursor;
|
||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
@ -210,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
cursor.Position = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
|
cursor.Position = LastSnappedPosition = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,20 +20,49 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGridExclusivity()
|
public void TestGridToggles()
|
||||||
{
|
{
|
||||||
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
||||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
|
|
||||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(false);
|
rectangularGridActive(false);
|
||||||
|
|
||||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
|
||||||
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
|
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(true);
|
rectangularGridActive(true);
|
||||||
|
|
||||||
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
|
||||||
|
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
|
rectangularGridActive(true);
|
||||||
|
|
||||||
|
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
|
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
|
rectangularGridActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDistanceSnapMomentaryToggle()
|
||||||
|
{
|
||||||
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
|
|
||||||
|
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
|
AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft));
|
||||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
|
AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft));
|
||||||
|
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGridSnapMomentaryToggle()
|
||||||
|
{
|
||||||
|
rectangularGridActive(false);
|
||||||
|
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||||
|
rectangularGridActive(true);
|
||||||
|
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||||
rectangularGridActive(false);
|
rectangularGridActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +79,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(0, 0)));
|
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(0, 0)));
|
||||||
else
|
else
|
||||||
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(1, 1)));
|
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(1, 1)));
|
||||||
|
|
||||||
AddStep("choose selection tool", () => InputManager.Key(Key.Number1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
// 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 TestSceneOsuModFreezeFrame : OsuModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestFreezeFrame()
|
||||||
|
{
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModFreezeFrame(),
|
||||||
|
PassCondition = () => true,
|
||||||
|
Autoplay = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -13,8 +13,11 @@ using osu.Framework.Extensions.EnumExtensions;
|
|||||||
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;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
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.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -44,12 +47,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
new SpinnerCompositionTool()
|
new SpinnerCompositionTool()
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
|
||||||
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
||||||
|
|
||||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
||||||
{
|
{
|
||||||
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }),
|
|
||||||
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
|
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,6 +61,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
|
||||||
@ -77,19 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
||||||
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||||
distanceSnapToggle.ValueChanged += _ =>
|
DistanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
||||||
{
|
|
||||||
updateDistanceSnapGrid();
|
|
||||||
|
|
||||||
if (distanceSnapToggle.Value == TernaryState.True)
|
|
||||||
rectangularGridSnapToggle.Value = TernaryState.False;
|
|
||||||
};
|
|
||||||
|
|
||||||
rectangularGridSnapToggle.ValueChanged += _ =>
|
|
||||||
{
|
|
||||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
|
||||||
distanceSnapToggle.Value = TernaryState.False;
|
|
||||||
};
|
|
||||||
|
|
||||||
// we may be entering the screen with a selection already active
|
// we may be entering the screen with a selection already active
|
||||||
updateDistanceSnapGrid();
|
updateDistanceSnapGrid();
|
||||||
@ -109,6 +101,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
||||||
|
|
||||||
|
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||||
|
{
|
||||||
|
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||||
|
float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position);
|
||||||
|
|
||||||
|
return actualDistance / expectedDistance;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -129,24 +129,46 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||||
{
|
{
|
||||||
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||||
|
{
|
||||||
|
// In the case of snapping to nearby objects, a time value is not provided.
|
||||||
|
// This matches the stable editor (which also uses current time), but with the introduction of time-snapping distance snap
|
||||||
|
// this could result in unexpected behaviour when distance snapping is turned on and a user attempts to place an object that is
|
||||||
|
// BOTH on a valid distance snap ring, and also at the same position as a previous object.
|
||||||
|
//
|
||||||
|
// We want to ensure that in this particular case, the time-snapping component of distance snap is still applied.
|
||||||
|
// The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over
|
||||||
|
// the time value if the proposed positions are roughly the same.
|
||||||
|
if (snapType.HasFlagFast(SnapType.Grids) && DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||||
|
{
|
||||||
|
(Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition));
|
||||||
|
if (Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1))
|
||||||
|
snapResult.Time = distanceSnappedTime;
|
||||||
|
}
|
||||||
|
|
||||||
return snapResult;
|
return snapResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||||
|
|
||||||
if (snapType.HasFlagFast(SnapType.Grids))
|
if (snapType.HasFlagFast(SnapType.Grids))
|
||||||
{
|
{
|
||||||
if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
if (DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||||
{
|
{
|
||||||
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||||
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
|
||||||
|
result.ScreenSpacePosition = distanceSnapGrid.ToScreenSpace(pos);
|
||||||
|
result.Time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||||
{
|
{
|
||||||
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition));
|
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
||||||
return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
|
||||||
|
result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
|
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
|
||||||
@ -199,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
distanceSnapGridCache.Invalidate();
|
distanceSnapGridCache.Invalidate();
|
||||||
distanceSnapGrid = null;
|
distanceSnapGrid = null;
|
||||||
|
|
||||||
if (distanceSnapToggle.Value != TernaryState.True)
|
if (DistanceSnapToggle.Value != TernaryState.True)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch (BlueprintContainer.CurrentTool)
|
switch (BlueprintContainer.CurrentTool)
|
||||||
@ -226,6 +248,42 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
handleToggleViaKey(e);
|
||||||
|
return base.OnKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyUp(KeyUpEvent e)
|
||||||
|
{
|
||||||
|
handleToggleViaKey(e);
|
||||||
|
base.OnKeyUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AdjustDistanceSpacing(GlobalAction action, float amount)
|
||||||
|
{
|
||||||
|
// To allow better visualisation, ensure that the spacing grid is visible before adjusting.
|
||||||
|
DistanceSnapToggle.Value = TernaryState.True;
|
||||||
|
|
||||||
|
return base.AdjustDistanceSpacing(action, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool gridSnapMomentary;
|
||||||
|
|
||||||
|
private void handleToggleViaKey(KeyboardEvent key)
|
||||||
|
{
|
||||||
|
bool shiftPressed = key.ShiftPressed;
|
||||||
|
|
||||||
|
if (shiftPressed != gridSnapMomentary)
|
||||||
|
{
|
||||||
|
gridSnapMomentary = shiftPressed;
|
||||||
|
rectangularGridSnapToggle.Value = rectangularGridSnapToggle.Value == TernaryState.False ? TernaryState.True : TernaryState.False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private DistanceSnapGrid createDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
|
private DistanceSnapGrid createDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
|
||||||
{
|
{
|
||||||
if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
|
if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
|
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
|
||||||
|
|
||||||
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
|
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
|
||||||
public BindableFloat Scale { get; } = new BindableFloat(4)
|
public BindableFloat Scale { get; } = new BindableFloat(4)
|
||||||
|
@ -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";
|
||||||
|
89
osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
Normal file
89
osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModFreezeFrame : Mod, IApplicableToDrawableHitObject, IApplicableToBeatmap
|
||||||
|
{
|
||||||
|
public override string Name => "Freeze Frame";
|
||||||
|
|
||||||
|
public override string Acronym => "FR";
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||||
|
|
||||||
|
//Alters the transforms of the approach circles, breaking the effects of these mods.
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModApproachDifferent) };
|
||||||
|
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
|
||||||
|
//mod breaks normal approach circle preempt
|
||||||
|
private double originalPreempt;
|
||||||
|
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var firstHitObject = beatmap.HitObjects.OfType<OsuHitObject>().FirstOrDefault();
|
||||||
|
if (firstHitObject == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double lastNewComboTime = 0;
|
||||||
|
|
||||||
|
originalPreempt = firstHitObject.TimePreempt;
|
||||||
|
|
||||||
|
foreach (var obj in beatmap.HitObjects.OfType<OsuHitObject>())
|
||||||
|
{
|
||||||
|
if (obj.NewCombo) { lastNewComboTime = obj.StartTime; }
|
||||||
|
|
||||||
|
applyFadeInAdjustment(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyFadeInAdjustment(OsuHitObject osuObject)
|
||||||
|
{
|
||||||
|
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
|
||||||
|
|
||||||
|
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
||||||
|
{
|
||||||
|
switch (nested)
|
||||||
|
{
|
||||||
|
//SliderRepeat wont layer correctly if preempt is changed.
|
||||||
|
case SliderRepeat:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
applyFadeInAdjustment(nested);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
drawableObject.ApplyCustomUpdateState += (drawableHitObject, _) =>
|
||||||
|
{
|
||||||
|
if (drawableHitObject is not DrawableHitCircle drawableHitCircle) return;
|
||||||
|
|
||||||
|
var hitCircle = drawableHitCircle.HitObject;
|
||||||
|
var approachCircle = drawableHitCircle.ApproachCircle;
|
||||||
|
|
||||||
|
// Reapply scale, ensuring the AR isn't changed due to the new preempt.
|
||||||
|
approachCircle.ClearTransforms(targetMember: nameof(approachCircle.Scale));
|
||||||
|
approachCircle.ScaleTo(4 * (float)(hitCircle.TimePreempt / originalPreempt));
|
||||||
|
|
||||||
|
using (drawableHitCircle.ApproachCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt))
|
||||||
|
approachCircle.ScaleTo(1, hitCircle.TimePreempt).Then().Expire();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -201,7 +201,8 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModMuted(),
|
new OsuModMuted(),
|
||||||
new OsuModNoScope(),
|
new OsuModNoScope(),
|
||||||
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||||
new ModAdaptiveSpeed()
|
new ModAdaptiveSpeed(),
|
||||||
|
new OsuModFreezeFrame()
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
|
@ -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,56 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
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 });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFlashlightAlwaysHasNonZeroSize()
|
||||||
|
{
|
||||||
|
bool failed = false;
|
||||||
|
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TestTaikoModFlashlight { ComboBasedSize = { Value = true } },
|
||||||
|
Autoplay = false,
|
||||||
|
PassCondition = () =>
|
||||||
|
{
|
||||||
|
failed |= this.ChildrenOfType<TestTaikoModFlashlight.TestTaikoFlashlight>().SingleOrDefault()?.FlashlightSize.Y == 0;
|
||||||
|
return !failed;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTaikoModFlashlight : TaikoModFlashlight
|
||||||
|
{
|
||||||
|
protected override Flashlight CreateFlashlight() => new TestTaikoFlashlight(this, Playfield);
|
||||||
|
|
||||||
|
public class TestTaikoFlashlight : TaikoFlashlight
|
||||||
|
{
|
||||||
|
public TestTaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield)
|
||||||
|
: base(modFlashlight, taikoPlayfield)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new Vector2 FlashlightSize => base.FlashlightSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
|
|
||||||
public override float DefaultFlashlightSize => 200;
|
public override float DefaultFlashlightSize => 200;
|
||||||
|
|
||||||
protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield);
|
protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, Playfield);
|
||||||
|
|
||||||
private TaikoPlayfield playfield = null!;
|
protected TaikoPlayfield Playfield { get; private set; } = null!;
|
||||||
|
|
||||||
public override void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
public override void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
Playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||||
base.ApplyToDrawableRuleset(drawableRuleset);
|
base.ApplyToDrawableRuleset(drawableRuleset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TaikoFlashlight : Flashlight
|
public class TaikoFlashlight : Flashlight
|
||||||
{
|
{
|
||||||
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);
|
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);
|
||||||
private readonly TaikoPlayfield taikoPlayfield;
|
private readonly TaikoPlayfield taikoPlayfield;
|
||||||
@ -47,21 +47,28 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
this.taikoPlayfield = taikoPlayfield;
|
this.taikoPlayfield = taikoPlayfield;
|
||||||
|
|
||||||
FlashlightSize = getSizeFor(0);
|
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||||
FlashlightSmoothness = 1.4f;
|
FlashlightSmoothness = 1.4f;
|
||||||
|
|
||||||
AddLayout(flashlightProperties);
|
AddLayout(flashlightProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 getSizeFor(int combo)
|
/// <summary>
|
||||||
|
/// Returns the aspect ratio-adjusted size of the flashlight.
|
||||||
|
/// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">
|
||||||
|
/// The size of the flashlight.
|
||||||
|
/// The value provided here should always come from <see cref="ModFlashlight{T}.Flashlight.GetSize"/>.
|
||||||
|
/// </param>
|
||||||
|
private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
|
||||||
{
|
{
|
||||||
// Preserve flashlight size through the playfield's aspect adjustment.
|
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
|
||||||
return new Vector2(0, GetSizeFor(combo) * 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), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string FragmentShader => "CircularFlashlight";
|
protected override string FragmentShader => "CircularFlashlight";
|
||||||
@ -75,7 +82,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 = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||||
|
|
||||||
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));
|
||||||
|
@ -106,6 +106,49 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
assertBeatSnap(16);
|
assertBeatSnap(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyboardNavigation()
|
||||||
|
{
|
||||||
|
pressKey(1);
|
||||||
|
assertBeatSnap(1);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
|
pressKey(2);
|
||||||
|
assertBeatSnap(2);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
|
pressKey(3);
|
||||||
|
assertBeatSnap(3);
|
||||||
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
|
|
||||||
|
pressKey(4);
|
||||||
|
assertBeatSnap(4);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
|
pressKey(5);
|
||||||
|
assertBeatSnap(5);
|
||||||
|
assertPreset(BeatDivisorType.Custom, 5);
|
||||||
|
|
||||||
|
pressKey(6);
|
||||||
|
assertBeatSnap(6);
|
||||||
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
|
|
||||||
|
pressKey(7);
|
||||||
|
assertBeatSnap(7);
|
||||||
|
assertPreset(BeatDivisorType.Custom, 7);
|
||||||
|
|
||||||
|
pressKey(8);
|
||||||
|
assertBeatSnap(8);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
|
void pressKey(int key) => AddStep($"press shift+{key}", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ShiftLeft);
|
||||||
|
InputManager.Key(Key.Number0 + key);
|
||||||
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBeatPresetNavigation()
|
public void TestBeatPresetNavigation()
|
||||||
{
|
{
|
||||||
|
32
osu.Game.Tests/Visual/Editing/TestSceneEditorBindings.cs
Normal file
32
osu.Game.Tests/Visual/Editing/TestSceneEditorBindings.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test editor hotkeys at a high level to ensure they all work well together.
|
||||||
|
/// </summary>
|
||||||
|
public class TestSceneEditorBindings : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatDivisorChangeHotkeys()
|
||||||
|
{
|
||||||
|
AddStep("hold shift", () => InputManager.PressKey(Key.LShift));
|
||||||
|
|
||||||
|
AddStep("press 4", () => InputManager.Key(Key.Number4));
|
||||||
|
AddAssert("snap updated to 4", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(4));
|
||||||
|
|
||||||
|
AddStep("press 6", () => InputManager.Key(Key.Number6));
|
||||||
|
AddAssert("snap updated to 6", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(6));
|
||||||
|
|
||||||
|
AddStep("release shift", () => InputManager.ReleaseKey(Key.LShift));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -155,6 +155,20 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClone()
|
||||||
|
{
|
||||||
|
var addedObject = new HitCircle { StartTime = 1000 };
|
||||||
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||||
|
|
||||||
|
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
AddStep("clone", () => Editor.Clone());
|
||||||
|
AddAssert("is two objects", () => EditorBeatmap.HitObjects.Count == 2);
|
||||||
|
AddStep("clone", () => Editor.Clone());
|
||||||
|
AddAssert("is three objects", () => EditorBeatmap.HitObjects.Count == 3);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCutNothing()
|
public void TestCutNothing()
|
||||||
{
|
{
|
||||||
@ -175,5 +189,22 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("paste hitobject", () => Editor.Paste());
|
AddStep("paste hitobject", () => Editor.Paste());
|
||||||
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCloneNothing()
|
||||||
|
{
|
||||||
|
// Add arbitrary object and copy to clipboard.
|
||||||
|
// This is tested to ensure that clone doesn't incorrectly read from the clipboard when no selection is made.
|
||||||
|
var addedObject = new HitCircle { StartTime = 1000 };
|
||||||
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||||
|
AddStep("copy hitobject", () => Editor.Copy());
|
||||||
|
|
||||||
|
AddStep("deselect all objects", () => EditorBeatmap.SelectedHitObjects.Clear());
|
||||||
|
|
||||||
|
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
AddStep("clone", () => Editor.Clone());
|
||||||
|
AddAssert("still one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
@ -165,10 +161,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("hold alt", () => InputManager.PressKey(Key.LAlt));
|
AddStep("hold alt", () => InputManager.PressKey(Key.LAlt));
|
||||||
|
|
||||||
AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5));
|
AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5));
|
||||||
AddAssert("distance spacing increased by 0.5", () => editorBeatmap.BeatmapInfo.DistanceSpacing == originalSpacing + 0.5);
|
|
||||||
|
|
||||||
AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt));
|
AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt));
|
||||||
AddStep("release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
AddStep("release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||||
|
|
||||||
|
AddAssert("distance spacing increased by 0.5", () => editorBeatmap.BeatmapInfo.DistanceSpacing == originalSpacing + 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EditorBeatmapContainer : Container
|
public class EditorBeatmapContainer : Container
|
||||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new MenuCursor()
|
new MenuCursorContainer()
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollContainer.Add(innerBox = new Box
|
scrollContainer.Add(innerBox = new Box
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -862,52 +862,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1);
|
AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestRandomFallbackOnNonMatchingPrevious()
|
|
||||||
{
|
|
||||||
List<BeatmapSetInfo> manySets = new List<BeatmapSetInfo>();
|
|
||||||
|
|
||||||
AddStep("populate maps", () =>
|
|
||||||
{
|
|
||||||
manySets.Clear();
|
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
manySets.Add(TestResources.CreateTestBeatmapSetInfo(3, new[]
|
|
||||||
{
|
|
||||||
// all taiko except for first
|
|
||||||
rulesets.GetRuleset(i > 0 ? 1 : 0)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loadBeatmaps(manySets);
|
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
AddStep("Reset filter", () => carousel.Filter(new FilterCriteria(), false));
|
|
||||||
|
|
||||||
AddStep("select first beatmap", () => carousel.SelectBeatmap(manySets.First().Beatmaps.First()));
|
|
||||||
|
|
||||||
AddStep("Toggle non-matching filter", () =>
|
|
||||||
{
|
|
||||||
carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("selection lost", () => carousel.SelectedBeatmapInfo == null);
|
|
||||||
|
|
||||||
AddStep("Restore different ruleset filter", () =>
|
|
||||||
{
|
|
||||||
carousel.Filter(new FilterCriteria { Ruleset = rulesets.GetRuleset(1) }, false);
|
|
||||||
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("selection changed", () => !carousel.SelectedBeatmapInfo!.Equals(manySets.First().Beatmaps.First()));
|
|
||||||
}
|
|
||||||
|
|
||||||
AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFilteringByUserStarDifficulty()
|
public void TestFilteringByUserStarDifficulty()
|
||||||
{
|
{
|
||||||
@ -955,6 +909,63 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
checkVisibleItemCount(true, 15);
|
checkVisibleItemCount(true, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCarouselSelectsNextWhenPreviousIsFiltered()
|
||||||
|
{
|
||||||
|
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
||||||
|
|
||||||
|
// 10 sets that go osu! -> taiko -> catch -> osu! -> ...
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var rulesetInfo = rulesets.AvailableRulesets.ElementAt(i % 3);
|
||||||
|
sets.Add(TestResources.CreateTestBeatmapSetInfo(5, new[] { rulesetInfo }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort mode is important to keep the ruleset order
|
||||||
|
loadBeatmaps(sets, () => new FilterCriteria { Sort = SortMode.Title });
|
||||||
|
setSelected(1, 1);
|
||||||
|
|
||||||
|
for (int i = 1; i < 10; i++)
|
||||||
|
{
|
||||||
|
var rulesetInfo = rulesets.AvailableRulesets.ElementAt(i % 3);
|
||||||
|
AddStep($"Set ruleset to {rulesetInfo.ShortName}", () =>
|
||||||
|
{
|
||||||
|
carousel.Filter(new FilterCriteria { Ruleset = rulesetInfo, Sort = SortMode.Title }, false);
|
||||||
|
});
|
||||||
|
waitForSelection(i + 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCarouselSelectsBackwardsWhenDistanceIsShorter()
|
||||||
|
{
|
||||||
|
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
||||||
|
|
||||||
|
// 10 sets that go taiko, osu!, osu!, osu!, taiko, osu!, osu!, osu!, ...
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var rulesetInfo = rulesets.AvailableRulesets.ElementAt(i % 4 == 0 ? 1 : 0);
|
||||||
|
sets.Add(TestResources.CreateTestBeatmapSetInfo(5, new[] { rulesetInfo }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort mode is important to keep the ruleset order
|
||||||
|
loadBeatmaps(sets, () => new FilterCriteria { Sort = SortMode.Title });
|
||||||
|
|
||||||
|
for (int i = 2; i < 10; i += 4)
|
||||||
|
{
|
||||||
|
setSelected(i, 1);
|
||||||
|
AddStep("Set ruleset to taiko", () =>
|
||||||
|
{
|
||||||
|
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), Sort = SortMode.Title }, false);
|
||||||
|
});
|
||||||
|
waitForSelection(i - 1, 1);
|
||||||
|
AddStep("Remove ruleset filter", () =>
|
||||||
|
{
|
||||||
|
carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadBeatmaps(List<BeatmapSetInfo> beatmapSets = null, Func<FilterCriteria> initialCriteria = null, Action<BeatmapCarousel> carouselAdjust = null, int? count = null,
|
private void loadBeatmaps(List<BeatmapSetInfo> beatmapSets = null, Func<FilterCriteria> initialCriteria = null, Action<BeatmapCarousel> carouselAdjust = null, int? count = null,
|
||||||
bool randomDifficulties = false)
|
bool randomDifficulties = false)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
@ -427,8 +435,10 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
addControlPoint(time, controlPoint, true);
|
addControlPoint(time, controlPoint, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID;
|
||||||
|
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
addControlPoint(time, new LegacyDifficultyControlPoint(beatLength)
|
addControlPoint(time, new LegacyDifficultyControlPoint(onlineRulesetID, beatLength)
|
||||||
#pragma warning restore 618
|
#pragma warning restore 618
|
||||||
{
|
{
|
||||||
SliderVelocity = speedMultiplier,
|
SliderVelocity = speedMultiplier,
|
||||||
|
@ -174,11 +174,15 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool GenerateTicks { get; private set; } = true;
|
public bool GenerateTicks { get; private set; } = true;
|
||||||
|
|
||||||
public LegacyDifficultyControlPoint(double beatLength)
|
public LegacyDifficultyControlPoint(int rulesetId, double beatLength)
|
||||||
: this()
|
: this()
|
||||||
{
|
{
|
||||||
// Note: In stable, the division occurs on floats, but with compiler optimisations turned on actually seems to occur on doubles via some .NET black magic (possibly inlining?).
|
// Note: In stable, the division occurs on floats, but with compiler optimisations turned on actually seems to occur on doubles via some .NET black magic (possibly inlining?).
|
||||||
BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100.0 : 1;
|
if (rulesetId == 1 || rulesetId == 3)
|
||||||
|
BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100.0 : 1;
|
||||||
|
else
|
||||||
|
BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 1000) / 100.0 : 1;
|
||||||
|
|
||||||
GenerateTicks = !double.IsNaN(beatLength);
|
GenerateTicks = !double.IsNaN(beatLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -13,7 +13,7 @@ using osu.Game.Configuration;
|
|||||||
namespace osu.Game.Graphics.Cursor
|
namespace osu.Game.Graphics.Cursor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container which provides the main <see cref="Cursor.MenuCursor"/>.
|
/// A container which provides the main <see cref="MenuCursorContainer"/>.
|
||||||
/// Also handles cases where a more localised cursor is provided by another component (via <see cref="IProvideCursor"/>).
|
/// Also handles cases where a more localised cursor is provided by another component (via <see cref="IProvideCursor"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GlobalCursorDisplay : Container, IProvideCursor
|
public class GlobalCursorDisplay : Container, IProvideCursor
|
||||||
@ -23,7 +23,9 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool ShowCursor = true;
|
internal bool ShowCursor = true;
|
||||||
|
|
||||||
public CursorContainer MenuCursor { get; }
|
CursorContainer IProvideCursor.Cursor => MenuCursor;
|
||||||
|
|
||||||
|
public MenuCursorContainer MenuCursor { get; }
|
||||||
|
|
||||||
public bool ProvidingUserCursor => true;
|
public bool ProvidingUserCursor => true;
|
||||||
|
|
||||||
@ -42,8 +44,8 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
{
|
{
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
MenuCursor = new MenuCursor { State = { Value = Visibility.Hidden } },
|
Content = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
Content = new Container { RelativeSizeAxes = Axes.Both }
|
MenuCursor = new MenuCursorContainer { State = { Value = Visibility.Hidden } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +66,7 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
|
|
||||||
if (!hasValidInput || !ShowCursor)
|
if (!hasValidInput || !ShowCursor)
|
||||||
{
|
{
|
||||||
currentOverrideProvider?.MenuCursor?.Hide();
|
currentOverrideProvider?.Cursor?.Hide();
|
||||||
currentOverrideProvider = null;
|
currentOverrideProvider = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -83,8 +85,8 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
if (currentOverrideProvider == newOverrideProvider)
|
if (currentOverrideProvider == newOverrideProvider)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
currentOverrideProvider?.MenuCursor?.Hide();
|
currentOverrideProvider?.Cursor?.Hide();
|
||||||
newOverrideProvider.MenuCursor?.Show();
|
newOverrideProvider.Cursor?.Show();
|
||||||
|
|
||||||
currentOverrideProvider = newOverrideProvider;
|
currentOverrideProvider = newOverrideProvider;
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
/// The cursor provided by this <see cref="IDrawable"/>.
|
/// The cursor provided by this <see cref="IDrawable"/>.
|
||||||
/// May be null if no cursor should be visible.
|
/// May be null if no cursor should be visible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CursorContainer MenuCursor { get; }
|
CursorContainer Cursor { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether <see cref="MenuCursor"/> should be displayed as the singular user cursor. This will temporarily hide any other user cursor.
|
/// Whether <see cref="Cursor"/> should be displayed as the singular user cursor. This will temporarily hide any other user cursor.
|
||||||
/// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays).
|
/// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ProvidingUserCursor { get; }
|
bool ProvidingUserCursor { get; }
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user