mirror of
https://github.com/ppy/osu.git
synced 2024-09-21 18:47:27 +08:00
Merge branch 'master' into adjust-flashlight
This commit is contained in:
commit
bce20e0a59
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1003.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1005.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.922.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1005.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -137,12 +137,13 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
|
|
||||||
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
|
||||||
|
|
||||||
var desktopWindow = (SDL2DesktopWindow)host.Window;
|
var desktopWindow = (SDL2DesktopWindow)host.Window;
|
||||||
|
|
||||||
|
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
||||||
|
if (iconStream != null)
|
||||||
|
desktopWindow.SetIconFromStream(iconStream);
|
||||||
|
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.SetIconFromStream(iconStream);
|
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
@ -12,11 +17,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatchTouchInput : OsuTestScene
|
public class TestSceneCatchTouchInput : OsuTestScene
|
||||||
{
|
{
|
||||||
private CatchTouchInputMapper catchTouchInputMapper = null!;
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
[SetUpSteps]
|
|
||||||
public void SetUpSteps()
|
|
||||||
{
|
{
|
||||||
|
CatchTouchInputMapper catchTouchInputMapper = null!;
|
||||||
|
|
||||||
AddStep("create input overlay", () =>
|
AddStep("create input overlay", () =>
|
||||||
{
|
{
|
||||||
Child = new CatchInputManager(new CatchRuleset().RulesetInfo)
|
Child = new CatchInputManager(new CatchRuleset().RulesetInfo)
|
||||||
@ -32,12 +37,30 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("show overlay", () => catchTouchInputMapper.Show());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasic()
|
public void TestWithoutRelax()
|
||||||
{
|
{
|
||||||
AddStep("show overlay", () => catchTouchInputMapper.Show());
|
AddStep("create drawable ruleset without relax mod", () =>
|
||||||
|
{
|
||||||
|
Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List<Mod>());
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for load", () => Child.IsLoaded);
|
||||||
|
AddAssert("check touch input is shown", () => this.ChildrenOfType<CatchTouchInputMapper>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWithRelax()
|
||||||
|
{
|
||||||
|
AddStep("create drawable ruleset with relax mod", () =>
|
||||||
|
{
|
||||||
|
Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List<Mod> { new CatchModRelax() });
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for load", () => Child.IsLoaded);
|
||||||
|
AddAssert("check touch input is not shown", () => !this.ChildrenOfType<CatchTouchInputMapper>().Any());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -36,7 +37,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
KeyBindingInputManager.Add(new CatchTouchInputMapper());
|
// With relax mod, input maps directly to x position and left/right buttons are not used.
|
||||||
|
if (!Mods.Any(m => m is ModRelax))
|
||||||
|
KeyBindingInputManager.Add(new CatchTouchInputMapper());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
||||||
|
@ -54,10 +54,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
|
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,20 +30,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
public bool UpdateResult() => base.UpdateResult(true);
|
public bool UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
|
||||||
{
|
|
||||||
base.UpdateInitialTransforms();
|
|
||||||
|
|
||||||
// This hitobject should never expire, so this is just a safe maximum.
|
|
||||||
LifetimeEnd = LifetimeStart + 30000;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
{
|
{
|
||||||
// suppress the base call explicitly.
|
// suppress the base call explicitly.
|
||||||
// the hold note head should never change its visual state on its own due to the "freezing" mechanic
|
// the hold note head should never change its visual state on its own due to the "freezing" mechanic
|
||||||
// (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line).
|
// (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line).
|
||||||
// it will be hidden along with its parenting hold note when required.
|
// it will be hidden along with its parenting hold note when required.
|
||||||
|
|
||||||
|
// Set `LifetimeEnd` explicitly to a non-`double.MaxValue` because otherwise this DHO is automatically expired.
|
||||||
|
LifetimeEnd = double.PositiveInfinity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnPressed(KeyBindingPressEvent<ManiaAction> e) => false; // Handled by the hold note
|
public override bool OnPressed(KeyBindingPressEvent<ManiaAction> e) => false; // Handled by the hold note
|
||||||
|
@ -23,10 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
// Leaving the default (10s) makes hitobjects not appear, as this offset is used for the initial state transforms.
|
|
||||||
// Calculated as DrawableManiaRuleset.MAX_TIME_RANGE + some additional allowance for velocity < 1.
|
|
||||||
protected override double InitialLifetimeOffset => 30000;
|
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private ManiaPlayfield playfield { get; set; }
|
private ManiaPlayfield playfield { get; set; }
|
||||||
|
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 251 B |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png
Normal file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
136
osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs
Normal file
136
osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Input.States;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Testing.Input;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneSmoke : OsuSkinnableTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestSmoking()
|
||||||
|
{
|
||||||
|
addStep("Create short smoke", 2_000);
|
||||||
|
addStep("Create medium smoke", 5_000);
|
||||||
|
addStep("Create long smoke", 10_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStep(string stepName, double duration)
|
||||||
|
{
|
||||||
|
var smokeContainers = new List<SmokeContainer>();
|
||||||
|
|
||||||
|
AddStep(stepName, () =>
|
||||||
|
{
|
||||||
|
smokeContainers.Clear();
|
||||||
|
SetContents(_ =>
|
||||||
|
{
|
||||||
|
smokeContainers.Add(new TestSmokeContainer
|
||||||
|
{
|
||||||
|
Duration = duration,
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
});
|
||||||
|
|
||||||
|
return new SmokingInputManager
|
||||||
|
{
|
||||||
|
Duration = duration,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(0.95f),
|
||||||
|
Child = smokeContainers[^1],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Until skinnable expires", () =>
|
||||||
|
{
|
||||||
|
if (smokeContainers.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Logger.Log("How many: " + smokeContainers.Count);
|
||||||
|
|
||||||
|
foreach (var smokeContainer in smokeContainers)
|
||||||
|
{
|
||||||
|
if (smokeContainer.Children.Count != 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SmokingInputManager : ManualInputManager
|
||||||
|
{
|
||||||
|
public double Duration { get; init; }
|
||||||
|
|
||||||
|
private double? startTime;
|
||||||
|
|
||||||
|
public SmokingInputManager()
|
||||||
|
{
|
||||||
|
UseParentInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
MoveMouseTo(ToScreenSpace(DrawSize / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
const float spin_angle = 4 * MathF.PI;
|
||||||
|
|
||||||
|
startTime ??= Time.Current;
|
||||||
|
|
||||||
|
float fraction = (float)((Time.Current - startTime) / Duration);
|
||||||
|
|
||||||
|
float angle = fraction * spin_angle;
|
||||||
|
float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2;
|
||||||
|
|
||||||
|
Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2;
|
||||||
|
MoveMouseTo(ToScreenSpace(pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSmokeContainer : SmokeContainer
|
||||||
|
{
|
||||||
|
public double Duration { get; init; }
|
||||||
|
|
||||||
|
private bool isPressing;
|
||||||
|
private bool isFinished;
|
||||||
|
|
||||||
|
private double? startTime;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
startTime ??= Time.Current + 0.1;
|
||||||
|
|
||||||
|
if (!isPressing && !isFinished && Time.Current > startTime)
|
||||||
|
{
|
||||||
|
OnPressed(new KeyBindingPressEvent<OsuAction>(new InputState(), OsuAction.Smoke));
|
||||||
|
isPressing = true;
|
||||||
|
isFinished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPressing && Time.Current > startTime + Duration)
|
||||||
|
{
|
||||||
|
OnReleased(new KeyBindingReleaseEvent<OsuAction>(new InputState(), OsuAction.Smoke));
|
||||||
|
isPressing = false;
|
||||||
|
isFinished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -204,12 +204,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// todo: temporary / arbitrary, used for lifetime optimisation.
|
// todo: temporary / arbitrary, used for lifetime optimisation.
|
||||||
this.Delay(800).FadeOut();
|
this.Delay(800).FadeOut();
|
||||||
|
|
||||||
// in the case of an early state change, the fade should be expedited to the current point in time.
|
|
||||||
if (HitStateUpdateTime < HitObject.StartTime)
|
|
||||||
ApproachCircle.FadeOut(50);
|
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
default:
|
||||||
|
ApproachCircle.FadeOut();
|
||||||
|
break;
|
||||||
|
|
||||||
case ArmedState.Idle:
|
case ArmedState.Idle:
|
||||||
HitArea.HitAction = null;
|
HitArea.HitAction = null;
|
||||||
break;
|
break;
|
||||||
|
@ -11,7 +11,9 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -64,6 +66,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void UpdateInitialTransforms()
|
||||||
|
{
|
||||||
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
|
// Dim should only be applied at a top level, as it will be implicitly applied to nested objects.
|
||||||
|
if (ParentHitObject == null)
|
||||||
|
{
|
||||||
|
// Of note, no one noticed this was missing for years, but it definitely feels like it should still exist.
|
||||||
|
// For now this is applied across all skins, and matches stable.
|
||||||
|
// For simplicity, dim colour is applied to the DrawableHitObject itself.
|
||||||
|
// We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod).
|
||||||
|
this.FadeColour(new Color4(195, 195, 195, 255));
|
||||||
|
using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
|
||||||
|
this.FadeColour(Color4.White, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
private OsuInputManager osuActionInputManager;
|
private OsuInputManager osuActionInputManager;
|
||||||
|
@ -80,6 +80,9 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
LeftButton,
|
LeftButton,
|
||||||
|
|
||||||
[Description("Right button")]
|
[Description("Right button")]
|
||||||
RightButton
|
RightButton,
|
||||||
|
|
||||||
|
[Description("Smoke")]
|
||||||
|
Smoke,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
|
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
|
||||||
new KeyBinding(InputKey.X, OsuAction.RightButton),
|
new KeyBinding(InputKey.X, OsuAction.RightButton),
|
||||||
|
new KeyBinding(InputKey.C, OsuAction.Smoke),
|
||||||
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
|
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
|
||||||
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
|
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
SliderBall,
|
SliderBall,
|
||||||
SliderBody,
|
SliderBody,
|
||||||
SpinnerBody,
|
SpinnerBody,
|
||||||
|
CursorSmoke,
|
||||||
ApproachCircle,
|
ApproachCircle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
Position = currentFrame.Position;
|
Position = currentFrame.Position;
|
||||||
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
|
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
|
||||||
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
|
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
|
||||||
|
if (currentFrame.Smoke) Actions.Add(OsuAction.Smoke);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
||||||
@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
state |= ReplayButtonState.Left1;
|
state |= ReplayButtonState.Left1;
|
||||||
if (Actions.Contains(OsuAction.RightButton))
|
if (Actions.Contains(OsuAction.RightButton))
|
||||||
state |= ReplayButtonState.Right1;
|
state |= ReplayButtonState.Right1;
|
||||||
|
if (Actions.Contains(OsuAction.Smoke))
|
||||||
|
state |= ReplayButtonState.Smoke;
|
||||||
|
|
||||||
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
|
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Scoring
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
{
|
{
|
||||||
public class OsuHitWindows : HitWindows
|
public class OsuHitWindows : HitWindows
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// osu! ruleset has a fixed miss window regardless of difficulty settings.
|
||||||
|
/// </summary>
|
||||||
|
public const double MISS_WINDOW = 400;
|
||||||
|
|
||||||
private static readonly DifficultyRange[] osu_ranges =
|
private static readonly DifficultyRange[] osu_ranges =
|
||||||
{
|
{
|
||||||
new DifficultyRange(HitResult.Great, 80, 50, 20),
|
new DifficultyRange(HitResult.Great, 80, 50, 20),
|
||||||
new DifficultyRange(HitResult.Ok, 140, 100, 60),
|
new DifficultyRange(HitResult.Ok, 140, 100, 60),
|
||||||
new DifficultyRange(HitResult.Meh, 200, 150, 100),
|
new DifficultyRange(HitResult.Meh, 200, 150, 100),
|
||||||
new DifficultyRange(HitResult.Miss, 400, 400, 400),
|
new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW),
|
||||||
};
|
};
|
||||||
|
|
||||||
public override bool IsHitResultAllowed(HitResult result)
|
public override bool IsHitResultAllowed(HitResult result)
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Textures;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
|
{
|
||||||
|
public class DefaultSmokeSegment : SmokeSegment
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore textures)
|
||||||
|
{
|
||||||
|
// ISkinSource doesn't currently fallback to global textures.
|
||||||
|
// We might want to change this in the future if the intention is to allow the user to skin this as per legacy skins.
|
||||||
|
Texture = textures.Get("Gameplay/osu/cursor-smoke");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs
Normal file
19
osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacySmokeSegment : SmokeSegment
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Texture = skin.GetTexture("cursor-smoke");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -106,6 +106,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.CursorSmoke:
|
||||||
|
if (GetTexture("cursor-smoke") != null)
|
||||||
|
return new LegacySmokeSegment();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case OsuSkinComponents.HitCircleText:
|
case OsuSkinComponents.HitCircleText:
|
||||||
if (!this.HasFont(LegacyFont.HitCircle))
|
if (!this.HasFont(LegacyFont.HitCircle))
|
||||||
return null;
|
return null;
|
||||||
|
366
osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
Normal file
366
osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning
|
||||||
|
{
|
||||||
|
public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable
|
||||||
|
{
|
||||||
|
private const int max_point_count = 18_000;
|
||||||
|
|
||||||
|
// fade anim values
|
||||||
|
private const double initial_fade_out_duration = 4000;
|
||||||
|
|
||||||
|
private const double re_fade_in_speed = 3;
|
||||||
|
private const double re_fade_in_duration = 50;
|
||||||
|
|
||||||
|
private const double final_fade_out_speed = 2;
|
||||||
|
private const double final_fade_out_duration = 8000;
|
||||||
|
|
||||||
|
private const float initial_alpha = 0.6f;
|
||||||
|
private const float re_fade_in_alpha = 1f;
|
||||||
|
|
||||||
|
private readonly int rotationSeed = RNG.Next();
|
||||||
|
|
||||||
|
// scale anim values
|
||||||
|
private const double scale_duration = 1200;
|
||||||
|
|
||||||
|
private const float initial_scale = 0.65f;
|
||||||
|
private const float final_scale = 1f;
|
||||||
|
|
||||||
|
// rotation anim values
|
||||||
|
private const double rotation_duration = 500;
|
||||||
|
|
||||||
|
private const float max_rotation = 0.25f;
|
||||||
|
|
||||||
|
public IShader? TextureShader { get; private set; }
|
||||||
|
public IShader? RoundedTextureShader { get; private set; }
|
||||||
|
|
||||||
|
protected Texture? Texture { get; set; }
|
||||||
|
|
||||||
|
private float radius => Texture?.DisplayWidth * 0.165f ?? 3;
|
||||||
|
|
||||||
|
protected readonly List<SmokePoint> SmokePoints = new List<SmokePoint>();
|
||||||
|
|
||||||
|
private float pointInterval => radius * 7f / 8;
|
||||||
|
|
||||||
|
private double smokeStartTime { get; set; } = double.MinValue;
|
||||||
|
|
||||||
|
private double smokeEndTime { get; set; } = double.MaxValue;
|
||||||
|
|
||||||
|
private float totalDistance;
|
||||||
|
private Vector2? lastPosition;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ShaderManager shaders)
|
||||||
|
{
|
||||||
|
RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
|
||||||
|
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
LifetimeStart = smokeStartTime = Time.Current;
|
||||||
|
|
||||||
|
totalDistance = pointInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 nextPointDirection()
|
||||||
|
{
|
||||||
|
float angle = RNG.NextSingle(0, 2 * MathF.PI);
|
||||||
|
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPosition(Vector2 position, double time)
|
||||||
|
{
|
||||||
|
lastPosition ??= position;
|
||||||
|
|
||||||
|
float delta = (position - (Vector2)lastPosition).LengthFast;
|
||||||
|
totalDistance += delta;
|
||||||
|
int count = (int)(totalDistance / pointInterval);
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
Vector2 increment = position - (Vector2)lastPosition;
|
||||||
|
increment.NormalizeFast();
|
||||||
|
|
||||||
|
Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
|
||||||
|
increment *= pointInterval;
|
||||||
|
|
||||||
|
if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time)
|
||||||
|
{
|
||||||
|
int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer());
|
||||||
|
SmokePoints.RemoveRange(index, SmokePoints.Count - index);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalDistance %= pointInterval;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
SmokePoints.Add(new SmokePoint
|
||||||
|
{
|
||||||
|
Position = pointPos,
|
||||||
|
Time = time,
|
||||||
|
Direction = nextPointDirection(),
|
||||||
|
});
|
||||||
|
|
||||||
|
pointPos += increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPosition = position;
|
||||||
|
|
||||||
|
if (SmokePoints.Count >= max_point_count)
|
||||||
|
FinishDrawing(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FinishDrawing(double time)
|
||||||
|
{
|
||||||
|
smokeEndTime = time;
|
||||||
|
|
||||||
|
double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, smokeEndTime - smokeStartTime);
|
||||||
|
LifetimeEnd = smokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawNode CreateDrawNode() => new SmokeDrawNode(this);
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected struct SmokePoint
|
||||||
|
{
|
||||||
|
public Vector2 Position;
|
||||||
|
public double Time;
|
||||||
|
public Vector2 Direction;
|
||||||
|
|
||||||
|
public struct UpperBoundComparer : IComparer<SmokePoint>
|
||||||
|
{
|
||||||
|
public int Compare(SmokePoint x, SmokePoint target)
|
||||||
|
{
|
||||||
|
// By returning -1 when the target value is equal to x, guarantees that the
|
||||||
|
// element at BinarySearch's returned index will always be the first element
|
||||||
|
// larger. Since 0 is never returned, the target is never "found", so the return
|
||||||
|
// value will be the index's complement.
|
||||||
|
|
||||||
|
return x.Time > target.Time ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class SmokeDrawNode : TexturedShaderDrawNode
|
||||||
|
{
|
||||||
|
protected new SmokeSegment Source => (SmokeSegment)base.Source;
|
||||||
|
|
||||||
|
protected double SmokeStartTime { get; private set; }
|
||||||
|
protected double SmokeEndTime { get; private set; }
|
||||||
|
protected double CurrentTime { get; private set; }
|
||||||
|
|
||||||
|
private readonly List<SmokePoint> points = new List<SmokePoint>();
|
||||||
|
private IVertexBatch<TexturedVertex2D>? quadBatch;
|
||||||
|
private float radius;
|
||||||
|
private Vector2 drawSize;
|
||||||
|
private Texture? texture;
|
||||||
|
|
||||||
|
// anim calculation vars (color, scale, direction)
|
||||||
|
private double initialFadeOutDurationTrunc;
|
||||||
|
private double firstVisiblePointTime;
|
||||||
|
|
||||||
|
private double initialFadeOutTime;
|
||||||
|
private double reFadeInTime;
|
||||||
|
private double finalFadeOutTime;
|
||||||
|
|
||||||
|
private Random rotationRNG = new Random();
|
||||||
|
|
||||||
|
public SmokeDrawNode(ITexturedShaderDrawable source)
|
||||||
|
: base(source)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ApplyState()
|
||||||
|
{
|
||||||
|
base.ApplyState();
|
||||||
|
|
||||||
|
points.Clear();
|
||||||
|
points.AddRange(Source.SmokePoints);
|
||||||
|
|
||||||
|
radius = Source.radius;
|
||||||
|
drawSize = Source.DrawSize;
|
||||||
|
texture = Source.Texture;
|
||||||
|
|
||||||
|
SmokeStartTime = Source.smokeStartTime;
|
||||||
|
SmokeEndTime = Source.smokeEndTime;
|
||||||
|
CurrentTime = Source.Clock.CurrentTime;
|
||||||
|
|
||||||
|
rotationRNG = new Random(Source.rotationSeed);
|
||||||
|
|
||||||
|
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
|
||||||
|
firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc;
|
||||||
|
|
||||||
|
initialFadeOutTime = CurrentTime;
|
||||||
|
reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed);
|
||||||
|
finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed override void Draw(IRenderer renderer)
|
||||||
|
{
|
||||||
|
base.Draw(renderer);
|
||||||
|
|
||||||
|
if (points.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(max_point_count / 10, 10);
|
||||||
|
texture ??= renderer.WhitePixel;
|
||||||
|
RectangleF textureRect = texture.GetTextureRect();
|
||||||
|
|
||||||
|
var shader = GetAppropriateShader(renderer);
|
||||||
|
|
||||||
|
renderer.SetBlend(BlendingParameters.Additive);
|
||||||
|
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
||||||
|
|
||||||
|
shader.Bind();
|
||||||
|
texture.Bind();
|
||||||
|
|
||||||
|
foreach (var point in points)
|
||||||
|
drawPointQuad(point, textureRect);
|
||||||
|
|
||||||
|
shader.Unbind();
|
||||||
|
renderer.PopLocalMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Color4 ColourAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour
|
||||||
|
? ((SRGBColour)DrawColourInfo.Colour).Linear
|
||||||
|
: DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, drawSize)).Linear;
|
||||||
|
|
||||||
|
protected virtual Color4 PointColour(SmokePoint point)
|
||||||
|
{
|
||||||
|
var color = Color4.White;
|
||||||
|
|
||||||
|
double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time;
|
||||||
|
|
||||||
|
if (timeDoingInitialFadeOut > 0)
|
||||||
|
{
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
|
||||||
|
color.A = (1 - fraction) * initial_alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.A > 0)
|
||||||
|
{
|
||||||
|
double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
|
||||||
|
double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
|
||||||
|
|
||||||
|
if (timeDoingFinalFadeOut > 0)
|
||||||
|
{
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
|
||||||
|
fraction = MathF.Pow(fraction, 5);
|
||||||
|
color.A = (1 - fraction) * re_fade_in_alpha;
|
||||||
|
}
|
||||||
|
else if (timeDoingReFadeIn > 0)
|
||||||
|
{
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
|
||||||
|
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
||||||
|
color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual float PointScale(SmokePoint point)
|
||||||
|
{
|
||||||
|
double timeDoingScale = CurrentTime - point.Time;
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingScale / scale_duration), 0, 1);
|
||||||
|
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
||||||
|
return fraction * (final_scale - initial_scale) + initial_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Vector2 PointDirection(SmokePoint point)
|
||||||
|
{
|
||||||
|
float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X);
|
||||||
|
float finalAngle = initialAngle + nextRotation();
|
||||||
|
|
||||||
|
double timeDoingRotation = CurrentTime - point.Time;
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1);
|
||||||
|
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
||||||
|
float angle = fraction * (finalAngle - initialAngle) + initialAngle;
|
||||||
|
|
||||||
|
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1);
|
||||||
|
|
||||||
|
private void drawPointQuad(SmokePoint point, RectangleF textureRect)
|
||||||
|
{
|
||||||
|
Debug.Assert(quadBatch != null);
|
||||||
|
|
||||||
|
var colour = PointColour(point);
|
||||||
|
float scale = PointScale(point);
|
||||||
|
var dir = PointDirection(point);
|
||||||
|
var ortho = dir.PerpendicularLeft;
|
||||||
|
|
||||||
|
if (colour.A == 0 || scale == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
|
||||||
|
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
|
||||||
|
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
|
||||||
|
var localBotRight = point.Position + (radius * scale * (ortho + dir));
|
||||||
|
|
||||||
|
quadBatch.Add(new TexturedVertex2D
|
||||||
|
{
|
||||||
|
Position = localTopLeft,
|
||||||
|
TexturePosition = textureRect.TopLeft,
|
||||||
|
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopLeft), colour),
|
||||||
|
});
|
||||||
|
quadBatch.Add(new TexturedVertex2D
|
||||||
|
{
|
||||||
|
Position = localTopRight,
|
||||||
|
TexturePosition = textureRect.TopRight,
|
||||||
|
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopRight), colour),
|
||||||
|
});
|
||||||
|
quadBatch.Add(new TexturedVertex2D
|
||||||
|
{
|
||||||
|
Position = localBotRight,
|
||||||
|
TexturePosition = textureRect.BottomRight,
|
||||||
|
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotRight), colour),
|
||||||
|
});
|
||||||
|
quadBatch.Add(new TexturedVertex2D
|
||||||
|
{
|
||||||
|
Position = localBotLeft,
|
||||||
|
TexturePosition = textureRect.BottomLeft,
|
||||||
|
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotLeft), colour),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
quadBatch?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||||
|
new SmokeContainer { RelativeSizeAxes = Axes.Both },
|
||||||
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
||||||
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||||
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
||||||
|
77
osu.Game.Rulesets.Osu/UI/SmokeContainer.cs
Normal file
77
osu.Game.Rulesets.Osu/UI/SmokeContainer.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages smoke trails generated from user input.
|
||||||
|
/// </summary>
|
||||||
|
public class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler<OsuAction>
|
||||||
|
{
|
||||||
|
private SmokeSkinnableDrawable? currentSegmentSkinnable;
|
||||||
|
|
||||||
|
private Vector2 lastMousePosition;
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 _) => true;
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == OsuAction.Smoke)
|
||||||
|
{
|
||||||
|
AddInternal(currentSegmentSkinnable = new SmokeSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment()));
|
||||||
|
|
||||||
|
// Add initial position immediately.
|
||||||
|
addPosition();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == OsuAction.Smoke)
|
||||||
|
{
|
||||||
|
if (currentSegmentSkinnable?.Drawable is SmokeSegment segment)
|
||||||
|
{
|
||||||
|
segment.FinishDrawing(Time.Current);
|
||||||
|
currentSegmentSkinnable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
lastMousePosition = e.MousePosition;
|
||||||
|
addPosition();
|
||||||
|
|
||||||
|
return base.OnMouseMove(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPosition() => (currentSegmentSkinnable?.Drawable as SmokeSegment)?.AddPosition(lastMousePosition, Time.Current);
|
||||||
|
|
||||||
|
private class SmokeSkinnableDrawable : SkinnableDrawable
|
||||||
|
{
|
||||||
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
|
public override double LifetimeStart => Drawable.LifetimeStart;
|
||||||
|
public override double LifetimeEnd => Drawable.LifetimeEnd;
|
||||||
|
|
||||||
|
public SmokeSkinnableDrawable(ISkinComponent component, Func<ISkinComponent, Drawable>? defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
|
||||||
|
: base(component, defaultImplementation, confineMode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
private int countOk;
|
private int countOk;
|
||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
private double accuracy;
|
||||||
|
|
||||||
private double effectiveMissCount;
|
private double effectiveMissCount;
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
|
accuracy = customAccuracy;
|
||||||
|
|
||||||
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
|
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
|
||||||
if (totalSuccessfulHits > 0)
|
if (totalSuccessfulHits > 0)
|
||||||
@ -87,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
||||||
difficultyValue *= 1.050 * lengthBonus;
|
difficultyValue *= 1.050 * lengthBonus;
|
||||||
|
|
||||||
return difficultyValue * Math.Pow(score.Accuracy, 2.0);
|
return difficultyValue * Math.Pow(accuracy, 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
|
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
|
||||||
@ -95,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
if (attributes.GreatHitWindow <= 0)
|
if (attributes.GreatHitWindow <= 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
|
double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
|
||||||
|
|
||||||
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
||||||
accuracyValue *= lengthBonus;
|
accuracyValue *= lengthBonus;
|
||||||
@ -110,5 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||||
|
|
||||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
||||||
|
|
||||||
|
private double customAccuracy => totalHits > 0 ? (countGreat * 300 + countOk * 150) / (totalHits * 300.0) : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -78,7 +77,7 @@ namespace osu.Game.Tests.Online
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
beatmaps.AllowImport = new TaskCompletionSource<bool>();
|
beatmaps.AllowImport.Reset();
|
||||||
|
|
||||||
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
|
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
|
||||||
|
|
||||||
@ -132,7 +131,7 @@ namespace osu.Game.Tests.Online
|
|||||||
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile));
|
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile));
|
||||||
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
||||||
|
|
||||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
AddStep("allow importing", () => beatmaps.AllowImport.Set());
|
||||||
AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
|
AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
|
||||||
AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet));
|
AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet));
|
||||||
addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable);
|
addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
@ -141,7 +140,7 @@ namespace osu.Game.Tests.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestTrackerRespectsSoftDeleting()
|
public void TestTrackerRespectsSoftDeleting()
|
||||||
{
|
{
|
||||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
AddStep("allow importing", () => beatmaps.AllowImport.Set());
|
||||||
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
|
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
|
||||||
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
|
|
||||||
@ -155,7 +154,7 @@ namespace osu.Game.Tests.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestTrackerRespectsChecksum()
|
public void TestTrackerRespectsChecksum()
|
||||||
{
|
{
|
||||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
AddStep("allow importing", () => beatmaps.AllowImport.Set());
|
||||||
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
|
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
|
||||||
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
|
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
|
|
||||||
@ -202,7 +201,7 @@ namespace osu.Game.Tests.Online
|
|||||||
|
|
||||||
private class TestBeatmapManager : BeatmapManager
|
private class TestBeatmapManager : BeatmapManager
|
||||||
{
|
{
|
||||||
public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>();
|
public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim();
|
||||||
|
|
||||||
public Live<BeatmapSetInfo> CurrentImport { get; private set; }
|
public Live<BeatmapSetInfo> CurrentImport { get; private set; }
|
||||||
|
|
||||||
@ -229,7 +228,9 @@ namespace osu.Game.Tests.Online
|
|||||||
|
|
||||||
public override Live<BeatmapSetInfo> ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
|
public override Live<BeatmapSetInfo> ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
testBeatmapManager.AllowImport.Task.WaitSafely();
|
if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken))
|
||||||
|
throw new TimeoutException("Timeout waiting for import to be allowed.");
|
||||||
|
|
||||||
return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken));
|
return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
addSeekStep(3000);
|
addSeekStep(3000);
|
||||||
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
|
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Select(kc => kc.CountPresses).Sum() == 15);
|
||||||
AddStep("clear results", () => Player.Results.Clear());
|
AddStep("clear results", () => Player.Results.Clear());
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
||||||
|
@ -11,8 +11,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -167,14 +169,39 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addHitObject(double time)
|
[Test]
|
||||||
|
public void TestVeryFlowScroll()
|
||||||
|
{
|
||||||
|
const double long_time_range = 100000;
|
||||||
|
var manualClock = new ManualClock();
|
||||||
|
|
||||||
|
AddStep("set manual clock", () =>
|
||||||
|
{
|
||||||
|
manualClock.CurrentTime = 0;
|
||||||
|
scrollContainers.ForEach(c => c.Clock = new FramedClock(manualClock));
|
||||||
|
|
||||||
|
setScrollAlgorithm(ScrollVisualisationMethod.Constant);
|
||||||
|
scrollContainers.ForEach(c => c.TimeRange = long_time_range);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add hit objects", () =>
|
||||||
|
{
|
||||||
|
addHitObject(long_time_range);
|
||||||
|
addHitObject(long_time_range + 100, 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("hit objects are alive", () => playfields.All(p => p.HitObjectContainer.AliveObjects.Count() == 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHitObject(double time, float size = 75)
|
||||||
{
|
{
|
||||||
playfields.ForEach(p =>
|
playfields.ForEach(p =>
|
||||||
{
|
{
|
||||||
var hitObject = new TestDrawableHitObject(time);
|
var hitObject = new TestHitObject(size) { StartTime = time };
|
||||||
setAnchor(hitObject, p);
|
var drawable = new TestDrawableHitObject(hitObject);
|
||||||
|
|
||||||
p.Add(hitObject);
|
setAnchor(drawable, p);
|
||||||
|
p.Add(drawable);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +275,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new TestScrollingHitObjectContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
|
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
|
||||||
@ -281,22 +310,41 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableHitObject : DrawableHitObject<HitObject>
|
private class TestHitObject : HitObject
|
||||||
{
|
{
|
||||||
public TestDrawableHitObject(double time)
|
public readonly float Size;
|
||||||
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
|
|
||||||
{
|
|
||||||
Origin = Anchor.Custom;
|
|
||||||
OriginPosition = new Vector2(75 / 4.0f);
|
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
public TestHitObject(float size)
|
||||||
|
{
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableHitObject : DrawableHitObject<TestHitObject>
|
||||||
|
{
|
||||||
|
public TestDrawableHitObject(TestHitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Size = new Vector2(hitObject.Size);
|
||||||
|
|
||||||
AddInternal(new Box
|
AddInternal(new Box
|
||||||
{
|
{
|
||||||
Size = new Vector2(75),
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
|
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestScrollingHitObjectContainer : ScrollingHitObjectContainer
|
||||||
|
{
|
||||||
|
protected override RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry)
|
||||||
|
{
|
||||||
|
if (entry.HitObject is TestHitObject testObject)
|
||||||
|
return new RectangleF().Inflate(testObject.Size / 2);
|
||||||
|
|
||||||
|
return base.GetConservativeBoundingBox(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v);
|
AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v);
|
||||||
AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v);
|
AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v);
|
||||||
AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.Combo.Value = v);
|
AddSliderStep("combo", 0, 10000, 0, v => scoreProcessor.HighestCombo.Value = v);
|
||||||
|
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected new OutroPlayer Player => (OutroPlayer)base.Player;
|
protected new OutroPlayer Player => (OutroPlayer)base.Player;
|
||||||
|
|
||||||
|
private double currentBeatmapDuration;
|
||||||
private double currentStoryboardDuration;
|
private double currentStoryboardDuration;
|
||||||
|
|
||||||
private bool showResults = true;
|
private bool showResults = true;
|
||||||
@ -45,7 +46,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
|
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
|
||||||
AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0));
|
AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0));
|
||||||
AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false);
|
AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false);
|
||||||
AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000);
|
AddStep("set beatmap duration to 0s", () => currentBeatmapDuration = 0);
|
||||||
|
AddStep("set storyboard duration to 8s", () => currentStoryboardDuration = 8000);
|
||||||
AddStep("set ShowResults = true", () => showResults = true);
|
AddStep("set ShowResults = true", () => showResults = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +153,24 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("player exited", () => Stack.CurrentScreen == null);
|
AddAssert("player exited", () => Stack.CurrentScreen == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPerformExitAfterOutro()
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("set beatmap duration to 4s", () => currentBeatmapDuration = 4000);
|
||||||
|
AddStep("set storyboard duration to 1s", () => currentStoryboardDuration = 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddStep("exit via pause", () => Player.ExitViaPause());
|
||||||
|
AddAssert("player paused", () => !Player.IsResuming);
|
||||||
|
|
||||||
|
AddStep("resume player", () => Player.Resume());
|
||||||
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool AllowFail => true;
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
@ -160,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap();
|
var beatmap = new Beatmap();
|
||||||
beatmap.HitObjects.Add(new HitCircle());
|
beatmap.HitObjects.Add(new HitCircle { StartTime = currentBeatmapDuration });
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +209,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private event Func<HealthProcessor, JudgementResult, bool> failConditions;
|
private event Func<HealthProcessor, JudgementResult, bool> failConditions;
|
||||||
|
|
||||||
public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true)
|
public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true)
|
||||||
: base(false, showResults)
|
: base(showResults: showResults)
|
||||||
{
|
{
|
||||||
this.failConditions = failConditions;
|
this.failConditions = failConditions;
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,10 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Catch;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -92,6 +94,31 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
returnToMenu();
|
returnToMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFromSongSelectWithFilter([Values] ScorePresentType type)
|
||||||
|
{
|
||||||
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
||||||
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
|
AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq");
|
||||||
|
AddUntilStep("wait for no results", () => Beatmap.IsDefault);
|
||||||
|
|
||||||
|
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
|
||||||
|
presentAndConfirm(firstImport, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type)
|
||||||
|
{
|
||||||
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
||||||
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
|
AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
|
||||||
|
|
||||||
|
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
|
||||||
|
presentAndConfirm(firstImport, type);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFromSongSelect([Values] ScorePresentType type)
|
public void TestFromSongSelect([Values] ScorePresentType type)
|
||||||
{
|
{
|
||||||
|
@ -89,6 +89,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections");
|
public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Mod presets"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"Mod presets");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Name"
|
/// "Name"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -44,11 +44,6 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
|
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// "Compact realm"
|
|
||||||
/// </summary>
|
|
||||||
public static LocalisableString CompactRealm => new TranslatableString(getKey(@"compact_realm"), @"Compact realm");
|
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard");
|
public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "You are running the latest release ({0})"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString RunningLatestRelease(string version) => new TranslatableString(getKey(@"running_latest_release"), @"You are running the latest release ({0})", version);
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,41 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory");
|
public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Migration in progress"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString MigrationInProgress => new TranslatableString(getKey(@"migration_in_progress"), @"Migration in progress");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "This could take a few minutes depending on the speed of your disk(s)."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString MigrationDescription => new TranslatableString(getKey(@"migration_description"), @"This could take a few minutes depending on the speed of your disk(s).");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Please avoid interacting with the game!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ProhibitedInteractDuringMigration => new TranslatableString(getKey(@"prohibited_interact_during_migration"), @"Please avoid interacting with the game!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString FailedCleanupNotification => new TranslatableString(getKey(@"failed_cleanup_notification"), @"Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Please select a new location"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SelectNewLocation => new TranslatableString(getKey(@"select_new_location"), @"Please select a new location");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "The target directory already seems to have an osu! install. Use that data instead?"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TargetDirectoryAlreadyInstalledOsu => new TranslatableString(getKey(@"target_directory_already_installed_osu"), @"The target directory already seems to have an osu! install. Use that data instead?");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "To complete this operation, osu! will close. Please open it again to use the new data location."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString RestartAndReOpenRequiredForCompletion => new TranslatableString(getKey(@"restart_and_re_open_required_for_completion"), @"To complete this operation, osu! will close. Please open it again to use the new data location.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Import beatmaps from stable"
|
/// "Import beatmaps from stable"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -84,6 +119,26 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RestoreAllRecentlyDeletedModPresets => new TranslatableString(getKey(@"restore_all_recently_deleted_mod_presets"), @"Restore all recently deleted mod presets");
|
public static LocalisableString RestoreAllRecentlyDeletedModPresets => new TranslatableString(getKey(@"restore_all_recently_deleted_mod_presets"), @"Restore all recently deleted mod presets");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Deleted all collections!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DeletedAllCollections => new TranslatableString(getKey(@"deleted_all_collections"), @"Deleted all collections!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Deleted all mod presets!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DeletedAllModPresets => new TranslatableString(getKey(@"deleted_all_mod_presets"), @"Deleted all mod presets!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Restored all deleted mod presets!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString RestoredAllDeletedModPresets => new TranslatableString(getKey(@"restored_all_deleted_mod_presets"), @"Restored all deleted mod presets!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Please select your osu!stable install location"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString StableDirectorySelectHeader => new TranslatableString(getKey(@"stable_directory_select_header"), @"Please select your osu!stable install location");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString NoTabletDetected => new TranslatableString(getKey(@"no_tablet_detected"), @"No tablet detected!");
|
public static LocalisableString NoTabletDetected => new TranslatableString(getKey(@"no_tablet_detected"), @"No tablet detected!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "If your tablet is not detected, please read [this FAQ]({0}) for troubleshooting steps."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NoTabletDetectedDescription(string url) => new TranslatableString(getKey(@"no_tablet_detected_description"), @"If your tablet is not detected, please read [this FAQ]({0}) for troubleshooting steps.", url);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Reset to full area"
|
/// "Reset to full area"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,9 +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.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -58,7 +58,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
{
|
{
|
||||||
await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state);
|
await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state);
|
||||||
}
|
}
|
||||||
catch (HubException exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
|
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
|
||||||
{
|
{
|
||||||
|
@ -561,9 +561,11 @@ namespace osu.Game
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This should be able to be performed from song select, but that is disabled for now
|
||||||
|
// due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios).
|
||||||
PerformFromScreen(screen =>
|
PerformFromScreen(screen =>
|
||||||
{
|
{
|
||||||
Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score");
|
Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset}) to match score");
|
||||||
|
|
||||||
Ruleset.Value = databasedScore.ScoreInfo.Ruleset;
|
Ruleset.Value = databasedScore.ScoreInfo.Ruleset;
|
||||||
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
|
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
|
||||||
@ -578,7 +580,7 @@ namespace osu.Game
|
|||||||
screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false));
|
screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, validScreens: new[] { typeof(PlaySongSelect) });
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task Import(params ImportTask[] imports)
|
public override Task Import(params ImportTask[] imports)
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Dialog
|
namespace osu.Game.Overlays.Dialog
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
/// <param name="message">The description of the action to be displayed to the user.</param>
|
/// <param name="message">The description of the action to be displayed to the user.</param>
|
||||||
/// <param name="onConfirm">An action to perform on confirmation.</param>
|
/// <param name="onConfirm">An action to perform on confirmation.</param>
|
||||||
/// <param name="onCancel">An optional action to perform on cancel.</param>
|
/// <param name="onCancel">An optional action to perform on cancel.</param>
|
||||||
public ConfirmDialog(string message, Action onConfirm, Action onCancel = null)
|
public ConfirmDialog(LocalisableString message, Action onConfirm, Action onCancel = null)
|
||||||
{
|
{
|
||||||
HeaderText = message;
|
HeaderText = message;
|
||||||
BodyText = "Last chance to turn back";
|
BodyText = "Last chance to turn back";
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
|||||||
},
|
},
|
||||||
new SettingsButton
|
new SettingsButton
|
||||||
{
|
{
|
||||||
Text = DebugSettingsStrings.CompactRealm,
|
Text = "Compact realm",
|
||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
// Blocking operations implicitly causes a Compact().
|
// Blocking operations implicitly causes a Compact().
|
||||||
|
@ -44,9 +44,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!LanguageExtensions.TryParseCultureCode(frameworkLocale.Value, out var locale))
|
frameworkLocale.BindValueChanged(locale =>
|
||||||
locale = Language.en;
|
{
|
||||||
languageSelection.Current.Value = locale;
|
if (!LanguageExtensions.TryParseCultureCode(locale.NewValue, out var language))
|
||||||
|
language = Language.en;
|
||||||
|
languageSelection.Current.Value = language;
|
||||||
|
}, true);
|
||||||
|
|
||||||
languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode());
|
languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode());
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
{
|
{
|
||||||
notifications?.Post(new SimpleNotification
|
notifications?.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Text = $"You are running the latest release ({game.Version})",
|
Text = GeneralSettingsStrings.RunningLatestRelease(game.Version),
|
||||||
Icon = FontAwesome.Solid.CheckCircle,
|
Icon = FontAwesome.Solid.CheckCircle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
windowModes.BindTo(host.Window.SupportedWindowModes);
|
windowModes.BindTo(host.Window.SupportedWindowModes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host.Window is WindowsWindow windowsWindow)
|
if (host.Renderer is IWindowsRenderer windowsRenderer)
|
||||||
fullscreenCapability.BindTo(windowsWindow.FullscreenCapability);
|
fullscreenCapability.BindTo(windowsRenderer.FullscreenCapability);
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours, LocalisationManager localisation)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -110,11 +110,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
|
||||||
{
|
{
|
||||||
t.NewLine();
|
t.NewLine();
|
||||||
t.AddText("If your tablet is not detected, please read ");
|
var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(RuntimeInfo.OS == RuntimeInfo.Platform.Windows
|
||||||
t.AddLink("this FAQ", LinkAction.External, RuntimeInfo.OS == RuntimeInfo.Platform.Windows
|
|
||||||
? @"https://opentabletdriver.net/Wiki/FAQ/Windows"
|
? @"https://opentabletdriver.net/Wiki/FAQ/Windows"
|
||||||
: @"https://opentabletdriver.net/Wiki/FAQ/Linux");
|
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value);
|
||||||
t.AddText(" for troubleshooting steps.");
|
t.AddLinks(formattedSource.Text, formattedSource.Links);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
public class BeatmapSettings : SettingsSubsection
|
public class BeatmapSettings : SettingsSubsection
|
||||||
{
|
{
|
||||||
protected override LocalisableString Header => "Beatmaps";
|
protected override LocalisableString Header => CommonStrings.Beatmaps;
|
||||||
|
|
||||||
private SettingsButton importBeatmapsButton = null!;
|
private SettingsButton importBeatmapsButton = null!;
|
||||||
private SettingsButton deleteBeatmapsButton = null!;
|
private SettingsButton deleteBeatmapsButton = null!;
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
public class CollectionsSettings : SettingsSubsection
|
public class CollectionsSettings : SettingsSubsection
|
||||||
{
|
{
|
||||||
protected override LocalisableString Header => "Collections";
|
protected override LocalisableString Header => CommonStrings.Collections;
|
||||||
|
|
||||||
private SettingsButton importCollectionsButton = null!;
|
private SettingsButton importCollectionsButton = null!;
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
private void deleteAllCollections()
|
private void deleteAllCollections()
|
||||||
{
|
{
|
||||||
realm.Write(r => r.RemoveAll<BeatmapCollection>());
|
realm.Write(r => r.RemoveAll<BeatmapCollection>());
|
||||||
notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" });
|
notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllCollections });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -71,14 +72,14 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = "Migration in progress",
|
Text = MaintenanceSettingsStrings.MigrationInProgress,
|
||||||
Font = OsuFont.Default.With(size: 40)
|
Font = OsuFont.Default.With(size: 40)
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = "This could take a few minutes depending on the speed of your disk(s).",
|
Text = MaintenanceSettingsStrings.MigrationDescription,
|
||||||
Font = OsuFont.Default.With(size: 30)
|
Font = OsuFont.Default.With(size: 30)
|
||||||
},
|
},
|
||||||
new LoadingSpinner(true)
|
new LoadingSpinner(true)
|
||||||
@ -89,7 +90,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = "Please avoid interacting with the game!",
|
Text = MaintenanceSettingsStrings.ProhibitedInteractDuringMigration,
|
||||||
Font = OsuFont.Default.With(size: 30)
|
Font = OsuFont.Default.With(size: 30)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -111,7 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
notifications.Post(new SimpleNotification
|
notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Text = "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.",
|
Text = MaintenanceSettingsStrings.FailedCleanupNotification,
|
||||||
Activated = () =>
|
Activated = () =>
|
||||||
{
|
{
|
||||||
originalStorage.PresentExternally();
|
originalStorage.PresentExternally();
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Logging;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
@ -35,7 +36,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
|
|
||||||
public override bool HideOverlaysOnEnter => true;
|
public override bool HideOverlaysOnEnter => true;
|
||||||
|
|
||||||
public override LocalisableString HeaderText => "Please select a new location";
|
public override LocalisableString HeaderText => MaintenanceSettingsStrings.SelectNewLocation;
|
||||||
|
|
||||||
protected override void OnSelection(DirectoryInfo directory)
|
protected override void OnSelection(DirectoryInfo directory)
|
||||||
{
|
{
|
||||||
@ -51,9 +52,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
// Quick test for whether there's already an osu! install at the target path.
|
// Quick test for whether there's already an osu! install at the target path.
|
||||||
if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME))
|
if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME))
|
||||||
{
|
{
|
||||||
dialogOverlay.Push(new ConfirmDialog("The target directory already seems to have an osu! install. Use that data instead?", () =>
|
dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.TargetDirectoryAlreadyInstalledOsu, () =>
|
||||||
{
|
{
|
||||||
dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () =>
|
dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.RestartAndReOpenRequiredForCompletion, () =>
|
||||||
{
|
{
|
||||||
(storage as OsuStorage)?.ChangeDataPath(target.FullName);
|
(storage as OsuStorage)?.ChangeDataPath(target.FullName);
|
||||||
game.Exit();
|
game.Exit();
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
public class ModPresetSettings : SettingsSubsection
|
public class ModPresetSettings : SettingsSubsection
|
||||||
{
|
{
|
||||||
protected override LocalisableString Header => "Mod presets";
|
protected override LocalisableString Header => CommonStrings.ModPresets;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private RealmAccess realm { get; set; } = null!;
|
private RealmAccess realm { get; set; } = null!;
|
||||||
@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
deleteAllButton.Enabled.Value = true;
|
deleteAllButton.Enabled.Value = true;
|
||||||
|
|
||||||
if (deletionTask.IsCompletedSuccessfully)
|
if (deletionTask.IsCompletedSuccessfully)
|
||||||
notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Deleted all mod presets!" });
|
notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllModPresets });
|
||||||
else if (deletionTask.IsFaulted)
|
else if (deletionTask.IsFaulted)
|
||||||
Logger.Error(deletionTask.Exception, "Failed to delete all mod presets");
|
Logger.Error(deletionTask.Exception, "Failed to delete all mod presets");
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
undeleteButton.Enabled.Value = true;
|
undeleteButton.Enabled.Value = true;
|
||||||
|
|
||||||
if (undeletionTask.IsCompletedSuccessfully)
|
if (undeletionTask.IsCompletedSuccessfully)
|
||||||
notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Restored all deleted mod presets!" });
|
notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.RestoredAllDeletedModPresets });
|
||||||
else if (undeletionTask.IsFaulted)
|
else if (undeletionTask.IsFaulted)
|
||||||
Logger.Error(undeletionTask.Exception, "Failed to restore mod presets");
|
Logger.Error(undeletionTask.Exception, "Failed to restore mod presets");
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
public class ScoreSettings : SettingsSubsection
|
public class ScoreSettings : SettingsSubsection
|
||||||
{
|
{
|
||||||
protected override LocalisableString Header => "Scores";
|
protected override LocalisableString Header => CommonStrings.Scores;
|
||||||
|
|
||||||
private SettingsButton importScoresButton = null!;
|
private SettingsButton importScoresButton = null!;
|
||||||
private SettingsButton deleteScoresButton = null!;
|
private SettingsButton deleteScoresButton = null!;
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
public class SkinSettings : SettingsSubsection
|
public class SkinSettings : SettingsSubsection
|
||||||
{
|
{
|
||||||
protected override LocalisableString Header => "Skins";
|
protected override LocalisableString Header => CommonStrings.Skins;
|
||||||
|
|
||||||
private SettingsButton importSkinsButton = null!;
|
private SettingsButton importSkinsButton = null!;
|
||||||
private SettingsButton deleteSkinsButton = null!;
|
private SettingsButton deleteSkinsButton = null!;
|
||||||
|
@ -46,6 +46,10 @@ namespace osu.Game.Replays.Legacy
|
|||||||
[IgnoreMember]
|
[IgnoreMember]
|
||||||
public bool MouseRight2 => ButtonState.HasFlagFast(ReplayButtonState.Right2);
|
public bool MouseRight2 => ButtonState.HasFlagFast(ReplayButtonState.Right2);
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[IgnoreMember]
|
||||||
|
public bool Smoke => ButtonState.HasFlagFast(ReplayButtonState.Smoke);
|
||||||
|
|
||||||
[Key(3)]
|
[Key(3)]
|
||||||
public ReplayButtonState ButtonState;
|
public ReplayButtonState ButtonState;
|
||||||
|
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -127,6 +127,16 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
|
|
||||||
private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight;
|
private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight;
|
||||||
|
|
||||||
|
public override void Add(HitObjectLifetimeEntry entry)
|
||||||
|
{
|
||||||
|
// Scroll info is not available until loaded.
|
||||||
|
// The lifetime of all entries will be updated in the first Update.
|
||||||
|
if (IsLoaded)
|
||||||
|
setComputedLifetimeStart(entry);
|
||||||
|
|
||||||
|
base.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
base.AddDrawable(entry, drawable);
|
base.AddDrawable(entry, drawable);
|
||||||
@ -145,7 +155,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
|
|
||||||
private void invalidateHitObject(DrawableHitObject hitObject)
|
private void invalidateHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
|
|
||||||
layoutComputed.Remove(hitObject);
|
layoutComputed.Remove(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,10 +166,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
|
|
||||||
layoutComputed.Clear();
|
layoutComputed.Clear();
|
||||||
|
|
||||||
// Reset lifetime to the conservative estimation.
|
|
||||||
// If a drawable becomes alive by this lifetime, its lifetime will be updated to a more precise lifetime in the next update.
|
|
||||||
foreach (var entry in Entries)
|
foreach (var entry in Entries)
|
||||||
entry.SetInitialLifetime();
|
setComputedLifetimeStart(entry);
|
||||||
|
|
||||||
scrollingInfo.Algorithm.Reset();
|
scrollingInfo.Algorithm.Reset();
|
||||||
|
|
||||||
@ -187,38 +194,46 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject)
|
/// <summary>
|
||||||
|
/// Get a conservative maximum bounding box of a <see cref="DrawableHitObject"/> corresponding to <paramref name="entry"/>.
|
||||||
|
/// It is used to calculate when the hit object appears.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry) => new RectangleF().Inflate(100);
|
||||||
|
|
||||||
|
private double computeDisplayStartTime(HitObjectLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
// Origin position may be relative to the parent size
|
RectangleF boundingBox = GetConservativeBoundingBox(entry);
|
||||||
Debug.Assert(hitObject.Parent != null);
|
float startOffset = 0;
|
||||||
|
|
||||||
float originAdjustment = 0.0f;
|
|
||||||
|
|
||||||
// calculate the dimension of the part of the hitobject that should already be visible
|
|
||||||
// when the hitobject origin first appears inside the scrolling container
|
|
||||||
switch (direction.Value)
|
switch (direction.Value)
|
||||||
{
|
{
|
||||||
case ScrollingDirection.Up:
|
case ScrollingDirection.Right:
|
||||||
originAdjustment = hitObject.OriginPosition.Y;
|
startOffset = boundingBox.Right;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ScrollingDirection.Down:
|
case ScrollingDirection.Down:
|
||||||
originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y;
|
startOffset = boundingBox.Bottom;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ScrollingDirection.Left:
|
case ScrollingDirection.Left:
|
||||||
originAdjustment = hitObject.OriginPosition.X;
|
startOffset = -boundingBox.Left;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ScrollingDirection.Right:
|
case ScrollingDirection.Up:
|
||||||
originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X;
|
startOffset = -boundingBox.Top;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
double computedStartTime = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength);
|
return scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setComputedLifetimeStart(HitObjectLifetimeEntry entry)
|
||||||
|
{
|
||||||
|
double computedStartTime = computeDisplayStartTime(entry);
|
||||||
|
|
||||||
// always load the hitobject before its first judgement offset
|
// always load the hitobject before its first judgement offset
|
||||||
return Math.Min(hitObject.HitObject.StartTime - hitObject.MaximumJudgementOffset, computedStartTime);
|
double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0;
|
||||||
|
entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLayoutRecursive(DrawableHitObject hitObject)
|
private void updateLayoutRecursive(DrawableHitObject hitObject)
|
||||||
@ -236,8 +251,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
{
|
{
|
||||||
updateLayoutRecursive(obj);
|
updateLayoutRecursive(obj);
|
||||||
|
|
||||||
// Nested hitobjects don't need to scroll, but they do need accurate positions
|
// Nested hitobjects don't need to scroll, but they do need accurate positions and start lifetime
|
||||||
updatePosition(obj, hitObject.HitObject.StartTime);
|
updatePosition(obj, hitObject.HitObject.StartTime);
|
||||||
|
setComputedLifetimeStart(obj.Entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Vector2 ScreenSpacePositionAtTime(double time) => HitObjectContainer.ScreenSpacePositionAtTime(time);
|
public virtual Vector2 ScreenSpacePositionAtTime(double time) => HitObjectContainer.ScreenSpacePositionAtTime(time);
|
||||||
|
|
||||||
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer();
|
protected sealed override HitObjectContainer CreateHitObjectContainer() => CreateScrollingHitObjectContainer();
|
||||||
|
|
||||||
|
protected virtual ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new ScrollingHitObjectContainer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -39,8 +40,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
private const float rank_text_width = 35f;
|
private const float rank_text_width = 35f;
|
||||||
|
|
||||||
private const float score_components_width = 85f;
|
|
||||||
|
|
||||||
private const float avatar_size = 25f;
|
private const float avatar_size = 25f;
|
||||||
|
|
||||||
private const double panel_transition_duration = 500;
|
private const double panel_transition_duration = 500;
|
||||||
@ -161,7 +160,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.Absolute, rank_text_width),
|
new Dimension(GridSizeMode.Absolute, rank_text_width),
|
||||||
new Dimension(),
|
new Dimension(),
|
||||||
new Dimension(GridSizeMode.AutoSize, maxSize: score_components_width),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
},
|
},
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
@ -286,8 +285,19 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add);
|
LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add);
|
||||||
|
|
||||||
TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true);
|
TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true);
|
||||||
Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true);
|
|
||||||
Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true);
|
Accuracy.BindValueChanged(v =>
|
||||||
|
{
|
||||||
|
accuracyText.Text = v.NewValue.FormatAccuracy();
|
||||||
|
updateDetailsWidth();
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
Combo.BindValueChanged(v =>
|
||||||
|
{
|
||||||
|
comboText.Text = $"{v.NewValue}x";
|
||||||
|
updateDetailsWidth();
|
||||||
|
}, true);
|
||||||
|
|
||||||
HasQuit.BindValueChanged(_ => updateState());
|
HasQuit.BindValueChanged(_ => updateState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,13 +313,10 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
private void changeExpandedState(ValueChangedEvent<bool> expanded)
|
private void changeExpandedState(ValueChangedEvent<bool> expanded)
|
||||||
{
|
{
|
||||||
scoreComponents.ClearTransforms();
|
|
||||||
|
|
||||||
if (expanded.NewValue)
|
if (expanded.NewValue)
|
||||||
{
|
{
|
||||||
gridContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutQuint);
|
gridContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutQuint);
|
||||||
|
|
||||||
scoreComponents.ResizeWidthTo(score_components_width, panel_transition_duration, Easing.OutQuint);
|
|
||||||
scoreComponents.FadeIn(panel_transition_duration, Easing.OutQuint);
|
scoreComponents.FadeIn(panel_transition_duration, Easing.OutQuint);
|
||||||
|
|
||||||
usernameText.FadeIn(panel_transition_duration, Easing.OutQuint);
|
usernameText.FadeIn(panel_transition_duration, Easing.OutQuint);
|
||||||
@ -318,11 +325,29 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
gridContainer.ResizeWidthTo(compact_width, panel_transition_duration, Easing.OutQuint);
|
gridContainer.ResizeWidthTo(compact_width, panel_transition_duration, Easing.OutQuint);
|
||||||
|
|
||||||
scoreComponents.ResizeWidthTo(0, panel_transition_duration, Easing.OutQuint);
|
|
||||||
scoreComponents.FadeOut(text_transition_duration, Easing.OutQuint);
|
scoreComponents.FadeOut(text_transition_duration, Easing.OutQuint);
|
||||||
|
|
||||||
usernameText.FadeOut(text_transition_duration, Easing.OutQuint);
|
usernameText.FadeOut(text_transition_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateDetailsWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private float? scoreComponentsTargetWidth;
|
||||||
|
|
||||||
|
private void updateDetailsWidth()
|
||||||
|
{
|
||||||
|
const float score_components_min_width = 88f;
|
||||||
|
|
||||||
|
float newWidth = Expanded.Value
|
||||||
|
? Math.Max(score_components_min_width, comboText.DrawWidth + accuracyText.DrawWidth + 25)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (scoreComponentsTargetWidth == newWidth)
|
||||||
|
return;
|
||||||
|
|
||||||
|
scoreComponentsTargetWidth = newWidth;
|
||||||
|
scoreComponents.ResizeWidthTo(newWidth, panel_transition_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
local.TotalScore.BindTarget = scoreProcessor.TotalScore;
|
local.TotalScore.BindTarget = scoreProcessor.TotalScore;
|
||||||
local.Accuracy.BindTarget = scoreProcessor.Accuracy;
|
local.Accuracy.BindTarget = scoreProcessor.Accuracy;
|
||||||
local.Combo.BindTarget = scoreProcessor.Combo;
|
local.Combo.BindTarget = scoreProcessor.HighestCombo;
|
||||||
|
|
||||||
// Local score should always show lower than any existing scores in cases of ties.
|
// Local score should always show lower than any existing scores in cases of ties.
|
||||||
local.DisplayOrder.Value = long.MaxValue;
|
local.DisplayOrder.Value = long.MaxValue;
|
||||||
|
@ -566,9 +566,6 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </param>
|
/// </param>
|
||||||
protected void PerformExit(bool showDialogFirst)
|
protected void PerformExit(bool showDialogFirst)
|
||||||
{
|
{
|
||||||
// if an exit has been requested, cancel any pending completion (the user has shown intention to exit).
|
|
||||||
resultsDisplayDelegate?.Cancel();
|
|
||||||
|
|
||||||
// there is a chance that an exit request occurs after the transition to results has already started.
|
// there is a chance that an exit request occurs after the transition to results has already started.
|
||||||
// even in such a case, the user has shown intent, so forcefully return to this screen (to proceed with the upwards exit process).
|
// even in such a case, the user has shown intent, so forcefully return to this screen (to proceed with the upwards exit process).
|
||||||
if (!this.IsCurrentScreen())
|
if (!this.IsCurrentScreen())
|
||||||
@ -603,6 +600,9 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if an exit has been requested, cancel any pending completion (the user has shown intention to exit).
|
||||||
|
resultsDisplayDelegate?.Cancel();
|
||||||
|
|
||||||
// The actual exit is performed if
|
// The actual exit is performed if
|
||||||
// - the pause / fail dialog was not requested
|
// - the pause / fail dialog was not requested
|
||||||
// - the pause / fail dialog was requested but is already displayed (user showing intention to exit).
|
// - the pause / fail dialog was requested but is already displayed (user showing intention to exit).
|
||||||
@ -780,19 +780,11 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A final display will only occur once all work is completed in <see cref="PrepareScoreForResultsAsync"/>. This means that even after calling this method, the results screen will never be shown until <see cref="JudgementProcessor.HasCompleted">ScoreProcessor.HasCompleted</see> becomes <see langword="true"/>.
|
/// A final display will only occur once all work is completed in <see cref="PrepareScoreForResultsAsync"/>. This means that even after calling this method, the results screen will never be shown until <see cref="JudgementProcessor.HasCompleted">ScoreProcessor.HasCompleted</see> becomes <see langword="true"/>.
|
||||||
///
|
|
||||||
/// Calling this method multiple times will have no effect.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="withDelay">Whether a minimum delay (<see cref="RESULTS_DISPLAY_DELAY"/>) should be added before the screen is displayed.</param>
|
/// <param name="withDelay">Whether a minimum delay (<see cref="RESULTS_DISPLAY_DELAY"/>) should be added before the screen is displayed.</param>
|
||||||
private void progressToResults(bool withDelay)
|
private void progressToResults(bool withDelay)
|
||||||
{
|
{
|
||||||
if (resultsDisplayDelegate != null)
|
resultsDisplayDelegate?.Cancel();
|
||||||
// Note that if progressToResults is called one withDelay=true and then withDelay=false, this no-delay timing will not be
|
|
||||||
// accounted for. shouldn't be a huge concern (a user pressing the skip button after a results progression has already been queued
|
|
||||||
// may take x00 more milliseconds than expected in the very rare edge case).
|
|
||||||
//
|
|
||||||
// If required we can handle this more correctly by rescheduling here.
|
|
||||||
return;
|
|
||||||
|
|
||||||
double delay = withDelay ? RESULTS_DISPLAY_DELAY : 0;
|
double delay = withDelay ? RESULTS_DISPLAY_DELAY : 0;
|
||||||
|
|
||||||
|
@ -138,7 +138,8 @@ namespace osu.Game.Screens.Select
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TriggerClick();
|
if (!e.Repeat)
|
||||||
|
TriggerClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,8 +261,8 @@ namespace osu.Game.Screens.Utility
|
|||||||
|
|
||||||
string exclusive = "unknown";
|
string exclusive = "unknown";
|
||||||
|
|
||||||
if (host.Window is WindowsWindow windowsWindow)
|
if (host.Renderer is IWindowsRenderer windowsRenderer)
|
||||||
exclusive = windowsWindow.FullscreenCapability.ToString();
|
exclusive = windowsRenderer.FullscreenCapability.ToString();
|
||||||
|
|
||||||
statusText.Clear();
|
statusText.Clear();
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.15.1" />
|
<PackageReference Include="Realm" Version="10.15.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.922.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.1005.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1003.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1005.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.20.1" />
|
<PackageReference Include="Sentry" Version="3.20.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
@ -61,8 +61,8 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.922.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1005.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1003.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.1005.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -82,7 +82,7 @@
|
|||||||
<PackageReference Include="DiffPlex" Version="1.7.1" />
|
<PackageReference Include="DiffPlex" Version="1.7.1" />
|
||||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.922.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.1005.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user