mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 11:37:28 +08:00
Merge branch 'master' into mod-overlay/preset-panel
This commit is contained in:
commit
d0e15d04cc
@ -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,
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
@ -32,12 +31,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
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]
|
||||
|
@ -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);
|
||||
|
||||
|
137
osu.Game/BackgroundBeatmapProcessor.cs
Normal file
137
osu.Game/BackgroundBeatmapProcessor.cs
Normal file
@ -0,0 +1,137 @@
|
||||
// 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)
|
||||
{
|
||||
Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})");
|
||||
beatmapUpdater.Process(set);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ 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 +22,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 +31,29 @@ 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 ScheduledDelegate? fadeOutDelegate;
|
||||
|
||||
private double aimDrawFPS;
|
||||
private double aimUpdateFPS;
|
||||
|
||||
private double lastUpdate;
|
||||
private ThrottledFrameClock drawClock = null!;
|
||||
private ThrottledFrameClock updateClock = null!;
|
||||
private ThrottledFrameClock inputClock = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
@ -44,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
private void load(OsuConfigManager config, GameHost gameHost)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@ -77,20 +96,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,6 +121,10 @@ 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()
|
||||
@ -135,13 +161,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private bool isDisplayed;
|
||||
|
||||
private ScheduledDelegate? fadeOutDelegate;
|
||||
|
||||
private double aimDrawFPS;
|
||||
private double aimUpdateFPS;
|
||||
|
||||
private void displayTemporarily()
|
||||
{
|
||||
if (!isDisplayed)
|
||||
@ -163,9 +182,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private GameHost gameHost { get; set; } = null!;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -176,50 +192,59 @@ 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();
|
||||
}
|
||||
|
||||
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 +255,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
else
|
||||
{
|
||||
double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz;
|
||||
double newAimFPS = inputClock.MaximumUpdateHz;
|
||||
|
||||
if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS)
|
||||
{
|
||||
@ -253,50 +278,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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString RestartTrack => new TranslatableString(getKey(@"restart_track"), @"Restart track");
|
||||
|
||||
/// <summary>
|
||||
/// "Beatmap saved"
|
||||
/// </summary>
|
||||
public static LocalisableString BeatmapSaved => new TranslatableString(getKey(@"beatmap_saved"), @"Beatmap saved");
|
||||
|
||||
/// <summary>
|
||||
/// "Skin saved"
|
||||
/// </summary>
|
||||
public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
@ -154,12 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
}, 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()
|
||||
@ -221,7 +217,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;
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,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;
|
||||
}
|
||||
|
||||
@ -145,12 +145,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
zoomedContentWidthCache.Validate();
|
||||
}
|
||||
|
||||
public void AdjustZoomRelatively(float change, float? focusPoint = null)
|
||||
{
|
||||
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);
|
||||
focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X;
|
||||
|
||||
transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing);
|
||||
|
||||
OnZoomChanged();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
@ -31,10 +32,11 @@ using osu.Game.Database;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -50,6 +52,7 @@ using osu.Game.Screens.Play;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
@ -169,6 +172,9 @@ namespace osu.Game.Screens.Edit
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||
|
||||
public Editor(EditorLoader loader = null)
|
||||
{
|
||||
this.loader = loader;
|
||||
@ -405,6 +411,7 @@ namespace osu.Game.Screens.Edit
|
||||
// no longer new after first user-triggered save.
|
||||
isNewBeatmap = false;
|
||||
updateLastSavedHash();
|
||||
onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.BeatmapSaved, editorBeatmap.BeatmapInfo.GetDisplayTitle()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -934,5 +941,13 @@ namespace osu.Game.Screens.Edit
|
||||
ControlPointInfo IBeatSyncProvider.ControlPoints => editorBeatmap.ControlPointInfo;
|
||||
IClock IBeatSyncProvider.Clock => clock;
|
||||
ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null;
|
||||
|
||||
private class BeatmapEditorToast : Toast
|
||||
{
|
||||
public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName)
|
||||
: base(InputSettingsStrings.EditorSection, value, beatmapDisplayName)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -115,34 +115,25 @@ 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
|
||||
{
|
||||
Text = "Show converted",
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
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),
|
||||
},
|
||||
sortTabs = new OsuTabControl<SortMode>
|
||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
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,
|
||||
@ -151,6 +142,26 @@ namespace osu.Game.Screens.Select
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -13,21 +13,26 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Edit.Components.Menus;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
[Cached(typeof(SkinEditor))]
|
||||
public class SkinEditor : VisibilityContainer, ICanAcceptFiles
|
||||
public class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler<PlatformAction>
|
||||
{
|
||||
public const double TRANSITION_DURATION = 500;
|
||||
|
||||
@ -68,6 +73,9 @@ namespace osu.Game.Skinning.Editor
|
||||
private EditorSidebar componentsSidebar;
|
||||
private EditorSidebar settingsSidebar;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||
|
||||
public SkinEditor()
|
||||
{
|
||||
}
|
||||
@ -199,6 +207,25 @@ namespace osu.Game.Skinning.Editor
|
||||
SelectedComponents.BindCollectionChanged((_, _) => Scheduler.AddOnce(populateSettings), true);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case PlatformAction.Save:
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<PlatformAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateTargetScreen(Drawable targetScreen)
|
||||
{
|
||||
this.targetScreen = targetScreen;
|
||||
@ -316,6 +343,7 @@ namespace osu.Game.Skinning.Editor
|
||||
currentSkin.Value.UpdateDrawableTarget(t);
|
||||
|
||||
skins.Save(skins.CurrentSkin.Value);
|
||||
onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString()));
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
@ -395,5 +423,13 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
game?.UnregisterImportHandler(this);
|
||||
}
|
||||
|
||||
private class SkinEditorToast : Toast
|
||||
{
|
||||
public SkinEditorToast(LocalisableString value, string skinDisplayName)
|
||||
: base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user