mirror of
https://github.com/osukey/osukey.git
synced 2025-04-29 02:37:25 +09:00
Merge remote-tracking branch 'upstream/master' into sample-control-points
This commit is contained in:
commit
608fa3cc73
@ -418,10 +418,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private void clearPlate(DroppedObjectAnimation animation)
|
private void clearPlate(DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var droppedObjects = caughtObjectContainer.Children.Select(getDroppedObject).ToArray();
|
var caughtObjects = caughtObjectContainer.Children.ToArray();
|
||||||
|
|
||||||
caughtObjectContainer.Clear(false);
|
caughtObjectContainer.Clear(false);
|
||||||
|
|
||||||
|
// Use the already returned PoolableDrawables for new objects
|
||||||
|
var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray();
|
||||||
|
|
||||||
droppedObjectTarget.AddRange(droppedObjects);
|
droppedObjectTarget.AddRange(droppedObjects);
|
||||||
|
|
||||||
foreach (var droppedObject in droppedObjects)
|
foreach (var droppedObject in droppedObjects)
|
||||||
@ -430,10 +433,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var droppedObject = getDroppedObject(caughtObject);
|
|
||||||
|
|
||||||
caughtObjectContainer.Remove(caughtObject, false);
|
caughtObjectContainer.Remove(caughtObject, false);
|
||||||
|
|
||||||
|
var droppedObject = getDroppedObject(caughtObject);
|
||||||
|
|
||||||
droppedObjectTarget.Add(droppedObject);
|
droppedObjectTarget.Add(droppedObject);
|
||||||
|
|
||||||
applyDropAnimation(droppedObject, animation);
|
applyDropAnimation(droppedObject, animation);
|
||||||
@ -456,6 +459,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define lifetime start for dropped objects to be disposed correctly when rewinding replay
|
||||||
|
d.LifetimeStart = Clock.CurrentTime;
|
||||||
d.Expire();
|
d.Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||||
drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false;
|
drawableTaikoRuleset.LockPlayfieldAspectRange.Value = false;
|
||||||
|
|
||||||
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||||
playfield.ClassicHitTargetPosition.Value = true;
|
playfield.ClassicHitTargetPosition.Value = true;
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
public new BindableDouble TimeRange => base.TimeRange;
|
public new BindableDouble TimeRange => base.TimeRange;
|
||||||
|
|
||||||
public readonly BindableBool LockPlayfieldMaxAspect = new BindableBool(true);
|
public readonly BindableBool LockPlayfieldAspectRange = new BindableBool(true);
|
||||||
|
|
||||||
public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;
|
public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;
|
||||||
|
|
||||||
@ -69,7 +69,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
const float scroll_rate = 10;
|
const float scroll_rate = 10;
|
||||||
|
|
||||||
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
|
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
|
||||||
float ratio = DrawHeight / 480;
|
// Width is used because it defines how many notes fit on the playfield.
|
||||||
|
// We clamp the ratio to the maximum aspect ratio to keep scroll speed consistent on widths lower than the default.
|
||||||
|
float ratio = Math.Max(DrawSize.X / 768f, TaikoPlayfieldAdjustmentContainer.MAXIMUM_ASPECT);
|
||||||
|
|
||||||
TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
|
TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
|
||||||
}
|
}
|
||||||
@ -92,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer
|
||||||
{
|
{
|
||||||
LockPlayfieldMaxAspect = { BindTarget = LockPlayfieldMaxAspect }
|
LockPlayfieldAspectRange = { BindTarget = LockPlayfieldAspectRange }
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
||||||
|
@ -11,9 +11,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||||
{
|
{
|
||||||
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
|
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
|
||||||
private const float default_aspect = 16f / 9f;
|
|
||||||
|
|
||||||
public readonly IBindable<bool> LockPlayfieldMaxAspect = new BindableBool(true);
|
public const float MAXIMUM_ASPECT = 16f / 9f;
|
||||||
|
public const float MINIMUM_ASPECT = 5f / 4f;
|
||||||
|
|
||||||
|
public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
@ -26,12 +28,22 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
//
|
//
|
||||||
// As a middle-ground, the aspect ratio can still be adjusted in the downwards direction but has a maximum limit.
|
// As a middle-ground, the aspect ratio can still be adjusted in the downwards direction but has a maximum limit.
|
||||||
// This is still a bit weird, because readability changes with window size, but it is what it is.
|
// This is still a bit weird, because readability changes with window size, but it is what it is.
|
||||||
if (LockPlayfieldMaxAspect.Value && Parent.ChildSize.X / Parent.ChildSize.Y > default_aspect)
|
if (LockPlayfieldAspectRange.Value)
|
||||||
height *= Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
|
{
|
||||||
|
float currentAspect = Parent.ChildSize.X / Parent.ChildSize.Y;
|
||||||
|
|
||||||
|
if (currentAspect > MAXIMUM_ASPECT)
|
||||||
|
height *= currentAspect / MAXIMUM_ASPECT;
|
||||||
|
else if (currentAspect < MINIMUM_ASPECT)
|
||||||
|
height *= currentAspect / MINIMUM_ASPECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions.
|
||||||
|
height = Math.Min(height, 1f / 3f);
|
||||||
Height = height;
|
Height = height;
|
||||||
|
|
||||||
// Position the taiko playfield exactly one playfield from the top of the screen.
|
// Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it.
|
||||||
|
// Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered.
|
||||||
RelativePositionAxes = Axes.Y;
|
RelativePositionAxes = Axes.Y;
|
||||||
Y = height;
|
Y = height;
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,36 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeVideoWithLowercaseExtension()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("video-with-lowercase-extension.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.Decode(stream);
|
||||||
|
var metadata = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeVideoWithUppercaseExtension()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("video-with-uppercase-extension.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.Decode(stream);
|
||||||
|
var metadata = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDecodeImageSpecifiedAsVideo()
|
public void TestDecodeImageSpecifiedAsVideo()
|
||||||
{
|
{
|
||||||
|
@ -95,6 +95,27 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLoopWithoutExplicitFadeOut()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyStoryboardDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("animation-loop-no-explicit-end-time.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
|
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||||
|
Assert.AreEqual(1, background.Elements.Count);
|
||||||
|
|
||||||
|
Assert.AreEqual(2000, background.Elements[0].StartTime);
|
||||||
|
Assert.AreEqual(2000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
|
||||||
|
|
||||||
|
Assert.AreEqual(3000, (background.Elements[0] as StoryboardAnimation)?.GetEndTime());
|
||||||
|
Assert.AreEqual(12000, (background.Elements[0] as StoryboardAnimation)?.EndTimeForDisplay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCorrectAnimationStartTime()
|
public void TestCorrectAnimationStartTime()
|
||||||
{
|
{
|
||||||
@ -169,6 +190,40 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeVideoWithLowercaseExtension()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyStoryboardDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("video-with-lowercase-extension.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
|
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||||
|
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
Assert.AreEqual("Video.avi", ((StoryboardVideo)video.Elements[0]).Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeVideoWithUppercaseExtension()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyStoryboardDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("video-with-uppercase-extension.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
|
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||||
|
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
Assert.AreEqual("Video.AVI", ((StoryboardVideo)video.Elements[0]).Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDecodeImageSpecifiedAsVideo()
|
public void TestDecodeImageSpecifiedAsVideo()
|
||||||
{
|
{
|
||||||
@ -179,8 +234,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
var storyboard = decoder.Decode(stream);
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
StoryboardLayer foreground = storyboard.Layers.Single(l => l.Name == "Video");
|
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||||
Assert.That(foreground.Elements.Count, Is.Zero);
|
Assert.That(video.Elements.Count, Is.Zero);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
[Events]
|
||||||
|
//Storyboard Layer 0 (Background)
|
||||||
|
Animation,Background,Centre,"img.jpg",320,240,2,150,LoopForever
|
||||||
|
F,0,2000,,0,1
|
||||||
|
L,2000,10
|
||||||
|
F,18,0,1000,1,0
|
@ -0,0 +1,5 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[Events]
|
||||||
|
0,0,"BG.jpg",0,0
|
||||||
|
Video,0,"Video.avi",0,0
|
@ -0,0 +1,5 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[Events]
|
||||||
|
0,0,"BG.jpg",0,0
|
||||||
|
Video,0,"Video.AVI",0,0
|
@ -311,6 +311,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
public bool IsDrawable => true;
|
public bool IsDrawable => true;
|
||||||
public double StartTime => double.MinValue;
|
public double StartTime => double.MinValue;
|
||||||
public double EndTime => double.MaxValue;
|
public double EndTime => double.MaxValue;
|
||||||
|
public double EndTimeForDisplay => double.MaxValue;
|
||||||
|
|
||||||
public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
|
public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
|
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -7,7 +7,9 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -17,13 +19,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public TestSceneKeyCounter()
|
public TestSceneKeyCounter()
|
||||||
{
|
{
|
||||||
KeyCounterDisplay kc = new DefaultKeyCounterDisplay
|
KeyCounterDisplay defaultDisplay = new DefaultKeyCounterDisplay
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
Position = new Vector2(0, 72.7f)
|
||||||
};
|
};
|
||||||
|
|
||||||
kc.AddRange(new InputTrigger[]
|
KeyCounterDisplay argonDisplay = new ArgonKeyCounterDisplay
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Position = new Vector2(0, -72.7f)
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultDisplay.AddRange(new InputTrigger[]
|
||||||
{
|
{
|
||||||
new KeyCounterKeyboardTrigger(Key.X),
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
new KeyCounterKeyboardTrigger(Key.X),
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
@ -31,30 +41,41 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||||
});
|
});
|
||||||
|
|
||||||
var testCounter = (DefaultKeyCounter)kc.Counters.First();
|
argonDisplay.AddRange(new InputTrigger[]
|
||||||
|
{
|
||||||
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
|
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||||
|
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||||
|
});
|
||||||
|
|
||||||
|
var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First();
|
||||||
|
|
||||||
AddStep("Add random", () =>
|
AddStep("Add random", () =>
|
||||||
{
|
{
|
||||||
Key key = (Key)((int)Key.A + RNG.Next(26));
|
Key key = (Key)((int)Key.A + RNG.Next(26));
|
||||||
kc.Add(new KeyCounterKeyboardTrigger(key));
|
defaultDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
||||||
|
argonDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
||||||
});
|
});
|
||||||
|
|
||||||
Key testKey = ((KeyCounterKeyboardTrigger)kc.Counters.First().Trigger).Key;
|
Key testKey = ((KeyCounterKeyboardTrigger)defaultDisplay.Counters.First().Trigger).Key;
|
||||||
|
|
||||||
void addPressKeyStep()
|
|
||||||
{
|
|
||||||
AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
|
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
|
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
|
||||||
AddStep("Disable counting", () => testCounter.IsCounting.Value = false);
|
AddStep("Disable counting", () =>
|
||||||
|
{
|
||||||
|
argonDisplay.IsCounting.Value = false;
|
||||||
|
defaultDisplay.IsCounting.Value = false;
|
||||||
|
});
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
|
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
|
||||||
|
|
||||||
Add(kc);
|
Add(defaultDisplay);
|
||||||
|
Add(argonDisplay);
|
||||||
|
|
||||||
|
void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
|
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestComboCounterIncrementing()
|
public void TestComboCounterIncrementing()
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
|
{
|
||||||
|
public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When entering the editor, a new beatmap is created as part of the asynchronous load process.
|
||||||
|
/// This test ensures that in the case of an early exit from the editor (ie. while it's still loading)
|
||||||
|
/// doesn't leave a dangling beatmap behind.
|
||||||
|
///
|
||||||
|
/// This may not fail 100% due to timing, but has a pretty high chance of hitting a failure so works well enough
|
||||||
|
/// as a test.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestCancelNavigationToEditor()
|
||||||
|
{
|
||||||
|
BeatmapSetInfo[] beatmapSets = null!;
|
||||||
|
|
||||||
|
AddStep("Fetch initial beatmaps", () => beatmapSets = allBeatmapSets());
|
||||||
|
|
||||||
|
AddStep("Set current beatmap to default", () => Game.Beatmap.SetDefault());
|
||||||
|
|
||||||
|
AddStep("Push editor loader", () => Game.ScreenStack.Push(new EditorLoader()));
|
||||||
|
AddUntilStep("Wait for loader current", () => Game.ScreenStack.CurrentScreen is EditorLoader);
|
||||||
|
AddStep("Close editor while loading", () => Game.ScreenStack.CurrentScreen.Exit());
|
||||||
|
|
||||||
|
AddUntilStep("Wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
|
AddAssert("Check no new beatmaps were made", () => allBeatmapSets().SequenceEqual(beatmapSets));
|
||||||
|
|
||||||
|
BeatmapSetInfo[] allBeatmapSets() => Game.Realm.Run(realm => realm.All<BeatmapSetInfo>().Where(x => !x.DeletePending).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -368,7 +368,7 @@ namespace osu.Game.Beatmaps
|
|||||||
// user requested abort
|
// user requested abort
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var video = b.Files.FirstOrDefault(f => OsuGameBase.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal)));
|
var video = b.Files.FirstOrDefault(f => OsuGameBase.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
if (video != null)
|
if (video != null)
|
||||||
{
|
{
|
||||||
|
@ -412,7 +412,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
|
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
|
||||||
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
|
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
|
||||||
// video extensions and handle similar to a background if it doesn't match.
|
// video extensions and handle similar to a background if it doesn't match.
|
||||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename)))
|
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
||||||
{
|
{
|
||||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
//
|
//
|
||||||
// This avoids potential weird crashes when ffmpeg attempts to parse an image file as a video
|
// This avoids potential weird crashes when ffmpeg attempts to parse an image file as a video
|
||||||
// (see https://github.com/ppy/osu/issues/22829#issuecomment-1465552451).
|
// (see https://github.com/ppy/osu/issues/22829#issuecomment-1465552451).
|
||||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path)))
|
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant()))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset));
|
storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset));
|
||||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (OsuGameBase.VIDEO_EXTENSIONS.Contains(File.Extension))
|
if (OsuGameBase.VIDEO_EXTENSIONS.Contains(File.Extension.ToLowerInvariant()))
|
||||||
return FontAwesome.Regular.FileVideo;
|
return FontAwesome.Regular.FileVideo;
|
||||||
|
|
||||||
switch (File.Extension)
|
switch (File.Extension)
|
||||||
|
@ -100,14 +100,14 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString TimelineTicks => new TranslatableString(getKey(@"timeline_ticks"), @"Ticks");
|
public static LocalisableString TimelineTicks => new TranslatableString(getKey(@"timeline_ticks"), @"Ticks");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "{0:0.0}°"
|
/// "{0:0}°"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RotationUnsnapped(float newRotation) => new TranslatableString(getKey(@"rotation_unsnapped"), @"{0:0.0}°", newRotation);
|
public static LocalisableString RotationUnsnapped(float newRotation) => new TranslatableString(getKey(@"rotation_unsnapped"), @"{0:0}°", newRotation);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "{0:0.0}° (snapped)"
|
/// "{0:0}° (snapped)"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RotationSnapped(float newRotation) => new TranslatableString(getKey(@"rotation_snapped"), @"{0:0.0}° (snapped)", newRotation);
|
public static LocalisableString RotationSnapped(float newRotation) => new TranslatableString(getKey(@"rotation_snapped"), @"{0:0}° (snapped)", newRotation);
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -31,6 +32,9 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; } = null!;
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RankingsOverlay? rankingsOverlay { get; set; }
|
||||||
|
|
||||||
private UserCoverBackground cover = null!;
|
private UserCoverBackground cover = null!;
|
||||||
private SupporterIcon supporterTag = null!;
|
private SupporterIcon supporterTag = null!;
|
||||||
private UpdateableAvatar avatar = null!;
|
private UpdateableAvatar avatar = null!;
|
||||||
@ -38,6 +42,7 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
private ExternalLinkButton openUserExternally = null!;
|
private ExternalLinkButton openUserExternally = null!;
|
||||||
private OsuSpriteText titleText = null!;
|
private OsuSpriteText titleText = null!;
|
||||||
private UpdateableFlag userFlag = null!;
|
private UpdateableFlag userFlag = null!;
|
||||||
|
private OsuHoverContainer userCountryContainer = null!;
|
||||||
private OsuSpriteText userCountryText = null!;
|
private OsuSpriteText userCountryText = null!;
|
||||||
private GroupBadgeFlow groupBadgeFlow = null!;
|
private GroupBadgeFlow groupBadgeFlow = null!;
|
||||||
private ToggleCoverButton coverToggle = null!;
|
private ToggleCoverButton coverToggle = null!;
|
||||||
@ -156,13 +161,17 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
Size = new Vector2(28, 20),
|
Size = new Vector2(28, 20),
|
||||||
ShowPlaceholderOnUnknown = false,
|
ShowPlaceholderOnUnknown = false,
|
||||||
},
|
},
|
||||||
userCountryText = new OsuSpriteText
|
userCountryContainer = new OsuHoverContainer
|
||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular),
|
AutoSizeAxes = Axes.Both,
|
||||||
Margin = new MarginPadding { Left = 5 },
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
}
|
Origin = Anchor.CentreLeft,
|
||||||
|
Margin = new MarginPadding { Left = 5 },
|
||||||
|
Child = userCountryText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -202,6 +211,7 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}";
|
openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}";
|
||||||
userFlag.CountryCode = user?.CountryCode ?? default;
|
userFlag.CountryCode = user?.CountryCode ?? default;
|
||||||
userCountryText.Text = (user?.CountryCode ?? default).GetDescription();
|
userCountryText.Text = (user?.CountryCode ?? default).GetDescription();
|
||||||
|
userCountryContainer.Action = () => rankingsOverlay?.ShowCountry(user?.CountryCode ?? default);
|
||||||
supporterTag.SupportLevel = user?.SupportLevel ?? 0;
|
supporterTag.SupportLevel = user?.SupportLevel ?? 0;
|
||||||
titleText.Text = user?.Title ?? string.Empty;
|
titleText.Text = user?.Title ?? string.Empty;
|
||||||
titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff");
|
titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff");
|
||||||
|
@ -256,7 +256,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host.Window is WindowsWindow)
|
if (host.Renderer is IWindowsRenderer)
|
||||||
{
|
{
|
||||||
switch (fullscreenCapability.Value)
|
switch (fullscreenCapability.Value)
|
||||||
{
|
{
|
||||||
|
@ -20,11 +20,31 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public virtual bool RestartOnFail => Restart.Value;
|
public virtual bool RestartOnFail => Restart.Value;
|
||||||
|
|
||||||
|
private Action? triggerFailureDelegate;
|
||||||
|
|
||||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||||
{
|
{
|
||||||
|
triggerFailureDelegate = healthProcessor.TriggerFailure;
|
||||||
healthProcessor.FailConditions += FailCondition;
|
healthProcessor.FailConditions += FailCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Immediately triggers a failure on the loaded <see cref="HealthProcessor"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected void TriggerFailure() => triggerFailureDelegate?.Invoke();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether <paramref name="result"/> should trigger a failure. Called every time a
|
||||||
|
/// judgement is applied to <paramref name="healthProcessor"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="healthProcessor">The loaded <see cref="HealthProcessor"/>.</param>
|
||||||
|
/// <param name="result">The latest <see cref="JudgementResult"/>.</param>
|
||||||
|
/// <returns>Whether the fail condition has been met.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method should only be used to trigger failures based on <paramref name="result"/>.
|
||||||
|
/// Using outside values to evaluate failure may introduce event ordering discrepancies, use
|
||||||
|
/// an <see cref="IApplicableMod"/> with <see cref="TriggerFailure"/> instead.
|
||||||
|
/// </remarks>
|
||||||
protected abstract bool FailCondition(HealthProcessor healthProcessor, JudgementResult result);
|
protected abstract bool FailCondition(HealthProcessor healthProcessor, JudgementResult result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,15 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasFailed { get; private set; }
|
public bool HasFailed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Immediately triggers a failure for this HealthProcessor.
|
||||||
|
/// </summary>
|
||||||
|
public void TriggerFailure()
|
||||||
|
{
|
||||||
|
if (Failed?.Invoke() != false)
|
||||||
|
HasFailed = true;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ApplyResultInternal(JudgementResult result)
|
protected override void ApplyResultInternal(JudgementResult result)
|
||||||
{
|
{
|
||||||
result.HealthAtJudgement = Health.Value;
|
result.HealthAtJudgement = Health.Value;
|
||||||
@ -42,10 +51,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
Health.Value += GetHealthIncreaseFor(result);
|
Health.Value += GetHealthIncreaseFor(result);
|
||||||
|
|
||||||
if (meetsAnyFailCondition(result))
|
if (meetsAnyFailCondition(result))
|
||||||
{
|
TriggerFailure();
|
||||||
if (Failed?.Invoke() != false)
|
|
||||||
HasFailed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void RevertResultInternal(JudgementResult result)
|
protected override void RevertResultInternal(JudgementResult result)
|
||||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
float oldRotation = cumulativeRotation.Value ?? 0;
|
float oldRotation = cumulativeRotation.Value ?? 0;
|
||||||
|
|
||||||
float newRotation = shouldSnap ? snap(rawCumulativeRotation, snap_step) : rawCumulativeRotation;
|
float newRotation = shouldSnap ? snap(rawCumulativeRotation, snap_step) : MathF.Round(rawCumulativeRotation);
|
||||||
newRotation = (newRotation - 180) % 360 + 180;
|
newRotation = (newRotation - 180) % 360 + 180;
|
||||||
|
|
||||||
cumulativeRotation.Value = newRotation;
|
cumulativeRotation.Value = newRotation;
|
||||||
|
@ -210,7 +210,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
// this is a bit haphazard, but guards against setting the lease Beatmap bindable if
|
// this is a bit haphazard, but guards against setting the lease Beatmap bindable if
|
||||||
// the editor has already been exited.
|
// the editor has already been exited.
|
||||||
if (!ValidForPush)
|
if (!ValidForPush)
|
||||||
|
{
|
||||||
|
beatmapManager.Delete(loadableBeatmap.BeatmapSetInfo);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
76
osu.Game/Screens/Play/ArgonKeyCounter.cs
Normal file
76
osu.Game/Screens/Play/ArgonKeyCounter.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play
|
||||||
|
{
|
||||||
|
public partial class ArgonKeyCounter : KeyCounter
|
||||||
|
{
|
||||||
|
private Circle inputIndicator = null!;
|
||||||
|
private OsuSpriteText countText = null!;
|
||||||
|
|
||||||
|
// These values were taken from Figma
|
||||||
|
private const float line_height = 3;
|
||||||
|
private const float name_font_size = 10;
|
||||||
|
private const float count_font_size = 14;
|
||||||
|
|
||||||
|
// Make things look bigger without using Scale
|
||||||
|
private const float scale_factor = 1.5f;
|
||||||
|
|
||||||
|
public ArgonKeyCounter(InputTrigger trigger)
|
||||||
|
: base(trigger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
inputIndicator = new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = line_height * scale_factor,
|
||||||
|
Alpha = 0.5f
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Position = new Vector2(0, -13) * scale_factor,
|
||||||
|
Font = OsuFont.Torus.With(size: name_font_size * scale_factor, weight: FontWeight.Bold),
|
||||||
|
Colour = colours.Blue0,
|
||||||
|
Text = Trigger.Name
|
||||||
|
},
|
||||||
|
countText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Font = OsuFont.Torus.With(size: count_font_size * scale_factor, weight: FontWeight.Bold),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Values from Figma didn't match visually
|
||||||
|
// So these were just eyeballed
|
||||||
|
Height = 30 * scale_factor;
|
||||||
|
Width = 35 * scale_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
IsActive.BindValueChanged(e => inputIndicator.Alpha = e.NewValue ? 1 : 0.5f, true);
|
||||||
|
CountPresses.BindValueChanged(e => countText.Text = e.NewValue.ToString(@"#,0"), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs
Normal file
40
osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play
|
||||||
|
{
|
||||||
|
public partial class ArgonKeyCounterDisplay : KeyCounterDisplay
|
||||||
|
{
|
||||||
|
private const int duration = 100;
|
||||||
|
|
||||||
|
protected override FillFlowContainer<KeyCounter> KeyFlow { get; }
|
||||||
|
|
||||||
|
public ArgonKeyCounterDisplay()
|
||||||
|
{
|
||||||
|
InternalChild = KeyFlow = new FillFlowContainer<KeyCounter>
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
Spacing = new Vector2(2),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
Size = KeyFlow.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override KeyCounter CreateCounter(InputTrigger trigger) => new ArgonKeyCounter(trigger);
|
||||||
|
|
||||||
|
protected override void UpdateVisibility()
|
||||||
|
=> KeyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = Name,
|
Text = Trigger.Name,
|
||||||
Font = OsuFont.Numeric.With(size: 12),
|
Font = OsuFont.Numeric.With(size: 12),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -13,13 +13,11 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
private const int duration = 100;
|
private const int duration = 100;
|
||||||
private const double key_fade_time = 80;
|
private const double key_fade_time = 80;
|
||||||
|
|
||||||
private readonly FillFlowContainer<DefaultKeyCounter> keyFlow;
|
protected override FillFlowContainer<KeyCounter> KeyFlow { get; }
|
||||||
|
|
||||||
public override IEnumerable<KeyCounter> Counters => keyFlow;
|
|
||||||
|
|
||||||
public DefaultKeyCounterDisplay()
|
public DefaultKeyCounterDisplay()
|
||||||
{
|
{
|
||||||
InternalChild = keyFlow = new FillFlowContainer<DefaultKeyCounter>
|
InternalChild = KeyFlow = new FillFlowContainer<KeyCounter>
|
||||||
{
|
{
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
@ -33,20 +31,19 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
// Don't use autosize as it will shrink to zero when KeyFlow is hidden.
|
// Don't use autosize as it will shrink to zero when KeyFlow is hidden.
|
||||||
// In turn this can cause the display to be masked off screen and never become visible again.
|
// In turn this can cause the display to be masked off screen and never become visible again.
|
||||||
Size = keyFlow.Size;
|
Size = KeyFlow.Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Add(InputTrigger trigger) =>
|
protected override KeyCounter CreateCounter(InputTrigger trigger) => new DefaultKeyCounter(trigger)
|
||||||
keyFlow.Add(new DefaultKeyCounter(trigger)
|
{
|
||||||
{
|
FadeTime = key_fade_time,
|
||||||
FadeTime = key_fade_time,
|
KeyDownTextColor = KeyDownTextColor,
|
||||||
KeyDownTextColor = KeyDownTextColor,
|
KeyUpTextColor = KeyUpTextColor,
|
||||||
KeyUpTextColor = KeyUpTextColor,
|
};
|
||||||
});
|
|
||||||
|
|
||||||
protected override void UpdateVisibility() =>
|
protected override void UpdateVisibility() =>
|
||||||
// Isolate changing visibility of the key counters from fading this component.
|
// Isolate changing visibility of the key counters from fading this component.
|
||||||
keyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
|
KeyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
|
||||||
|
|
||||||
private Color4 keyDownTextColor = Color4.DarkGray;
|
private Color4 keyDownTextColor = Color4.DarkGray;
|
||||||
|
|
||||||
@ -58,7 +55,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
if (value != keyDownTextColor)
|
if (value != keyDownTextColor)
|
||||||
{
|
{
|
||||||
keyDownTextColor = value;
|
keyDownTextColor = value;
|
||||||
foreach (var child in keyFlow)
|
foreach (var child in KeyFlow.Cast<DefaultKeyCounter>())
|
||||||
child.KeyDownTextColor = value;
|
child.KeyDownTextColor = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,7 +71,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
if (value != keyUpTextColor)
|
if (value != keyUpTextColor)
|
||||||
{
|
{
|
||||||
keyUpTextColor = value;
|
keyUpTextColor = value;
|
||||||
foreach (var child in keyFlow)
|
foreach (var child in KeyFlow.Cast<DefaultKeyCounter>())
|
||||||
child.KeyUpTextColor = value;
|
child.KeyUpTextColor = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
Trigger.OnActivate += Activate;
|
Trigger.OnActivate += Activate;
|
||||||
Trigger.OnDeactivate += Deactivate;
|
Trigger.OnDeactivate += Deactivate;
|
||||||
|
|
||||||
Name = trigger.Name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void increment()
|
private void increment()
|
||||||
|
@ -29,7 +29,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="KeyCounter"/>s contained in this <see cref="KeyCounterDisplay"/>.
|
/// The <see cref="KeyCounter"/>s contained in this <see cref="KeyCounterDisplay"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract IEnumerable<KeyCounter> Counters { get; }
|
public IEnumerable<KeyCounter> Counters => KeyFlow;
|
||||||
|
|
||||||
|
protected abstract FillFlowContainer<KeyCounter> KeyFlow { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the actions reported by all <see cref="InputTrigger"/>s within this <see cref="KeyCounterDisplay"/> should be counted.
|
/// Whether the actions reported by all <see cref="InputTrigger"/>s within this <see cref="KeyCounterDisplay"/> should be counted.
|
||||||
@ -53,13 +55,22 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a <see cref="InputTrigger"/> to this display.
|
/// Add a <see cref="InputTrigger"/> to this display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Add(InputTrigger trigger);
|
public void Add(InputTrigger trigger)
|
||||||
|
{
|
||||||
|
var keyCounter = CreateCounter(trigger);
|
||||||
|
|
||||||
|
KeyFlow.Add(keyCounter);
|
||||||
|
|
||||||
|
IsCounting.BindTo(keyCounter.IsCounting);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a range of <see cref="InputTrigger"/> to this display.
|
/// Add a range of <see cref="InputTrigger"/> to this display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
|
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
|
||||||
|
|
||||||
|
protected abstract KeyCounter CreateCounter(InputTrigger trigger);
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
@ -85,7 +85,7 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
Loop = animation.LoopType == AnimationLoopType.LoopForever;
|
Loop = animation.LoopType == AnimationLoopType.LoopForever;
|
||||||
|
|
||||||
LifetimeStart = animation.StartTime;
|
LifetimeStart = animation.StartTime;
|
||||||
LifetimeEnd = animation.EndTime;
|
LifetimeEnd = animation.EndTimeForDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
|
@ -82,7 +82,7 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
Position = sprite.InitialPosition;
|
Position = sprite.InitialPosition;
|
||||||
|
|
||||||
LifetimeStart = sprite.StartTime;
|
LifetimeStart = sprite.StartTime;
|
||||||
LifetimeEnd = sprite.EndTime;
|
LifetimeEnd = sprite.EndTimeForDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
|
@ -12,9 +12,17 @@ namespace osu.Game.Storyboards
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which the <see cref="IStoryboardElement"/> ends.
|
/// The time at which the <see cref="IStoryboardElement"/> ends.
|
||||||
|
/// This is consumed to extend the length of a storyboard to ensure all visuals are played to completion.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
double EndTime { get; }
|
double EndTime { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time this element displays until.
|
||||||
|
/// This is used for lifetime purposes, and includes long playing animations which don't necessarily extend
|
||||||
|
/// a storyboard's play time.
|
||||||
|
/// </summary>
|
||||||
|
double EndTimeForDisplay { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The duration of the StoryboardElement.
|
/// The duration of the StoryboardElement.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -84,6 +81,19 @@ namespace osu.Game.Storyboards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double EndTimeForDisplay
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double latestEndTime = TimelineGroup.EndTime;
|
||||||
|
|
||||||
|
foreach (var l in loops)
|
||||||
|
latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations);
|
||||||
|
|
||||||
|
return latestEndTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands);
|
public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands);
|
||||||
|
|
||||||
private delegate void DrawablePropertyInitializer<in T>(Drawable drawable, T value);
|
private delegate void DrawablePropertyInitializer<in T>(Drawable drawable, T value);
|
||||||
@ -114,7 +124,7 @@ namespace osu.Game.Storyboards
|
|||||||
public virtual Drawable CreateDrawable()
|
public virtual Drawable CreateDrawable()
|
||||||
=> new DrawableStoryboardSprite(this);
|
=> new DrawableStoryboardSprite(this);
|
||||||
|
|
||||||
public void ApplyTransforms(Drawable drawable, IEnumerable<Tuple<CommandTimelineGroup, double>> triggeredGroups = null)
|
public void ApplyTransforms(Drawable drawable, IEnumerable<Tuple<CommandTimelineGroup, double>>? triggeredGroups = null)
|
||||||
{
|
{
|
||||||
// For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
|
// For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
|
||||||
// To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list
|
// To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list
|
||||||
@ -156,7 +166,7 @@ namespace osu.Game.Storyboards
|
|||||||
|
|
||||||
foreach (var command in commands)
|
foreach (var command in commands)
|
||||||
{
|
{
|
||||||
DrawablePropertyInitializer<T> initFunc = null;
|
DrawablePropertyInitializer<T>? initFunc = null;
|
||||||
|
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
{
|
{
|
||||||
@ -169,7 +179,7 @@ namespace osu.Game.Storyboards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<CommandTimeline<T>.TypedCommand> getCommands<T>(CommandTimelineSelector<T> timelineSelector, IEnumerable<Tuple<CommandTimelineGroup, double>> triggeredGroups)
|
private IEnumerable<CommandTimeline<T>.TypedCommand> getCommands<T>(CommandTimelineSelector<T> timelineSelector, IEnumerable<Tuple<CommandTimelineGroup, double>>? triggeredGroups)
|
||||||
{
|
{
|
||||||
var commands = TimelineGroup.GetCommands(timelineSelector);
|
var commands = TimelineGroup.GetCommands(timelineSelector);
|
||||||
foreach (var loop in loops)
|
foreach (var loop in loops)
|
||||||
@ -198,11 +208,11 @@ namespace osu.Game.Storyboards
|
|||||||
{
|
{
|
||||||
public double StartTime => command.StartTime;
|
public double StartTime => command.StartTime;
|
||||||
|
|
||||||
private readonly DrawablePropertyInitializer<T> initializeProperty;
|
private readonly DrawablePropertyInitializer<T>? initializeProperty;
|
||||||
private readonly DrawableTransformer<T> transform;
|
private readonly DrawableTransformer<T> transform;
|
||||||
private readonly CommandTimeline<T>.TypedCommand command;
|
private readonly CommandTimeline<T>.TypedCommand command;
|
||||||
|
|
||||||
public GeneratedCommand([NotNull] CommandTimeline<T>.TypedCommand command, [CanBeNull] DrawablePropertyInitializer<T> initializeProperty, [NotNull] DrawableTransformer<T> transform)
|
public GeneratedCommand(CommandTimeline<T>.TypedCommand command, DrawablePropertyInitializer<T>? initializeProperty, DrawableTransformer<T> transform)
|
||||||
{
|
{
|
||||||
this.command = command;
|
this.command = command;
|
||||||
this.initializeProperty = initializeProperty;
|
this.initializeProperty = initializeProperty;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user