mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +08:00
Merge branch 'master' into remove-nullable-disable-in-the-mods-for-catch-ruleset
This commit is contained in:
commit
88db835e76
@ -51,8 +51,8 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.720.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.722.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
private float halfCatcherWidth;
|
||||
|
||||
public override int Version => 20220701;
|
||||
|
||||
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
|
@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
private readonly bool isForCurrentRuleset;
|
||||
private readonly double originalOverallDifficulty;
|
||||
|
||||
public override int Version => 20220701;
|
||||
|
||||
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
private const double difficulty_multiplier = 0.0675;
|
||||
private double hitWindowGreat;
|
||||
|
||||
public override int Version => 20220701;
|
||||
|
||||
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
|
@ -23,13 +23,29 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
|
||||
protected Container PlayfieldContainer { get; private set; }
|
||||
|
||||
private ControlPointInfo controlPointInfo { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
controlPointInfo = new ControlPointInfo();
|
||||
controlPointInfo.Add(0, new TimingControlPoint());
|
||||
|
||||
IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||
IWorkingBeatmap beatmap = CreateWorkingBeatmap(CreateBeatmap(new TaikoRuleset().RulesetInfo));
|
||||
|
||||
Add(PlayfieldContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
|
||||
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset)) }
|
||||
});
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
return new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
@ -41,19 +57,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
Title = @"Sample Beatmap",
|
||||
Author = { Username = @"peppy" },
|
||||
},
|
||||
Ruleset = new TaikoRuleset().RulesetInfo
|
||||
Ruleset = ruleset
|
||||
},
|
||||
ControlPointInfo = controlPointInfo
|
||||
});
|
||||
|
||||
Add(PlayfieldContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
|
||||
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200),
|
||||
Child = new InputDrum(playfield.HitObjectContainer)
|
||||
Child = new InputDrum()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
49
osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
Normal file
49
osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneDrumTouchInputArea : OsuTestScene
|
||||
{
|
||||
private DrumTouchInputArea drumTouchInputArea = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create drum", () =>
|
||||
{
|
||||
Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new InputDrum
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Height = 0.2f,
|
||||
},
|
||||
drumTouchInputArea = new DrumTouchInputArea
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDrum()
|
||||
{
|
||||
AddStep("show drum", () => drumTouchInputArea.Show());
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
private const double colour_skill_multiplier = 0.01;
|
||||
private const double stamina_skill_multiplier = 0.021;
|
||||
|
||||
public override int Version => 20220701;
|
||||
|
||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
|
@ -10,8 +10,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
@ -115,9 +113,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
public readonly Sprite Rim;
|
||||
public readonly Sprite Centre;
|
||||
|
||||
[Resolved]
|
||||
private DrumSampleTriggerSource sampleTriggerSource { get; set; }
|
||||
|
||||
public LegacyHalfDrum(bool flipped)
|
||||
{
|
||||
Masking = true;
|
||||
@ -152,12 +147,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
if (e.Action == CentreAction)
|
||||
{
|
||||
target = Centre;
|
||||
sampleTriggerSource.Play(HitType.Centre);
|
||||
}
|
||||
else if (e.Action == RimAction)
|
||||
{
|
||||
target = Rim;
|
||||
sampleTriggerSource.Play(HitType.Rim);
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
|
@ -4,11 +4,13 @@
|
||||
#nullable disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
[Cached] // Used for touch input, see DrumTouchInputArea.
|
||||
public class TaikoInputManager : RulesetInputManager<TaikoAction>
|
||||
{
|
||||
public TaikoInputManager(RulesetInfo ruleset)
|
||||
|
@ -8,18 +8,18 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Rulesets.Timing;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
@ -56,6 +56,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MaxValue
|
||||
});
|
||||
|
||||
KeyBindingInputManager.Add(new DrumTouchInputArea());
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
|
59
osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs
Normal file
59
osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
internal class DrumSamplePlayer : CompositeDrawable, IKeyBindingHandler<TaikoAction>
|
||||
{
|
||||
private readonly DrumSampleTriggerSource leftRimSampleTriggerSource;
|
||||
private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource;
|
||||
private readonly DrumSampleTriggerSource rightCentreSampleTriggerSource;
|
||||
private readonly DrumSampleTriggerSource rightRimSampleTriggerSource;
|
||||
|
||||
public DrumSamplePlayer(HitObjectContainer hitObjectContainer)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
|
||||
leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
|
||||
rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
|
||||
rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
|
||||
};
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case TaikoAction.LeftRim:
|
||||
leftRimSampleTriggerSource.Play(HitType.Rim);
|
||||
break;
|
||||
|
||||
case TaikoAction.LeftCentre:
|
||||
leftCentreSampleTriggerSource.Play(HitType.Centre);
|
||||
break;
|
||||
|
||||
case TaikoAction.RightCentre:
|
||||
rightCentreSampleTriggerSource.Play(HitType.Centre);
|
||||
break;
|
||||
|
||||
case TaikoAction.RightRim:
|
||||
rightRimSampleTriggerSource.Play(HitType.Rim);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
243
osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
Normal file
243
osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
Normal file
@ -0,0 +1,243 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// An overlay that captures and displays osu!taiko mouse and touch input.
|
||||
/// </summary>
|
||||
public class DrumTouchInputArea : VisibilityContainer
|
||||
{
|
||||
// visibility state affects our child. we always want to handle input.
|
||||
public override bool PropagatePositionalInputSubTree => true;
|
||||
public override bool PropagateNonPositionalInputSubTree => true;
|
||||
|
||||
private KeyBindingContainer<TaikoAction> keyBindingContainer = null!;
|
||||
|
||||
private readonly Dictionary<object, TaikoAction> trackedActions = new Dictionary<object, TaikoAction>();
|
||||
|
||||
private Container mainContent = null!;
|
||||
|
||||
private QuarterCircle leftCentre = null!;
|
||||
private QuarterCircle rightCentre = null!;
|
||||
private QuarterCircle leftRim = null!;
|
||||
private QuarterCircle rightRim = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TaikoInputManager taikoInputManager, OsuColour colours)
|
||||
{
|
||||
Debug.Assert(taikoInputManager.KeyBindingContainer != null);
|
||||
keyBindingContainer = taikoInputManager.KeyBindingContainer;
|
||||
|
||||
// Container should handle input everywhere.
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
const float centre_region = 0.80f;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 350,
|
||||
Y = 20,
|
||||
Masking = true,
|
||||
FillMode = FillMode.Fit,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
mainContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.Blue)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = -2,
|
||||
},
|
||||
rightRim = new QuarterCircle(TaikoAction.RightRim, colours.Blue)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = 2,
|
||||
Rotation = 90,
|
||||
},
|
||||
leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.Pink)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = -2,
|
||||
Scale = new Vector2(centre_region),
|
||||
},
|
||||
rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.Pink)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = 2,
|
||||
Scale = new Vector2(centre_region),
|
||||
Rotation = 90,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
// Hide whenever the keyboard is used.
|
||||
Hide();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (!validMouse(e))
|
||||
return false;
|
||||
|
||||
handleDown(e.Button, e.ScreenSpaceMousePosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
if (!validMouse(e))
|
||||
return;
|
||||
|
||||
handleUp(e.Button);
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
protected override bool OnTouchDown(TouchDownEvent e)
|
||||
{
|
||||
handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnTouchUp(TouchUpEvent e)
|
||||
{
|
||||
handleUp(e.Touch.Source);
|
||||
base.OnTouchUp(e);
|
||||
}
|
||||
|
||||
private void handleDown(object source, Vector2 position)
|
||||
{
|
||||
Show();
|
||||
|
||||
TaikoAction taikoAction = getTaikoActionFromInput(position);
|
||||
|
||||
// Not too sure how this can happen, but let's avoid throwing.
|
||||
if (trackedActions.ContainsKey(source))
|
||||
return;
|
||||
|
||||
trackedActions.Add(source, taikoAction);
|
||||
keyBindingContainer.TriggerPressed(taikoAction);
|
||||
}
|
||||
|
||||
private void handleUp(object source)
|
||||
{
|
||||
keyBindingContainer.TriggerReleased(trackedActions[source]);
|
||||
trackedActions.Remove(source);
|
||||
}
|
||||
|
||||
private bool validMouse(MouseButtonEvent e) =>
|
||||
leftRim.Contains(e.ScreenSpaceMouseDownPosition) || rightRim.Contains(e.ScreenSpaceMouseDownPosition);
|
||||
|
||||
private TaikoAction getTaikoActionFromInput(Vector2 inputPosition)
|
||||
{
|
||||
bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition);
|
||||
bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2;
|
||||
|
||||
if (leftSide)
|
||||
return centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim;
|
||||
|
||||
return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim;
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
mainContent.FadeIn(500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
mainContent.FadeOut(300);
|
||||
}
|
||||
|
||||
private class QuarterCircle : CompositeDrawable, IKeyBindingHandler<TaikoAction>
|
||||
{
|
||||
private readonly Circle overlay;
|
||||
|
||||
private readonly TaikoAction handledAction;
|
||||
|
||||
private readonly Circle circle;
|
||||
|
||||
public override bool Contains(Vector2 screenSpacePos) => circle.Contains(screenSpacePos);
|
||||
|
||||
public QuarterCircle(TaikoAction handledAction, Color4 colour)
|
||||
{
|
||||
this.handledAction = handledAction;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
FillMode = FillMode.Fit;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colour.Multiply(1.4f).Darken(2.8f),
|
||||
Alpha = 0.8f,
|
||||
Scale = new Vector2(2),
|
||||
},
|
||||
overlay = new Circle
|
||||
{
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Colour = colour,
|
||||
Scale = new Vector2(2),
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
if (e.Action == handledAction)
|
||||
overlay.FadeTo(1f, 80, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||
{
|
||||
if (e.Action == handledAction)
|
||||
overlay.FadeOut(1000, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,8 +12,6 @@ using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@ -27,13 +25,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
private const float middle_split = 0.025f;
|
||||
|
||||
[Cached]
|
||||
private DrumSampleTriggerSource sampleTriggerSource;
|
||||
|
||||
public InputDrum(HitObjectContainer hitObjectContainer)
|
||||
public InputDrum()
|
||||
{
|
||||
sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer);
|
||||
|
||||
AutoSizeAxes = Axes.X;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
}
|
||||
@ -48,7 +41,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
},
|
||||
sampleTriggerSource
|
||||
};
|
||||
}
|
||||
|
||||
@ -116,9 +108,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
private readonly Sprite centre;
|
||||
private readonly Sprite centreHit;
|
||||
|
||||
[Resolved]
|
||||
private DrumSampleTriggerSource sampleTriggerSource { get; set; }
|
||||
|
||||
public TaikoHalfDrum(bool flipped)
|
||||
{
|
||||
Masking = true;
|
||||
@ -179,15 +168,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
target = centreHit;
|
||||
back = centre;
|
||||
|
||||
sampleTriggerSource.Play(HitType.Centre);
|
||||
}
|
||||
else if (e.Action == RimAction)
|
||||
{
|
||||
target = rimHit;
|
||||
back = rim;
|
||||
|
||||
sampleTriggerSource.Play(HitType.Rim);
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
inputDrum = new InputDrum(HitObjectContainer)
|
||||
inputDrum = new InputDrum
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
@ -164,6 +164,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
drumRollHitContainer.CreateProxy(),
|
||||
new DrumSamplePlayer(HitObjectContainer),
|
||||
// this is added at the end of the hierarchy to receive input before taiko objects.
|
||||
// but is proxied below everything to not cover visual effects such as hit explosions.
|
||||
inputDrum,
|
||||
|
100
osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
Normal file
100
osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class WorkingBeatmapManagerTest : OsuTestScene
|
||||
{
|
||||
private BeatmapManager beatmaps = null!;
|
||||
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio, RulesetStore rulesets)
|
||||
{
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGetWorkingBeatmap() => AddStep("run test", () =>
|
||||
{
|
||||
Assert.That(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()), Is.Not.Null);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestCachedRetrievalNoFiles() => AddStep("run test", () =>
|
||||
{
|
||||
var beatmap = importedSet.Beatmaps.First();
|
||||
|
||||
Assert.That(beatmap.BeatmapSet?.Files, Is.Empty);
|
||||
|
||||
var first = beatmaps.GetWorkingBeatmap(beatmap);
|
||||
var second = beatmaps.GetWorkingBeatmap(beatmap);
|
||||
|
||||
Assert.That(first, Is.SameAs(second));
|
||||
Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestCachedRetrievalWithFiles() => AddStep("run test", () =>
|
||||
{
|
||||
var beatmap = Realm.Run(r => r.Find<BeatmapInfo>(importedSet.Beatmaps.First().ID).Detach());
|
||||
|
||||
Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||
|
||||
var first = beatmaps.GetWorkingBeatmap(beatmap);
|
||||
var second = beatmaps.GetWorkingBeatmap(beatmap);
|
||||
|
||||
Assert.That(first, Is.SameAs(second));
|
||||
Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestForcedRefetchRetrievalNoFiles() => AddStep("run test", () =>
|
||||
{
|
||||
var beatmap = importedSet.Beatmaps.First();
|
||||
|
||||
Assert.That(beatmap.BeatmapSet?.Files, Is.Empty);
|
||||
|
||||
var first = beatmaps.GetWorkingBeatmap(beatmap);
|
||||
var second = beatmaps.GetWorkingBeatmap(beatmap, true);
|
||||
Assert.That(first, Is.Not.SameAs(second));
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestForcedRefetchRetrievalWithFiles() => AddStep("run test", () =>
|
||||
{
|
||||
var beatmap = Realm.Run(r => r.Find<BeatmapInfo>(importedSet.Beatmaps.First().ID).Detach());
|
||||
|
||||
Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||
|
||||
var first = beatmaps.GetWorkingBeatmap(beatmap);
|
||||
var second = beatmaps.GetWorkingBeatmap(beatmap, true);
|
||||
Assert.That(first, Is.Not.SameAs(second));
|
||||
});
|
||||
}
|
||||
}
|
132
osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs
Normal file
132
osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.Database
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo
|
||||
{
|
||||
public IBindable<bool> IsPlaying => isPlaying;
|
||||
|
||||
private readonly Bindable<bool> isPlaying = new Bindable<bool>();
|
||||
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase osu)
|
||||
{
|
||||
importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely();
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Set not playing", () => isPlaying.Value = false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDifficultyProcessing()
|
||||
{
|
||||
AddAssert("Difficulty is initially set", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||
return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Reset difficulty", () =>
|
||||
{
|
||||
Realm.Write(r =>
|
||||
{
|
||||
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||
foreach (var b in beatmapSetInfo.Beatmaps)
|
||||
b.StarRating = -1;
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Run background processor", () =>
|
||||
{
|
||||
Add(new TestBackgroundBeatmapProcessor());
|
||||
});
|
||||
|
||||
AddUntilStep("wait for difficulties repopulated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||
return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDifficultyProcessingWhilePlaying()
|
||||
{
|
||||
AddAssert("Difficulty is initially set", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||
return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Set playing", () => isPlaying.Value = true);
|
||||
|
||||
AddStep("Reset difficulty", () =>
|
||||
{
|
||||
Realm.Write(r =>
|
||||
{
|
||||
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||
foreach (var b in beatmapSetInfo.Beatmaps)
|
||||
b.StarRating = -1;
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Run background processor", () =>
|
||||
{
|
||||
Add(new TestBackgroundBeatmapProcessor());
|
||||
});
|
||||
|
||||
AddWaitStep("wait some", 500);
|
||||
|
||||
AddAssert("Difficulty still not populated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||
return beatmapSetInfo.Beatmaps.All(b => b.StarRating == -1);
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Set not playing", () => isPlaying.Value = false);
|
||||
|
||||
AddUntilStep("wait for difficulties repopulated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||
return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor
|
||||
{
|
||||
protected override int TimeToSleepDuringGameplay => 10;
|
||||
}
|
||||
}
|
||||
}
|
@ -142,7 +142,6 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||
|
||||
Assert.NotNull(beatmapSet);
|
||||
@ -311,6 +310,7 @@ namespace osu.Game.Tests.Database
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(temp);
|
||||
Directory.Delete(extractedFolder, true);
|
||||
}
|
||||
});
|
||||
|
@ -32,31 +32,29 @@ namespace osu.Game.Tests.Database
|
||||
[Test]
|
||||
public void TestAccessAfterStorageMigrate()
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
||||
{
|
||||
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||
|
||||
Live<BeatmapInfo>? liveBeatmap = null;
|
||||
|
||||
realm.Run(r =>
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
r.Write(_ => r.Add(beatmap));
|
||||
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||
|
||||
liveBeatmap = beatmap.ToLive(realm);
|
||||
});
|
||||
Live<BeatmapInfo>? liveBeatmap = null;
|
||||
|
||||
realm.Run(r =>
|
||||
{
|
||||
r.Write(_ => r.Add(beatmap));
|
||||
|
||||
liveBeatmap = beatmap.ToLive(realm);
|
||||
});
|
||||
|
||||
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
||||
{
|
||||
migratedStorage.DeleteDirectory(string.Empty);
|
||||
|
||||
using (realm.BlockAllOperations("testing"))
|
||||
{
|
||||
storage.Migrate(migratedStorage);
|
||||
}
|
||||
|
||||
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -341,14 +339,12 @@ namespace osu.Game.Tests.Database
|
||||
liveBeatmap.PerformRead(resolved =>
|
||||
{
|
||||
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
Assert.AreEqual(2, outerRealm.All<BeatmapInfo>().Count());
|
||||
Assert.AreEqual(1, changesTriggered);
|
||||
|
||||
// can access properties without a crash.
|
||||
Assert.IsFalse(resolved.Hidden);
|
||||
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
outerRealm.Write(r =>
|
||||
{
|
||||
// can use with the main context.
|
||||
|
@ -4,11 +4,11 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO;
|
||||
@ -20,22 +20,15 @@ namespace osu.Game.Tests.Database
|
||||
[TestFixture]
|
||||
public abstract class RealmTest
|
||||
{
|
||||
private static readonly TemporaryNativeStorage storage;
|
||||
|
||||
static RealmTest()
|
||||
{
|
||||
storage = new TemporaryNativeStorage("realm-test");
|
||||
storage.DeleteDirectory(string.Empty);
|
||||
}
|
||||
|
||||
protected void RunTestWithRealm(Action<RealmAccess, OsuStorage> testAction, [CallerMemberName] string caller = "")
|
||||
protected void RunTestWithRealm([InstantHandle] Action<RealmAccess, OsuStorage> testAction, [CallerMemberName] string caller = "")
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
|
||||
{
|
||||
host.Run(new RealmTestGame(() =>
|
||||
{
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
|
||||
var defaultStorage = host.Storage;
|
||||
|
||||
var testStorage = new OsuStorage(host, defaultStorage);
|
||||
|
||||
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
|
||||
{
|
||||
@ -58,7 +51,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
host.Run(new RealmTestGame(async () =>
|
||||
{
|
||||
var testStorage = storage.GetStorageForDirectory(caller);
|
||||
var testStorage = host.Storage;
|
||||
|
||||
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
|
||||
{
|
||||
@ -116,7 +109,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
private class RealmTestGame : Framework.Game
|
||||
{
|
||||
public RealmTestGame(Func<Task> work)
|
||||
public RealmTestGame([InstantHandle] Func<Task> work)
|
||||
{
|
||||
// ReSharper disable once AsyncVoidLambda
|
||||
Scheduler.Add(async () =>
|
||||
@ -126,7 +119,7 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
}
|
||||
|
||||
public RealmTestGame(Action work)
|
||||
public RealmTestGame([InstantHandle] Action work)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -15,6 +16,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.Gameplay
|
||||
@ -91,6 +93,47 @@ namespace osu.Game.Tests.Gameplay
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFailScore()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new TestHitObject(),
|
||||
new TestHitObject(HitResult.LargeTickHit),
|
||||
new TestHitObject(HitResult.SmallTickHit),
|
||||
new TestHitObject(HitResult.SmallBonus),
|
||||
new TestHitObject(),
|
||||
new TestHitObject(HitResult.LargeTickHit),
|
||||
new TestHitObject(HitResult.SmallTickHit),
|
||||
new TestHitObject(HitResult.LargeBonus),
|
||||
}
|
||||
};
|
||||
|
||||
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus });
|
||||
|
||||
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
|
||||
scoreProcessor.FailScore(score);
|
||||
|
||||
Assert.That(score.Rank, Is.EqualTo(ScoreRank.F));
|
||||
Assert.That(score.Passed, Is.False);
|
||||
Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(7));
|
||||
Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1));
|
||||
Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1));
|
||||
Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1));
|
||||
Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1));
|
||||
Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2));
|
||||
Assert.That(score.Statistics[HitResult.SmallBonus], Is.EqualTo(1));
|
||||
Assert.That(score.Statistics[HitResult.IgnoreMiss], Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
{
|
||||
public override HitResult MaxResult { get; }
|
||||
@ -100,5 +143,17 @@ namespace osu.Game.Tests.Gameplay
|
||||
MaxResult = maxResult;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHitObject : HitObject
|
||||
{
|
||||
private readonly HitResult maxResult;
|
||||
|
||||
public TestHitObject(HitResult maxResult = HitResult.Perfect)
|
||||
{
|
||||
this.maxResult = maxResult;
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => new TestJudgement(maxResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -110,30 +109,30 @@ namespace osu.Game.Tests.Online
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeserialiseSubmittableScoreWithEmptyMods()
|
||||
public void TestDeserialiseSoloScoreWithEmptyMods()
|
||||
{
|
||||
var score = new SubmittableScore(new ScoreInfo
|
||||
var score = SoloScoreInfo.ForSubmission(new ScoreInfo
|
||||
{
|
||||
User = new APIUser(),
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
});
|
||||
|
||||
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
|
||||
var deserialised = JsonConvert.DeserializeObject<SoloScoreInfo>(JsonConvert.SerializeObject(score));
|
||||
|
||||
Assert.That(deserialised?.Mods.Length, Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeserialiseSubmittableScoreWithCustomModSetting()
|
||||
public void TestDeserialiseSoloScoreWithCustomModSetting()
|
||||
{
|
||||
var score = new SubmittableScore(new ScoreInfo
|
||||
var score = SoloScoreInfo.ForSubmission(new ScoreInfo
|
||||
{
|
||||
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } },
|
||||
User = new APIUser(),
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
});
|
||||
|
||||
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
|
||||
var deserialised = JsonConvert.DeserializeObject<SoloScoreInfo>(JsonConvert.SerializeObject(score));
|
||||
|
||||
Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2));
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Online
|
||||
@ -15,12 +15,12 @@ namespace osu.Game.Tests.Online
|
||||
/// Basic testing to ensure our attribute-based naming is correctly working.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class TestSubmittableScoreJsonSerialization
|
||||
public class TestSoloScoreInfoJsonSerialization
|
||||
{
|
||||
[Test]
|
||||
public void TestScoreSerialisationViaExtensionMethod()
|
||||
{
|
||||
var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
|
||||
var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo());
|
||||
|
||||
string serialised = score.Serialize();
|
||||
|
||||
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Online
|
||||
[Test]
|
||||
public void TestScoreSerialisationWithoutSettings()
|
||||
{
|
||||
var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
|
||||
var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo());
|
||||
|
||||
string serialised = JsonConvert.SerializeObject(score);
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
@ -14,30 +13,22 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
public override Drawable CreateTestComponent() => Empty();
|
||||
|
||||
[Test]
|
||||
[FlakyTest]
|
||||
/*
|
||||
* Fail rate around 0.3%
|
||||
*
|
||||
* TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : range halved
|
||||
* --TearDown
|
||||
* at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal()
|
||||
* at osu.Framework.Threading.Scheduler.Update()
|
||||
* at osu.Framework.Graphics.Drawable.UpdateSubTree()
|
||||
*/
|
||||
public void TestVisibleRangeUpdatesOnZoomChange()
|
||||
{
|
||||
double initialVisibleRange = 0;
|
||||
|
||||
AddUntilStep("wait for load", () => MusicController.TrackLoaded);
|
||||
|
||||
AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||
|
||||
AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
|
||||
AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1));
|
||||
AddStep("range halved", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange / 2).Within(1)));
|
||||
AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
|
||||
AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1));
|
||||
AddStep("range doubled", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange * 2).Within(1)));
|
||||
|
||||
AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||
AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1));
|
||||
AddStep("range restored", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange).Within(1)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -45,6 +36,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
double initialVisibleRange = 0;
|
||||
|
||||
AddUntilStep("wait for load", () => MusicController.TrackLoaded);
|
||||
|
||||
AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
|
||||
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(30)
|
||||
},
|
||||
scrollContainer = new ZoomableScrollContainer
|
||||
scrollContainer = new ZoomableScrollContainer(1, 60, 1)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -80,21 +80,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestZoomRangeUpdate()
|
||||
{
|
||||
AddStep("set zoom to 2", () => scrollContainer.Zoom = 2);
|
||||
AddStep("set min zoom to 5", () => scrollContainer.MinZoom = 5);
|
||||
AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
|
||||
|
||||
AddStep("set max zoom to 10", () => scrollContainer.MaxZoom = 10);
|
||||
AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
|
||||
|
||||
AddStep("set min zoom to 20", () => scrollContainer.MinZoom = 20);
|
||||
AddStep("set max zoom to 40", () => scrollContainer.MaxZoom = 40);
|
||||
AddAssert("zoom = 20", () => scrollContainer.Zoom == 20);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestZoom0()
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public TestScenePause()
|
||||
{
|
||||
base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
|
||||
base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
@ -15,7 +14,6 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
@ -30,9 +28,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
private const long online_score_id = 2553163309;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private TestReplayDownloadButton downloadButton;
|
||||
|
||||
[Resolved]
|
||||
@ -211,21 +206,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||
}
|
||||
|
||||
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true)
|
||||
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) => new ScoreInfo
|
||||
{
|
||||
return new APIScore
|
||||
OnlineID = hasOnlineId ? online_score_id : 0,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(),
|
||||
Hash = replayAvailable ? "online" : string.Empty,
|
||||
User = new APIUser
|
||||
{
|
||||
OnlineID = hasOnlineId ? online_score_id : 0,
|
||||
RulesetID = 0,
|
||||
Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
|
||||
HasReplay = replayAvailable,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 39828,
|
||||
Username = @"WubWoofWolf",
|
||||
}
|
||||
}.CreateScoreInfo(rulesets, beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First());
|
||||
}
|
||||
Id = 39828,
|
||||
Username = @"WubWoofWolf",
|
||||
}
|
||||
};
|
||||
|
||||
private class TestReplayDownloadButton : ReplayDownloadButton
|
||||
{
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private TestReplayRecorder recorder;
|
||||
|
||||
[Cached]
|
||||
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
|
||||
private GameplayState gameplayState;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
replay = new Replay();
|
||||
|
||||
Add(new GridContainer
|
||||
gameplayState = TestGameplayState.Create(new OsuRuleset());
|
||||
gameplayState.Score.Replay = replay;
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
Recorder = recorder = new TestReplayRecorder(new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo =
|
||||
{
|
||||
BeatmapInfo = gameplayState.Beatmap.BeatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
}
|
||||
})
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Brown,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Recording",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
|
||||
{
|
||||
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.DarkBlue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Playback",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) },
|
||||
Child = createContent(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
recorder = null;
|
||||
}
|
||||
|
||||
private Drawable createContent() => new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
Recorder = recorder = new TestReplayRecorder(gameplayState.Score)
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Brown,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Recording",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
|
||||
{
|
||||
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.DarkBlue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Playback",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
||||
{
|
||||
public TestFramedReplayInputHandler(Replay replay)
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Spectator;
|
||||
@ -43,6 +44,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRestart()
|
||||
{
|
||||
AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
|
||||
|
||||
AddStep("exit player", () => Player.Exit());
|
||||
AddStep("reload player", LoadPlayer);
|
||||
AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
|
||||
AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
|
||||
|
||||
AddWaitStep("wait", 5);
|
||||
AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
|
||||
}
|
||||
|
||||
public override void TearDownSteps()
|
||||
{
|
||||
base.TearDownSteps();
|
||||
|
@ -24,7 +24,6 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -633,7 +632,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
||||
|
||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
|
||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<RoomSubScreen>().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden);
|
||||
|
||||
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
||||
|
||||
|
@ -16,6 +16,10 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Select.Details;
|
||||
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
@ -34,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Resolved]
|
||||
private IRulesetStore rulesets { get; set; }
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() => SelectedMods.Value = Array.Empty<Mod>());
|
||||
|
||||
[Test]
|
||||
public void TestLoading()
|
||||
{
|
||||
@ -205,6 +212,21 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectedModsDontAffectStatistics()
|
||||
{
|
||||
AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet()));
|
||||
AddAssert("AR displayed as 0", () => overlay.ChildrenOfType<AdvancedStats.StatisticRow>().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null));
|
||||
AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[]
|
||||
{
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
ApproachRate = { Value = 10 }
|
||||
}
|
||||
});
|
||||
AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType<AdvancedStats.StatisticRow>().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHide()
|
||||
{
|
||||
|
@ -141,6 +141,19 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddUntilStep("best score not displayed", () => scoresContainer.ChildrenOfType<DrawableTopScore>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUnprocessedPP()
|
||||
{
|
||||
AddStep("Load scores with unprocessed PP", () =>
|
||||
{
|
||||
var allScores = createScores();
|
||||
allScores.Scores[0].PP = null;
|
||||
allScores.UserScore = createUserBest();
|
||||
allScores.UserScore.Score.PP = null;
|
||||
scoresContainer.Scores = allScores;
|
||||
});
|
||||
}
|
||||
|
||||
private int onlineID = 1;
|
||||
|
||||
private APIScoresCollection createScores()
|
||||
|
@ -7,6 +7,7 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
@ -99,6 +100,23 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Accuracy = 0.55879
|
||||
};
|
||||
|
||||
var unprocessedPPScore = new SoloScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
Beatmap = new APIBeatmap
|
||||
{
|
||||
BeatmapSet = new APIBeatmapSet
|
||||
{
|
||||
Title = "C18H27NO3(extend)",
|
||||
Artist = "Team Grimoire",
|
||||
},
|
||||
DifficultyName = "[4K] Cataclysmic Hypernova",
|
||||
Status = BeatmapOnlineStatus.Ranked,
|
||||
},
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
Accuracy = 0.55879
|
||||
};
|
||||
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@ -112,6 +130,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)),
|
||||
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)),
|
||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)),
|
||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unprocessedPPScore)),
|
||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)),
|
||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)),
|
||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)),
|
||||
|
@ -21,12 +21,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[TestFixture]
|
||||
public class TestSceneCursors : OsuManualInputManagerTestScene
|
||||
{
|
||||
private readonly MenuCursorContainer menuCursorContainer;
|
||||
private readonly GlobalCursorDisplay globalCursorDisplay;
|
||||
private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6];
|
||||
|
||||
public TestSceneCursors()
|
||||
{
|
||||
Child = menuCursorContainer = new MenuCursorContainer
|
||||
Child = globalCursorDisplay = new GlobalCursorDisplay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
@ -96,11 +96,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private void testUserCursor()
|
||||
{
|
||||
AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
|
||||
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor));
|
||||
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor));
|
||||
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].MenuCursor));
|
||||
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].MenuCursor));
|
||||
AddStep("Move out", moveOut);
|
||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -111,13 +111,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private void testLocalCursor()
|
||||
{
|
||||
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
||||
AddAssert("Check global cursor at mouse", () => checkAtMouse(menuCursorContainer.Cursor));
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||
AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor));
|
||||
AddStep("Move out", moveOut);
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -128,12 +128,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private void testUserCursorOverride()
|
||||
{
|
||||
AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
||||
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
|
||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
|
||||
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
|
||||
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor));
|
||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
||||
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor));
|
||||
AddStep("Move out", moveOut);
|
||||
AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor));
|
||||
AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor));
|
||||
AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].MenuCursor));
|
||||
AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -143,13 +143,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private void testMultipleLocalCursors()
|
||||
{
|
||||
AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
||||
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor));
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor));
|
||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
||||
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor));
|
||||
AddStep("Move out", moveOut);
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -159,13 +159,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private void testUserOverrideWithLocal()
|
||||
{
|
||||
AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10)));
|
||||
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
|
||||
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
|
||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
||||
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor));
|
||||
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor));
|
||||
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor));
|
||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
||||
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor));
|
||||
AddStep("Move out", moveOut);
|
||||
AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor));
|
||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
||||
AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].MenuCursor));
|
||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public bool SmoothTransition;
|
||||
|
||||
public CursorContainer Cursor { get; }
|
||||
public CursorContainer MenuCursor { get; }
|
||||
public bool ProvidingUserCursor { get; }
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor);
|
||||
@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Origin = Anchor.Centre,
|
||||
Text = providesUserCursor ? "User cursor" : "Local cursor"
|
||||
},
|
||||
Cursor = new TestCursorContainer
|
||||
MenuCursor = new TestCursorContainer
|
||||
{
|
||||
State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible },
|
||||
}
|
||||
|
@ -8,8 +8,10 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
@ -17,11 +19,26 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneLabelledSliderBar : OsuTestScene
|
||||
{
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription);
|
||||
[Test]
|
||||
public void TestBasic() => createSliderBar();
|
||||
|
||||
private void createSliderBar(bool hasDescription = false)
|
||||
[Test]
|
||||
public void TestDescription()
|
||||
{
|
||||
createSliderBar();
|
||||
AddStep("set description", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.Description = "this text describes the component"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSize()
|
||||
{
|
||||
createSliderBar();
|
||||
AddStep("set zero width", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(0, 200, Easing.OutQuint)));
|
||||
AddStep("set negative width", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(-1, 200, Easing.OutQuint)));
|
||||
AddStep("revert back", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(1, 200, Easing.OutQuint)));
|
||||
}
|
||||
|
||||
private void createSliderBar()
|
||||
{
|
||||
AddStep("create component", () =>
|
||||
{
|
||||
@ -38,6 +55,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
new LabelledSliderBar<double>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = new BindableDouble(5)
|
||||
{
|
||||
MinValue = 0,
|
||||
@ -45,7 +64,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Precision = 1,
|
||||
},
|
||||
Label = "a sample component",
|
||||
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -54,10 +72,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
flow.Add(new OverlayColourContainer(colour)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = new LabelledSliderBar<double>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = new BindableDouble(5)
|
||||
{
|
||||
MinValue = 0,
|
||||
@ -65,7 +87,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Precision = 1,
|
||||
},
|
||||
Label = "a sample component",
|
||||
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneModPresetColumn : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
|
||||
[Test]
|
||||
public void TestBasicAppearance()
|
||||
{
|
||||
ModPresetColumn modPresetColumn = null!;
|
||||
|
||||
AddStep("create content", () => Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(30),
|
||||
Child = modPresetColumn = new ModPresetColumn
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Presets = createTestPresets().ToArray()
|
||||
}
|
||||
});
|
||||
AddStep("change presets", () => modPresetColumn.Presets = createTestPresets().Skip(1).ToArray());
|
||||
}
|
||||
|
||||
private static IEnumerable<ModPreset> createTestPresets() => new[]
|
||||
{
|
||||
new ModPreset
|
||||
{
|
||||
Name = "First preset",
|
||||
Description = "Please ignore",
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModHardRock(),
|
||||
new OsuModDoubleTime()
|
||||
}
|
||||
},
|
||||
new ModPreset
|
||||
{
|
||||
Name = "AR0",
|
||||
Description = "For good readers",
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
ApproachRate = { Value = 0 }
|
||||
}
|
||||
}
|
||||
},
|
||||
new ModPreset
|
||||
{
|
||||
Name = "This preset is going to have an extraordinarily long name",
|
||||
Description = "This is done so that the capability to truncate overlong texts may be demonstrated",
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModFlashlight(),
|
||||
new OsuModSpinIn()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneModPresetPanel : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
|
||||
[Test]
|
||||
public void TestVariousModPresets()
|
||||
{
|
||||
AddStep("create content", () => Child = new FillFlowContainer
|
||||
{
|
||||
Width = 300,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(0, 5),
|
||||
ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset))
|
||||
});
|
||||
}
|
||||
|
||||
private static IEnumerable<ModPreset> createTestPresets() => new[]
|
||||
{
|
||||
new ModPreset
|
||||
{
|
||||
Name = "First preset",
|
||||
Description = "Please ignore",
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModHardRock(),
|
||||
new OsuModDoubleTime()
|
||||
}
|
||||
},
|
||||
new ModPreset
|
||||
{
|
||||
Name = "AR0",
|
||||
Description = "For good readers",
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
ApproachRate = { Value = 0 }
|
||||
}
|
||||
}
|
||||
},
|
||||
new ModPreset
|
||||
{
|
||||
Name = "This preset is going to have an extraordinarily long name",
|
||||
Description = "This is done so that the capability to truncate overlong texts may be demonstrated",
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModFlashlight(),
|
||||
new OsuModSpinIn()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(2f),
|
||||
Direction = FillDirection.Horizontal,
|
||||
ChildrenEnumerable = Enumerable.Range(0, 15).Select(i => new FillFlowContainer
|
||||
ChildrenEnumerable = Enumerable.Range(-1, 15).Select(i => new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -27,7 +28,6 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
||||
{
|
||||
var osu = new TestTournament(runOnLoadComplete: () =>
|
||||
{
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
var storage = host.Storage.GetStorageForDirectory(Path.Combine("tournaments", "default"));
|
||||
|
||||
using (var stream = storage.CreateFileSafely("bracket.json"))
|
||||
@ -85,7 +85,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
||||
|
||||
public new Task BracketLoadTask => base.BracketLoadTask;
|
||||
|
||||
public TestTournament(bool resetRuleset = false, Action runOnLoadComplete = null)
|
||||
public TestTournament(bool resetRuleset = false, [InstantHandle] Action runOnLoadComplete = null)
|
||||
{
|
||||
this.resetRuleset = resetRuleset;
|
||||
this.runOnLoadComplete = runOnLoadComplete;
|
||||
|
@ -70,10 +70,10 @@ namespace osu.Game.Tournament
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display
|
||||
GlobalCursorDisplay.MenuCursor.AlwaysPresent = true; // required for tooltip display
|
||||
|
||||
// we don't want to show the menu cursor as it would appear on stream output.
|
||||
MenuCursorContainer.Cursor.Alpha = 0;
|
||||
GlobalCursorDisplay.MenuCursor.Alpha = 0;
|
||||
|
||||
base.LoadComplete();
|
||||
|
||||
|
144
osu.Game/BackgroundBeatmapProcessor.cs
Normal file
144
osu.Game/BackgroundBeatmapProcessor.cs
Normal file
@ -0,0 +1,144 @@
|
||||
// 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 System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game
|
||||
{
|
||||
public class BackgroundBeatmapProcessor : Component
|
||||
{
|
||||
[Resolved]
|
||||
private RulesetStore rulesetStore { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realmAccess { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapUpdater beatmapUpdater { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> gameBeatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ILocalUserPlayInfo? localUserPlayInfo { get; set; }
|
||||
|
||||
protected virtual int TimeToSleepDuringGameplay => 30000;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
Logger.Log("Beginning background beatmap processing..");
|
||||
checkForOutdatedStarRatings();
|
||||
processBeatmapSetsWithMissingMetrics();
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
if (t.Exception?.InnerException is ObjectDisposedException)
|
||||
{
|
||||
Logger.Log("Finished background aborted during shutdown");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Log("Finished background beatmap processing!");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the databased difficulty calculation version matches the latest ruleset provided version.
|
||||
/// If it doesn't, clear out any existing difficulties so they can be incrementally recalculated.
|
||||
/// </summary>
|
||||
private void checkForOutdatedStarRatings()
|
||||
{
|
||||
foreach (var ruleset in rulesetStore.AvailableRulesets)
|
||||
{
|
||||
// beatmap being passed in is arbitrary here. just needs to be non-null.
|
||||
int currentVersion = ruleset.CreateInstance().CreateDifficultyCalculator(gameBeatmap.Value).Version;
|
||||
|
||||
if (ruleset.LastAppliedDifficultyVersion < currentVersion)
|
||||
{
|
||||
Logger.Log($"Resetting star ratings for {ruleset.Name} (difficulty calculation version updated from {ruleset.LastAppliedDifficultyVersion} to {currentVersion})");
|
||||
|
||||
int countReset = 0;
|
||||
|
||||
realmAccess.Write(r =>
|
||||
{
|
||||
foreach (var b in r.All<BeatmapInfo>())
|
||||
{
|
||||
if (b.Ruleset.ShortName == ruleset.ShortName)
|
||||
{
|
||||
b.StarRating = -1;
|
||||
countReset++;
|
||||
}
|
||||
}
|
||||
|
||||
r.Find<RulesetInfo>(ruleset.ShortName).LastAppliedDifficultyVersion = currentVersion;
|
||||
});
|
||||
|
||||
Logger.Log($"Finished resetting {countReset} beatmap sets for {ruleset.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processBeatmapSetsWithMissingMetrics()
|
||||
{
|
||||
HashSet<Guid> beatmapSetIds = new HashSet<Guid>();
|
||||
|
||||
Logger.Log("Querying for beatmap sets to reprocess...");
|
||||
|
||||
realmAccess.Run(r =>
|
||||
{
|
||||
foreach (var b in r.All<BeatmapInfo>().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null)))
|
||||
{
|
||||
Debug.Assert(b.BeatmapSet != null);
|
||||
beatmapSetIds.Add(b.BeatmapSet.ID);
|
||||
}
|
||||
});
|
||||
|
||||
Logger.Log($"Found {beatmapSetIds.Count} beatmap sets which require reprocessing.");
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (var id in beatmapSetIds)
|
||||
{
|
||||
while (localUserPlayInfo?.IsPlaying.Value == true)
|
||||
{
|
||||
Logger.Log("Background processing sleeping due to active gameplay...");
|
||||
Thread.Sleep(TimeToSleepDuringGameplay);
|
||||
}
|
||||
|
||||
realmAccess.Run(r =>
|
||||
{
|
||||
var set = r.Find<BeatmapSetInfo>(id);
|
||||
|
||||
if (set != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})");
|
||||
beatmapUpdater.Process(set);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Background processing failed on {set}: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -87,7 +87,11 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public string Hash { get; set; } = string.Empty;
|
||||
|
||||
public double StarRating { get; set; }
|
||||
/// <summary>
|
||||
/// Defaults to -1 (meaning not-yet-calculated).
|
||||
/// Will likely be superseded with a better storage considering ruleset/mods.
|
||||
/// </summary>
|
||||
public double StarRating { get; set; } = -1;
|
||||
|
||||
[Indexed]
|
||||
public string MD5Hash { get; set; } = string.Empty;
|
||||
|
@ -439,12 +439,15 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
if (beatmapInfo != null)
|
||||
{
|
||||
// Detached sets don't come with files.
|
||||
// If we seem to be missing files, now is a good time to re-fetch.
|
||||
if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0)
|
||||
{
|
||||
if (refetch)
|
||||
workingBeatmapCache.Invalidate(beatmapInfo);
|
||||
|
||||
// Detached beatmapsets don't come with files as an optimisation (see `RealmObjectExtensions.beatmap_set_mapper`).
|
||||
// If we seem to be missing files, now is a good time to re-fetch.
|
||||
bool missingFiles = beatmapInfo.BeatmapSet?.Files.Count == 0;
|
||||
|
||||
if (refetch || beatmapInfo.IsManaged || missingFiles)
|
||||
{
|
||||
Guid id = beatmapInfo.ID;
|
||||
beatmapInfo = Realm.Run(r => r.Find<BeatmapInfo>(id)?.Detach()) ?? beatmapInfo;
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
displayedStars.BindValueChanged(s =>
|
||||
{
|
||||
starsText.Text = s.NewValue.ToLocalisableString("0.00");
|
||||
starsText.Text = s.NewValue < 0 ? "-" : s.NewValue.ToLocalisableString("0.00");
|
||||
|
||||
background.Colour = colours.ForStarDifficulty(s.NewValue);
|
||||
|
||||
|
@ -91,6 +91,7 @@ namespace osu.Game.Configuration
|
||||
// Input
|
||||
SetDefault(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f);
|
||||
SetDefault(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f);
|
||||
SetDefault(OsuSetting.GameplayCursorDuringTouch, false);
|
||||
SetDefault(OsuSetting.AutoCursorSize, false);
|
||||
|
||||
SetDefault(OsuSetting.MouseDisableButtons, false);
|
||||
@ -292,6 +293,7 @@ namespace osu.Game.Configuration
|
||||
MenuCursorSize,
|
||||
GameplayCursorSize,
|
||||
AutoCursorSize,
|
||||
GameplayCursorDuringTouch,
|
||||
DimLevel,
|
||||
BlurLevel,
|
||||
LightenDuringBreaks,
|
||||
|
@ -63,8 +63,9 @@ namespace osu.Game.Database
|
||||
/// 17 2022-07-16 Added CountryCode to RealmUser.
|
||||
/// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo.
|
||||
/// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo.
|
||||
/// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1.
|
||||
/// </summary>
|
||||
private const int schema_version = 19;
|
||||
private const int schema_version = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
@ -780,6 +781,15 @@ namespace osu.Game.Database
|
||||
case 14:
|
||||
foreach (var beatmap in migration.NewRealm.All<BeatmapInfo>())
|
||||
beatmap.UserSettings = new BeatmapUserSettings();
|
||||
|
||||
break;
|
||||
|
||||
case 20:
|
||||
// As we now have versioned difficulty calculations, let's reset
|
||||
// all star ratings and have `BackgroundBeatmapProcessor` recalculate them.
|
||||
foreach (var beatmap in migration.NewRealm.All<BeatmapInfo>())
|
||||
beatmap.StarRating = -1;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
92
osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs
Normal file
92
osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs
Normal file
@ -0,0 +1,92 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Game.Configuration;
|
||||
|
||||
namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
/// <summary>
|
||||
/// A container which provides the main <see cref="Cursor.MenuCursor"/>.
|
||||
/// Also handles cases where a more localised cursor is provided by another component (via <see cref="IProvideCursor"/>).
|
||||
/// </summary>
|
||||
public class GlobalCursorDisplay : Container, IProvideCursor
|
||||
{
|
||||
/// <summary>
|
||||
/// Control whether any cursor should be displayed.
|
||||
/// </summary>
|
||||
internal bool ShowCursor = true;
|
||||
|
||||
public CursorContainer MenuCursor { get; }
|
||||
|
||||
public bool ProvidingUserCursor => true;
|
||||
|
||||
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
private Bindable<bool> showDuringTouch = null!;
|
||||
|
||||
private InputManager inputManager = null!;
|
||||
|
||||
private IProvideCursor? currentOverrideProvider;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
public GlobalCursorDisplay()
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
MenuCursor = new MenuCursor { State = { Value = Visibility.Hidden } },
|
||||
Content = new Container { RelativeSizeAxes = Axes.Both }
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
showDuringTouch = config.GetBindable<bool>(OsuSetting.GameplayCursorDuringTouch);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var lastMouseSource = inputManager.CurrentState.Mouse.LastSource;
|
||||
bool hasValidInput = lastMouseSource != null && (showDuringTouch.Value || lastMouseSource is not ISourcedFromTouch);
|
||||
|
||||
if (!hasValidInput || !ShowCursor)
|
||||
{
|
||||
currentOverrideProvider?.MenuCursor?.Hide();
|
||||
currentOverrideProvider = null;
|
||||
return;
|
||||
}
|
||||
|
||||
IProvideCursor newOverrideProvider = this;
|
||||
|
||||
foreach (var d in inputManager.HoveredDrawables)
|
||||
{
|
||||
if (d is IProvideCursor p && p.ProvidingUserCursor)
|
||||
{
|
||||
newOverrideProvider = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentOverrideProvider == newOverrideProvider)
|
||||
return;
|
||||
|
||||
currentOverrideProvider?.MenuCursor?.Hide();
|
||||
newOverrideProvider.MenuCursor?.Show();
|
||||
|
||||
currentOverrideProvider = newOverrideProvider;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,10 +17,10 @@ namespace osu.Game.Graphics.Cursor
|
||||
/// The cursor provided by this <see cref="IDrawable"/>.
|
||||
/// May be null if no cursor should be visible.
|
||||
/// </summary>
|
||||
CursorContainer Cursor { get; }
|
||||
CursorContainer MenuCursor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether <see cref="Cursor"/> should be displayed as the singular user cursor. This will temporarily hide any other user cursor.
|
||||
/// Whether <see cref="MenuCursor"/> should be displayed as the singular user cursor. This will temporarily hide any other user cursor.
|
||||
/// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays).
|
||||
/// </summary>
|
||||
bool ProvidingUserCursor { get; }
|
||||
|
@ -1,83 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
|
||||
namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
/// <summary>
|
||||
/// A container which provides a <see cref="MenuCursor"/> which can be overridden by hovered <see cref="Drawable"/>s.
|
||||
/// </summary>
|
||||
public class MenuCursorContainer : Container, IProvideCursor
|
||||
{
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
/// <summary>
|
||||
/// Whether any cursors can be displayed.
|
||||
/// </summary>
|
||||
internal bool CanShowCursor = true;
|
||||
|
||||
public CursorContainer Cursor { get; }
|
||||
public bool ProvidingUserCursor => true;
|
||||
|
||||
public MenuCursorContainer()
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
Cursor = new MenuCursor { State = { Value = Visibility.Hidden } },
|
||||
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||
});
|
||||
}
|
||||
|
||||
private InputManager inputManager;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
private IProvideCursor currentTarget;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var lastMouseSource = inputManager.CurrentState.Mouse.LastSource;
|
||||
bool hasValidInput = lastMouseSource != null && !(lastMouseSource is ISourcedFromTouch);
|
||||
|
||||
if (!hasValidInput || !CanShowCursor)
|
||||
{
|
||||
currentTarget?.Cursor?.Hide();
|
||||
currentTarget = null;
|
||||
return;
|
||||
}
|
||||
|
||||
IProvideCursor newTarget = this;
|
||||
|
||||
foreach (var d in inputManager.HoveredDrawables)
|
||||
{
|
||||
if (d is IProvideCursor p && p.ProvidingUserCursor)
|
||||
{
|
||||
newTarget = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTarget == newTarget)
|
||||
return;
|
||||
|
||||
currentTarget?.Cursor?.Hide();
|
||||
newTarget.Cursor?.Show();
|
||||
|
||||
currentTarget = newTarget;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,9 +10,8 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -22,8 +21,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class FPSCounter : VisibilityContainer, IHasCustomTooltip
|
||||
{
|
||||
private RollingCounter<double> counterUpdateFrameTime = null!;
|
||||
private RollingCounter<double> counterDrawFPS = null!;
|
||||
private OsuSpriteText counterUpdateFrameTime = null!;
|
||||
private OsuSpriteText counterDrawFPS = null!;
|
||||
|
||||
private Container mainContent = null!;
|
||||
|
||||
@ -31,10 +30,32 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private Container counters = null!;
|
||||
|
||||
private const double min_time_between_updates = 10;
|
||||
|
||||
private const double spike_time_ms = 20;
|
||||
|
||||
private const float idle_background_alpha = 0.4f;
|
||||
|
||||
private readonly BindableBool showFpsDisplay = new BindableBool(true);
|
||||
|
||||
private double displayedFpsCount;
|
||||
private double displayedFrameTime;
|
||||
|
||||
private bool isDisplayed;
|
||||
|
||||
private double aimDrawFPS;
|
||||
private double aimUpdateFPS;
|
||||
|
||||
private double lastUpdate;
|
||||
private ThrottledFrameClock drawClock = null!;
|
||||
private ThrottledFrameClock updateClock = null!;
|
||||
private ThrottledFrameClock inputClock = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The last time value where the display was required (due to a significant change or hovering).
|
||||
/// </summary>
|
||||
private double lastDisplayRequiredTime;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
@ -44,7 +65,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
private void load(OsuConfigManager config, GameHost gameHost)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@ -77,20 +98,23 @@ namespace osu.Game.Graphics.UserInterface
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
counterUpdateFrameTime = new FrameTimeCounter
|
||||
counterUpdateFrameTime = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Margin = new MarginPadding(1),
|
||||
Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold),
|
||||
Spacing = new Vector2(-1),
|
||||
Y = -2,
|
||||
},
|
||||
counterDrawFPS = new FramesPerSecondCounter
|
||||
counterDrawFPS = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Margin = new MarginPadding(2),
|
||||
Font = OsuFont.Default.With(fixedWidth: true, size: 13, weight: FontWeight.SemiBold),
|
||||
Spacing = new Vector2(-2),
|
||||
Y = 10,
|
||||
Scale = new Vector2(0.8f),
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -99,19 +123,23 @@ namespace osu.Game.Graphics.UserInterface
|
||||
};
|
||||
|
||||
config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay);
|
||||
|
||||
drawClock = gameHost.DrawThread.Clock;
|
||||
updateClock = gameHost.UpdateThread.Clock;
|
||||
inputClock = gameHost.InputThread.Clock;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
displayTemporarily();
|
||||
requestDisplay();
|
||||
|
||||
showFpsDisplay.BindValueChanged(showFps =>
|
||||
{
|
||||
State.Value = showFps.NewValue ? Visibility.Visible : Visibility.Hidden;
|
||||
if (showFps.NewValue)
|
||||
displayTemporarily();
|
||||
requestDisplay();
|
||||
}, true);
|
||||
|
||||
State.BindValueChanged(state => showFpsDisplay.Value = state.NewValue == Visibility.Visible);
|
||||
@ -124,48 +152,17 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
background.FadeTo(1, 200);
|
||||
displayTemporarily();
|
||||
requestDisplay();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
background.FadeTo(idle_background_alpha, 200);
|
||||
displayTemporarily();
|
||||
requestDisplay();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private bool isDisplayed;
|
||||
|
||||
private ScheduledDelegate? fadeOutDelegate;
|
||||
|
||||
private double aimDrawFPS;
|
||||
private double aimUpdateFPS;
|
||||
|
||||
private void displayTemporarily()
|
||||
{
|
||||
if (!isDisplayed)
|
||||
{
|
||||
mainContent.FadeTo(1, 300, Easing.OutQuint);
|
||||
isDisplayed = true;
|
||||
}
|
||||
|
||||
fadeOutDelegate?.Cancel();
|
||||
fadeOutDelegate = null;
|
||||
|
||||
if (!IsHovered)
|
||||
{
|
||||
fadeOutDelegate = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
mainContent.FadeTo(0, 300, Easing.OutQuint);
|
||||
isDisplayed = false;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private GameHost gameHost { get; set; } = null!;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -176,50 +173,75 @@ namespace osu.Game.Graphics.UserInterface
|
||||
// frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier).
|
||||
bool aimRatesChanged = updateAimFPS();
|
||||
|
||||
// TODO: this is wrong (elapsed clock time, not actual run time).
|
||||
double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime;
|
||||
double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime;
|
||||
double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond;
|
||||
|
||||
const double spike_time_ms = 20;
|
||||
|
||||
bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms;
|
||||
bool hasUpdateSpike = displayedFrameTime < spike_time_ms && updateClock.ElapsedFrameTime > spike_time_ms;
|
||||
// use elapsed frame time rather then FramesPerSecond to better catch stutter frames.
|
||||
bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms;
|
||||
bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms;
|
||||
|
||||
// If the frame time spikes up, make sure it shows immediately on the counter.
|
||||
if (hasUpdateSpike)
|
||||
counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime);
|
||||
else
|
||||
counterUpdateFrameTime.Current.Value = newUpdateFrameTime;
|
||||
// note that we use an elapsed time here of 1 intentionally.
|
||||
// this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower.
|
||||
displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : 100, 1);
|
||||
|
||||
if (hasDrawSpike)
|
||||
// show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show.
|
||||
counterDrawFPS.SetCountWithoutRolling(1000 / newDrawFrameTime);
|
||||
displayedFpsCount = 1000 / drawClock.ElapsedFrameTime;
|
||||
else
|
||||
counterDrawFPS.Current.Value = newDrawFps;
|
||||
displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, 100, Time.Elapsed);
|
||||
|
||||
counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS);
|
||||
if (Time.Current - lastUpdate > min_time_between_updates)
|
||||
{
|
||||
updateFpsDisplay();
|
||||
updateFrameTimeDisplay();
|
||||
|
||||
double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount;
|
||||
counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS);
|
||||
lastUpdate = Time.Current;
|
||||
}
|
||||
|
||||
bool hasSignificantChanges = aimRatesChanged
|
||||
|| hasDrawSpike
|
||||
|| hasUpdateSpike
|
||||
|| counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8
|
||||
|| displayedUpdateFPS < aimUpdateFPS * 0.8;
|
||||
|| displayedFpsCount < aimDrawFPS * 0.8
|
||||
|| 1000 / displayedFrameTime < aimUpdateFPS * 0.8;
|
||||
|
||||
if (hasSignificantChanges)
|
||||
displayTemporarily();
|
||||
requestDisplay();
|
||||
else if (isDisplayed && Time.Current - lastDisplayRequiredTime > 2000)
|
||||
{
|
||||
mainContent.FadeTo(0, 300, Easing.OutQuint);
|
||||
isDisplayed = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestDisplay()
|
||||
{
|
||||
lastDisplayRequiredTime = Time.Current;
|
||||
|
||||
if (!isDisplayed)
|
||||
{
|
||||
mainContent.FadeTo(1, 300, Easing.OutQuint);
|
||||
isDisplayed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFpsDisplay()
|
||||
{
|
||||
counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS);
|
||||
counterDrawFPS.Text = $"{displayedFpsCount:#,0}fps";
|
||||
}
|
||||
|
||||
private void updateFrameTimeDisplay()
|
||||
{
|
||||
counterUpdateFrameTime.Text = displayedFrameTime < 5
|
||||
? $"{displayedFrameTime:N1}ms"
|
||||
: $"{displayedFrameTime:N0}ms";
|
||||
|
||||
counterUpdateFrameTime.Colour = getColour((1000 / displayedFrameTime) / aimUpdateFPS);
|
||||
}
|
||||
|
||||
private bool updateAimFPS()
|
||||
{
|
||||
if (gameHost.UpdateThread.Clock.Throttling)
|
||||
if (updateClock.Throttling)
|
||||
{
|
||||
double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz;
|
||||
double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz;
|
||||
double newAimDrawFPS = drawClock.MaximumUpdateHz;
|
||||
double newAimUpdateFPS = updateClock.MaximumUpdateHz;
|
||||
|
||||
if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS)
|
||||
{
|
||||
@ -230,7 +252,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
else
|
||||
{
|
||||
double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz;
|
||||
double newAimFPS = inputClock.MaximumUpdateHz;
|
||||
|
||||
if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS)
|
||||
{
|
||||
@ -253,50 +275,5 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public ITooltip GetCustomTooltip() => new FPSCounterTooltip();
|
||||
|
||||
public object TooltipContent => this;
|
||||
|
||||
public class FramesPerSecondCounter : RollingCounter<double>
|
||||
{
|
||||
protected override double RollingDuration => 1000;
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText()
|
||||
{
|
||||
return new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold),
|
||||
Spacing = new Vector2(-2),
|
||||
};
|
||||
}
|
||||
|
||||
protected override LocalisableString FormatCount(double count)
|
||||
{
|
||||
return $"{count:#,0}fps";
|
||||
}
|
||||
}
|
||||
|
||||
public class FrameTimeCounter : RollingCounter<double>
|
||||
{
|
||||
protected override double RollingDuration => 1000;
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText()
|
||||
{
|
||||
return new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold),
|
||||
Spacing = new Vector2(-1),
|
||||
};
|
||||
}
|
||||
|
||||
protected override LocalisableString FormatCount(double count)
|
||||
{
|
||||
if (count < 1)
|
||||
return $"{count:N1}ms";
|
||||
|
||||
return $"{count:N0}ms";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,10 +228,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
LeftBox.Scale = new Vector2(Math.Clamp(
|
||||
RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1);
|
||||
RightBox.Scale = new Vector2(Math.Clamp(
|
||||
DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1);
|
||||
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
|
||||
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
|
||||
}
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation");
|
||||
|
||||
/// <summary>
|
||||
/// "Personal Presets"
|
||||
/// </summary>
|
||||
public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap");
|
||||
|
||||
/// <summary>
|
||||
/// "Show gameplay cursor during touch input"
|
||||
/// </summary>
|
||||
public static LocalisableString GameplayCursorDuringTouch => new TranslatableString(getKey(@"gameplay_cursor_during_touch"), @"Show gameplay cursor during touch input");
|
||||
|
||||
/// <summary>
|
||||
/// "Beatmap skins"
|
||||
/// </summary>
|
||||
|
@ -1,162 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
public class APIScore : IScoreInfo
|
||||
{
|
||||
[JsonProperty(@"score")]
|
||||
public long TotalScore { get; set; }
|
||||
|
||||
[JsonProperty(@"max_combo")]
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
[JsonProperty(@"user")]
|
||||
public APIUser User { get; set; }
|
||||
|
||||
[JsonProperty(@"id")]
|
||||
public long OnlineID { get; set; }
|
||||
|
||||
[JsonProperty(@"replay")]
|
||||
public bool HasReplay { get; set; }
|
||||
|
||||
[JsonProperty(@"created_at")]
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
[JsonProperty(@"beatmap")]
|
||||
[CanBeNull]
|
||||
public APIBeatmap Beatmap { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty(@"pp")]
|
||||
public double? PP { get; set; }
|
||||
|
||||
[JsonProperty(@"beatmapset")]
|
||||
[CanBeNull]
|
||||
public APIBeatmapSet BeatmapSet
|
||||
{
|
||||
set
|
||||
{
|
||||
// in the deserialisation case we need to ferry this data across.
|
||||
// the order of properties returned by the API guarantees that the beatmap is populated by this point.
|
||||
if (!(Beatmap is APIBeatmap apiBeatmap))
|
||||
throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response");
|
||||
|
||||
apiBeatmap.BeatmapSet = value;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<string, int> Statistics { get; set; }
|
||||
|
||||
[JsonProperty(@"mode_int")]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[JsonProperty(@"mods")]
|
||||
private string[] mods { set => Mods = value.Select(acronym => new APIMod { Acronym = acronym }); }
|
||||
|
||||
[NotNull]
|
||||
public IEnumerable<APIMod> Mods { get; set; } = Array.Empty<APIMod>();
|
||||
|
||||
[JsonProperty("rank")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ScoreRank Rank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
||||
/// </summary>
|
||||
/// <param name="rulesets">A ruleset store, used to populate a ruleset instance in the returned score.</param>
|
||||
/// <param name="beatmap">An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap).</param>
|
||||
/// <returns></returns>
|
||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
||||
{
|
||||
var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally");
|
||||
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray();
|
||||
|
||||
// all API scores provided by this class are considered to be legacy.
|
||||
modInstances = modInstances.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
|
||||
|
||||
var scoreInfo = new ScoreInfo
|
||||
{
|
||||
TotalScore = TotalScore,
|
||||
MaxCombo = MaxCombo,
|
||||
BeatmapInfo = beatmap ?? new BeatmapInfo(),
|
||||
User = User,
|
||||
Accuracy = Accuracy,
|
||||
OnlineID = OnlineID,
|
||||
Date = Date,
|
||||
PP = PP,
|
||||
Hash = HasReplay ? "online" : string.Empty, // todo: temporary?
|
||||
Rank = Rank,
|
||||
Ruleset = ruleset,
|
||||
Mods = modInstances,
|
||||
};
|
||||
|
||||
if (Statistics != null)
|
||||
{
|
||||
foreach (var kvp in Statistics)
|
||||
{
|
||||
switch (kvp.Key)
|
||||
{
|
||||
case @"count_geki":
|
||||
scoreInfo.SetCountGeki(kvp.Value);
|
||||
break;
|
||||
|
||||
case @"count_300":
|
||||
scoreInfo.SetCount300(kvp.Value);
|
||||
break;
|
||||
|
||||
case @"count_katu":
|
||||
scoreInfo.SetCountKatu(kvp.Value);
|
||||
break;
|
||||
|
||||
case @"count_100":
|
||||
scoreInfo.SetCount100(kvp.Value);
|
||||
break;
|
||||
|
||||
case @"count_50":
|
||||
scoreInfo.SetCount50(kvp.Value);
|
||||
break;
|
||||
|
||||
case @"count_miss":
|
||||
scoreInfo.SetCountMiss(kvp.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scoreInfo;
|
||||
}
|
||||
|
||||
public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID };
|
||||
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => throw new NotImplementedException();
|
||||
|
||||
#region Implementation of IScoreInfo
|
||||
|
||||
IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
|
||||
IUser IScoreInfo.User => User;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -151,6 +151,23 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
PP = PP,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="SoloScoreInfo"/> from a local score for score submission.
|
||||
/// </summary>
|
||||
/// <param name="score">The local score.</param>
|
||||
public static SoloScoreInfo ForSubmission(ScoreInfo score) => new SoloScoreInfo
|
||||
{
|
||||
Rank = score.Rank,
|
||||
TotalScore = (int)score.TotalScore,
|
||||
Accuracy = score.Accuracy,
|
||||
PP = score.PP,
|
||||
MaxCombo = score.MaxCombo,
|
||||
RulesetID = score.RulesetID,
|
||||
Passed = score.Passed,
|
||||
Mods = score.APIMods,
|
||||
Statistics = score.Statistics,
|
||||
};
|
||||
|
||||
public long OnlineID => ID ?? -1;
|
||||
}
|
||||
}
|
||||
|
@ -7,20 +7,20 @@ using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Online.Rooms
|
||||
{
|
||||
public abstract class SubmitScoreRequest : APIRequest<MultiplayerScore>
|
||||
{
|
||||
public readonly SubmittableScore Score;
|
||||
public readonly SoloScoreInfo Score;
|
||||
|
||||
protected readonly long ScoreId;
|
||||
|
||||
protected SubmitScoreRequest(ScoreInfo scoreInfo, long scoreId)
|
||||
{
|
||||
Score = new SubmittableScore(scoreInfo);
|
||||
Score = SoloScoreInfo.ForSubmission(scoreInfo);
|
||||
ScoreId = scoreId;
|
||||
}
|
||||
|
||||
|
@ -1,72 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Online.Solo
|
||||
{
|
||||
/// <summary>
|
||||
/// A class specifically for sending scores to the API during score submission.
|
||||
/// This is used instead of <see cref="APIScore"/> due to marginally different serialisation naming requirements.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SubmittableScore
|
||||
{
|
||||
[JsonProperty("rank")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ScoreRank Rank { get; set; }
|
||||
|
||||
[JsonProperty("total_score")]
|
||||
public long TotalScore { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty(@"pp")]
|
||||
public double? PP { get; set; }
|
||||
|
||||
[JsonProperty("max_combo")]
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
[JsonProperty("ruleset_id")]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[JsonProperty("passed")]
|
||||
public bool Passed { get; set; }
|
||||
|
||||
// Used for API serialisation/deserialisation.
|
||||
[JsonProperty("mods")]
|
||||
public APIMod[] Mods { get; set; }
|
||||
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<HitResult, int> Statistics { get; set; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public SubmittableScore()
|
||||
{
|
||||
}
|
||||
|
||||
public SubmittableScore(ScoreInfo score)
|
||||
{
|
||||
Rank = score.Rank;
|
||||
TotalScore = score.TotalScore;
|
||||
Accuracy = score.Accuracy;
|
||||
PP = score.PP;
|
||||
MaxCombo = score.MaxCombo;
|
||||
RulesetID = score.RulesetID;
|
||||
Passed = score.Passed;
|
||||
Mods = score.APIMods;
|
||||
Statistics = score.Statistics;
|
||||
}
|
||||
}
|
||||
}
|
@ -206,6 +206,11 @@ namespace osu.Game.Online.Spectator
|
||||
if (!IsPlaying)
|
||||
return;
|
||||
|
||||
// Disposal can take some time, leading to EndPlaying potentially being called after a future play session.
|
||||
// Account for this by ensuring the score of the current play matches the one in the provided state.
|
||||
if (currentScore != state.Score)
|
||||
return;
|
||||
|
||||
if (pendingFrames.Count > 0)
|
||||
purgePendingFrames();
|
||||
|
||||
|
@ -716,7 +716,7 @@ namespace osu.Game
|
||||
// The next time this is updated is in UpdateAfterChildren, which occurs too late and results
|
||||
// in the cursor being shown for a few frames during the intro.
|
||||
// This prevents the cursor from showing until we have a screen with CursorVisible = true
|
||||
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
|
||||
GlobalCursorDisplay.ShowCursor = menuScreen?.CursorVisible ?? false;
|
||||
|
||||
// todo: all archive managers should be able to be looped here.
|
||||
SkinManager.PostNotification = n => Notifications.Post(n);
|
||||
@ -904,6 +904,8 @@ namespace osu.Game
|
||||
|
||||
loadComponentSingleFile(CreateHighPerformanceSession(), Add);
|
||||
|
||||
loadComponentSingleFile(new BackgroundBeatmapProcessor(), Add);
|
||||
|
||||
chatOverlay.State.BindValueChanged(_ => updateChatPollRate());
|
||||
// Multiplayer modes need to increase poll rate temporarily.
|
||||
API.Activity.BindValueChanged(_ => updateChatPollRate(), true);
|
||||
@ -1229,7 +1231,7 @@ namespace osu.Game
|
||||
ScreenOffsetContainer.X = horizontalOffset;
|
||||
overlayContent.X = horizontalOffset * 1.2f;
|
||||
|
||||
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
||||
GlobalCursorDisplay.ShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
||||
}
|
||||
|
||||
private void screenChanged(IScreen current, IScreen newScreen)
|
||||
|
@ -138,7 +138,7 @@ namespace osu.Game
|
||||
|
||||
protected RealmKeyBindingStore KeyBindingStore { get; private set; }
|
||||
|
||||
protected MenuCursorContainer MenuCursorContainer { get; private set; }
|
||||
protected GlobalCursorDisplay GlobalCursorDisplay { get; private set; }
|
||||
|
||||
protected MusicController MusicController { get; private set; }
|
||||
|
||||
@ -280,8 +280,7 @@ namespace osu.Game
|
||||
AddInternal(difficultyCache);
|
||||
|
||||
// TODO: OsuGame or OsuGameBase?
|
||||
beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage);
|
||||
|
||||
dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage));
|
||||
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
|
||||
dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints));
|
||||
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
|
||||
@ -341,10 +340,10 @@ namespace osu.Game
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = CreateScalingContainer().WithChildren(new Drawable[]
|
||||
{
|
||||
(MenuCursorContainer = new MenuCursorContainer
|
||||
(GlobalCursorDisplay = new GlobalCursorDisplay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}).WithChild(content = new OsuTooltipContainer(MenuCursorContainer.Cursor)
|
||||
}).WithChild(content = new OsuTooltipContainer(GlobalCursorDisplay.MenuCursor)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}),
|
||||
|
@ -6,7 +6,6 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
@ -25,6 +24,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Scoring.Drawables;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
{
|
||||
@ -179,8 +179,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
|
||||
if (showPerformancePoints)
|
||||
{
|
||||
Debug.Assert(score.PP != null);
|
||||
content.Add(new StatisticText(score.PP.Value, format: @"N0"));
|
||||
if (score.PP != null)
|
||||
content.Add(new StatisticText(score.PP, format: @"N0"));
|
||||
else
|
||||
content.Add(new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(text_size) });
|
||||
}
|
||||
|
||||
content.Add(new ScoreboardTime(score.Date, text_size)
|
||||
@ -222,19 +224,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
|
||||
private class StatisticText : OsuSpriteText, IHasTooltip
|
||||
{
|
||||
private readonly double count;
|
||||
private readonly double? count;
|
||||
private readonly double? maxCount;
|
||||
private readonly bool showTooltip;
|
||||
|
||||
public LocalisableString TooltipText => maxCount == null || !showTooltip ? string.Empty : $"{count}/{maxCount}";
|
||||
|
||||
public StatisticText(double count, double? maxCount = null, string format = null, bool showTooltip = true)
|
||||
public StatisticText(double? count, double? maxCount = null, string format = null, bool showTooltip = true)
|
||||
{
|
||||
this.count = count;
|
||||
this.maxCount = maxCount;
|
||||
this.showTooltip = showTooltip;
|
||||
|
||||
Text = count.ToLocalisableString(format);
|
||||
Text = count?.ToLocalisableString(format) ?? default;
|
||||
Font = OsuFont.GetFont(size: text_size);
|
||||
}
|
||||
|
||||
|
@ -12,14 +12,17 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
@ -121,7 +124,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
|
||||
|
||||
ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0;
|
||||
ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default;
|
||||
|
||||
if (value.PP is double pp)
|
||||
ppColumn.Text = pp.ToLocalisableString(@"N0");
|
||||
else
|
||||
ppColumn.Drawable = new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(smallFont.Size) };
|
||||
|
||||
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
|
||||
modsColumn.Mods = value.Mods;
|
||||
@ -197,30 +204,48 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
}
|
||||
}
|
||||
|
||||
private class TextColumn : InfoColumn
|
||||
private class TextColumn : InfoColumn, IHasCurrentValue<string>
|
||||
{
|
||||
private readonly SpriteText text;
|
||||
|
||||
public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null)
|
||||
: this(title, new OsuSpriteText { Font = font }, minWidth)
|
||||
{
|
||||
}
|
||||
|
||||
private TextColumn(LocalisableString title, SpriteText text, float? minWidth = null)
|
||||
: base(title, text, minWidth)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
private readonly OsuTextFlowContainer text;
|
||||
|
||||
public LocalisableString Text
|
||||
{
|
||||
set => text.Text = value;
|
||||
}
|
||||
|
||||
public Drawable Drawable
|
||||
{
|
||||
set
|
||||
{
|
||||
text.Clear();
|
||||
text.AddArbitraryDrawable(value);
|
||||
}
|
||||
}
|
||||
|
||||
private Bindable<string> current;
|
||||
|
||||
public Bindable<string> Current
|
||||
{
|
||||
get => text.Current;
|
||||
set => text.Current = value;
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
text.Clear();
|
||||
text.AddText(value.Value, t => t.Current = current = value);
|
||||
}
|
||||
}
|
||||
|
||||
public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null)
|
||||
: this(title, new OsuTextFlowContainer(t => t.Font = font)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both
|
||||
}, minWidth)
|
||||
{
|
||||
}
|
||||
|
||||
private TextColumn(LocalisableString title, OsuTextFlowContainer text, float? minWidth = null)
|
||||
: base(title, text, minWidth)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,10 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -12,6 +15,8 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.BeatmapSet;
|
||||
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||
using osu.Game.Overlays.Comments;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Select.Details;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -25,6 +30,14 @@ namespace osu.Game.Overlays
|
||||
|
||||
private readonly Bindable<APIBeatmapSet> beatmapSet = new Bindable<APIBeatmapSet>();
|
||||
|
||||
/// <remarks>
|
||||
/// Isolates the beatmap set overlay from the game-wide selected mods bindable
|
||||
/// to avoid affecting the beatmap details section (i.e. <see cref="AdvancedStats.StatisticRow"/>).
|
||||
/// </remarks>
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
public BeatmapSetOverlay()
|
||||
: base(OverlayColourScheme.Blue)
|
||||
{
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Masking = true,
|
||||
CornerRadius = ModPanel.CORNER_RADIUS,
|
||||
CornerRadius = ModSelectPanel.CORNER_RADIUS,
|
||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = multiplier_value_area_width + ModPanel.CORNER_RADIUS
|
||||
Width = multiplier_value_area_width + ModSelectPanel.CORNER_RADIUS
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Masking = true,
|
||||
CornerRadius = ModPanel.CORNER_RADIUS,
|
||||
CornerRadius = ModSelectPanel.CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
contentBackground = new Box
|
||||
|
@ -12,14 +12,10 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Mods.Input;
|
||||
@ -29,10 +25,8 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModColumn : CompositeDrawable
|
||||
public class ModColumn : ModSelectColumn
|
||||
{
|
||||
public readonly Container TopLevelContent;
|
||||
|
||||
public readonly ModType ModType;
|
||||
|
||||
private IReadOnlyList<ModState> availableMods = Array.Empty<ModState>();
|
||||
@ -62,149 +56,29 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this column should accept user input.
|
||||
/// </summary>
|
||||
public Bindable<bool> Active = new BindableBool(true);
|
||||
|
||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
|
||||
|
||||
protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod);
|
||||
|
||||
private readonly bool allowIncompatibleSelection;
|
||||
|
||||
private readonly TextFlowContainer headerText;
|
||||
private readonly Box headerBackground;
|
||||
private readonly Container contentContainer;
|
||||
private readonly Box contentBackground;
|
||||
private readonly FillFlowContainer<ModPanel> panelFlow;
|
||||
private readonly ToggleAllCheckbox? toggleAllCheckbox;
|
||||
|
||||
private Colour4 accentColour;
|
||||
|
||||
private Bindable<ModSelectHotkeyStyle> hotkeyStyle = null!;
|
||||
private IModHotkeyHandler hotkeyHandler = null!;
|
||||
|
||||
private Task? latestLoadTask;
|
||||
internal bool ItemsLoaded => latestLoadTask == null;
|
||||
|
||||
private const float header_height = 42;
|
||||
|
||||
public ModColumn(ModType modType, bool allowIncompatibleSelection)
|
||||
{
|
||||
ModType = modType;
|
||||
this.allowIncompatibleSelection = allowIncompatibleSelection;
|
||||
|
||||
Width = 320;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
||||
|
||||
Container controlContainer;
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
TopLevelContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = ModPanel.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height + ModPanel.CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
headerBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height + ModPanel.CORNER_RADIUS
|
||||
},
|
||||
headerText = new OsuTextFlowContainer(t =>
|
||||
{
|
||||
t.Font = OsuFont.TorusAlternate.With(size: 17);
|
||||
t.Shadow = false;
|
||||
t.Colour = Colour4.Black;
|
||||
})
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 17,
|
||||
Bottom = ModPanel.CORNER_RADIUS
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = header_height },
|
||||
Child = contentContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = ModPanel.CORNER_RADIUS,
|
||||
BorderThickness = 3,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
contentBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
controlContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Horizontal = 14 }
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new OsuScrollContainer(Direction.Vertical)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ClampExtension = 100,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Child = panelFlow = new FillFlowContainer<ModPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0, 7),
|
||||
Padding = new MarginPadding(7)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
createHeaderText();
|
||||
HeaderText = ModType.Humanize(LetterCasing.Title);
|
||||
|
||||
if (allowIncompatibleSelection)
|
||||
{
|
||||
controlContainer.Height = 35;
|
||||
controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this)
|
||||
ControlContainer.Height = 35;
|
||||
ControlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
@ -212,7 +86,7 @@ namespace osu.Game.Overlays.Mods
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
||||
});
|
||||
panelFlow.Padding = new MarginPadding
|
||||
ItemsFlow.Padding = new MarginPadding
|
||||
{
|
||||
Top = 0,
|
||||
Bottom = 7,
|
||||
@ -221,33 +95,17 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private void createHeaderText()
|
||||
{
|
||||
IEnumerable<string> headerTextWords = ModType.Humanize(LetterCasing.Title).Split(' ');
|
||||
|
||||
if (headerTextWords.Count() > 1)
|
||||
{
|
||||
headerText.AddText($"{headerTextWords.First()} ", t => t.Font = t.Font.With(weight: FontWeight.SemiBold));
|
||||
headerTextWords = headerTextWords.Skip(1);
|
||||
}
|
||||
|
||||
headerText.AddText(string.Join(' ', headerTextWords));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider, OsuColour colours, OsuConfigManager configManager)
|
||||
private void load(OsuColour colours, OsuConfigManager configManager)
|
||||
{
|
||||
headerBackground.Colour = accentColour = colours.ForModType(ModType);
|
||||
AccentColour = colours.ForModType(ModType);
|
||||
|
||||
if (toggleAllCheckbox != null)
|
||||
{
|
||||
toggleAllCheckbox.AccentColour = accentColour;
|
||||
toggleAllCheckbox.AccentHoverColour = accentColour.Lighten(0.3f);
|
||||
toggleAllCheckbox.AccentColour = AccentColour;
|
||||
toggleAllCheckbox.AccentHoverColour = AccentColour.Lighten(0.3f);
|
||||
}
|
||||
|
||||
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3);
|
||||
contentBackground.Colour = colourProvider.Background4;
|
||||
|
||||
hotkeyStyle = configManager.GetBindable<ModSelectHotkeyStyle>(OsuSetting.ModSelectHotkeyStyle);
|
||||
}
|
||||
|
||||
@ -278,7 +136,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
||||
{
|
||||
panelFlow.ChildrenEnumerable = loaded;
|
||||
ItemsFlow.ChildrenEnumerable = loaded;
|
||||
updateState();
|
||||
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||
loadTask.ContinueWith(_ =>
|
||||
|
@ -1,144 +1,42 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModPanel : OsuClickableContainer
|
||||
public class ModPanel : ModSelectPanel
|
||||
{
|
||||
public Mod Mod => modState.Mod;
|
||||
public BindableBool Active => modState.Active;
|
||||
public override BindableBool Active => modState.Active;
|
||||
public BindableBool Filtered => modState.Filtered;
|
||||
|
||||
protected override float IdleSwitchWidth => 54;
|
||||
protected override float ExpandedSwitchWidth => 70;
|
||||
|
||||
private readonly ModState modState;
|
||||
|
||||
protected readonly Box Background;
|
||||
protected readonly Container SwitchContainer;
|
||||
protected readonly Container MainContentContainer;
|
||||
protected readonly Box TextBackground;
|
||||
protected readonly FillFlowContainer TextFlow;
|
||||
|
||||
[Resolved]
|
||||
protected OverlayColourProvider ColourProvider { get; private set; } = null!;
|
||||
|
||||
protected const double TRANSITION_DURATION = 150;
|
||||
|
||||
public const float CORNER_RADIUS = 7;
|
||||
|
||||
protected const float HEIGHT = 42;
|
||||
protected const float IDLE_SWITCH_WIDTH = 54;
|
||||
protected const float EXPANDED_SWITCH_WIDTH = 70;
|
||||
|
||||
private Colour4 activeColour;
|
||||
|
||||
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool();
|
||||
private Sample? sampleOff;
|
||||
private Sample? sampleOn;
|
||||
|
||||
public ModPanel(ModState modState)
|
||||
{
|
||||
this.modState = modState;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 42;
|
||||
Title = Mod.Name;
|
||||
Description = Mod.Description;
|
||||
|
||||
// all below properties are applied to `Content` rather than the `ModPanel` in its entirety
|
||||
// to allow external components to set these properties on the panel without affecting
|
||||
// its "internal" appearance.
|
||||
Content.Masking = true;
|
||||
Content.CornerRadius = CORNER_RADIUS;
|
||||
Content.BorderThickness = 2;
|
||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
||||
|
||||
Children = new Drawable[]
|
||||
SwitchContainer.Child = new ModSwitchSmall(Mod)
|
||||
{
|
||||
Background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
SwitchContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Child = new ModSwitchSmall(Mod)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Active = { BindTarget = Active },
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
||||
}
|
||||
},
|
||||
MainContentContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
TextBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
TextFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 17.5f,
|
||||
Vertical = 4
|
||||
},
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = Mod.Name,
|
||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Left = -18 * ShearedOverlayContainer.SHEAR
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = Mod.Description,
|
||||
Font = OsuFont.Default.With(size: 12),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true,
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Active = { BindTarget = Active },
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
||||
};
|
||||
|
||||
Action = Active.Toggle;
|
||||
}
|
||||
|
||||
public ModPanel(Mod mod)
|
||||
@ -146,122 +44,21 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||
|
||||
activeColour = colours.ForModType(Mod.Type);
|
||||
|
||||
if (samplePlaybackDisabler != null)
|
||||
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
||||
AccentColour = colours.ForModType(Mod.Type);
|
||||
}
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Active.BindValueChanged(_ =>
|
||||
{
|
||||
playStateChangeSamples();
|
||||
UpdateState();
|
||||
});
|
||||
|
||||
Filtered.BindValueChanged(_ => updateFilterState(), true);
|
||||
|
||||
UpdateState();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void playStateChangeSamples()
|
||||
{
|
||||
if (samplePlaybackDisabled.Value)
|
||||
return;
|
||||
|
||||
if (Active.Value)
|
||||
sampleOn?.Play();
|
||||
else
|
||||
sampleOff?.Play();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
UpdateState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
UpdateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private bool mouseDown;
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left)
|
||||
mouseDown = true;
|
||||
|
||||
UpdateState();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
mouseDown = false;
|
||||
|
||||
UpdateState();
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : ColourProvider.Background3;
|
||||
protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : ColourProvider.Background2;
|
||||
protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White;
|
||||
|
||||
protected virtual void UpdateState()
|
||||
{
|
||||
float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH;
|
||||
double transitionDuration = TRANSITION_DURATION;
|
||||
|
||||
Colour4 backgroundColour = BackgroundColour;
|
||||
Colour4 foregroundColour = ForegroundColour;
|
||||
Colour4 textColour = TextColour;
|
||||
|
||||
// Hover affects colour of button background
|
||||
if (IsHovered)
|
||||
{
|
||||
backgroundColour = backgroundColour.Lighten(0.1f);
|
||||
foregroundColour = foregroundColour.Lighten(0.1f);
|
||||
}
|
||||
|
||||
// Mouse down adds a halfway tween of the movement
|
||||
if (mouseDown)
|
||||
{
|
||||
targetWidth = (float)Interpolation.Lerp(IDLE_SWITCH_WIDTH, EXPANDED_SWITCH_WIDTH, 0.5f);
|
||||
transitionDuration *= 4;
|
||||
}
|
||||
|
||||
Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint);
|
||||
Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint);
|
||||
SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint);
|
||||
MainContentContainer.TransformTo(nameof(Padding), new MarginPadding
|
||||
{
|
||||
Left = targetWidth,
|
||||
Right = CORNER_RADIUS
|
||||
}, transitionDuration, Easing.OutQuint);
|
||||
TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint);
|
||||
TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
#region Filtering support
|
||||
|
||||
public void ApplyFilter(Func<Mod, bool>? filter)
|
||||
{
|
||||
Filtered.Value = filter != null && !filter.Invoke(Mod);
|
||||
}
|
||||
|
||||
private void updateFilterState()
|
||||
{
|
||||
this.FadeTo(Filtered.Value ? 0 : 1);
|
||||
|
77
osu.Game/Overlays/Mods/ModPresetColumn.cs
Normal file
77
osu.Game/Overlays/Mods/ModPresetColumn.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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModPresetColumn : ModSelectColumn
|
||||
{
|
||||
private IReadOnlyList<ModPreset> presets = Array.Empty<ModPreset>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the collection of available mod presets.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ModPreset> Presets
|
||||
{
|
||||
get => presets;
|
||||
set
|
||||
{
|
||||
presets = value;
|
||||
|
||||
if (IsLoaded)
|
||||
asyncLoadPanels();
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
AccentColour = colours.Orange1;
|
||||
HeaderText = ModSelectOverlayStrings.PersonalPresets;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
asyncLoadPanels();
|
||||
}
|
||||
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
private Task? latestLoadTask;
|
||||
internal bool ItemsLoaded => latestLoadTask == null;
|
||||
|
||||
private void asyncLoadPanels()
|
||||
{
|
||||
cancellationTokenSource?.Cancel();
|
||||
|
||||
var panels = presets.Select(preset => new ModPresetPanel(preset)
|
||||
{
|
||||
Shear = Vector2.Zero
|
||||
});
|
||||
|
||||
Task? loadTask;
|
||||
|
||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
||||
{
|
||||
ItemsFlow.ChildrenEnumerable = loaded;
|
||||
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||
loadTask.ContinueWith(_ =>
|
||||
{
|
||||
if (loadTask == latestLoadTask)
|
||||
latestLoadTask = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
35
osu.Game/Overlays/Mods/ModPresetPanel.cs
Normal file
35
osu.Game/Overlays/Mods/ModPresetPanel.cs
Normal file
@ -0,0 +1,35 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModPresetPanel : ModSelectPanel, IHasCustomTooltip<ModPreset>
|
||||
{
|
||||
public readonly ModPreset Preset;
|
||||
|
||||
public override BindableBool Active { get; } = new BindableBool();
|
||||
|
||||
public ModPresetPanel(ModPreset preset)
|
||||
{
|
||||
Preset = preset;
|
||||
|
||||
Title = preset.Name;
|
||||
Description = preset.Description;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
AccentColour = colours.Orange1;
|
||||
}
|
||||
|
||||
public ModPreset TooltipContent => Preset;
|
||||
public ITooltip<ModPreset> GetCustomTooltip() => new ModPresetTooltip(ColourProvider);
|
||||
}
|
||||
}
|
115
osu.Game/Overlays/Mods/ModPresetTooltip.cs
Normal file
115
osu.Game/Overlays/Mods/ModPresetTooltip.cs
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModPresetTooltip : VisibilityContainer, ITooltip<ModPreset>
|
||||
{
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
private const double transition_duration = 200;
|
||||
|
||||
public ModPresetTooltip(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Width = 250;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 7;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background6
|
||||
},
|
||||
Content = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(7),
|
||||
Spacing = new Vector2(7)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ModPreset? lastPreset;
|
||||
|
||||
public void SetContent(ModPreset preset)
|
||||
{
|
||||
if (preset == lastPreset)
|
||||
return;
|
||||
|
||||
lastPreset = preset;
|
||||
Content.ChildrenEnumerable = preset.Mods.Select(mod => new ModPresetRow(mod));
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint);
|
||||
protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint);
|
||||
|
||||
public void Move(Vector2 pos) => Position = pos;
|
||||
|
||||
private class ModPresetRow : FillFlowContainer
|
||||
{
|
||||
public ModPresetRow(Mod mod)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Direction = FillDirection.Vertical;
|
||||
Spacing = new Vector2(4);
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(7),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ModSwitchTiny(mod)
|
||||
{
|
||||
Active = { Value = true },
|
||||
Scale = new Vector2(0.6f),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = mod.Name,
|
||||
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Bottom = 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(mod.SettingDescription))
|
||||
{
|
||||
AddInternal(new OsuTextFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Left = 14 },
|
||||
Text = mod.SettingDescription
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
177
osu.Game/Overlays/Mods/ModSelectColumn.cs
Normal file
177
osu.Game/Overlays/Mods/ModSelectColumn.cs
Normal file
@ -0,0 +1,177 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public abstract class ModSelectColumn : CompositeDrawable, IHasAccentColour
|
||||
{
|
||||
public readonly Container TopLevelContent;
|
||||
|
||||
public LocalisableString HeaderText
|
||||
{
|
||||
set => createHeaderText(value);
|
||||
}
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => headerBackground.Colour;
|
||||
set => headerBackground.Colour = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this column should accept user input.
|
||||
/// </summary>
|
||||
public readonly Bindable<bool> Active = new BindableBool(true);
|
||||
|
||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
|
||||
|
||||
protected readonly Container ControlContainer;
|
||||
protected readonly FillFlowContainer ItemsFlow;
|
||||
|
||||
private readonly TextFlowContainer headerText;
|
||||
private readonly Box headerBackground;
|
||||
private readonly Container contentContainer;
|
||||
private readonly Box contentBackground;
|
||||
|
||||
private const float header_height = 42;
|
||||
|
||||
protected ModSelectColumn()
|
||||
{
|
||||
Width = 320;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
TopLevelContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = ModSelectPanel.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height + ModSelectPanel.CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
headerBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height + ModSelectPanel.CORNER_RADIUS
|
||||
},
|
||||
headerText = new OsuTextFlowContainer(t =>
|
||||
{
|
||||
t.Font = OsuFont.TorusAlternate.With(size: 17);
|
||||
t.Shadow = false;
|
||||
t.Colour = Colour4.Black;
|
||||
})
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 17,
|
||||
Bottom = ModSelectPanel.CORNER_RADIUS
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = header_height },
|
||||
Child = contentContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = ModSelectPanel.CORNER_RADIUS,
|
||||
BorderThickness = 3,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
contentBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
ControlContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Horizontal = 14 }
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new OsuScrollContainer(Direction.Vertical)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ClampExtension = 100,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Child = ItemsFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0, 7),
|
||||
Padding = new MarginPadding(7)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void createHeaderText(LocalisableString text)
|
||||
{
|
||||
headerText.Clear();
|
||||
|
||||
int wordIndex = 0;
|
||||
|
||||
headerText.AddText(text, t =>
|
||||
{
|
||||
if (wordIndex == 0)
|
||||
t.Font = t.Font.With(weight: FontWeight.SemiBold);
|
||||
wordIndex += 1;
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3);
|
||||
contentBackground.Colour = colourProvider.Background4;
|
||||
}
|
||||
}
|
||||
}
|
252
osu.Game/Overlays/Mods/ModSelectPanel.cs
Normal file
252
osu.Game/Overlays/Mods/ModSelectPanel.cs
Normal file
@ -0,0 +1,252 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public abstract class ModSelectPanel : OsuClickableContainer, IHasAccentColour
|
||||
{
|
||||
public abstract BindableBool Active { get; }
|
||||
|
||||
public Color4 AccentColour { get; set; }
|
||||
|
||||
public LocalisableString Title
|
||||
{
|
||||
get => titleText.Text;
|
||||
set => titleText.Text = value;
|
||||
}
|
||||
|
||||
public LocalisableString Description
|
||||
{
|
||||
get => descriptionText.Text;
|
||||
set => descriptionText.Text = value;
|
||||
}
|
||||
|
||||
public const float CORNER_RADIUS = 7;
|
||||
|
||||
protected const float HEIGHT = 42;
|
||||
|
||||
protected virtual float IdleSwitchWidth => 14;
|
||||
protected virtual float ExpandedSwitchWidth => 30;
|
||||
protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3;
|
||||
protected virtual Colour4 ForegroundColour => Active.Value ? AccentColour : ColourProvider.Background2;
|
||||
protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White;
|
||||
|
||||
protected const double TRANSITION_DURATION = 150;
|
||||
|
||||
protected readonly Box Background;
|
||||
protected readonly Container SwitchContainer;
|
||||
protected readonly Container MainContentContainer;
|
||||
protected readonly Box TextBackground;
|
||||
protected readonly FillFlowContainer TextFlow;
|
||||
|
||||
[Resolved]
|
||||
protected OverlayColourProvider ColourProvider { get; private set; } = null!;
|
||||
|
||||
private readonly OsuSpriteText titleText;
|
||||
private readonly OsuSpriteText descriptionText;
|
||||
|
||||
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool();
|
||||
private Sample? sampleOff;
|
||||
private Sample? sampleOn;
|
||||
|
||||
protected ModSelectPanel()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = HEIGHT;
|
||||
|
||||
// all below properties are applied to `Content` rather than the `ModPanel` in its entirety
|
||||
// to allow external components to set these properties on the panel without affecting
|
||||
// its "internal" appearance.
|
||||
Content.Masking = true;
|
||||
Content.CornerRadius = CORNER_RADIUS;
|
||||
Content.BorderThickness = 2;
|
||||
|
||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
SwitchContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
MainContentContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
TextBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
TextFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 17.5f,
|
||||
Vertical = 4
|
||||
},
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new[]
|
||||
{
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true,
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Left = -18 * ShearedOverlayContainer.SHEAR
|
||||
}
|
||||
},
|
||||
descriptionText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Default.With(size: 12),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true,
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Action = () => Active.Toggle();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
||||
{
|
||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||
|
||||
if (samplePlaybackDisabler != null)
|
||||
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
||||
}
|
||||
|
||||
protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Active.BindValueChanged(_ =>
|
||||
{
|
||||
playStateChangeSamples();
|
||||
UpdateState();
|
||||
});
|
||||
|
||||
UpdateState();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void playStateChangeSamples()
|
||||
{
|
||||
if (samplePlaybackDisabled.Value)
|
||||
return;
|
||||
|
||||
if (Active.Value)
|
||||
sampleOn?.Play();
|
||||
else
|
||||
sampleOff?.Play();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
UpdateState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
UpdateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private bool mouseDown;
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left)
|
||||
mouseDown = true;
|
||||
|
||||
UpdateState();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
mouseDown = false;
|
||||
|
||||
UpdateState();
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
protected virtual void UpdateState()
|
||||
{
|
||||
float targetWidth = Active.Value ? ExpandedSwitchWidth : IdleSwitchWidth;
|
||||
double transitionDuration = TRANSITION_DURATION;
|
||||
|
||||
Colour4 backgroundColour = BackgroundColour;
|
||||
Colour4 foregroundColour = ForegroundColour;
|
||||
Colour4 textColour = TextColour;
|
||||
|
||||
// Hover affects colour of button background
|
||||
if (IsHovered)
|
||||
{
|
||||
backgroundColour = backgroundColour.Lighten(0.1f);
|
||||
foregroundColour = foregroundColour.Lighten(0.1f);
|
||||
}
|
||||
|
||||
// Mouse down adds a halfway tween of the movement
|
||||
if (mouseDown)
|
||||
{
|
||||
targetWidth = (float)Interpolation.Lerp(IdleSwitchWidth, ExpandedSwitchWidth, 0.5f);
|
||||
transitionDuration *= 4;
|
||||
}
|
||||
|
||||
Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint);
|
||||
Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint);
|
||||
SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint);
|
||||
MainContentContainer.TransformTo(nameof(Padding), new MarginPadding
|
||||
{
|
||||
Left = targetWidth,
|
||||
Right = CORNER_RADIUS
|
||||
}, transitionDuration, Easing.OutQuint);
|
||||
TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint);
|
||||
TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring.Drawables;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
@ -218,39 +219,42 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
|
||||
private Drawable createDrawablePerformance()
|
||||
{
|
||||
if (Score.PP.HasValue)
|
||||
if (!Score.PP.HasValue)
|
||||
{
|
||||
return new FillFlowContainer
|
||||
if (Score.Beatmap?.Status.GrantsPerformancePoints() == true)
|
||||
return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 };
|
||||
|
||||
return new OsuSpriteText
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||
Text = $"{Score.PP:0}",
|
||||
Colour = colourProvider.Highlight1
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
||||
Text = "pp",
|
||||
Colour = colourProvider.Light3
|
||||
}
|
||||
}
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||
Text = "-",
|
||||
Colour = colourProvider.Highlight1
|
||||
};
|
||||
}
|
||||
|
||||
return new OsuSpriteText
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||
Text = "-",
|
||||
Colour = colourProvider.Highlight1
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||
Text = $"{Score.PP:0}",
|
||||
Colour = colourProvider.Highlight1
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
||||
Text = "pp",
|
||||
Colour = colourProvider.Light3
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -42,12 +42,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
CreateDrawableAccuracy(),
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 50,
|
||||
Size = new Vector2(50, 14),
|
||||
Child = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
|
||||
Text = $"{Score.PP * weight:0}pp",
|
||||
Text = Score.PP.HasValue ? $"{Score.PP * weight:0}pp" : string.Empty,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
LabelText = SkinSettingsStrings.AutoCursorSize,
|
||||
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = SkinSettingsStrings.GameplayCursorDuringTouch,
|
||||
Current = config.GetBindable<bool>(OsuSetting.GameplayCursorDuringTouch)
|
||||
},
|
||||
};
|
||||
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
||||
|
@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
private readonly IRulesetInfo ruleset;
|
||||
private readonly IWorkingBeatmap beatmap;
|
||||
|
||||
/// <summary>
|
||||
/// A yymmdd version which is used to discern when reprocessing is required.
|
||||
/// </summary>
|
||||
public virtual int Version => 0;
|
||||
|
||||
protected DifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
|
35
osu.Game/Rulesets/Mods/ModPreset.cs
Normal file
35
osu.Game/Rulesets/Mods/ModPreset.cs
Normal file
@ -0,0 +1,35 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// A mod preset is a named collection of configured mods.
|
||||
/// Presets are presented to the user in the mod select overlay for convenience.
|
||||
/// </summary>
|
||||
public class ModPreset
|
||||
{
|
||||
/// <summary>
|
||||
/// The ruleset that the preset is valid for.
|
||||
/// </summary>
|
||||
public RulesetInfo RulesetInfo { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the mod preset.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The description of the mod preset.
|
||||
/// </summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The set of configured mods that are part of the preset.
|
||||
/// </summary>
|
||||
public ICollection<Mod> Mods { get; set; } = Array.Empty<Mod>();
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Rulesets
|
||||
@ -22,6 +23,11 @@ namespace osu.Game.Rulesets
|
||||
|
||||
public string InstantiationInfo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the last applied <see cref="DifficultyCalculator.Version"/>
|
||||
/// </summary>
|
||||
public int LastAppliedDifficultyVersion { get; set; }
|
||||
|
||||
public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID)
|
||||
{
|
||||
ShortName = shortName;
|
||||
@ -86,7 +92,8 @@ namespace osu.Game.Rulesets
|
||||
Name = Name,
|
||||
ShortName = ShortName,
|
||||
InstantiationInfo = InstantiationInfo,
|
||||
Available = Available
|
||||
Available = Available,
|
||||
LastAppliedDifficultyVersion = LastAppliedDifficultyVersion,
|
||||
};
|
||||
|
||||
public Ruleset CreateInstance()
|
||||
|
@ -126,6 +126,9 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private bool beatmapApplied;
|
||||
|
||||
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
||||
|
||||
private Dictionary<HitResult, int>? maximumResultCounts;
|
||||
|
||||
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
|
||||
private HitObject? lastHitObject;
|
||||
|
||||
@ -410,12 +413,16 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
base.Reset(storeResults);
|
||||
|
||||
scoreResultCounts.Clear();
|
||||
hitEvents.Clear();
|
||||
lastHitObject = null;
|
||||
|
||||
if (storeResults)
|
||||
{
|
||||
maximumScoringValues = currentScoringValues;
|
||||
maximumResultCounts = new Dictionary<HitResult, int>(scoreResultCounts);
|
||||
}
|
||||
|
||||
scoreResultCounts.Clear();
|
||||
|
||||
currentScoringValues = default;
|
||||
currentMaximumScoringValues = default;
|
||||
@ -423,6 +430,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
TotalScore.Value = 0;
|
||||
Accuracy.Value = 1;
|
||||
Combo.Value = 0;
|
||||
Rank.Disabled = false;
|
||||
Rank.Value = ScoreRank.X;
|
||||
HighestCombo.Value = 0;
|
||||
}
|
||||
@ -445,6 +453,36 @@ namespace osu.Game.Rulesets.Scoring
|
||||
score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the given score with remaining statistics as "missed" and marks it with <see cref="ScoreRank.F"/> rank.
|
||||
/// </summary>
|
||||
public void FailScore(ScoreInfo score)
|
||||
{
|
||||
if (Rank.Value == ScoreRank.F)
|
||||
return;
|
||||
|
||||
score.Passed = false;
|
||||
Rank.Value = ScoreRank.F;
|
||||
|
||||
Debug.Assert(maximumResultCounts != null);
|
||||
|
||||
if (maximumResultCounts.TryGetValue(HitResult.LargeTickHit, out int maximumLargeTick))
|
||||
scoreResultCounts[HitResult.LargeTickMiss] = maximumLargeTick - scoreResultCounts.GetValueOrDefault(HitResult.LargeTickHit);
|
||||
|
||||
if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick))
|
||||
scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit);
|
||||
|
||||
int maximumBonusOrIgnore = maximumResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value);
|
||||
int currentBonusOrIgnore = scoreResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value);
|
||||
scoreResultCounts[HitResult.IgnoreMiss] = maximumBonusOrIgnore - currentBonusOrIgnore;
|
||||
|
||||
int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value;
|
||||
int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value);
|
||||
scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic;
|
||||
|
||||
PopulateScore(score);
|
||||
}
|
||||
|
||||
public override void ResetFromReplayFrame(ReplayFrame frame)
|
||||
{
|
||||
base.ResetFromReplayFrame(frame);
|
||||
|
@ -380,7 +380,7 @@ namespace osu.Game.Rulesets.UI
|
||||
// only show the cursor when within the playfield, by default.
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
CursorContainer IProvideCursor.Cursor => Playfield.Cursor;
|
||||
CursorContainer IProvideCursor.MenuCursor => Playfield.Cursor;
|
||||
|
||||
public override GameplayCursorContainer Cursor => Playfield.Cursor;
|
||||
|
||||
|
@ -0,0 +1,27 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Scoring.Drawables
|
||||
{
|
||||
/// <summary>
|
||||
/// A placeholder used in PP columns for scores with unprocessed PP value.
|
||||
/// </summary>
|
||||
public class UnprocessedPerformancePointsPlaceholder : SpriteIcon, IHasTooltip
|
||||
{
|
||||
public LocalisableString TooltipText => ScoresStrings.StatusProcessing;
|
||||
|
||||
public UnprocessedPerformancePointsPlaceholder()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle;
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -172,6 +173,10 @@ namespace osu.Game.Scoring
|
||||
|
||||
// We can compute the max combo locally after the async beatmap difficulty computation.
|
||||
var difficulty = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (difficulty == null)
|
||||
Logger.Log($"Couldn't get beatmap difficulty for beatmap {score.BeatmapInfo.OnlineID}");
|
||||
|
||||
return difficulty?.MaxCombo;
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timeline's scroll position in the last frame.
|
||||
/// </summary>
|
||||
@ -68,8 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
/// </summary>
|
||||
private float defaultTimelineZoom;
|
||||
|
||||
private readonly Bindable<double> timelineZoomScale = new BindableDouble(1.0);
|
||||
|
||||
public Timeline(Drawable userContent)
|
||||
{
|
||||
this.userContent = userContent;
|
||||
@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
private Bindable<float> waveformOpacity;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, EditorBeatmap editorBeatmap, OsuColour colours, OsuConfigManager config)
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours, OsuConfigManager config)
|
||||
{
|
||||
CentreMarker centreMarker;
|
||||
|
||||
@ -145,21 +146,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
waveform.Waveform = b.NewValue.Waveform;
|
||||
track = b.NewValue.Track;
|
||||
|
||||
// todo: i don't think this is safe, the track may not be loaded yet.
|
||||
if (track.Length > 0)
|
||||
{
|
||||
MaxZoom = getZoomLevelForVisibleMilliseconds(500);
|
||||
MinZoom = getZoomLevelForVisibleMilliseconds(10000);
|
||||
defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000);
|
||||
}
|
||||
setupTimelineZoom();
|
||||
}, true);
|
||||
|
||||
timelineZoomScale.Value = editorBeatmap.BeatmapInfo.TimelineZoom;
|
||||
timelineZoomScale.BindValueChanged(scale =>
|
||||
{
|
||||
Zoom = (float)(defaultTimelineZoom * scale.NewValue);
|
||||
editorBeatmap.BeatmapInfo.TimelineZoom = scale.NewValue;
|
||||
}, true);
|
||||
Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -209,6 +199,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
scrollToTrackTime();
|
||||
}
|
||||
|
||||
private void setupTimelineZoom()
|
||||
{
|
||||
if (!track.IsLoaded)
|
||||
{
|
||||
Scheduler.AddOnce(setupTimelineZoom);
|
||||
return;
|
||||
}
|
||||
|
||||
defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000);
|
||||
|
||||
float initialZoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom);
|
||||
SetupZoom(initialZoom, getZoomLevelForVisibleMilliseconds(10000), getZoomLevelForVisibleMilliseconds(500));
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
// if this is not a precision scroll event, let the editor handle the seek itself (for snapping support)
|
||||
@ -221,7 +225,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
protected override void OnZoomChanged()
|
||||
{
|
||||
base.OnZoomChanged();
|
||||
timelineZoomScale.Value = Zoom / defaultTimelineZoom;
|
||||
editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom;
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Height = 0.5f,
|
||||
Icon = FontAwesome.Solid.SearchPlus,
|
||||
Action = () => changeZoom(1)
|
||||
Action = () => Timeline.AdjustZoomRelatively(1)
|
||||
},
|
||||
new TimelineButton
|
||||
{
|
||||
@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Height = 0.5f,
|
||||
Icon = FontAwesome.Solid.SearchMinus,
|
||||
Action = () => changeZoom(-1)
|
||||
Action = () => Timeline.AdjustZoomRelatively(-1)
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -153,7 +153,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current);
|
||||
Timeline.TicksVisible.BindTo(ticksCheckbox.Current);
|
||||
}
|
||||
|
||||
private void changeZoom(float change) => Timeline.Zoom += change;
|
||||
}
|
||||
}
|
||||
|
@ -32,20 +32,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
private readonly Container zoomedContent;
|
||||
protected override Container<Drawable> Content => zoomedContent;
|
||||
private float currentZoom = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The current zoom level of <see cref="ZoomableScrollContainer" />.
|
||||
/// It may differ from <see cref="Zoom" /> during transitions.
|
||||
/// The current zoom level of <see cref="ZoomableScrollContainer"/>.
|
||||
/// It may differ from <see cref="Zoom"/> during transitions.
|
||||
/// </summary>
|
||||
public float CurrentZoom => currentZoom;
|
||||
public float CurrentZoom { get; private set; } = 1;
|
||||
|
||||
private bool isZoomSetUp;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private IFrameBasedClock editorClock { get; set; }
|
||||
|
||||
private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize);
|
||||
|
||||
public ZoomableScrollContainer()
|
||||
private float minZoom;
|
||||
private float maxZoom;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ZoomableScrollContainer"/> with no zoom range.
|
||||
/// Functionality will be disabled until zoom is set up via <see cref="SetupZoom"/>.
|
||||
/// </summary>
|
||||
protected ZoomableScrollContainer()
|
||||
: base(Direction.Horizontal)
|
||||
{
|
||||
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
|
||||
@ -53,46 +61,36 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
AddLayout(zoomedContentWidthCache);
|
||||
}
|
||||
|
||||
private float minZoom = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum zoom level allowed.
|
||||
/// Creates a <see cref="ZoomableScrollContainer"/> with a defined zoom range.
|
||||
/// </summary>
|
||||
public float MinZoom
|
||||
public ZoomableScrollContainer(float minimum, float maximum, float initial)
|
||||
: this()
|
||||
{
|
||||
get => minZoom;
|
||||
set
|
||||
{
|
||||
if (value < 1)
|
||||
throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value));
|
||||
|
||||
minZoom = value;
|
||||
|
||||
// ensure zoom range is in valid state before updating zoom.
|
||||
if (MinZoom < MaxZoom)
|
||||
updateZoom();
|
||||
}
|
||||
SetupZoom(initial, minimum, maximum);
|
||||
}
|
||||
|
||||
private float maxZoom = 60;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum zoom level allowed.
|
||||
/// Sets up the minimum and maximum range of this zoomable scroll container, along with the initial zoom value.
|
||||
/// </summary>
|
||||
public float MaxZoom
|
||||
/// <param name="initial">The initial zoom value, applied immediately.</param>
|
||||
/// <param name="minimum">The minimum zoom value.</param>
|
||||
/// <param name="maximum">The maximum zoom value.</param>
|
||||
protected void SetupZoom(float initial, float minimum, float maximum)
|
||||
{
|
||||
get => maxZoom;
|
||||
set
|
||||
{
|
||||
if (value < 1)
|
||||
throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value));
|
||||
if (minimum < 1)
|
||||
throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be >= 1.", nameof(maximum));
|
||||
|
||||
maxZoom = value;
|
||||
if (maximum < 1)
|
||||
throw new ArgumentException($"{nameof(maximum)} ({maximum}) must be >= 1.", nameof(maximum));
|
||||
|
||||
// ensure zoom range is in valid state before updating zoom.
|
||||
if (MaxZoom > MinZoom)
|
||||
updateZoom();
|
||||
}
|
||||
if (minimum > maximum)
|
||||
throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be less than {nameof(maximum)} ({maximum})");
|
||||
|
||||
minZoom = minimum;
|
||||
maxZoom = maximum;
|
||||
CurrentZoom = zoomTarget = initial;
|
||||
isZoomSetUp = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -104,14 +102,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
set => updateZoom(value);
|
||||
}
|
||||
|
||||
private void updateZoom(float? value = null)
|
||||
private void updateZoom(float value)
|
||||
{
|
||||
float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom);
|
||||
if (!isZoomSetUp)
|
||||
return;
|
||||
|
||||
float newZoom = Math.Clamp(value, minZoom, maxZoom);
|
||||
|
||||
if (IsLoaded)
|
||||
setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X);
|
||||
else
|
||||
currentZoom = zoomTarget = newZoom;
|
||||
CurrentZoom = zoomTarget = newZoom;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -127,7 +128,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
if (e.AltPressed)
|
||||
{
|
||||
// zoom when holding alt.
|
||||
setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X);
|
||||
AdjustZoomRelatively(e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -141,16 +142,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
private void updateZoomedContentWidth()
|
||||
{
|
||||
zoomedContent.Width = DrawWidth * currentZoom;
|
||||
zoomedContent.Width = DrawWidth * CurrentZoom;
|
||||
zoomedContentWidthCache.Validate();
|
||||
}
|
||||
|
||||
public void AdjustZoomRelatively(float change, float? focusPoint = null)
|
||||
{
|
||||
if (!isZoomSetUp)
|
||||
return;
|
||||
|
||||
const float zoom_change_sensitivity = 0.02f;
|
||||
|
||||
setZoomTarget(zoomTarget + change * (maxZoom - minZoom) * zoom_change_sensitivity, focusPoint);
|
||||
}
|
||||
|
||||
private float zoomTarget = 1;
|
||||
|
||||
private void setZoomTarget(float newZoom, float focusPoint)
|
||||
private void setZoomTarget(float newZoom, float? focusPoint = null)
|
||||
{
|
||||
zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom);
|
||||
transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing);
|
||||
zoomTarget = Math.Clamp(newZoom, minZoom, maxZoom);
|
||||
focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X;
|
||||
|
||||
transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing);
|
||||
|
||||
OnZoomChanged();
|
||||
}
|
||||
@ -183,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
private readonly float scrollOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Transforms <see cref="ZoomableScrollContainer.currentZoom"/> to a new value.
|
||||
/// Transforms <see cref="ZoomableScrollContainer.CurrentZoom"/> to a new value.
|
||||
/// </summary>
|
||||
/// <param name="focusPoint">The focus point in absolute coordinates local to the content.</param>
|
||||
/// <param name="contentSize">The size of the content.</param>
|
||||
@ -195,7 +208,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
this.scrollOffset = scrollOffset;
|
||||
}
|
||||
|
||||
public override string TargetMember => nameof(currentZoom);
|
||||
public override string TargetMember => nameof(CurrentZoom);
|
||||
|
||||
private float valueAt(double time)
|
||||
{
|
||||
@ -213,7 +226,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
float expectedWidth = d.DrawWidth * newZoom;
|
||||
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
|
||||
|
||||
d.currentZoom = newZoom;
|
||||
d.CurrentZoom = newZoom;
|
||||
d.updateZoomedContentWidth();
|
||||
|
||||
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
|
||||
@ -222,7 +235,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
d.ScrollTo(targetOffset, false);
|
||||
}
|
||||
|
||||
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom;
|
||||
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
ScoreInfo =
|
||||
{
|
||||
BeatmapInfo = beatmap.BeatmapInfo,
|
||||
Ruleset = ruleset.RulesetInfo
|
||||
}
|
||||
};
|
||||
|
@ -267,12 +267,7 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
FailOverlay = new FailOverlay
|
||||
{
|
||||
SaveReplay = () =>
|
||||
{
|
||||
Score.ScoreInfo.Passed = false;
|
||||
Score.ScoreInfo.Rank = ScoreRank.F;
|
||||
return prepareAndImportScore();
|
||||
},
|
||||
SaveReplay = prepareAndImportScore,
|
||||
OnRetry = Restart,
|
||||
OnQuit = () => PerformExit(true),
|
||||
},
|
||||
@ -831,7 +826,6 @@ namespace osu.Game.Screens.Play
|
||||
return false;
|
||||
|
||||
GameplayState.HasFailed = true;
|
||||
Score.ScoreInfo.Passed = false;
|
||||
|
||||
updateGameplayState();
|
||||
|
||||
@ -849,9 +843,16 @@ namespace osu.Game.Screens.Play
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called back when the transform finishes
|
||||
/// <summary>
|
||||
/// Invoked when the fail animation has finished.
|
||||
/// </summary>
|
||||
private void onFailComplete()
|
||||
{
|
||||
// fail completion is a good point to mark a score as failed,
|
||||
// since the last judgement that caused the fail only applies to score processor after onFail.
|
||||
// todo: this should probably be handled better.
|
||||
ScoreProcessor.FailScore(Score.ScoreInfo);
|
||||
|
||||
GameplayClockContainer.Stop();
|
||||
|
||||
FailOverlay.Retries = RestartCount;
|
||||
@ -1028,10 +1029,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
|
||||
if (prepareScoreForDisplayTask == null)
|
||||
{
|
||||
Score.ScoreInfo.Passed = false;
|
||||
Score.ScoreInfo.Rank = ScoreRank.F;
|
||||
}
|
||||
ScoreProcessor.FailScore(Score.ScoreInfo);
|
||||
|
||||
// EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.
|
||||
// To resolve test failures, forcefully end playing synchronously when this screen exits.
|
||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
FillMode = FillMode.Fit,
|
||||
}
|
||||
},
|
||||
scoreCounter = new TotalScoreCounter
|
||||
scoreCounter = new TotalScoreCounter(!withFlair)
|
||||
{
|
||||
Margin = new MarginPadding { Top = 0, Bottom = 5 },
|
||||
Current = { Value = 0 },
|
||||
|
@ -4,6 +4,8 @@
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
@ -21,13 +23,19 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
{
|
||||
private readonly APIUser user;
|
||||
|
||||
private Sample appearanceSample;
|
||||
|
||||
private readonly bool playAppearanceSound;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ExpandedPanelTopContent"/>.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="APIUser"/> to display.</param>
|
||||
public ExpandedPanelTopContent(APIUser user)
|
||||
/// <param name="playAppearanceSound">Whether the appearance sample should play</param>
|
||||
public ExpandedPanelTopContent(APIUser user, bool playAppearanceSound = false)
|
||||
{
|
||||
this.user = user;
|
||||
this.playAppearanceSound = playAppearanceSound;
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
@ -35,8 +43,10 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
appearanceSample = audio.Samples.Get(@"Results/score-panel-top-appear");
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@ -62,5 +72,13 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (playAppearanceSound)
|
||||
appearanceSample?.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,11 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -22,11 +26,35 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
|
||||
protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING;
|
||||
|
||||
public TotalScoreCounter()
|
||||
private readonly bool playSamples;
|
||||
|
||||
private readonly Bindable<double> tickPlaybackRate = new Bindable<double>();
|
||||
|
||||
private double lastSampleTime;
|
||||
|
||||
private DrawableSample sampleTick;
|
||||
|
||||
public TotalScoreCounter(bool playSamples = false)
|
||||
{
|
||||
// Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369
|
||||
AutoSizeAxes = Axes.Y;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
this.playSamples = playSamples;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
AddInternal(sampleTick = new DrawableSample(audio.Samples.Get(@"Results/score-tick-lesser")));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (playSamples)
|
||||
Current.BindValueChanged(_ => startTicking());
|
||||
}
|
||||
|
||||
protected override LocalisableString FormatCount(long count) => count.ToString("N0");
|
||||
@ -39,5 +67,35 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true);
|
||||
s.Spacing = new Vector2(-5, 0);
|
||||
});
|
||||
|
||||
public override long DisplayedCount
|
||||
{
|
||||
get => base.DisplayedCount;
|
||||
set
|
||||
{
|
||||
if (base.DisplayedCount == value)
|
||||
return;
|
||||
|
||||
base.DisplayedCount = value;
|
||||
|
||||
if (playSamples && Time.Current > lastSampleTime + tickPlaybackRate.Value)
|
||||
{
|
||||
sampleTick?.Play();
|
||||
lastSampleTime = Time.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startTicking()
|
||||
{
|
||||
const double tick_debounce_rate_start = 10f;
|
||||
const double tick_debounce_rate_end = 100f;
|
||||
const double tick_volume_start = 0.5f;
|
||||
const double tick_volume_end = 1.0f;
|
||||
|
||||
this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start);
|
||||
this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, RollingDuration, Easing.OutSine);
|
||||
sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, RollingDuration, Easing.OutSine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -60,6 +62,8 @@ namespace osu.Game.Screens.Ranking
|
||||
private readonly bool allowRetry;
|
||||
private readonly bool allowWatchingReplay;
|
||||
|
||||
private Sample popInSample;
|
||||
|
||||
protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true)
|
||||
{
|
||||
Score = score;
|
||||
@ -70,10 +74,12 @@ namespace osu.Game.Screens.Ranking
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
FillFlowContainer buttons;
|
||||
|
||||
popInSample = audio.Samples.Get(@"UI/overlay-pop-in");
|
||||
|
||||
InternalChild = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -244,6 +250,8 @@ namespace osu.Game.Screens.Ranking
|
||||
});
|
||||
|
||||
bottomPanel.FadeTo(1, 250);
|
||||
|
||||
popInSample?.Play();
|
||||
}
|
||||
|
||||
public override bool OnExiting(ScreenExitEvent e)
|
||||
|
@ -6,13 +6,16 @@
|
||||
using System;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Contracted;
|
||||
using osu.Game.Screens.Ranking.Expanded;
|
||||
@ -93,9 +96,12 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
public readonly ScoreInfo Score;
|
||||
|
||||
private bool displayWithFlair;
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
|
||||
private Container content;
|
||||
private DrawableAudioMixer mixer;
|
||||
|
||||
private bool displayWithFlair;
|
||||
|
||||
private Container topLayerContainer;
|
||||
private Drawable topLayerBackground;
|
||||
@ -107,6 +113,8 @@ namespace osu.Game.Screens.Ranking
|
||||
private Container middleLayerContentContainer;
|
||||
private Drawable middleLayerContent;
|
||||
|
||||
private DrawableSample samplePanelFocus;
|
||||
|
||||
public ScorePanel(ScoreInfo score, bool isNewLocalScore = false)
|
||||
{
|
||||
Score = score;
|
||||
@ -116,13 +124,13 @@ namespace osu.Game.Screens.Ranking
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
// ScorePanel doesn't include the top extruding area in its own size.
|
||||
// Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale.
|
||||
const float vertical_fudge = 20;
|
||||
|
||||
InternalChild = content = new Container
|
||||
InternalChild = mixer = new DrawableAudioMixer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -174,7 +182,8 @@ namespace osu.Game.Screens.Ranking
|
||||
},
|
||||
middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
}
|
||||
},
|
||||
samplePanelFocus = new DrawableSample(audio.Samples.Get(@"Results/score-panel-focus"))
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -202,12 +211,32 @@ namespace osu.Game.Screens.Ranking
|
||||
state = value;
|
||||
|
||||
if (IsLoaded)
|
||||
{
|
||||
updateState();
|
||||
|
||||
if (value == PanelState.Expanded)
|
||||
playAppearSample();
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
mixer.Balance.Value = (ScreenSpaceDrawQuad.Centre.X / game.ScreenSpaceDrawQuad.Width) * 2 - 1;
|
||||
}
|
||||
|
||||
private void playAppearSample()
|
||||
{
|
||||
var channel = samplePanelFocus?.GetChannel();
|
||||
if (channel == null) return;
|
||||
|
||||
channel.Frequency.Value = 0.99 + RNG.NextDouble(0.2);
|
||||
channel.Play();
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
topLayerContent?.FadeOut(content_fade_duration).Expire();
|
||||
@ -221,7 +250,8 @@ namespace osu.Game.Screens.Ranking
|
||||
topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||
middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||
|
||||
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 });
|
||||
bool firstLoad = topLayerContent == null;
|
||||
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User, firstLoad) { Alpha = 0 });
|
||||
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 });
|
||||
|
||||
// only the first expanded display should happen with flair.
|
||||
@ -244,7 +274,7 @@ namespace osu.Game.Screens.Ranking
|
||||
break;
|
||||
}
|
||||
|
||||
content.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint);
|
||||
mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint);
|
||||
|
||||
bool topLayerExpanded = topLayerContainer.Y < 0;
|
||||
|
||||
|
@ -8,7 +8,10 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -35,6 +38,10 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
private readonly Container content;
|
||||
private readonly LoadingSpinner spinner;
|
||||
|
||||
private bool wasOpened;
|
||||
private Sample popInSample;
|
||||
private Sample popOutSample;
|
||||
|
||||
public StatisticsPanel()
|
||||
{
|
||||
InternalChild = new Container
|
||||
@ -56,9 +63,12 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
Score.BindValueChanged(populateStatistics, true);
|
||||
|
||||
popInSample = audio.Samples.Get(@"Results/statistics-panel-pop-in");
|
||||
popOutSample = audio.Samples.Get(@"Results/statistics-panel-pop-out");
|
||||
}
|
||||
|
||||
private CancellationTokenSource loadCancellation;
|
||||
@ -81,18 +91,16 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
spinner.Show();
|
||||
|
||||
var localCancellationSource = loadCancellation = new CancellationTokenSource();
|
||||
IBeatmap playableBeatmap = null;
|
||||
|
||||
var workingBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo);
|
||||
|
||||
// Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events.
|
||||
Task.Run(() =>
|
||||
{
|
||||
playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods);
|
||||
}, loadCancellation.Token).ContinueWith(_ => Schedule(() =>
|
||||
Task.Run(() => workingBeatmap.GetPlayableBeatmap(newScore.Ruleset, newScore.Mods), loadCancellation.Token).ContinueWith(task => Schedule(() =>
|
||||
{
|
||||
bool hitEventsAvailable = newScore.HitEvents.Count != 0;
|
||||
Container<Drawable> container;
|
||||
|
||||
var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap);
|
||||
var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, task.GetResultSafely());
|
||||
|
||||
if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents))
|
||||
{
|
||||
@ -216,9 +224,21 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(150, Easing.OutQuint);
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.FadeIn(150, Easing.OutQuint);
|
||||
|
||||
protected override void PopOut() => this.FadeOut(150, Easing.OutQuint);
|
||||
popInSample?.Play();
|
||||
wasOpened = true;
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.FadeOut(150, Easing.OutQuint);
|
||||
|
||||
if (wasOpened)
|
||||
popOutSample?.Play();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
|
@ -115,42 +115,53 @@ namespace osu.Game.Screens.Select
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
},
|
||||
new FillFlowContainer
|
||||
new GridContainer
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Direction = FillDirection.Horizontal,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(OsuTabControl<SortMode>.HORIZONTAL_SPACING, 0),
|
||||
Children = new Drawable[]
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new OsuTabControlCheckbox
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, OsuTabControl<SortMode>.HORIZONTAL_SPACING),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, OsuTabControl<SortMode>.HORIZONTAL_SPACING),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
Text = "Show converted",
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
sortTabs = new OsuTabControl<SortMode>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
Height = 24,
|
||||
AutoSort = true,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
AccentColour = colours.GreenLight,
|
||||
Current = { BindTarget = sortMode }
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = SortStrings.Default,
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
Margin = new MarginPadding(5),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = SortStrings.Default,
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
Margin = new MarginPadding(5),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
Empty(),
|
||||
sortTabs = new OsuTabControl<SortMode>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 24,
|
||||
AutoSort = true,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
AccentColour = colours.GreenLight,
|
||||
Current = { BindTarget = sortMode }
|
||||
},
|
||||
Empty(),
|
||||
new OsuTabControlCheckbox
|
||||
{
|
||||
Text = "Show converted",
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Screens.Utility
|
||||
|
||||
public readonly Bindable<LatencyVisualMode> VisualMode = new Bindable<LatencyVisualMode>();
|
||||
|
||||
public CursorContainer? Cursor { get; private set; }
|
||||
public CursorContainer? MenuCursor { get; private set; }
|
||||
|
||||
public bool ProvidingUserCursor => IsActiveArea.Value;
|
||||
|
||||
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Utility
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
Cursor = new LatencyCursorContainer
|
||||
MenuCursor = new LatencyCursorContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
@ -105,7 +105,7 @@ namespace osu.Game.Screens.Utility
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
Cursor = new LatencyCursorContainer
|
||||
MenuCursor = new LatencyCursorContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
@ -119,7 +119,7 @@ namespace osu.Game.Screens.Utility
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
Cursor = new LatencyCursorContainer
|
||||
MenuCursor = new LatencyCursorContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
|
@ -171,6 +171,11 @@ namespace osu.Game.Tests.Visual
|
||||
API.Login("Rhythm Champion", "osu!");
|
||||
|
||||
Dependencies.Get<SessionStatics>().SetValue(Static.MutedAudioNotificationShownOnce, true);
|
||||
|
||||
// set applied version to latest so that the BackgroundBeatmapProcessor doesn't consider
|
||||
// beatmap star ratings as outdated and reset them throughout the test.
|
||||
foreach (var ruleset in RulesetStore.AvailableRulesets)
|
||||
ruleset.LastAppliedDifficultyVersion = ruleset.CreateInstance().CreateDifficultyCalculator(Beatmap.Default).Version;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -38,11 +38,11 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
protected OsuManualInputManagerTestScene()
|
||||
{
|
||||
MenuCursorContainer cursorContainer;
|
||||
GlobalCursorDisplay cursorDisplay;
|
||||
|
||||
CompositeDrawable mainContent = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both };
|
||||
CompositeDrawable mainContent = cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor)
|
||||
cursorDisplay.Child = content = new OsuTooltipContainer(cursorDisplay.MenuCursor)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
|
@ -36,8 +36,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.14.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.720.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.722.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
||||
<PackageReference Include="Sentry" Version="3.19.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
|
@ -61,8 +61,8 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.720.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.722.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||
<PropertyGroup>
|
||||
@ -84,7 +84,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.720.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.722.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user