mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 20:13:22 +08:00
Merge branch 'master' into add-editor-object-clone
This commit is contained in:
commit
bcdf24b972
@ -20,20 +20,27 @@ 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)));
|
||||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
rectangularGridActive(true);
|
||||||
|
|
||||||
|
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
|
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(false);
|
rectangularGridActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -80,19 +80,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();
|
||||||
@ -134,22 +122,27 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||||
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)
|
||||||
|
@ -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)
|
||||||
|
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 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:
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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
|
#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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -26,24 +26,24 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (labelText != null)
|
if (LabelTextFlowContainer != null)
|
||||||
labelText.Text = value;
|
LabelTextFlowContainer.Text = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MarginPadding LabelPadding
|
public MarginPadding LabelPadding
|
||||||
{
|
{
|
||||||
get => labelText?.Padding ?? new MarginPadding();
|
get => LabelTextFlowContainer?.Padding ?? new MarginPadding();
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (labelText != null)
|
if (LabelTextFlowContainer != null)
|
||||||
labelText.Padding = value;
|
LabelTextFlowContainer.Padding = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly Nub Nub;
|
protected readonly Nub Nub;
|
||||||
|
|
||||||
private readonly OsuTextFlowContainer labelText;
|
protected readonly OsuTextFlowContainer LabelTextFlowContainer;
|
||||||
private Sample sampleChecked;
|
private Sample sampleChecked;
|
||||||
private Sample sampleUnchecked;
|
private Sample sampleUnchecked;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
labelText = new OsuTextFlowContainer(ApplyLabelParameters)
|
LabelTextFlowContainer = new OsuTextFlowContainer(ApplyLabelParameters)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -70,19 +70,19 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Nub.Anchor = Anchor.CentreRight;
|
Nub.Anchor = Anchor.CentreRight;
|
||||||
Nub.Origin = Anchor.CentreRight;
|
Nub.Origin = Anchor.CentreRight;
|
||||||
Nub.Margin = new MarginPadding { Right = nub_padding };
|
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
|
else
|
||||||
{
|
{
|
||||||
Nub.Anchor = Anchor.CentreLeft;
|
Nub.Anchor = Anchor.CentreLeft;
|
||||||
Nub.Origin = Anchor.CentreLeft;
|
Nub.Origin = Anchor.CentreLeft;
|
||||||
Nub.Margin = new MarginPadding { Left = nub_padding };
|
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);
|
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>
|
/// <summary>
|
||||||
|
@ -31,6 +31,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
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 CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
||||||
|
|
||||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
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 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 CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
||||||
|
|
||||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||||
|
@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed)
|
if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (checkLeftToggleFromKey(e.Key, out int leftIndex))
|
if (checkLeftToggleFromKey(e.Key, out int leftIndex))
|
||||||
|
@ -199,8 +199,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
if (stringAddBank == @"none")
|
if (stringAddBank == @"none")
|
||||||
stringAddBank = null;
|
stringAddBank = null;
|
||||||
|
|
||||||
bankInfo.Normal = stringBank;
|
bankInfo.BankForNormal = stringBank;
|
||||||
bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
||||||
|
|
||||||
if (split.Length > 2)
|
if (split.Length > 2)
|
||||||
bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
|
bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
|
||||||
@ -447,32 +447,54 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
var soundTypes = new List<HitSampleInfo>
|
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.
|
// 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
|
// 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))
|
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
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))
|
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))
|
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;
|
return soundTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SampleBankInfo
|
private class SampleBankInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An optional overriding filename which causes all bank/sample specifications to be ignored.
|
||||||
|
/// </summary>
|
||||||
public string Filename;
|
public string Filename;
|
||||||
|
|
||||||
public string Normal;
|
/// <summary>
|
||||||
public string Add;
|
/// 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;
|
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 int CustomSampleBank;
|
||||||
|
|
||||||
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
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)
|
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);
|
=> 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)
|
Optional<bool> newIsLayered = default)
|
||||||
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
=> 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)
|
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)
|
Optional<bool> newIsLayered = default)
|
||||||
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
||||||
|
|
||||||
|
@ -25,6 +25,26 @@ namespace osu.Game.Screens.Edit
|
|||||||
BindValueChanged(_ => ensureValidDivisor());
|
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()
|
private void updateBindableProperties()
|
||||||
{
|
{
|
||||||
ensureValidDivisor();
|
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
|
internal class DivisorDisplay : OsuAnimatedButton, IHasPopover
|
||||||
{
|
{
|
||||||
public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor();
|
public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor();
|
||||||
@ -306,17 +317,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BeatDivisor.ValidDivisors.Value.Presets.Contains(divisor))
|
BeatDivisor.SetArbitraryDivisor(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;
|
|
||||||
|
|
||||||
this.HidePopover();
|
this.HidePopover();
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,15 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.Containers;
|
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 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;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
flashingDrawable
|
flashingDrawable
|
||||||
.FadeTo(flash_opacity)
|
.FadeTo(flash_opacity)
|
||||||
.Then()
|
.Then()
|
||||||
.FadeOut(timingPoint.BeatLength * 0.75f);
|
.FadeOut(Math.Max(80, timingPoint.BeatLength - 80), Easing.OutSine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user