mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 07:42:57 +08:00
Merge branch 'grid-momentary-shortcuts' into read-current-distance-snap
This commit is contained in:
commit
7d59bc6e2f
@ -10,7 +10,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -23,7 +22,6 @@ using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
|
||||
@ -35,8 +33,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
private CatchDistanceSnapGrid distanceSnapGrid;
|
||||
|
||||
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
private InputManager inputManager;
|
||||
|
||||
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
||||
@ -126,11 +122,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
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)
|
||||
{
|
||||
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
@ -202,7 +193,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
private void updateDistanceSnapGrid()
|
||||
{
|
||||
if (distanceSnapToggle.Value != TernaryState.True)
|
||||
if (DistanceSnapToggle.Value != TernaryState.True)
|
||||
{
|
||||
distanceSnapGrid.Hide();
|
||||
return;
|
||||
|
@ -20,20 +20,49 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestGridExclusivity()
|
||||
public void TestGridToggles()
|
||||
{
|
||||
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(false);
|
||||
|
||||
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);
|
||||
|
||||
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)));
|
||||
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());
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
else
|
||||
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]
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -13,8 +13,10 @@ using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -44,12 +46,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
new SpinnerCompositionTool()
|
||||
};
|
||||
|
||||
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
||||
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
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 })
|
||||
});
|
||||
|
||||
@ -80,19 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
||||
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
distanceSnapToggle.ValueChanged += _ =>
|
||||
{
|
||||
updateDistanceSnapGrid();
|
||||
|
||||
if (distanceSnapToggle.Value == TernaryState.True)
|
||||
rectangularGridSnapToggle.Value = TernaryState.False;
|
||||
};
|
||||
|
||||
rectangularGridSnapToggle.ValueChanged += _ =>
|
||||
{
|
||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||
distanceSnapToggle.Value = TernaryState.False;
|
||||
};
|
||||
DistanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
|
||||
// we may be entering the screen with a selection already active
|
||||
updateDistanceSnapGrid();
|
||||
@ -142,22 +130,27 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||
return snapResult;
|
||||
|
||||
SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
|
||||
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));
|
||||
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
||||
|
||||
result.ScreenSpacePosition = distanceSnapGrid.ToScreenSpace(pos);
|
||||
result.Time = time;
|
||||
}
|
||||
|
||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||
{
|
||||
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||
return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
||||
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
||||
|
||||
result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos);
|
||||
}
|
||||
}
|
||||
|
||||
return base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
|
||||
@ -210,7 +203,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
distanceSnapGridCache.Invalidate();
|
||||
distanceSnapGrid = null;
|
||||
|
||||
if (distanceSnapToggle.Value != TernaryState.True)
|
||||
if (DistanceSnapToggle.Value != TernaryState.True)
|
||||
return;
|
||||
|
||||
switch (BlueprintContainer.CurrentTool)
|
||||
@ -237,6 +230,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)
|
||||
{
|
||||
if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
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)]
|
||||
public BindableFloat Scale { get; } = new BindableFloat(4)
|
||||
|
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();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -201,7 +201,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModMuted(),
|
||||
new OsuModNoScope(),
|
||||
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||
new ModAdaptiveSpeed()
|
||||
new ModAdaptiveSpeed(),
|
||||
new OsuModFreezeFrame()
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
CircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
|
||||
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Anchor = 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,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
TimeRange = { Value = 5000 },
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[Test]
|
||||
public void DrumrollTest()
|
||||
{
|
||||
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
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -16,8 +15,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[Test]
|
||||
public void TestHits()
|
||||
{
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -13,6 +14,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -32,6 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
|
||||
private const double pre_beat_transition_time = 80;
|
||||
|
||||
private const float flash_opacity = 0.3f;
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (!effectPoint.KiaiMode)
|
||||
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)
|
||||
return;
|
||||
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
}
|
||||
|
||||
// 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");
|
||||
if (foregroundLayer != null)
|
||||
|
@ -106,6 +106,49 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
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]
|
||||
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);
|
||||
}
|
||||
|
||||
[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]
|
||||
public void TestCutNothing()
|
||||
{
|
||||
@ -175,5 +189,22 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("paste hitobject", () => Editor.Paste());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,10 +161,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("hold alt", () => InputManager.PressKey(Key.LAlt));
|
||||
|
||||
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 ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
|
||||
AddAssert("distance spacing increased by 0.5", () => editorBeatmap.BeatmapInfo.DistanceSpacing == originalSpacing + 0.5);
|
||||
}
|
||||
|
||||
public class EditorBeatmapContainer : Container
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,18 +3,17 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
public class TestSceneDirectorySelector : OsuTestScene
|
||||
public class TestSceneDirectorySelector : ThemeComparisonTestScene
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
protected override Drawable CreateContent() => new OsuDirectorySelector
|
||||
{
|
||||
Add(new OsuDirectorySelector { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -4,23 +4,43 @@
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
public class TestSceneFileSelector : OsuTestScene
|
||||
public class TestSceneFileSelector : ThemeComparisonTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestAllFiles()
|
||||
{
|
||||
AddStep("create", () => Child = new OsuFileSelector { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[Test]
|
||||
public void TestJpgFilesOnly()
|
||||
{
|
||||
AddStep("create", () => Child = new OsuFileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both });
|
||||
AddStep("create", () =>
|
||||
{
|
||||
Cell(0, 0).Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.GreySeaFoam
|
||||
},
|
||||
new OsuFileSelector(validFileExtensions: new[] { ".jpg" })
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => new OsuFileSelector
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Tournament
|
||||
@ -31,7 +32,9 @@ namespace osu.Game.Tournament
|
||||
|
||||
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();
|
||||
|
@ -239,17 +239,17 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
|
||||
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = Model.ID });
|
||||
|
||||
req.Success += res =>
|
||||
req.Success += res => Schedule(() =>
|
||||
{
|
||||
Model.Beatmap = new TournamentBeatmap(res);
|
||||
updatePanel();
|
||||
};
|
||||
});
|
||||
|
||||
req.Failure += _ =>
|
||||
req.Failure += _ => Schedule(() =>
|
||||
{
|
||||
Model.Beatmap = null;
|
||||
updatePanel();
|
||||
};
|
||||
});
|
||||
|
||||
API.Queue(req);
|
||||
}, true);
|
||||
|
@ -26,24 +26,24 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
set
|
||||
{
|
||||
if (labelText != null)
|
||||
labelText.Text = value;
|
||||
if (LabelTextFlowContainer != null)
|
||||
LabelTextFlowContainer.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
public MarginPadding LabelPadding
|
||||
{
|
||||
get => labelText?.Padding ?? new MarginPadding();
|
||||
get => LabelTextFlowContainer?.Padding ?? new MarginPadding();
|
||||
set
|
||||
{
|
||||
if (labelText != null)
|
||||
labelText.Padding = value;
|
||||
if (LabelTextFlowContainer != null)
|
||||
LabelTextFlowContainer.Padding = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly Nub Nub;
|
||||
|
||||
private readonly OsuTextFlowContainer labelText;
|
||||
protected readonly OsuTextFlowContainer LabelTextFlowContainer;
|
||||
private Sample sampleChecked;
|
||||
private Sample sampleUnchecked;
|
||||
|
||||
@ -56,7 +56,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
labelText = new OsuTextFlowContainer(ApplyLabelParameters)
|
||||
LabelTextFlowContainer = new OsuTextFlowContainer(ApplyLabelParameters)
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -70,19 +70,19 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Nub.Anchor = Anchor.CentreRight;
|
||||
Nub.Origin = Anchor.CentreRight;
|
||||
Nub.Margin = new MarginPadding { Right = nub_padding };
|
||||
labelText.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding * 2 };
|
||||
LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding * 2 };
|
||||
}
|
||||
else
|
||||
{
|
||||
Nub.Anchor = Anchor.CentreLeft;
|
||||
Nub.Origin = Anchor.CentreLeft;
|
||||
Nub.Margin = new MarginPadding { Left = nub_padding };
|
||||
labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 };
|
||||
LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 };
|
||||
}
|
||||
|
||||
Nub.Current.BindTo(Current);
|
||||
|
||||
Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1;
|
||||
Current.DisabledChanged += disabled => LabelTextFlowContainer.Alpha = Nub.Alpha = disabled ? 0.3f : 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -31,6 +31,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
||||
|
||||
protected override Drawable CreateHiddenToggleButton() => new OsuDirectorySelectorHiddenToggle { Current = { BindTarget = ShowHiddenItems } };
|
||||
|
||||
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
||||
|
||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||
|
@ -0,0 +1,38 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
internal class OsuDirectorySelectorHiddenToggle : OsuCheckbox
|
||||
{
|
||||
public OsuDirectorySelectorHiddenToggle()
|
||||
{
|
||||
RelativeSizeAxes = Axes.None;
|
||||
AutoSizeAxes = Axes.None;
|
||||
Size = new Vector2(100, 50);
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.CentreLeft;
|
||||
LabelTextFlowContainer.Anchor = Anchor.CentreLeft;
|
||||
LabelTextFlowContainer.Origin = Anchor.CentreLeft;
|
||||
LabelText = @"Show hidden";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? overlayColourProvider, OsuColour colours)
|
||||
{
|
||||
if (overlayColourProvider != null)
|
||||
return;
|
||||
|
||||
Nub.AccentColour = colours.GreySeaFoamLighter;
|
||||
Nub.GlowingAccentColour = Color4.White;
|
||||
Nub.GlowColour = Color4.White;
|
||||
}
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
||||
|
||||
protected override Drawable CreateHiddenToggleButton() => new OsuDirectorySelectorHiddenToggle { Current = { BindTarget = ShowHiddenItems } };
|
||||
|
||||
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
||||
|
||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||
|
@ -31,14 +31,17 @@ namespace osu.Game.Input.Bindings
|
||||
parentInputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
// IMPORTANT: Do not change the order of key bindings in this list.
|
||||
// It is used to decide the order of precedence (see note in DatabasedKeyBindingContainer).
|
||||
// IMPORTANT: Take care when changing order of the items in the enumerable.
|
||||
// It is used to decide the order of precedence, with the earlier items having higher precedence.
|
||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||
.Concat(OverlayKeyBindings)
|
||||
.Concat(EditorKeyBindings)
|
||||
.Concat(InGameKeyBindings)
|
||||
.Concat(SongSelectKeyBindings)
|
||||
.Concat(AudioControlKeyBindings);
|
||||
.Concat(AudioControlKeyBindings)
|
||||
// Overlay bindings may conflict with more local cases like the editor so they are checked last.
|
||||
// It has generally been agreed on that local screens like the editor should have priority,
|
||||
// based on such usages potentially requiring a lot more key bindings that may be "shared" with global ones.
|
||||
.Concat(OverlayKeyBindings);
|
||||
|
||||
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
||||
{
|
||||
@ -87,6 +90,7 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
||||
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.EditorCloneSelection),
|
||||
new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft),
|
||||
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
||||
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
||||
@ -343,5 +347,8 @@ namespace osu.Game.Input.Bindings
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleProfile))]
|
||||
ToggleProfile,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCloneSelection))]
|
||||
EditorCloneSelection
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +184,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM");
|
||||
|
||||
/// <summary>
|
||||
/// "Clone selection"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorCloneSelection => new TranslatableString(getKey(@"editor_clone_selection"), @"Clone selection");
|
||||
|
||||
/// <summary>
|
||||
/// "Cycle grid display mode"
|
||||
/// </summary>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@ -12,6 +13,7 @@ using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
@ -23,6 +25,7 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
@ -52,6 +55,10 @@ namespace osu.Game.Rulesets.Edit
|
||||
[Resolved(canBeNull: true)]
|
||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||
|
||||
protected readonly Bindable<TernaryState> DistanceSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
private bool distanceSnapMomentary;
|
||||
|
||||
protected DistancedHitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
@ -94,7 +101,6 @@ namespace osu.Game.Rulesets.Edit
|
||||
Debug.Assert(objects != null);
|
||||
|
||||
DistanceSpacingMultiplier.Value = ReadCurrentDistanceSnap(objects.Value.before, objects.Value.after);
|
||||
// TODO: This should probably also force distance spacing grid on.
|
||||
},
|
||||
RelativeSizeAxes = Axes.X,
|
||||
}
|
||||
@ -170,13 +176,44 @@ namespace osu.Game.Rulesets.Edit
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
||||
{
|
||||
new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void handleToggleViaKey(KeyboardEvent key)
|
||||
{
|
||||
bool altPressed = key.AltPressed;
|
||||
|
||||
if (altPressed != distanceSnapMomentary)
|
||||
{
|
||||
distanceSnapMomentary = altPressed;
|
||||
DistanceSnapToggle.Value = DistanceSnapToggle.Value == TernaryState.False ? TernaryState.True : TernaryState.False;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.EditorIncreaseDistanceSpacing:
|
||||
case GlobalAction.EditorDecreaseDistanceSpacing:
|
||||
return adjustDistanceSpacing(e.Action, adjust_step);
|
||||
return AdjustDistanceSpacing(e.Action, adjust_step);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -192,13 +229,13 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
case GlobalAction.EditorIncreaseDistanceSpacing:
|
||||
case GlobalAction.EditorDecreaseDistanceSpacing:
|
||||
return adjustDistanceSpacing(e.Action, e.ScrollAmount * adjust_step);
|
||||
return AdjustDistanceSpacing(e.Action, e.ScrollAmount * adjust_step);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool adjustDistanceSpacing(GlobalAction action, float amount)
|
||||
protected virtual bool AdjustDistanceSpacing(GlobalAction action, float amount)
|
||||
{
|
||||
if (DistanceSpacingMultiplier.Disabled)
|
||||
return false;
|
||||
|
@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed)
|
||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed)
|
||||
return false;
|
||||
|
||||
if (checkLeftToggleFromKey(e.Key, out int leftIndex))
|
||||
|
@ -199,8 +199,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
if (stringAddBank == @"none")
|
||||
stringAddBank = null;
|
||||
|
||||
bankInfo.Normal = stringBank;
|
||||
bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
||||
bankInfo.BankForNormal = stringBank;
|
||||
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
||||
|
||||
if (split.Length > 2)
|
||||
bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
|
||||
@ -447,32 +447,54 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
var soundTypes = new List<HitSampleInfo>
|
||||
{
|
||||
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
|
||||
};
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Whistle))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Clap))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
return soundTypes;
|
||||
}
|
||||
|
||||
private class SampleBankInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional overriding filename which causes all bank/sample specifications to be ignored.
|
||||
/// </summary>
|
||||
public string Filename;
|
||||
|
||||
public string Normal;
|
||||
public string Add;
|
||||
/// <summary>
|
||||
/// The bank identifier to use for the base ("hitnormal") sample.
|
||||
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||
/// </summary>
|
||||
public string BankForNormal;
|
||||
|
||||
/// <summary>
|
||||
/// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap").
|
||||
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||
/// </summary>
|
||||
public string BankForAdditions;
|
||||
|
||||
/// <summary>
|
||||
/// Hit sample volume (0-100).
|
||||
/// See <see cref="HitSampleInfo.Volume"/>.
|
||||
/// </summary>
|
||||
public int Volume;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the custom sample bank. Is only used if 2 or above for "reasons".
|
||||
/// This will add a suffix to lookups, allowing extended bank lookups (ie. "normal-hitnormal-2").
|
||||
/// See <see cref="HitSampleInfo.Suffix"/>.
|
||||
/// </summary>
|
||||
public int CustomSampleBank;
|
||||
|
||||
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
||||
@ -503,7 +525,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
||||
=> With(newName, newBank, newVolume);
|
||||
|
||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default, Optional<int> newCustomSampleBank = default,
|
||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default,
|
||||
Optional<int> newCustomSampleBank = default,
|
||||
Optional<bool> newIsLayered = default)
|
||||
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
||||
|
||||
@ -537,7 +560,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
Path.ChangeExtension(Filename, null)
|
||||
};
|
||||
|
||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default, Optional<int> newCustomSampleBank = default,
|
||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default,
|
||||
Optional<int> newCustomSampleBank = default,
|
||||
Optional<bool> newIsLayered = default)
|
||||
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
||||
|
||||
|
@ -25,6 +25,26 @@ namespace osu.Game.Screens.Edit
|
||||
BindValueChanged(_ => ensureValidDivisor());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a divisor, updating the valid divisor range appropriately.
|
||||
/// </summary>
|
||||
/// <param name="divisor">The intended divisor.</param>
|
||||
public void SetArbitraryDivisor(int divisor)
|
||||
{
|
||||
// If the current valid divisor range doesn't contain the proposed value, attempt to find one which does.
|
||||
if (!ValidDivisors.Value.Presets.Contains(divisor))
|
||||
{
|
||||
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
|
||||
ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
||||
else if (BeatDivisorPresetCollection.TRIPLETS.Presets.Contains(divisor))
|
||||
ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS;
|
||||
else
|
||||
ValidDivisors.Value = BeatDivisorPresetCollection.Custom(divisor);
|
||||
}
|
||||
|
||||
Value = divisor;
|
||||
}
|
||||
|
||||
private void updateBindableProperties()
|
||||
{
|
||||
ensureValidDivisor();
|
||||
|
@ -209,6 +209,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.ShiftPressed && e.Key >= Key.Number1 && e.Key <= Key.Number9)
|
||||
{
|
||||
beatDivisor.SetArbitraryDivisor(e.Key - Key.Number0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
internal class DivisorDisplay : OsuAnimatedButton, IHasPopover
|
||||
{
|
||||
public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor();
|
||||
@ -306,17 +317,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BeatDivisor.ValidDivisors.Value.Presets.Contains(divisor))
|
||||
{
|
||||
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
|
||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
||||
else if (BeatDivisorPresetCollection.TRIPLETS.Presets.Contains(divisor))
|
||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS;
|
||||
else
|
||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.Custom(divisor);
|
||||
}
|
||||
|
||||
BeatDivisor.Value = divisor;
|
||||
BeatDivisor.SetArbitraryDivisor(divisor);
|
||||
|
||||
this.HidePopover();
|
||||
}
|
||||
|
@ -304,6 +304,7 @@ namespace osu.Game.Screens.Edit
|
||||
cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut),
|
||||
copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy),
|
||||
pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste),
|
||||
cloneMenuItem = new EditorMenuItem("Clone", MenuItemType.Standard, Clone),
|
||||
}
|
||||
},
|
||||
new MenuItem("View")
|
||||
@ -575,6 +576,10 @@ namespace osu.Game.Screens.Edit
|
||||
this.Exit();
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorCloneSelection:
|
||||
Clone();
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorComposeMode:
|
||||
Mode.Value = EditorScreenMode.Compose;
|
||||
return true;
|
||||
@ -741,6 +746,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
private EditorMenuItem cutMenuItem;
|
||||
private EditorMenuItem copyMenuItem;
|
||||
private EditorMenuItem cloneMenuItem;
|
||||
private EditorMenuItem pasteMenuItem;
|
||||
|
||||
private readonly BindableWithCurrent<bool> canCut = new BindableWithCurrent<bool>();
|
||||
@ -750,7 +756,11 @@ namespace osu.Game.Screens.Edit
|
||||
private void setUpClipboardActionAvailability()
|
||||
{
|
||||
canCut.Current.BindValueChanged(cut => cutMenuItem.Action.Disabled = !cut.NewValue, true);
|
||||
canCopy.Current.BindValueChanged(copy => copyMenuItem.Action.Disabled = !copy.NewValue, true);
|
||||
canCopy.Current.BindValueChanged(copy =>
|
||||
{
|
||||
copyMenuItem.Action.Disabled = !copy.NewValue;
|
||||
cloneMenuItem.Action.Disabled = !copy.NewValue;
|
||||
}, true);
|
||||
canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true);
|
||||
}
|
||||
|
||||
@ -765,6 +775,21 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected void Copy() => currentScreen?.Copy();
|
||||
|
||||
protected void Clone()
|
||||
{
|
||||
// Avoid attempting to clone if copying is not available (as it may result in pasting something unexpected).
|
||||
if (!canCopy.Value)
|
||||
return;
|
||||
|
||||
// This is an initial implementation just to get an idea of how people used this function.
|
||||
// There are a couple of differences from osu!stable's implementation which will require more work to match:
|
||||
// - The "clipboard" is not populated during the duplication process.
|
||||
// - The duplicated hitobjects are inserted after the original pattern (add one beat_length and then quantize using beat snap).
|
||||
// - The duplicated hitobjects are selected (but this is also applied for all paste operations so should be changed there).
|
||||
Copy();
|
||||
Paste();
|
||||
}
|
||||
|
||||
protected void Paste() => currentScreen?.Paste();
|
||||
|
||||
#endregion
|
||||
|
@ -7,15 +7,15 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
internal class KiaiFlashingDrawable : BeatSyncedContainer
|
||||
public class LegacyKiaiFlashingDrawable : BeatSyncedContainer
|
||||
{
|
||||
private readonly Drawable flashingDrawable;
|
||||
|
||||
private const float flash_opacity = 0.3f;
|
||||
private const float flash_opacity = 0.55f;
|
||||
|
||||
public KiaiFlashingDrawable(Func<Drawable?> creationFunc)
|
||||
public LegacyKiaiFlashingDrawable(Func<Drawable?> creationFunc)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
flashingDrawable
|
||||
.FadeTo(flash_opacity)
|
||||
.Then()
|
||||
.FadeOut(timingPoint.BeatLength * 0.75f);
|
||||
.FadeOut(Math.Max(80, timingPoint.BeatLength - 80), Easing.OutSine);
|
||||
}
|
||||
}
|
||||
}
|
@ -110,6 +110,8 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
public new void Paste() => base.Paste();
|
||||
|
||||
public new void Clone() => base.Clone();
|
||||
|
||||
public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo);
|
||||
|
||||
public new void CreateNewDifficulty(RulesetInfo rulesetInfo) => base.CreateNewDifficulty(rulesetInfo);
|
||||
|
Loading…
Reference in New Issue
Block a user