mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 15:47:26 +08:00
Merge branch 'ppy:master' into Freeze_frame_implementation
This commit is contained in:
commit
33a435e2ef
@ -52,7 +52,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.901.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.908.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
43
osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs
Normal file
43
osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// 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.Catch.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneCatchTouchInput : OsuTestScene
|
||||||
|
{
|
||||||
|
private CatchTouchInputMapper catchTouchInputMapper = null!;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("create input overlay", () =>
|
||||||
|
{
|
||||||
|
Child = new CatchInputManager(new CatchRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
catchTouchInputMapper = new CatchTouchInputMapper
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
AddStep("show overlay", () => catchTouchInputMapper.Show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,13 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public class CatchInputManager : RulesetInputManager<CatchAction>
|
public class CatchInputManager : RulesetInputManager<CatchAction>
|
||||||
{
|
{
|
||||||
public CatchInputManager(RulesetInfo ruleset)
|
public CatchInputManager(RulesetInfo ruleset)
|
||||||
|
277
osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs
Normal file
277
osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
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.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
public class CatchTouchInputMapper : VisibilityContainer
|
||||||
|
{
|
||||||
|
public override bool PropagatePositionalInputSubTree => true;
|
||||||
|
public override bool PropagateNonPositionalInputSubTree => true;
|
||||||
|
|
||||||
|
private readonly Dictionary<object, TouchCatchAction> trackedActionSources = new Dictionary<object, TouchCatchAction>();
|
||||||
|
|
||||||
|
private KeyBindingContainer<CatchAction> keyBindingContainer = null!;
|
||||||
|
|
||||||
|
private Container mainContent = null!;
|
||||||
|
|
||||||
|
private InputArea leftBox = null!;
|
||||||
|
private InputArea rightBox = null!;
|
||||||
|
private InputArea leftDashBox = null!;
|
||||||
|
private InputArea rightDashBox = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(CatchInputManager catchInputManager, OsuColour colours)
|
||||||
|
{
|
||||||
|
const float width = 0.15f;
|
||||||
|
|
||||||
|
keyBindingContainer = catchInputManager.KeyBindingContainer;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
mainContent = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = width,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.5f,
|
||||||
|
},
|
||||||
|
leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.5f,
|
||||||
|
Colour = colours.Gray9,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = width,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.5f,
|
||||||
|
Colour = colours.Gray9,
|
||||||
|
},
|
||||||
|
rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.5f,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
// Hide whenever the keyboard is used.
|
||||||
|
Hide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
return updateAction(e.Button, getTouchCatchActionFromInput(e.ScreenSpaceMousePosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnTouchDown(TouchDownEvent e)
|
||||||
|
{
|
||||||
|
return updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
Show();
|
||||||
|
|
||||||
|
TouchCatchAction? action = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition);
|
||||||
|
|
||||||
|
// multiple mouse buttons may be pressed and handling the same action.
|
||||||
|
foreach (MouseButton button in e.PressedButtons)
|
||||||
|
updateAction(button, action);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTouchMove(TouchMoveEvent e)
|
||||||
|
{
|
||||||
|
updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position));
|
||||||
|
base.OnTouchMove(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
updateAction(e.Button, null);
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTouchUp(TouchUpEvent e)
|
||||||
|
{
|
||||||
|
updateAction(e.Touch.Source, null);
|
||||||
|
base.OnTouchUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool updateAction(object source, TouchCatchAction? newAction)
|
||||||
|
{
|
||||||
|
TouchCatchAction? actionBefore = null;
|
||||||
|
|
||||||
|
if (trackedActionSources.TryGetValue(source, out TouchCatchAction found))
|
||||||
|
actionBefore = found;
|
||||||
|
|
||||||
|
if (actionBefore != newAction)
|
||||||
|
{
|
||||||
|
if (newAction != null)
|
||||||
|
trackedActionSources[source] = newAction.Value;
|
||||||
|
else
|
||||||
|
trackedActionSources.Remove(source);
|
||||||
|
|
||||||
|
updatePressedActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newAction != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePressedActions()
|
||||||
|
{
|
||||||
|
Show();
|
||||||
|
|
||||||
|
if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.MoveLeft))
|
||||||
|
keyBindingContainer.TriggerPressed(CatchAction.MoveLeft);
|
||||||
|
else
|
||||||
|
keyBindingContainer.TriggerReleased(CatchAction.MoveLeft);
|
||||||
|
|
||||||
|
if (trackedActionSources.ContainsValue(TouchCatchAction.DashRight) || trackedActionSources.ContainsValue(TouchCatchAction.MoveRight))
|
||||||
|
keyBindingContainer.TriggerPressed(CatchAction.MoveRight);
|
||||||
|
else
|
||||||
|
keyBindingContainer.TriggerReleased(CatchAction.MoveRight);
|
||||||
|
|
||||||
|
if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.DashRight))
|
||||||
|
keyBindingContainer.TriggerPressed(CatchAction.Dash);
|
||||||
|
else
|
||||||
|
keyBindingContainer.TriggerReleased(CatchAction.Dash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TouchCatchAction? getTouchCatchActionFromInput(Vector2 screenSpaceInputPosition)
|
||||||
|
{
|
||||||
|
if (leftDashBox.Contains(screenSpaceInputPosition))
|
||||||
|
return TouchCatchAction.DashLeft;
|
||||||
|
if (rightDashBox.Contains(screenSpaceInputPosition))
|
||||||
|
return TouchCatchAction.DashRight;
|
||||||
|
if (leftBox.Contains(screenSpaceInputPosition))
|
||||||
|
return TouchCatchAction.MoveLeft;
|
||||||
|
if (rightBox.Contains(screenSpaceInputPosition))
|
||||||
|
return TouchCatchAction.MoveRight;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn() => mainContent.FadeIn(300, Easing.OutQuint);
|
||||||
|
|
||||||
|
protected override void PopOut() => mainContent.FadeOut(300, Easing.OutQuint);
|
||||||
|
|
||||||
|
private class InputArea : CompositeDrawable, IKeyBindingHandler<CatchAction>
|
||||||
|
{
|
||||||
|
private readonly TouchCatchAction handledAction;
|
||||||
|
|
||||||
|
private readonly Box highlightOverlay;
|
||||||
|
|
||||||
|
private readonly IEnumerable<KeyValuePair<object, TouchCatchAction>> trackedActions;
|
||||||
|
|
||||||
|
private bool isHighlighted;
|
||||||
|
|
||||||
|
public InputArea(TouchCatchAction handledAction, IEnumerable<KeyValuePair<object, TouchCatchAction>> trackedActions)
|
||||||
|
{
|
||||||
|
this.handledAction = handledAction;
|
||||||
|
this.trackedActions = trackedActions;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 10,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.15f,
|
||||||
|
},
|
||||||
|
highlightOverlay = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<CatchAction> _)
|
||||||
|
{
|
||||||
|
updateHighlight();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<CatchAction> _)
|
||||||
|
{
|
||||||
|
updateHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHighlight()
|
||||||
|
{
|
||||||
|
bool isHandling = trackedActions.Any(a => a.Value == handledAction);
|
||||||
|
|
||||||
|
if (isHandling == isHighlighted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isHighlighted = isHandling;
|
||||||
|
highlightOverlay.FadeTo(isHighlighted ? 0.1f : 0, isHighlighted ? 80 : 400, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TouchCatchAction
|
||||||
|
{
|
||||||
|
MoveLeft,
|
||||||
|
MoveRight,
|
||||||
|
DashLeft,
|
||||||
|
DashRight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -32,6 +33,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450);
|
TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
KeyBindingInputManager.Add(new CatchTouchInputMapper());
|
||||||
|
}
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Configuration.Tracking;
|
using osu.Framework.Configuration.Tracking;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
|||||||
scrollTime => new SettingDescription(
|
scrollTime => new SettingDescription(
|
||||||
rawValue: scrollTime,
|
rawValue: scrollTime,
|
||||||
name: "Scroll Speed",
|
name: "Scroll Speed",
|
||||||
value: $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)} ({scrollTime}ms)"
|
value: $"{scrollTime}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)})"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
LabelText = "Scrolling direction",
|
LabelText = "Scrolling direction",
|
||||||
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||||
},
|
},
|
||||||
new SettingsSlider<double, TimeSlider>
|
new SettingsSlider<double, ManiaScrollSlider>
|
||||||
{
|
{
|
||||||
LabelText = "Scroll speed",
|
LabelText = "Scroll speed",
|
||||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
||||||
@ -47,5 +46,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ManiaScrollSlider : OsuSliderBar<double>
|
||||||
|
{
|
||||||
|
public override LocalisableString TooltipText => $"{Current.Value}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -9,6 +10,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Osu.Utils;
|
using osu.Game.Rulesets.Osu.Utils;
|
||||||
|
|
||||||
@ -25,40 +27,100 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
||||||
|
|
||||||
private Random? rng;
|
private Random random = null!;
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
if (!(beatmap is OsuBeatmap osuBeatmap))
|
if (beatmap is not OsuBeatmap osuBeatmap)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Seed.Value ??= RNG.Next();
|
Seed.Value ??= RNG.Next();
|
||||||
|
|
||||||
rng = new Random((int)Seed.Value);
|
random = new Random((int)Seed.Value);
|
||||||
|
|
||||||
var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects);
|
var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects);
|
||||||
|
|
||||||
float rateOfChangeMultiplier = 0;
|
// Offsets the angles of all hit objects in a "section" by the same amount.
|
||||||
|
float sectionOffset = 0;
|
||||||
|
|
||||||
foreach (var positionInfo in positionInfos)
|
// Whether the angles are positive or negative (clockwise or counter-clockwise flow).
|
||||||
|
bool flowDirection = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < positionInfos.Count; i++)
|
||||||
{
|
{
|
||||||
// rateOfChangeMultiplier only changes every 5 iterations in a combo
|
if (shouldStartNewSection(osuBeatmap, positionInfos, i))
|
||||||
// to prevent shaky-line-shaped streams
|
|
||||||
if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0)
|
|
||||||
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
|
|
||||||
|
|
||||||
if (positionInfo == positionInfos.First())
|
|
||||||
{
|
{
|
||||||
positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2);
|
sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.0008f);
|
||||||
positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
|
flowDirection = !flowDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
positionInfos[i].DistanceFromPrevious = (float)(random.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2);
|
||||||
|
positionInfos[i].RelativeAngle = (float)(random.NextDouble() * 2 * Math.PI - Math.PI);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
positionInfo.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, positionInfo.DistanceFromPrevious / (playfield_diagonal * 0.5f));
|
// Offsets only the angle of the current hit object if a flow change occurs.
|
||||||
|
float flowChangeOffset = 0;
|
||||||
|
|
||||||
|
// Offsets only the angle of the current hit object.
|
||||||
|
float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f);
|
||||||
|
|
||||||
|
if (shouldApplyFlowChange(positionInfos, i))
|
||||||
|
{
|
||||||
|
flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f);
|
||||||
|
flowDirection = !flowDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
float totalOffset =
|
||||||
|
// sectionOffset and oneTimeOffset should mainly affect patterns with large spacing.
|
||||||
|
(sectionOffset + oneTimeOffset) * positionInfos[i].DistanceFromPrevious +
|
||||||
|
// flowChangeOffset should mainly affect streams.
|
||||||
|
flowChangeOffset * (playfield_diagonal - positionInfos[i].DistanceFromPrevious);
|
||||||
|
|
||||||
|
positionInfos[i].RelativeAngle = getRelativeTargetAngle(positionInfos[i].DistanceFromPrevious, totalOffset, flowDirection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
|
osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <param name="targetDistance">The target distance between the previous and the current <see cref="OsuHitObject"/>.</param>
|
||||||
|
/// <param name="offset">The angle (in rad) by which the target angle should be offset.</param>
|
||||||
|
/// <param name="flowDirection">Whether the relative angle should be positive or negative.</param>
|
||||||
|
private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection)
|
||||||
|
{
|
||||||
|
float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310))) + 0.5 + offset);
|
||||||
|
float relativeAngle = (float)Math.PI - angle;
|
||||||
|
return flowDirection ? -relativeAngle : relativeAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <returns>Whether a new section should be started at the current <see cref="OsuHitObject"/>.</returns>
|
||||||
|
private bool shouldStartNewSection(OsuBeatmap beatmap, IReadOnlyList<OsuHitObjectGenerationUtils.ObjectPositionInfo> positionInfos, int i)
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Exclude new-combo-spam and 1-2-combos.
|
||||||
|
bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 &&
|
||||||
|
positionInfos[i - 1].HitObject.NewCombo;
|
||||||
|
bool previousObjectWasOnDownbeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true);
|
||||||
|
bool previousObjectWasOnBeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject);
|
||||||
|
|
||||||
|
return (previousObjectStartedCombo && random.NextDouble() < 0.6f) ||
|
||||||
|
previousObjectWasOnDownbeat ||
|
||||||
|
(previousObjectWasOnBeat && random.NextDouble() < 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <returns>Whether a flow change should be applied at the current <see cref="OsuHitObject"/>.</returns>
|
||||||
|
private bool shouldApplyFlowChange(IReadOnlyList<OsuHitObjectGenerationUtils.ObjectPositionInfo> positionInfos, int i)
|
||||||
|
{
|
||||||
|
// Exclude new-combo-spam and 1-2-combos.
|
||||||
|
bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 &&
|
||||||
|
positionInfos[i - 1].HitObject.NewCombo;
|
||||||
|
|
||||||
|
return previousObjectStartedCombo && random.NextDouble() < 0.6f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
currentRotation += angle;
|
currentRotation += angle;
|
||||||
// rate has to be applied each frame, because it's not guaranteed to be constant throughout playback
|
// rate has to be applied each frame, because it's not guaranteed to be constant throughout playback
|
||||||
// (see: ModTimeRamp)
|
// (see: ModTimeRamp)
|
||||||
drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate));
|
drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetState(DrawableHitObject obj)
|
private void resetState(DrawableHitObject obj)
|
||||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -186,5 +187,39 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
length * MathF.Sin(angle)
|
length * MathF.Sin(angle)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <param name="beatmap">The beatmap hitObject is a part of.</param>
|
||||||
|
/// <param name="hitObject">The <see cref="OsuHitObject"/> that should be checked.</param>
|
||||||
|
/// <param name="downbeatsOnly">If true, this method only returns true if hitObject is on a downbeat.
|
||||||
|
/// If false, it returns true if hitObject is on any beat.</param>
|
||||||
|
/// <returns>true if hitObject is on a (down-)beat, false otherwise.</returns>
|
||||||
|
public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false)
|
||||||
|
{
|
||||||
|
var timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||||
|
|
||||||
|
double timeSinceTimingPoint = hitObject.StartTime - timingPoint.Time;
|
||||||
|
|
||||||
|
double beatLength = timingPoint.BeatLength;
|
||||||
|
|
||||||
|
if (downbeatsOnly)
|
||||||
|
beatLength *= timingPoint.TimeSignature.Numerator;
|
||||||
|
|
||||||
|
// Ensure within 1ms of expected location.
|
||||||
|
return Math.Abs(timeSinceTimingPoint + 1) % beatLength < 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a random number from a normal distribution using the Box-Muller transform.
|
||||||
|
/// </summary>
|
||||||
|
public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1)
|
||||||
|
{
|
||||||
|
// Generate 2 random numbers in the interval (0,1].
|
||||||
|
// x1 must not be 0 since log(0) = undefined.
|
||||||
|
double x1 = 1 - rng.NextDouble();
|
||||||
|
double x2 = 1 - rng.NextDouble();
|
||||||
|
|
||||||
|
double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(2 * Math.PI * x2);
|
||||||
|
return mean + stdDev * (float)stdNormal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
@ -13,21 +14,20 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
[TestCase(0)]
|
[TestCase(0)]
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate)
|
public void TestTrueGameplayRateWithGameplayAdjustment(double underlyingClockRate)
|
||||||
{
|
{
|
||||||
var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate });
|
var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate });
|
||||||
var gameplayClock = new TestGameplayClockContainer(framedClock);
|
var gameplayClock = new TestGameplayClockContainer(framedClock);
|
||||||
|
|
||||||
Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0));
|
Assert.That(gameplayClock.GetTrueGameplayRate(), Is.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestGameplayClockContainer : GameplayClockContainer
|
private class TestGameplayClockContainer : GameplayClockContainer
|
||||||
{
|
{
|
||||||
public override IEnumerable<double> NonGameplayAdjustments => new[] { 0.0 };
|
|
||||||
|
|
||||||
public TestGameplayClockContainer(IFrameBasedClock underlyingClock)
|
public TestGameplayClockContainer(IFrameBasedClock underlyingClock)
|
||||||
: base(underlyingClock)
|
: base(underlyingClock)
|
||||||
{
|
{
|
||||||
|
AdjustmentsFromMods.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
osu.Game.Tests/Resources/Archives/modified-default-20220818.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-default-20220818.osk
Normal file
Binary file not shown.
@ -36,7 +36,9 @@ namespace osu.Game.Tests.Skins
|
|||||||
"Archives/modified-default-20220723.osk",
|
"Archives/modified-default-20220723.osk",
|
||||||
"Archives/modified-classic-20220723.osk",
|
"Archives/modified-classic-20220723.osk",
|
||||||
// Covers legacy song progress, UR counter, colour hit error metre.
|
// Covers legacy song progress, UR counter, colour hit error metre.
|
||||||
"Archives/modified-classic-20220801.osk"
|
"Archives/modified-classic-20220801.osk",
|
||||||
|
// Covers clicks/s counter
|
||||||
|
"Archives/modified-default-20220818.osk"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -15,12 +15,14 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards;
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Beatmaps
|
namespace osu.Game.Tests.Visual.Beatmaps
|
||||||
{
|
{
|
||||||
@ -295,5 +297,22 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
|
|
||||||
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
|
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlayButtonByTouchInput()
|
||||||
|
{
|
||||||
|
AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo)));
|
||||||
|
|
||||||
|
// mimics touch input
|
||||||
|
AddStep("touch play button area on first card", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(firstCard().ChildrenOfType<PlayButton>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first card is playing", () => firstCard().ChildrenOfType<PlayButton>().Single().Playing.Value);
|
||||||
|
|
||||||
|
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
@ -58,6 +59,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
State = { Value = DownloadState.NotDownloaded },
|
||||||
Scale = new Vector2(2)
|
Scale = new Vector2(2)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
AddStep("enable dim", () => thumbnail.Dimmed.Value = true);
|
AddStep("enable dim", () => thumbnail.Dimmed.Value = true);
|
||||||
AddUntilStep("button visible", () => playButton.IsPresent);
|
AddUntilStep("button visible", () => playButton.Alpha == 1);
|
||||||
|
|
||||||
AddStep("click button", () =>
|
AddStep("click button", () =>
|
||||||
{
|
{
|
||||||
@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
|
|
||||||
AddStep("disable dim", () => thumbnail.Dimmed.Value = false);
|
AddStep("disable dim", () => thumbnail.Dimmed.Value = false);
|
||||||
AddWaitStep("wait some", 3);
|
AddWaitStep("wait some", 3);
|
||||||
AddAssert("button still visible", () => playButton.IsPresent);
|
AddAssert("button still visible", () => playButton.Alpha == 1);
|
||||||
|
|
||||||
// The track plays in real-time, so we need to check for progress in increments to avoid timeout.
|
// The track plays in real-time, so we need to check for progress in increments to avoid timeout.
|
||||||
AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.25);
|
AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.25);
|
||||||
@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.75);
|
AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.75);
|
||||||
|
|
||||||
AddUntilStep("wait for track to end", () => !playButton.Playing.Value);
|
AddUntilStep("wait for track to end", () => !playButton.Playing.Value);
|
||||||
AddUntilStep("button hidden", () => !playButton.IsPresent);
|
AddUntilStep("button hidden", () => playButton.Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType<SpriteIcon>().Any(icon => icon.Icon.Equals(usage)));
|
private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType<SpriteIcon>().Any(icon => icon.Icon.Equals(usage)));
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneClicksPerSecondCalculator : OsuTestScene
|
||||||
|
{
|
||||||
|
private ClicksPerSecondCalculator calculator = null!;
|
||||||
|
|
||||||
|
private TestGameplayClock manualGameplayClock = null!;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("create components", () =>
|
||||||
|
{
|
||||||
|
manualGameplayClock = new TestGameplayClock();
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
calculator = new ClicksPerSecondCalculator(),
|
||||||
|
new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) },
|
||||||
|
Child = new ClicksPerSecondCounter
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(5),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicConsistency()
|
||||||
|
{
|
||||||
|
seek(1000);
|
||||||
|
AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 }));
|
||||||
|
checkClicksPerSecondValue(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRateAdjustConsistency()
|
||||||
|
{
|
||||||
|
seek(1000);
|
||||||
|
AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 }));
|
||||||
|
checkClicksPerSecondValue(10);
|
||||||
|
AddStep("set rate 0.5x", () => manualGameplayClock.TrueGameplayRate = 0.5);
|
||||||
|
checkClicksPerSecondValue(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputsDiscardedOnRewind()
|
||||||
|
{
|
||||||
|
seek(1000);
|
||||||
|
AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 }));
|
||||||
|
checkClicksPerSecondValue(10);
|
||||||
|
seek(500);
|
||||||
|
checkClicksPerSecondValue(6);
|
||||||
|
seek(1000);
|
||||||
|
checkClicksPerSecondValue(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => calculator.Value, () => Is.EqualTo(i));
|
||||||
|
|
||||||
|
private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time;
|
||||||
|
|
||||||
|
private void seek(double time) => AddStep($"Seek to {time}ms", () => seekClockImmediately(time));
|
||||||
|
|
||||||
|
private void addInputs(IEnumerable<double> inputs)
|
||||||
|
{
|
||||||
|
double baseTime = manualGameplayClock.CurrentTime;
|
||||||
|
|
||||||
|
foreach (double timestamp in inputs)
|
||||||
|
{
|
||||||
|
seekClockImmediately(timestamp);
|
||||||
|
calculator.AddInputTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
seekClockImmediately(baseTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestGameplayClock : IGameplayClock
|
||||||
|
{
|
||||||
|
public double CurrentTime { get; set; }
|
||||||
|
|
||||||
|
public double Rate => 1;
|
||||||
|
|
||||||
|
public bool IsRunning => true;
|
||||||
|
|
||||||
|
public double TrueGameplayRate { set => adjustableAudioComponent.Tempo.Value = value; }
|
||||||
|
|
||||||
|
private readonly AudioAdjustments adjustableAudioComponent = new AudioAdjustments();
|
||||||
|
|
||||||
|
public void ProcessFrame()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public double ElapsedFrameTime => throw new NotImplementedException();
|
||||||
|
public double FramesPerSecond => throw new NotImplementedException();
|
||||||
|
public FrameTimeInfo TimeInfo => throw new NotImplementedException();
|
||||||
|
public double StartTime => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public IAdjustableAudioComponent AdjustmentsFromMods => adjustableAudioComponent;
|
||||||
|
|
||||||
|
public IEnumerable<double> NonGameplayAdjustments => throw new NotImplementedException();
|
||||||
|
public IBindable<bool> IsPaused => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,8 +78,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
||||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||||
|
AddAssert("sprites present", () => sprites.All(s => s.IsPresent));
|
||||||
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1)));
|
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1)));
|
||||||
AddAssert("zero width", () => sprites.All(s => s.ScreenSpaceDrawQuad.Width == 0));
|
AddAssert("sprites not present", () => sprites.All(s => !s.IsPresent));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -370,7 +370,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void confirmNoTrackAdjustments()
|
private void confirmNoTrackAdjustments()
|
||||||
{
|
{
|
||||||
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
|
AddUntilStep("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value, () => Is.EqualTo(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restart() => AddStep("restart", () => Player.Restart());
|
private void restart() => AddStep("restart", () => Player.Restart());
|
||||||
|
@ -81,9 +81,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
CreateTest();
|
CreateTest();
|
||||||
|
|
||||||
AddUntilStep("fail screen displayed", () => Player.ChildrenOfType<FailOverlay>().First().State.Value == Visibility.Visible);
|
AddUntilStep("fail screen displayed", () => Player.ChildrenOfType<FailOverlay>().First().State.Value == Visibility.Visible);
|
||||||
|
AddUntilStep("wait for button clickable", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().Enabled.Value);
|
||||||
|
|
||||||
AddUntilStep("score not in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) == null));
|
AddUntilStep("score not in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) == null));
|
||||||
AddStep("click save button", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().TriggerClick());
|
AddStep("click save button", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().TriggerClick());
|
||||||
AddUntilStep("score not in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
|
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||||
|
|
||||||
AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
|
AddAssert("state is unknown", () => downloadButton.State.Value == DownloadState.Unknown);
|
||||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public class TestSceneSkinEditor : PlayerTestScene
|
public class TestSceneSkinEditor : PlayerTestScene
|
||||||
{
|
{
|
||||||
private SkinEditor skinEditor;
|
private SkinEditor? skinEditor;
|
||||||
|
|
||||||
protected override bool Autoplay => true;
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
@ -42,29 +40,33 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Player.ScaleTo(0.4f);
|
Player.ScaleTo(0.4f);
|
||||||
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
||||||
});
|
});
|
||||||
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
AddUntilStep("wait for loaded", () => skinEditor!.IsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestToggleEditor()
|
public void TestToggleEditor()
|
||||||
{
|
{
|
||||||
AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility());
|
AddToggleStep("toggle editor visibility", _ => skinEditor!.ToggleVisibility());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEditComponent()
|
public void TestEditComponent()
|
||||||
{
|
{
|
||||||
BarHitErrorMeter hitErrorMeter = null;
|
BarHitErrorMeter hitErrorMeter = null!;
|
||||||
|
|
||||||
AddStep("select bar hit error blueprint", () =>
|
AddStep("select bar hit error blueprint", () =>
|
||||||
{
|
{
|
||||||
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First(b => b.Item is BarHitErrorMeter);
|
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First(b => b.Item is BarHitErrorMeter);
|
||||||
|
|
||||||
hitErrorMeter = (BarHitErrorMeter)blueprint.Item;
|
hitErrorMeter = (BarHitErrorMeter)blueprint.Item;
|
||||||
skinEditor.SelectedComponents.Clear();
|
skinEditor!.SelectedComponents.Clear();
|
||||||
skinEditor.SelectedComponents.Add(blueprint.Item);
|
skinEditor.SelectedComponents.Add(blueprint.Item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("move by keyboard", () => InputManager.Key(Key.Right));
|
||||||
|
|
||||||
|
AddAssert("hitErrorMeter moved", () => hitErrorMeter.X != 0);
|
||||||
|
|
||||||
AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault);
|
AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault);
|
||||||
|
|
||||||
AddStep("hover first slider", () =>
|
AddStep("hover first slider", () =>
|
||||||
|
@ -91,8 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case StopCountdownRequest:
|
case StopCountdownRequest:
|
||||||
multiplayerRoom.Countdown = null;
|
clearRoomCountdown();
|
||||||
raiseRoomUpdated();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -244,14 +243,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("start countdown", () => multiplayerClient.Object.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(1) }).WaitSafely());
|
AddStep("start countdown", () => multiplayerClient.Object.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(1) }).WaitSafely());
|
||||||
AddUntilStep("countdown started", () => multiplayerRoom.Countdown != null);
|
AddUntilStep("countdown started", () => multiplayerRoom.ActiveCountdowns.Any());
|
||||||
|
|
||||||
AddStep("transfer host to local user", () => transferHost(localUser));
|
AddStep("transfer host to local user", () => transferHost(localUser));
|
||||||
AddUntilStep("local user is host", () => multiplayerRoom.Host?.Equals(multiplayerClient.Object.LocalUser) == true);
|
AddUntilStep("local user is host", () => multiplayerRoom.Host?.Equals(multiplayerClient.Object.LocalUser) == true);
|
||||||
|
|
||||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
checkLocalUserState(MultiplayerUserState.Ready);
|
checkLocalUserState(MultiplayerUserState.Ready);
|
||||||
AddAssert("countdown still active", () => multiplayerRoom.Countdown != null);
|
AddAssert("countdown still active", () => multiplayerRoom.ActiveCountdowns.Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -392,7 +391,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void setRoomCountdown(TimeSpan duration)
|
private void setRoomCountdown(TimeSpan duration)
|
||||||
{
|
{
|
||||||
multiplayerRoom.Countdown = new MatchStartCountdown { TimeRemaining = duration };
|
multiplayerRoom.ActiveCountdowns.Add(new MatchStartCountdown { TimeRemaining = duration });
|
||||||
|
raiseRoomUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearRoomCountdown()
|
||||||
|
{
|
||||||
|
multiplayerRoom.ActiveCountdowns.Clear();
|
||||||
raiseRoomUpdated();
|
raiseRoomUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,9 +13,11 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -332,6 +334,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType<DrawableRuleset>().Single().FrameStableClock.CurrentTime > 30000);
|
AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType<DrawableRuleset>().Single().FrameStableClock.CurrentTime > 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGameplayRateAdjust()
|
||||||
|
{
|
||||||
|
start(getPlayerIds(4), mods: new[] { new APIMod(new OsuModDoubleTime()) });
|
||||||
|
|
||||||
|
loadSpectateScreen();
|
||||||
|
|
||||||
|
sendFrames(getPlayerIds(4), 300);
|
||||||
|
|
||||||
|
AddUntilStep("wait for correct track speed", () => Beatmap.Value.Track.Rate, () => Is.EqualTo(1.5));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPlayersLeaveWhileSpectating()
|
public void TestPlayersLeaveWhileSpectating()
|
||||||
{
|
{
|
||||||
@ -420,7 +434,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
|
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
|
||||||
|
|
||||||
private void start(int[] userIds, int? beatmapId = null)
|
private void start(int[] userIds, int? beatmapId = null, APIMod[]? mods = null)
|
||||||
{
|
{
|
||||||
AddStep("start play", () =>
|
AddStep("start play", () =>
|
||||||
{
|
{
|
||||||
@ -429,10 +443,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
var user = new MultiplayerRoomUser(id)
|
var user = new MultiplayerRoomUser(id)
|
||||||
{
|
{
|
||||||
User = new APIUser { Id = id },
|
User = new APIUser { Id = id },
|
||||||
|
Mods = mods ?? Array.Empty<APIMod>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
OnlinePlayDependencies.MultiplayerClient.AddUser(user.User, true);
|
OnlinePlayDependencies.MultiplayerClient.AddUser(user, true);
|
||||||
SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId);
|
SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId, mods);
|
||||||
|
|
||||||
playingUsers.Add(user);
|
playingUsers.Add(user);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ using osu.Game.Database;
|
|||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Catch;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
@ -68,37 +67,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestBeatmapRevertedOnExitIfNoSelection()
|
|
||||||
{
|
|
||||||
BeatmapInfo selectedBeatmap = null;
|
|
||||||
|
|
||||||
AddStep("select beatmap",
|
|
||||||
() => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.Ruleset.OnlineID == new OsuRuleset().LegacyID).ElementAt(1)));
|
|
||||||
AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
|
|
||||||
|
|
||||||
AddStep("exit song select", () => songSelect.Exit());
|
|
||||||
AddAssert("beatmap reverted", () => Beatmap.IsDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestModsRevertedOnExitIfNoSelection()
|
|
||||||
{
|
|
||||||
AddStep("change mods", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
|
|
||||||
|
|
||||||
AddStep("exit song select", () => songSelect.Exit());
|
|
||||||
AddAssert("mods reverted", () => SelectedMods.Value.Count == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestRulesetRevertedOnExitIfNoSelection()
|
|
||||||
{
|
|
||||||
AddStep("change ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
|
|
||||||
|
|
||||||
AddStep("exit song select", () => songSelect.Exit());
|
|
||||||
AddAssert("ruleset reverted", () => Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBeatmapConfirmed()
|
public void TestBeatmapConfirmed()
|
||||||
{
|
{
|
||||||
@ -152,8 +120,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
public new BeatmapCarousel Carousel => base.Carousel;
|
public new BeatmapCarousel Carousel => base.Carousel;
|
||||||
|
|
||||||
public TestMultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
|
public TestMultiplayerMatchSongSelect(Room room)
|
||||||
: base(room, null, beatmap, ruleset)
|
: base(room)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private int onlineID = 1;
|
private ulong onlineID = 1;
|
||||||
|
|
||||||
private APIScoresCollection createScores()
|
private APIScoresCollection createScores()
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
{
|
{
|
||||||
public class TestSceneHitEventTimingDistributionGraph : OsuTestScene
|
public class TestSceneHitEventTimingDistributionGraph : OsuTestScene
|
||||||
{
|
{
|
||||||
private HitEventTimingDistributionGraph graph;
|
private HitEventTimingDistributionGraph graph = null!;
|
||||||
|
|
||||||
private static readonly HitObject placeholder_object = new HitCircle();
|
private static readonly HitObject placeholder_object = new HitCircle();
|
||||||
|
|
||||||
@ -43,6 +41,65 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList());
|
createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSparse()
|
||||||
|
{
|
||||||
|
createTest(new List<HitEvent>
|
||||||
|
{
|
||||||
|
new HitEvent(-7, HitResult.Perfect, placeholder_object, placeholder_object, null),
|
||||||
|
new HitEvent(-6, HitResult.Perfect, placeholder_object, placeholder_object, null),
|
||||||
|
new HitEvent(-5, HitResult.Perfect, placeholder_object, placeholder_object, null),
|
||||||
|
new HitEvent(5, HitResult.Perfect, placeholder_object, placeholder_object, null),
|
||||||
|
new HitEvent(6, HitResult.Perfect, placeholder_object, placeholder_object, null),
|
||||||
|
new HitEvent(7, HitResult.Perfect, placeholder_object, placeholder_object, null),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVariousTypesOfHitResult()
|
||||||
|
{
|
||||||
|
createTest(CreateDistributedHitEvents(0, 50).Select(h =>
|
||||||
|
{
|
||||||
|
double offset = Math.Abs(h.TimeOffset);
|
||||||
|
HitResult result = offset > 36 ? HitResult.Miss
|
||||||
|
: offset > 32 ? HitResult.Meh
|
||||||
|
: offset > 24 ? HitResult.Ok
|
||||||
|
: offset > 16 ? HitResult.Good
|
||||||
|
: offset > 8 ? HitResult.Great
|
||||||
|
: HitResult.Perfect;
|
||||||
|
return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null);
|
||||||
|
}).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleWindowsOfHitResult()
|
||||||
|
{
|
||||||
|
var wide = CreateDistributedHitEvents(0, 50).Select(h =>
|
||||||
|
{
|
||||||
|
double offset = Math.Abs(h.TimeOffset);
|
||||||
|
HitResult result = offset > 36 ? HitResult.Miss
|
||||||
|
: offset > 32 ? HitResult.Meh
|
||||||
|
: offset > 24 ? HitResult.Ok
|
||||||
|
: offset > 16 ? HitResult.Good
|
||||||
|
: offset > 8 ? HitResult.Great
|
||||||
|
: HitResult.Perfect;
|
||||||
|
|
||||||
|
return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null);
|
||||||
|
});
|
||||||
|
var narrow = CreateDistributedHitEvents(0, 50).Select(h =>
|
||||||
|
{
|
||||||
|
double offset = Math.Abs(h.TimeOffset);
|
||||||
|
HitResult result = offset > 25 ? HitResult.Miss
|
||||||
|
: offset > 20 ? HitResult.Meh
|
||||||
|
: offset > 15 ? HitResult.Ok
|
||||||
|
: offset > 10 ? HitResult.Good
|
||||||
|
: offset > 5 ? HitResult.Great
|
||||||
|
: HitResult.Perfect;
|
||||||
|
return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null);
|
||||||
|
});
|
||||||
|
createTest(wide.Concat(narrow).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestZeroTimeOffset()
|
public void TestZeroTimeOffset()
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -13,6 +12,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
@ -46,11 +46,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestScenePlaySongSelect : ScreenTestScene
|
public class TestScenePlaySongSelect : ScreenTestScene
|
||||||
{
|
{
|
||||||
private BeatmapManager manager;
|
private BeatmapManager manager = null!;
|
||||||
private RulesetStore rulesets;
|
private RulesetStore rulesets = null!;
|
||||||
private MusicController music;
|
private MusicController music = null!;
|
||||||
private WorkingBeatmap defaultBeatmap;
|
private WorkingBeatmap defaultBeatmap = null!;
|
||||||
private TestSongSelect songSelect;
|
private OsuConfigManager config = null!;
|
||||||
|
private TestSongSelect? songSelect;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
@ -69,8 +70,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
|
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuConfigManager config;
|
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
@ -85,7 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
songSelect = null;
|
songSelect = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("delete all beatmaps", () => manager?.Delete());
|
AddStep("delete all beatmaps", () => manager.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -98,7 +97,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
AddUntilStep("wait for placeholder hidden", () => getPlaceholder()?.State.Value == Visibility.Hidden);
|
AddUntilStep("wait for placeholder hidden", () => getPlaceholder()?.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
AddStep("delete all beatmaps", () => manager?.Delete());
|
AddStep("delete all beatmaps", () => manager.Delete());
|
||||||
AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible);
|
AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +143,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
|
||||||
AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
|
AddAssert("filter count is 1", () => songSelect?.FilterCount == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -156,7 +155,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
waitForInitialSelection();
|
waitForInitialSelection();
|
||||||
|
|
||||||
WorkingBeatmap selected = null;
|
WorkingBeatmap? selected = null;
|
||||||
|
|
||||||
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
||||||
|
|
||||||
@ -166,7 +165,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
InputManager.Key(Key.Enter);
|
InputManager.Key(Key.Enter);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen());
|
||||||
AddAssert("ensure selection changed", () => selected != Beatmap.Value);
|
AddAssert("ensure selection changed", () => selected != Beatmap.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
waitForInitialSelection();
|
waitForInitialSelection();
|
||||||
|
|
||||||
WorkingBeatmap selected = null;
|
WorkingBeatmap? selected = null;
|
||||||
|
|
||||||
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
||||||
|
|
||||||
@ -189,7 +188,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
InputManager.Key(Key.Down);
|
InputManager.Key(Key.Down);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen());
|
||||||
AddAssert("ensure selection didn't change", () => selected == Beatmap.Value);
|
AddAssert("ensure selection didn't change", () => selected == Beatmap.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,23 +201,23 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
||||||
|
|
||||||
WorkingBeatmap selected = null;
|
WorkingBeatmap? selected = null;
|
||||||
|
|
||||||
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
||||||
|
|
||||||
AddUntilStep("wait for beatmaps to load", () => songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>().Any());
|
AddUntilStep("wait for beatmaps to load", () => songSelect!.Carousel.ChildrenOfType<DrawableCarouselBeatmap>().Any());
|
||||||
|
|
||||||
AddStep("select next and enter", () =>
|
AddStep("select next and enter", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>()
|
InputManager.MoveMouseTo(songSelect!.Carousel.ChildrenOfType<DrawableCarouselBeatmap>()
|
||||||
.First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect.Carousel.SelectedBeatmapInfo)));
|
.First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect!.Carousel.SelectedBeatmapInfo)));
|
||||||
|
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
|
|
||||||
InputManager.Key(Key.Enter);
|
InputManager.Key(Key.Enter);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen());
|
||||||
AddAssert("ensure selection changed", () => selected != Beatmap.Value);
|
AddAssert("ensure selection changed", () => selected != Beatmap.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,14 +230,14 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
waitForInitialSelection();
|
waitForInitialSelection();
|
||||||
|
|
||||||
WorkingBeatmap selected = null;
|
WorkingBeatmap? selected = null;
|
||||||
|
|
||||||
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
||||||
|
|
||||||
AddStep("select next and enter", () =>
|
AddStep("select next and enter", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>()
|
InputManager.MoveMouseTo(songSelect!.Carousel.ChildrenOfType<DrawableCarouselBeatmap>()
|
||||||
.First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect.Carousel.SelectedBeatmapInfo)));
|
.First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect!.Carousel.SelectedBeatmapInfo)));
|
||||||
|
|
||||||
InputManager.PressButton(MouseButton.Left);
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
|
||||||
@ -247,7 +246,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
InputManager.ReleaseButton(MouseButton.Left);
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen());
|
||||||
AddAssert("ensure selection didn't change", () => selected == Beatmap.Value);
|
AddAssert("ensure selection didn't change", () => selected == Beatmap.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,11 +259,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
|
||||||
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
|
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
|
||||||
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen());
|
||||||
|
|
||||||
AddStep("return", () => songSelect.MakeCurrent());
|
AddStep("return", () => songSelect!.MakeCurrent());
|
||||||
AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
||||||
AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
|
AddAssert("filter count is 1", () => songSelect!.FilterCount == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -278,13 +277,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
|
||||||
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
|
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
|
||||||
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen());
|
||||||
|
|
||||||
AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
|
AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
|
||||||
|
|
||||||
AddStep("return", () => songSelect.MakeCurrent());
|
AddStep("return", () => songSelect!.MakeCurrent());
|
||||||
AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
||||||
AddAssert("filter count is 2", () => songSelect.FilterCount == 2);
|
AddAssert("filter count is 2", () => songSelect!.FilterCount == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -295,7 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
|
||||||
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
|
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
|
||||||
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen());
|
||||||
|
|
||||||
AddStep("update beatmap", () =>
|
AddStep("update beatmap", () =>
|
||||||
{
|
{
|
||||||
@ -304,9 +303,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Beatmap.Value = manager.GetWorkingBeatmap(anotherBeatmap);
|
Beatmap.Value = manager.GetWorkingBeatmap(anotherBeatmap);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("return", () => songSelect.MakeCurrent());
|
AddStep("return", () => songSelect!.MakeCurrent());
|
||||||
AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
||||||
AddAssert("carousel updated", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(Beatmap.Value.BeatmapInfo));
|
AddAssert("carousel updated", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(Beatmap.Value.BeatmapInfo) == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -318,15 +317,15 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
checkMusicPlaying(true);
|
checkMusicPlaying(true);
|
||||||
AddStep("select first", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.First()));
|
AddStep("select first", () => songSelect!.Carousel.SelectBeatmap(songSelect!.Carousel.BeatmapSets.First().Beatmaps.First()));
|
||||||
checkMusicPlaying(true);
|
checkMusicPlaying(true);
|
||||||
|
|
||||||
AddStep("manual pause", () => music.TogglePause());
|
AddStep("manual pause", () => music.TogglePause());
|
||||||
checkMusicPlaying(false);
|
checkMusicPlaying(false);
|
||||||
AddStep("select next difficulty", () => songSelect.Carousel.SelectNext(skipDifficulties: false));
|
AddStep("select next difficulty", () => songSelect!.Carousel.SelectNext(skipDifficulties: false));
|
||||||
checkMusicPlaying(false);
|
checkMusicPlaying(false);
|
||||||
|
|
||||||
AddStep("select next set", () => songSelect.Carousel.SelectNext());
|
AddStep("select next set", () => songSelect!.Carousel.SelectNext());
|
||||||
checkMusicPlaying(true);
|
checkMusicPlaying(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,13 +365,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
public void TestDummy()
|
public void TestDummy()
|
||||||
{
|
{
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
AddUntilStep("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap);
|
AddUntilStep("dummy selected", () => songSelect!.CurrentBeatmap == defaultBeatmap);
|
||||||
|
|
||||||
AddUntilStep("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap);
|
AddUntilStep("dummy shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap == defaultBeatmap);
|
||||||
|
|
||||||
addManyTestMaps();
|
addManyTestMaps();
|
||||||
|
|
||||||
AddUntilStep("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
|
AddUntilStep("random map selected", () => songSelect!.CurrentBeatmap != defaultBeatmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -381,7 +380,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
createSongSelect();
|
createSongSelect();
|
||||||
addManyTestMaps();
|
addManyTestMaps();
|
||||||
|
|
||||||
AddUntilStep("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
|
AddUntilStep("random map selected", () => songSelect!.CurrentBeatmap != defaultBeatmap);
|
||||||
|
|
||||||
AddStep(@"Sort by Artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist));
|
AddStep(@"Sort by Artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist));
|
||||||
AddStep(@"Sort by Title", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title));
|
AddStep(@"Sort by Title", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title));
|
||||||
@ -398,7 +397,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
addRulesetImportStep(2);
|
addRulesetImportStep(2);
|
||||||
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
|
AddUntilStep("no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -408,13 +407,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
changeRuleset(2);
|
changeRuleset(2);
|
||||||
addRulesetImportStep(2);
|
addRulesetImportStep(2);
|
||||||
addRulesetImportStep(1);
|
addRulesetImportStep(1);
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2);
|
||||||
|
|
||||||
changeRuleset(1);
|
changeRuleset(1);
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 1);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 1);
|
||||||
|
|
||||||
changeRuleset(0);
|
changeRuleset(0);
|
||||||
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
|
AddUntilStep("no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -423,7 +422,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
createSongSelect();
|
createSongSelect();
|
||||||
changeRuleset(0);
|
changeRuleset(0);
|
||||||
|
|
||||||
Live<BeatmapSetInfo> original = null!;
|
Live<BeatmapSetInfo>? original = null;
|
||||||
int originalOnlineSetID = 0;
|
int originalOnlineSetID = 0;
|
||||||
|
|
||||||
AddStep(@"Sort by artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist));
|
AddStep(@"Sort by artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist));
|
||||||
@ -431,12 +430,17 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("import original", () =>
|
AddStep("import original", () =>
|
||||||
{
|
{
|
||||||
original = manager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely();
|
original = manager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely();
|
||||||
originalOnlineSetID = original!.Value.OnlineID;
|
|
||||||
|
Debug.Assert(original != null);
|
||||||
|
|
||||||
|
originalOnlineSetID = original.Value.OnlineID;
|
||||||
});
|
});
|
||||||
|
|
||||||
// This will move the beatmap set to a different location in the carousel.
|
// This will move the beatmap set to a different location in the carousel.
|
||||||
AddStep("Update original with bogus info", () =>
|
AddStep("Update original with bogus info", () =>
|
||||||
{
|
{
|
||||||
|
Debug.Assert(original != null);
|
||||||
|
|
||||||
original.PerformWrite(set =>
|
original.PerformWrite(set =>
|
||||||
{
|
{
|
||||||
foreach (var beatmap in set.Beatmaps)
|
foreach (var beatmap in set.Beatmaps)
|
||||||
@ -457,13 +461,19 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
manager.Import(testBeatmapSetInfo);
|
manager.Import(testBeatmapSetInfo);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID);
|
||||||
|
|
||||||
Task<Live<BeatmapSetInfo>> updateTask = null!;
|
Task<Live<BeatmapSetInfo>?> updateTask = null!;
|
||||||
AddStep("update beatmap", () => updateTask = manager.ImportAsUpdate(new ProgressNotification(), new ImportTask(TestResources.GetQuickTestBeatmapForImport()), original.Value));
|
|
||||||
|
AddStep("update beatmap", () =>
|
||||||
|
{
|
||||||
|
Debug.Assert(original != null);
|
||||||
|
|
||||||
|
updateTask = manager.ImportAsUpdate(new ProgressNotification(), new ImportTask(TestResources.GetQuickTestBeatmapForImport()), original.Value);
|
||||||
|
});
|
||||||
AddUntilStep("wait for update completion", () => updateTask.IsCompleted);
|
AddUntilStep("wait for update completion", () => updateTask.IsCompleted);
|
||||||
|
|
||||||
AddUntilStep("retained selection", () => songSelect.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID);
|
AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -473,13 +483,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
changeRuleset(2);
|
changeRuleset(2);
|
||||||
|
|
||||||
addRulesetImportStep(2);
|
addRulesetImportStep(2);
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2);
|
||||||
|
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
BeatmapInfo target = null;
|
BeatmapInfo? target = null;
|
||||||
|
|
||||||
AddStep("select beatmap/ruleset externally", () =>
|
AddStep("select beatmap/ruleset externally", () =>
|
||||||
{
|
{
|
||||||
@ -490,10 +500,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Beatmap.Value = manager.GetWorkingBeatmap(target);
|
Beatmap.Value = manager.GetWorkingBeatmap(target);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target));
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(target) == true);
|
||||||
|
|
||||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target));
|
AddUntilStep("selection shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -503,13 +513,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
changeRuleset(2);
|
changeRuleset(2);
|
||||||
|
|
||||||
addRulesetImportStep(2);
|
addRulesetImportStep(2);
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2);
|
||||||
|
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
BeatmapInfo target = null;
|
BeatmapInfo? target = null;
|
||||||
|
|
||||||
AddStep("select beatmap/ruleset externally", () =>
|
AddStep("select beatmap/ruleset externally", () =>
|
||||||
{
|
{
|
||||||
@ -520,12 +530,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0);
|
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target));
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(target) == true);
|
||||||
|
|
||||||
AddUntilStep("has correct ruleset", () => Ruleset.Value.OnlineID == 0);
|
AddUntilStep("has correct ruleset", () => Ruleset.Value.OnlineID == 0);
|
||||||
|
|
||||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target));
|
AddUntilStep("selection shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -543,12 +553,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("change ruleset", () =>
|
AddStep("change ruleset", () =>
|
||||||
{
|
{
|
||||||
SelectedMods.ValueChanged += onModChange;
|
SelectedMods.ValueChanged += onModChange;
|
||||||
songSelect.Ruleset.ValueChanged += onRulesetChange;
|
songSelect!.Ruleset.ValueChanged += onRulesetChange;
|
||||||
|
|
||||||
Ruleset.Value = new TaikoRuleset().RulesetInfo;
|
Ruleset.Value = new TaikoRuleset().RulesetInfo;
|
||||||
|
|
||||||
SelectedMods.ValueChanged -= onModChange;
|
SelectedMods.ValueChanged -= onModChange;
|
||||||
songSelect.Ruleset.ValueChanged -= onRulesetChange;
|
songSelect!.Ruleset.ValueChanged -= onRulesetChange;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex);
|
AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex);
|
||||||
@ -579,18 +589,18 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
addManyTestMaps();
|
addManyTestMaps();
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null);
|
||||||
|
|
||||||
bool startRequested = false;
|
bool startRequested = false;
|
||||||
|
|
||||||
AddStep("set filter and finalize", () =>
|
AddStep("set filter and finalize", () =>
|
||||||
{
|
{
|
||||||
songSelect.StartRequested = () => startRequested = true;
|
songSelect!.StartRequested = () => startRequested = true;
|
||||||
|
|
||||||
songSelect.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" });
|
songSelect!.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" });
|
||||||
songSelect.FinaliseSelection();
|
songSelect!.FinaliseSelection();
|
||||||
|
|
||||||
songSelect.StartRequested = null;
|
songSelect!.StartRequested = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("start not requested", () => !startRequested);
|
AddAssert("start not requested", () => !startRequested);
|
||||||
@ -610,15 +620,15 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
// used for filter check below
|
// used for filter check below
|
||||||
AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
|
AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
|
||||||
|
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null);
|
||||||
|
|
||||||
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nonono");
|
AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nonono");
|
||||||
|
|
||||||
AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
|
AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
|
||||||
|
|
||||||
AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
|
AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null);
|
||||||
|
|
||||||
BeatmapInfo target = null;
|
BeatmapInfo? target = null;
|
||||||
|
|
||||||
int targetRuleset = differentRuleset ? 1 : 0;
|
int targetRuleset = differentRuleset ? 1 : 0;
|
||||||
|
|
||||||
@ -632,24 +642,24 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Beatmap.Value = manager.GetWorkingBeatmap(target);
|
Beatmap.Value = manager.GetWorkingBeatmap(target);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null);
|
||||||
|
|
||||||
AddAssert("selected only shows expected ruleset (plus converts)", () =>
|
AddAssert("selected only shows expected ruleset (plus converts)", () =>
|
||||||
{
|
{
|
||||||
var selectedPanel = songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().First(s => s.Item.State.Value == CarouselItemState.Selected);
|
var selectedPanel = songSelect!.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().First(s => s.Item.State.Value == CarouselItemState.Selected);
|
||||||
|
|
||||||
// special case for converts checked here.
|
// special case for converts checked here.
|
||||||
return selectedPanel.ChildrenOfType<FilterableDifficultyIcon>().All(i =>
|
return selectedPanel.ChildrenOfType<FilterableDifficultyIcon>().All(i =>
|
||||||
i.IsFiltered || i.Item.BeatmapInfo.Ruleset.OnlineID == targetRuleset || i.Item.BeatmapInfo.Ruleset.OnlineID == 0);
|
i.IsFiltered || i.Item.BeatmapInfo.Ruleset.OnlineID == targetRuleset || i.Item.BeatmapInfo.Ruleset.OnlineID == 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true);
|
AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true);
|
||||||
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target));
|
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target));
|
||||||
|
|
||||||
AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty);
|
AddStep("reset filter text", () => songSelect!.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty);
|
||||||
|
|
||||||
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.MatchesOnlineID(target) == true);
|
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.MatchesOnlineID(target) == true);
|
||||||
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target));
|
AddAssert("carousel still correct", () => songSelect!.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -662,15 +672,15 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
changeRuleset(0);
|
changeRuleset(0);
|
||||||
|
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null);
|
||||||
|
|
||||||
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nonono");
|
AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nonono");
|
||||||
|
|
||||||
AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
|
AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
|
||||||
|
|
||||||
AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
|
AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null);
|
||||||
|
|
||||||
BeatmapInfo target = null;
|
BeatmapInfo? target = null;
|
||||||
|
|
||||||
AddStep("select beatmap externally", () =>
|
AddStep("select beatmap externally", () =>
|
||||||
{
|
{
|
||||||
@ -682,15 +692,15 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Beatmap.Value = manager.GetWorkingBeatmap(target);
|
Beatmap.Value = manager.GetWorkingBeatmap(target);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
|
AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null);
|
||||||
|
|
||||||
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true);
|
AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true);
|
||||||
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target));
|
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target));
|
||||||
|
|
||||||
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nononoo");
|
AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nononoo");
|
||||||
|
|
||||||
AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap);
|
AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap);
|
||||||
AddAssert("carousel lost selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
|
AddAssert("carousel lost selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -711,11 +721,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
||||||
|
|
||||||
AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
|
AddAssert("autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay);
|
||||||
|
|
||||||
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen());
|
||||||
|
|
||||||
AddAssert("no mods selected", () => songSelect.Mods.Value.Count == 0);
|
AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -738,11 +748,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
||||||
|
|
||||||
AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
|
AddAssert("autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay);
|
||||||
|
|
||||||
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen());
|
||||||
|
|
||||||
AddAssert("autoplay still selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
|
AddAssert("autoplay still selected", () => songSelect!.Mods.Value.Single() is ModAutoplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -765,11 +775,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
||||||
|
|
||||||
AddAssert("only autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
|
AddAssert("only autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay);
|
||||||
|
|
||||||
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen());
|
||||||
|
|
||||||
AddAssert("relax returned", () => songSelect.Mods.Value.Single() is ModRelax);
|
AddAssert("relax returned", () => songSelect!.Mods.Value.Single() is ModRelax);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -778,10 +788,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Guid? previousID = null;
|
Guid? previousID = null;
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last()));
|
AddStep("Move to last difficulty", () => songSelect!.Carousel.SelectBeatmap(songSelect!.Carousel.BeatmapSets.First().Beatmaps.Last()));
|
||||||
AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmapInfo.ID);
|
AddStep("Store current ID", () => previousID = songSelect!.Carousel.SelectedBeatmapInfo!.ID);
|
||||||
AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First()));
|
AddStep("Hide first beatmap", () => manager.Hide(songSelect!.Carousel.SelectedBeatmapSet!.Beatmaps.First()));
|
||||||
AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmapInfo.ID == previousID);
|
AddAssert("Selected beatmap has not changed", () => songSelect!.Carousel.SelectedBeatmapInfo?.ID == previousID);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -792,17 +802,24 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("wait for selection", () => !Beatmap.IsDefault);
|
AddUntilStep("wait for selection", () => !Beatmap.IsDefault);
|
||||||
|
|
||||||
DrawableCarouselBeatmapSet set = null;
|
DrawableCarouselBeatmapSet set = null!;
|
||||||
AddStep("Find the DrawableCarouselBeatmapSet", () =>
|
AddStep("Find the DrawableCarouselBeatmapSet", () =>
|
||||||
{
|
{
|
||||||
set = songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().First();
|
set = songSelect!.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().First();
|
||||||
});
|
});
|
||||||
|
|
||||||
FilterableDifficultyIcon difficultyIcon = null;
|
FilterableDifficultyIcon difficultyIcon = null!;
|
||||||
|
|
||||||
AddUntilStep("Find an icon", () =>
|
AddUntilStep("Find an icon", () =>
|
||||||
{
|
{
|
||||||
return (difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
|
var foundIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
|
||||||
.FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex())) != null;
|
.FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex());
|
||||||
|
|
||||||
|
if (foundIcon == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
difficultyIcon = foundIcon;
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Click on a difficulty", () =>
|
AddStep("Click on a difficulty", () =>
|
||||||
@ -815,21 +832,24 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon));
|
AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon));
|
||||||
|
|
||||||
double? maxBPM = null;
|
double? maxBPM = null;
|
||||||
AddStep("Filter some difficulties", () => songSelect.Carousel.Filter(new FilterCriteria
|
AddStep("Filter some difficulties", () => songSelect!.Carousel.Filter(new FilterCriteria
|
||||||
{
|
{
|
||||||
BPM = new FilterCriteria.OptionalRange<double>
|
BPM = new FilterCriteria.OptionalRange<double>
|
||||||
{
|
{
|
||||||
Min = maxBPM = songSelect.Carousel.SelectedBeatmapSet.MaxBPM,
|
Min = maxBPM = songSelect!.Carousel.SelectedBeatmapSet!.MaxBPM,
|
||||||
IsLowerInclusive = true
|
IsLowerInclusive = true
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
BeatmapInfo filteredBeatmap = null;
|
BeatmapInfo? filteredBeatmap = null;
|
||||||
FilterableDifficultyIcon filteredIcon = null;
|
FilterableDifficultyIcon? filteredIcon = null;
|
||||||
|
|
||||||
AddStep("Get filtered icon", () =>
|
AddStep("Get filtered icon", () =>
|
||||||
{
|
{
|
||||||
var selectedSet = songSelect.Carousel.SelectedBeatmapSet;
|
var selectedSet = songSelect!.Carousel.SelectedBeatmapSet;
|
||||||
|
|
||||||
|
Debug.Assert(selectedSet != null);
|
||||||
|
|
||||||
filteredBeatmap = selectedSet.Beatmaps.First(b => b.BPM < maxBPM);
|
filteredBeatmap = selectedSet.Beatmaps.First(b => b.BPM < maxBPM);
|
||||||
int filteredBeatmapIndex = getBeatmapIndex(selectedSet, filteredBeatmap);
|
int filteredBeatmapIndex = getBeatmapIndex(selectedSet, filteredBeatmap);
|
||||||
filteredIcon = set.ChildrenOfType<FilterableDifficultyIcon>().ElementAt(filteredBeatmapIndex);
|
filteredIcon = set.ChildrenOfType<FilterableDifficultyIcon>().ElementAt(filteredBeatmapIndex);
|
||||||
@ -842,7 +862,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(filteredBeatmap));
|
AddAssert("Selected beatmap correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(filteredBeatmap) == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -907,14 +927,14 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets));
|
manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets));
|
||||||
});
|
});
|
||||||
|
|
||||||
DrawableCarouselBeatmapSet set = null;
|
DrawableCarouselBeatmapSet? set = null;
|
||||||
AddUntilStep("Find the DrawableCarouselBeatmapSet", () =>
|
AddUntilStep("Find the DrawableCarouselBeatmapSet", () =>
|
||||||
{
|
{
|
||||||
set = songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().FirstOrDefault();
|
set = songSelect!.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().FirstOrDefault();
|
||||||
return set != null;
|
return set != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
FilterableDifficultyIcon difficultyIcon = null;
|
FilterableDifficultyIcon? difficultyIcon = null;
|
||||||
AddUntilStep("Find an icon for different ruleset", () =>
|
AddUntilStep("Find an icon for different ruleset", () =>
|
||||||
{
|
{
|
||||||
difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
|
difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
|
||||||
@ -937,7 +957,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.OnlineID == 3);
|
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.OnlineID == 3);
|
||||||
|
|
||||||
AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet?.OnlineID == previousSetID);
|
AddAssert("Selected beatmap still same set", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == previousSetID);
|
||||||
AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3);
|
AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -948,7 +968,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
|
||||||
BeatmapSetInfo imported = null;
|
BeatmapSetInfo? imported = null;
|
||||||
|
|
||||||
AddStep("import huge difficulty count map", () =>
|
AddStep("import huge difficulty count map", () =>
|
||||||
{
|
{
|
||||||
@ -956,20 +976,27 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value;
|
imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First()));
|
AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported?.Beatmaps.First()));
|
||||||
|
|
||||||
DrawableCarouselBeatmapSet set = null;
|
DrawableCarouselBeatmapSet? set = null;
|
||||||
AddUntilStep("Find the DrawableCarouselBeatmapSet", () =>
|
AddUntilStep("Find the DrawableCarouselBeatmapSet", () =>
|
||||||
{
|
{
|
||||||
set = songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().FirstOrDefault();
|
set = songSelect!.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().FirstOrDefault();
|
||||||
return set != null;
|
return set != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
GroupedDifficultyIcon groupIcon = null;
|
GroupedDifficultyIcon groupIcon = null!;
|
||||||
|
|
||||||
AddUntilStep("Find group icon for different ruleset", () =>
|
AddUntilStep("Find group icon for different ruleset", () =>
|
||||||
{
|
{
|
||||||
return (groupIcon = set.ChildrenOfType<GroupedDifficultyIcon>()
|
var foundIcon = set.ChildrenOfType<GroupedDifficultyIcon>()
|
||||||
.FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3)) != null;
|
.FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3);
|
||||||
|
|
||||||
|
if (foundIcon == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
groupIcon = foundIcon;
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("Check ruleset is osu!", () => Ruleset.Value.OnlineID == 0);
|
AddAssert("Check ruleset is osu!", () => Ruleset.Value.OnlineID == 0);
|
||||||
@ -1004,7 +1031,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
// this ruleset change should be overridden by the present.
|
// this ruleset change should be overridden by the present.
|
||||||
Ruleset.Value = getSwitchBeatmap().Ruleset;
|
Ruleset.Value = getSwitchBeatmap().Ruleset;
|
||||||
|
|
||||||
songSelect.PresentScore(new ScoreInfo
|
songSelect!.PresentScore(new ScoreInfo
|
||||||
{
|
{
|
||||||
User = new APIUser { Username = "woo" },
|
User = new APIUser { Username = "woo" },
|
||||||
BeatmapInfo = getPresentBeatmap(),
|
BeatmapInfo = getPresentBeatmap(),
|
||||||
@ -1012,7 +1039,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for results screen presented", () => !songSelect!.IsCurrentScreen());
|
||||||
|
|
||||||
AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap()));
|
AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap()));
|
||||||
AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0);
|
AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0);
|
||||||
@ -1038,10 +1065,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
// this beatmap change should be overridden by the present.
|
// this beatmap change should be overridden by the present.
|
||||||
Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap());
|
Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap());
|
||||||
|
|
||||||
songSelect.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap()));
|
songSelect!.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap()));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen());
|
AddUntilStep("wait for results screen presented", () => !songSelect!.IsCurrentScreen());
|
||||||
|
|
||||||
AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap()));
|
AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap()));
|
||||||
AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0);
|
AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0);
|
||||||
@ -1054,23 +1081,29 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
|
||||||
AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1));
|
AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1));
|
||||||
AddUntilStep("mod overlay shown", () => songSelect.ModSelect.State.Value == Visibility.Visible);
|
AddUntilStep("mod overlay shown", () => songSelect!.ModSelect.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1));
|
AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1));
|
||||||
AddUntilStep("mod overlay hidden", () => songSelect.ModSelect.State.Value == Visibility.Hidden);
|
AddUntilStep("mod overlay hidden", () => songSelect!.ModSelect.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForInitialSelection()
|
private void waitForInitialSelection()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
||||||
AddUntilStep("wait for difficulty panels visible", () => songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>().Any());
|
AddUntilStep("wait for difficulty panels visible", () => songSelect!.Carousel.ChildrenOfType<DrawableCarouselBeatmap>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info);
|
private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info);
|
||||||
|
|
||||||
private NoResultsPlaceholder getPlaceholder() => songSelect.ChildrenOfType<NoResultsPlaceholder>().FirstOrDefault();
|
private NoResultsPlaceholder? getPlaceholder() => songSelect!.ChildrenOfType<NoResultsPlaceholder>().FirstOrDefault();
|
||||||
|
|
||||||
private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo);
|
private int getCurrentBeatmapIndex()
|
||||||
|
{
|
||||||
|
Debug.Assert(songSelect!.Carousel.SelectedBeatmapSet != null);
|
||||||
|
Debug.Assert(songSelect!.Carousel.SelectedBeatmapInfo != null);
|
||||||
|
|
||||||
|
return getBeatmapIndex(songSelect!.Carousel.SelectedBeatmapSet, songSelect!.Carousel.SelectedBeatmapInfo);
|
||||||
|
}
|
||||||
|
|
||||||
private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon)
|
private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon)
|
||||||
{
|
{
|
||||||
@ -1079,14 +1112,14 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private void addRulesetImportStep(int id)
|
private void addRulesetImportStep(int id)
|
||||||
{
|
{
|
||||||
Live<BeatmapSetInfo> imported = null;
|
Live<BeatmapSetInfo>? imported = null;
|
||||||
AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id));
|
AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id));
|
||||||
// This is specifically for cases where the add is happening post song select load.
|
// This is specifically for cases where the add is happening post song select load.
|
||||||
// For cases where song select is null, the assertions are provided by the load checks.
|
// For cases where song select is null, the assertions are provided by the load checks.
|
||||||
AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID));
|
AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect!.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Live<BeatmapSetInfo> importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray()));
|
private Live<BeatmapSetInfo>? importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray()));
|
||||||
|
|
||||||
private void checkMusicPlaying(bool playing) =>
|
private void checkMusicPlaying(bool playing) =>
|
||||||
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
|
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
|
||||||
@ -1098,8 +1131,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
private void createSongSelect()
|
private void createSongSelect()
|
||||||
{
|
{
|
||||||
AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect()));
|
AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect()));
|
||||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for present", () => songSelect!.IsCurrentScreen());
|
||||||
AddUntilStep("wait for carousel loaded", () => songSelect.Carousel.IsAlive);
|
AddUntilStep("wait for carousel loaded", () => songSelect!.Carousel.IsAlive);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -1123,12 +1156,14 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
rulesets?.Dispose();
|
|
||||||
|
if (rulesets.IsNotNull())
|
||||||
|
rulesets.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSongSelect : PlaySongSelect
|
private class TestSongSelect : PlaySongSelect
|
||||||
{
|
{
|
||||||
public Action StartRequested;
|
public Action? StartRequested;
|
||||||
|
|
||||||
public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
|
public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
|
||||||
|
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
// 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.Globalization;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneSizePreservingSpriteText : OsuGridTestScene
|
||||||
|
{
|
||||||
|
private readonly List<Container> parentContainers = new List<Container>();
|
||||||
|
private readonly List<UprightAspectMaintainingContainer> childContainers = new List<UprightAspectMaintainingContainer>();
|
||||||
|
private readonly OsuSpriteText osuSpriteText = new OsuSpriteText();
|
||||||
|
private readonly SizePreservingSpriteText sizePreservingSpriteText = new SizePreservingSpriteText();
|
||||||
|
|
||||||
|
public TestSceneSizePreservingSpriteText()
|
||||||
|
: base(1, 2)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
UprightAspectMaintainingContainer childContainer;
|
||||||
|
Container parentContainer = new Container
|
||||||
|
{
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Rotation = 45,
|
||||||
|
Y = -200,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Colour4.Red,
|
||||||
|
},
|
||||||
|
childContainer = new UprightAspectMaintainingContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Colour4.Blue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Container cellInfo = new Container
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = 100,
|
||||||
|
},
|
||||||
|
Child = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = (i == 0) ? "OsuSpriteText" : "SizePreservingSpriteText",
|
||||||
|
Font = OsuFont.GetFont(Typeface.Inter, weight: FontWeight.Bold, size: 40),
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
parentContainers.Add(parentContainer);
|
||||||
|
childContainers.Add(childContainer);
|
||||||
|
Cell(i).Add(cellInfo);
|
||||||
|
Cell(i).Add(parentContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
childContainers[0].Add(osuSpriteText);
|
||||||
|
childContainers[1].Add(sizePreservingSpriteText);
|
||||||
|
osuSpriteText.Font = sizePreservingSpriteText.Font = OsuFont.GetFont(Typeface.Venera, weight: FontWeight.Bold, size: 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
osuSpriteText.Text = sizePreservingSpriteText.Text = DateTime.Now.ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneUprightAspectMaintainingContainer : OsuGridTestScene
|
||||||
|
{
|
||||||
|
private const int rows = 3;
|
||||||
|
private const int columns = 4;
|
||||||
|
|
||||||
|
private readonly ScaleMode[] scaleModeValues = { ScaleMode.NoScaling, ScaleMode.Horizontal, ScaleMode.Vertical };
|
||||||
|
private readonly float[] scalingFactorValues = { 1.0f / 3, 1.0f / 2, 1.0f, 1.5f };
|
||||||
|
|
||||||
|
private readonly List<List<Container>> parentContainers = new List<List<Container>>(rows);
|
||||||
|
private readonly List<List<UprightAspectMaintainingContainer>> childContainers = new List<List<UprightAspectMaintainingContainer>>(rows);
|
||||||
|
|
||||||
|
// Preferably should be set to (4 * 2^n)
|
||||||
|
private const int rotation_step_count = 3;
|
||||||
|
|
||||||
|
private readonly List<int> flipStates = new List<int>();
|
||||||
|
private readonly List<float> rotationSteps = new List<float>();
|
||||||
|
private readonly List<float> scaleSteps = new List<float>();
|
||||||
|
|
||||||
|
public TestSceneUprightAspectMaintainingContainer()
|
||||||
|
: base(rows, columns)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < rows; i++)
|
||||||
|
{
|
||||||
|
parentContainers.Add(new List<Container>());
|
||||||
|
childContainers.Add(new List<UprightAspectMaintainingContainer>());
|
||||||
|
|
||||||
|
for (int j = 0; j < columns; j++)
|
||||||
|
{
|
||||||
|
UprightAspectMaintainingContainer child;
|
||||||
|
Container parent = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Height = 80,
|
||||||
|
Width = 80,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = new Color4(255, 0, 0, 160),
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Parent",
|
||||||
|
},
|
||||||
|
child = new UprightAspectMaintainingContainer
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
|
||||||
|
// These are the parameters being Tested
|
||||||
|
Scaling = scaleModeValues[i],
|
||||||
|
ScalingFactor = scalingFactorValues[j],
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = new Color4(0, 0, 255, 160),
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Text",
|
||||||
|
Font = OsuFont.Numeric,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 4,
|
||||||
|
Vertical = 4,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Container cellInfo = new Container
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Scaling: " + scaleModeValues[i].ToString(),
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "ScalingFactor: " + scalingFactorValues[j].ToString("0.00"),
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = 15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Cell(i * columns + j).Add(cellInfo);
|
||||||
|
Cell(i * columns + j).Add(parent);
|
||||||
|
parentContainers[i].Add(parent);
|
||||||
|
childContainers[i].Add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flipStates.AddRange(new[] { 1, -1 });
|
||||||
|
rotationSteps.AddRange(Enumerable.Range(0, rotation_step_count).Select(x => 360f * ((float)x / rotation_step_count)));
|
||||||
|
scaleSteps.AddRange(new[] { 1, 0.3f, 1.5f });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ExplicitlySizedParent()
|
||||||
|
{
|
||||||
|
var parentStates = from xFlip in flipStates
|
||||||
|
from yFlip in flipStates
|
||||||
|
from xScale in scaleSteps
|
||||||
|
from yScale in scaleSteps
|
||||||
|
from rotation in rotationSteps
|
||||||
|
select new { xFlip, yFlip, xScale, yScale, rotation };
|
||||||
|
|
||||||
|
foreach (var state in parentStates)
|
||||||
|
{
|
||||||
|
Vector2 parentScale = new Vector2(state.xFlip * state.xScale, state.yFlip * state.yScale);
|
||||||
|
float parentRotation = state.rotation;
|
||||||
|
|
||||||
|
AddStep("S: (" + parentScale.X.ToString("0.00") + ", " + parentScale.Y.ToString("0.00") + "), R: " + parentRotation.ToString("0.00"), () =>
|
||||||
|
{
|
||||||
|
foreach (List<Container> list in parentContainers)
|
||||||
|
{
|
||||||
|
foreach (Container container in list)
|
||||||
|
{
|
||||||
|
container.Scale = parentScale;
|
||||||
|
container.Rotation = parentRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Check if state is valid", () =>
|
||||||
|
{
|
||||||
|
foreach (int i in Enumerable.Range(0, parentContainers.Count))
|
||||||
|
{
|
||||||
|
foreach (int j in Enumerable.Range(0, parentContainers[i].Count))
|
||||||
|
{
|
||||||
|
if (!uprightAspectMaintainingContainerStateIsValid(parentContainers[i][j], childContainers[i][j]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool uprightAspectMaintainingContainerStateIsValid(Container parent, UprightAspectMaintainingContainer child)
|
||||||
|
{
|
||||||
|
Matrix3 parentMatrix = parent.DrawInfo.Matrix;
|
||||||
|
Matrix3 childMatrix = child.DrawInfo.Matrix;
|
||||||
|
Vector3 childScale = childMatrix.ExtractScale();
|
||||||
|
Vector3 parentScale = parentMatrix.ExtractScale();
|
||||||
|
|
||||||
|
// Orientation check
|
||||||
|
if (!(isNearlyZero(MathF.Abs(childMatrix.M21)) && isNearlyZero(MathF.Abs(childMatrix.M12))))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// flip check
|
||||||
|
if (!(childMatrix.M11 * childMatrix.M22 > 0))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Aspect ratio check
|
||||||
|
if (!isNearlyZero(childScale.X - childScale.Y))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// ScalingMode check
|
||||||
|
switch (child.Scaling)
|
||||||
|
{
|
||||||
|
case ScaleMode.NoScaling:
|
||||||
|
if (!(isNearlyZero(childMatrix.M11 - 1.0f) && isNearlyZero(childMatrix.M22 - 1.0f)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScaleMode.Vertical:
|
||||||
|
if (!(checkScaling(child.ScalingFactor, parentScale.Y, childScale.Y)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScaleMode.Horizontal:
|
||||||
|
if (!(checkScaling(child.ScalingFactor, parentScale.X, childScale.X)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool checkScaling(float scalingFactor, float parentScale, float childScale)
|
||||||
|
{
|
||||||
|
if (scalingFactor <= 1.0f)
|
||||||
|
{
|
||||||
|
if (!isNearlyZero(1.0f + (parentScale - 1.0f) * scalingFactor - childScale))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (scalingFactor > 1.0f)
|
||||||
|
{
|
||||||
|
if (parentScale < 1.0f)
|
||||||
|
{
|
||||||
|
if (!isNearlyZero((parentScale * (1.0f / scalingFactor)) - childScale))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (!isNearlyZero(parentScale * scalingFactor - childScale))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isNearlyZero(float f, float epsilon = Precision.FLOAT_EPSILON)
|
||||||
|
{
|
||||||
|
return f < epsilon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -106,13 +106,16 @@ namespace osu.Game.Tournament.Models
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialise this match with zeroed scores. Will be a noop if either team is not present.
|
/// Initialise this match with zeroed scores. Will be a noop if either team is not present or if either of the scores are non-zero.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartMatch()
|
public void StartMatch()
|
||||||
{
|
{
|
||||||
if (Team1.Value == null || Team2.Value == null)
|
if (Team1.Value == null || Team2.Value == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (Team1Score.Value > 0 || Team2Score.Value > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
Team1Score.Value = 0;
|
Team1Score.Value = 0;
|
||||||
Team2Score.Value = 0;
|
Team2Score.Value = 0;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -14,6 +15,7 @@ using osu.Framework.Logging;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Game
|
namespace osu.Game
|
||||||
@ -23,6 +25,9 @@ namespace osu.Game
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private RulesetStore rulesetStore { get; set; } = null!;
|
private RulesetStore rulesetStore { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScoreManager scoreManager { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private RealmAccess realmAccess { get; set; } = null!;
|
private RealmAccess realmAccess { get; set; } = null!;
|
||||||
|
|
||||||
@ -46,6 +51,7 @@ namespace osu.Game
|
|||||||
Logger.Log("Beginning background beatmap processing..");
|
Logger.Log("Beginning background beatmap processing..");
|
||||||
checkForOutdatedStarRatings();
|
checkForOutdatedStarRatings();
|
||||||
processBeatmapSetsWithMissingMetrics();
|
processBeatmapSetsWithMissingMetrics();
|
||||||
|
processScoresWithMissingStatistics();
|
||||||
}).ContinueWith(t =>
|
}).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
if (t.Exception?.InnerException is ObjectDisposedException)
|
if (t.Exception?.InnerException is ObjectDisposedException)
|
||||||
@ -140,5 +146,52 @@ namespace osu.Game
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processScoresWithMissingStatistics()
|
||||||
|
{
|
||||||
|
HashSet<Guid> scoreIds = new HashSet<Guid>();
|
||||||
|
|
||||||
|
Logger.Log("Querying for scores to reprocess...");
|
||||||
|
|
||||||
|
realmAccess.Run(r =>
|
||||||
|
{
|
||||||
|
foreach (var score in r.All<ScoreInfo>())
|
||||||
|
{
|
||||||
|
if (score.Statistics.Sum(kvp => kvp.Value) > 0 && score.MaximumStatistics.Sum(kvp => kvp.Value) == 0)
|
||||||
|
scoreIds.Add(score.ID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.Log($"Found {scoreIds.Count} scores which require reprocessing.");
|
||||||
|
|
||||||
|
foreach (var id in scoreIds)
|
||||||
|
{
|
||||||
|
while (localUserPlayInfo?.IsPlaying.Value == true)
|
||||||
|
{
|
||||||
|
Logger.Log("Background processing sleeping due to active gameplay...");
|
||||||
|
Thread.Sleep(TimeToSleepDuringGameplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var score = scoreManager.Query(s => s.ID == id);
|
||||||
|
|
||||||
|
scoreManager.PopulateMaximumStatistics(score);
|
||||||
|
|
||||||
|
// Can't use async overload because we're not on the update thread.
|
||||||
|
// ReSharper disable once MethodHasAsyncOverload
|
||||||
|
realmAccess.Write(r =>
|
||||||
|
{
|
||||||
|
r.Find<ScoreInfo>(id).MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics);
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.Log($"Populated maximum statistics for score {id}");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
{
|
{
|
||||||
public class DownloadButton : BeatmapCardIconButton
|
public class DownloadButton : BeatmapCardIconButton
|
||||||
{
|
{
|
||||||
public IBindable<DownloadState> State => state;
|
public Bindable<DownloadState> State { get; } = new Bindable<DownloadState>();
|
||||||
private readonly Bindable<DownloadState> state = new Bindable<DownloadState>();
|
|
||||||
|
|
||||||
private readonly APIBeatmapSet beatmapSet;
|
private readonly APIBeatmapSet beatmapSet;
|
||||||
|
|
||||||
@ -48,14 +47,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
preferNoVideo.BindValueChanged(_ => updateState());
|
preferNoVideo.BindValueChanged(_ => updateState());
|
||||||
state.BindValueChanged(_ => updateState(), true);
|
State.BindValueChanged(_ => updateState(), true);
|
||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
switch (state.Value)
|
switch (State.Value)
|
||||||
{
|
{
|
||||||
|
case DownloadState.Unknown:
|
||||||
|
Action = null;
|
||||||
|
TooltipText = string.Empty;
|
||||||
|
break;
|
||||||
|
|
||||||
case DownloadState.Downloading:
|
case DownloadState.Downloading:
|
||||||
case DownloadState.Importing:
|
case DownloadState.Importing:
|
||||||
Action = null;
|
Action = null;
|
||||||
|
@ -41,6 +41,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
|
|
||||||
Anchor = Origin = Anchor.Centre;
|
Anchor = Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
// needed for touch input to work when card is not hovered/expanded
|
||||||
|
AlwaysPresent = true;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
icon = new SpriteIcon
|
icon = new SpriteIcon
|
||||||
|
@ -121,7 +121,18 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
finalClockSource.ProcessFrame();
|
|
||||||
|
if (Source != null && Source is not IAdjustableClock && Source.CurrentTime < decoupledClock.CurrentTime)
|
||||||
|
{
|
||||||
|
// InterpolatingFramedClock won't interpolate backwards unless its source has an ElapsedFrameTime.
|
||||||
|
// See https://github.com/ppy/osu-framework/blob/ba1385330cc501f34937e08257e586c84e35d772/osu.Framework/Timing/InterpolatingFramedClock.cs#L91-L93
|
||||||
|
// This is not always the case here when doing large seeks.
|
||||||
|
// (Of note, this is not an issue if the source is adjustable, as the source is seeked to be in time by DecoupleableInterpolatingFramedClock).
|
||||||
|
// Rather than trying to get around this by fixing the framework clock stack, let's work around it for now.
|
||||||
|
Seek(Source.CurrentTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
finalClockSource.ProcessFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double TotalAppliedOffset
|
public double TotalAppliedOffset
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Layout;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Containers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A container that reverts any rotation (and optionally scale) applied by its direct parent.
|
||||||
|
/// </summary>
|
||||||
|
public class UprightAspectMaintainingContainer : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controls how much this container scales compared to its parent (default is 1.0f).
|
||||||
|
/// </summary>
|
||||||
|
public float ScalingFactor { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the scaling of this container.
|
||||||
|
/// </summary>
|
||||||
|
public ScaleMode Scaling { get; set; } = ScaleMode.Vertical;
|
||||||
|
|
||||||
|
private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent);
|
||||||
|
|
||||||
|
public UprightAspectMaintainingContainer()
|
||||||
|
{
|
||||||
|
AddLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!layout.IsValid)
|
||||||
|
{
|
||||||
|
keepUprightAndUnstretched();
|
||||||
|
layout.Validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent.
|
||||||
|
/// </summary>
|
||||||
|
private void keepUprightAndUnstretched()
|
||||||
|
{
|
||||||
|
// Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale.
|
||||||
|
var parentMatrix = Parent.DrawInfo.Matrix;
|
||||||
|
|
||||||
|
// Remove Translation.>
|
||||||
|
parentMatrix.M31 = 0.0f;
|
||||||
|
parentMatrix.M32 = 0.0f;
|
||||||
|
|
||||||
|
Matrix3 reversedParent = parentMatrix.Inverted();
|
||||||
|
|
||||||
|
// Extract the rotation.
|
||||||
|
float angle = MathF.Atan2(reversedParent.M12, reversedParent.M11);
|
||||||
|
Rotation = MathHelper.RadiansToDegrees(angle);
|
||||||
|
|
||||||
|
// Remove rotation from the C matrix so that it only contains shear and scale.
|
||||||
|
Matrix3 m = Matrix3.CreateRotationZ(-angle);
|
||||||
|
reversedParent *= m;
|
||||||
|
|
||||||
|
// Extract shear.
|
||||||
|
float alpha = reversedParent.M21 / reversedParent.M22;
|
||||||
|
Shear = new Vector2(-alpha, 0);
|
||||||
|
|
||||||
|
// Etract scale.
|
||||||
|
float sx = reversedParent.M11;
|
||||||
|
float sy = reversedParent.M22;
|
||||||
|
|
||||||
|
Vector3 parentScale = parentMatrix.ExtractScale();
|
||||||
|
|
||||||
|
float usedScale = 1.0f;
|
||||||
|
|
||||||
|
switch (Scaling)
|
||||||
|
{
|
||||||
|
case ScaleMode.Horizontal:
|
||||||
|
usedScale = parentScale.X;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScaleMode.Vertical:
|
||||||
|
usedScale = parentScale.Y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Scaling != ScaleMode.NoScaling)
|
||||||
|
{
|
||||||
|
if (ScalingFactor < 1.0f)
|
||||||
|
usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor;
|
||||||
|
if (ScalingFactor > 1.0f)
|
||||||
|
usedScale = (usedScale < 1.0f) ? usedScale * (1.0f / ScalingFactor) : usedScale * ScalingFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scale = new Vector2(sx * usedScale, sy * usedScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ScaleMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Prevent this container from scaling.
|
||||||
|
/// </summary>
|
||||||
|
NoScaling,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent.
|
||||||
|
/// </summary>
|
||||||
|
Vertical,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent.
|
||||||
|
/// </summary>
|
||||||
|
Horizontal,
|
||||||
|
}
|
||||||
|
}
|
@ -102,26 +102,31 @@ namespace osu.Game.Graphics
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the colour for a <see cref="HitResult"/>.
|
/// Retrieves the colour for a <see cref="HitResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color4 ForHitResult(HitResult judgement)
|
public Color4 ForHitResult(HitResult result)
|
||||||
{
|
{
|
||||||
switch (judgement)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HitResult.Perfect:
|
case HitResult.SmallTickMiss:
|
||||||
case HitResult.Great:
|
case HitResult.LargeTickMiss:
|
||||||
return Blue;
|
case HitResult.Miss:
|
||||||
|
return Red;
|
||||||
case HitResult.Ok:
|
|
||||||
case HitResult.Good:
|
|
||||||
return Green;
|
|
||||||
|
|
||||||
case HitResult.Meh:
|
case HitResult.Meh:
|
||||||
return Yellow;
|
return Yellow;
|
||||||
|
|
||||||
case HitResult.Miss:
|
case HitResult.Ok:
|
||||||
return Red;
|
return Green;
|
||||||
|
|
||||||
|
case HitResult.Good:
|
||||||
|
return GreenLight;
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
case HitResult.Great:
|
||||||
|
return Blue;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Color4.White;
|
return BlueLight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
108
osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs
Normal file
108
osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// 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 osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Sprites
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A wrapped version of <see cref="OsuSpriteText"/> which will expand in size based on text content, but never shrink back down.
|
||||||
|
/// </summary>
|
||||||
|
public class SizePreservingSpriteText : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly OsuSpriteText text = new OsuSpriteText();
|
||||||
|
|
||||||
|
private Vector2 maximumSize;
|
||||||
|
|
||||||
|
public SizePreservingSpriteText(Vector2? minimumSize = null)
|
||||||
|
{
|
||||||
|
text.Origin = Anchor.Centre;
|
||||||
|
text.Anchor = Anchor.Centre;
|
||||||
|
|
||||||
|
AddInternal(text);
|
||||||
|
maximumSize = minimumSize ?? Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
Width = maximumSize.X = MathF.Max(maximumSize.X, text.Width);
|
||||||
|
Height = maximumSize.Y = MathF.Max(maximumSize.Y, text.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new Axes AutoSizeAxes
|
||||||
|
{
|
||||||
|
get => Axes.None;
|
||||||
|
set => throw new InvalidOperationException("You can't set AutoSizeAxes of this container");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the text to be displayed.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString Text
|
||||||
|
{
|
||||||
|
get => text.Text;
|
||||||
|
set => text.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains information on the font used to display the text.
|
||||||
|
/// </summary>
|
||||||
|
public FontUsage Font
|
||||||
|
{
|
||||||
|
get => text.Font;
|
||||||
|
set => text.Font = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if a shadow should be displayed around the text.
|
||||||
|
/// </summary>
|
||||||
|
public bool Shadow
|
||||||
|
{
|
||||||
|
get => text.Shadow;
|
||||||
|
set => text.Shadow = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The colour of the shadow displayed around the text. A shadow will only be displayed if the <see cref="Shadow"/> property is set to true.
|
||||||
|
/// </summary>
|
||||||
|
public Color4 ShadowColour
|
||||||
|
{
|
||||||
|
get => text.ShadowColour;
|
||||||
|
set => text.ShadowColour = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The offset of the shadow displayed around the text. A shadow will only be displayed if the <see cref="Shadow"/> property is set to true.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 ShadowOffset
|
||||||
|
{
|
||||||
|
get => text.ShadowOffset;
|
||||||
|
set => text.ShadowOffset = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the <see cref="SpriteText"/>'s vertical size should be equal to <see cref="FontUsage.Size"/> (the full height) or precisely the size of used characters.
|
||||||
|
/// Set to false to allow better centering of individual characters/numerals/etc.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseFullGlyphHeight
|
||||||
|
{
|
||||||
|
get => text.UseFullGlyphHeight;
|
||||||
|
set => text.UseFullGlyphHeight = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsPresent => text.IsPresent;
|
||||||
|
|
||||||
|
public override string ToString() => text.ToString();
|
||||||
|
|
||||||
|
public float LineBaseHeight => text.LineBaseHeight;
|
||||||
|
|
||||||
|
public IEnumerable<LocalisableString> FilterTerms => text.FilterTerms;
|
||||||
|
}
|
||||||
|
}
|
@ -81,12 +81,12 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public int? LegacyTotalScore { get; set; }
|
public int? LegacyTotalScore { get; set; }
|
||||||
|
|
||||||
[JsonProperty("legacy_score_id")]
|
[JsonProperty("legacy_score_id")]
|
||||||
public uint? LegacyScoreId { get; set; }
|
public ulong? LegacyScoreId { get; set; }
|
||||||
|
|
||||||
#region osu-web API additions (not stored to database).
|
#region osu-web API additions (not stored to database).
|
||||||
|
|
||||||
[JsonProperty("id")]
|
[JsonProperty("id")]
|
||||||
public long? ID { get; set; }
|
public ulong? ID { get; set; }
|
||||||
|
|
||||||
[JsonProperty("user")]
|
[JsonProperty("user")]
|
||||||
public APIUser? User { get; set; }
|
public APIUser? User { get; set; }
|
||||||
@ -190,6 +190,6 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
|
MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
|
||||||
};
|
};
|
||||||
|
|
||||||
public long OnlineID => ID ?? -1;
|
public long OnlineID => (long?)ID ?? -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ namespace osu.Game.Online
|
|||||||
{
|
{
|
||||||
public enum DownloadState
|
public enum DownloadState
|
||||||
{
|
{
|
||||||
|
Unknown,
|
||||||
NotDownloaded,
|
NotDownloaded,
|
||||||
Downloading,
|
Downloading,
|
||||||
Importing,
|
Importing,
|
||||||
|
@ -1,20 +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.
|
|
||||||
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer.Countdown
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates a change to the <see cref="MultiplayerRoom"/>'s countdown.
|
|
||||||
/// </summary>
|
|
||||||
[MessagePackObject]
|
|
||||||
public class CountdownChangedEvent : MatchServerEvent
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The new countdown.
|
|
||||||
/// </summary>
|
|
||||||
[Key(0)]
|
|
||||||
public MultiplayerCountdown? Countdown { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,28 @@
|
|||||||
|
// 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 MessagePack;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Multiplayer.Countdown
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a countdown started in the <see cref="MultiplayerRoom"/>.
|
||||||
|
/// </summary>
|
||||||
|
[MessagePackObject]
|
||||||
|
public class CountdownStartedEvent : MatchServerEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The countdown that was started.
|
||||||
|
/// </summary>
|
||||||
|
[Key(0)]
|
||||||
|
public readonly MultiplayerCountdown Countdown;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
[SerializationConstructor]
|
||||||
|
public CountdownStartedEvent(MultiplayerCountdown countdown)
|
||||||
|
{
|
||||||
|
Countdown = countdown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
// 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 MessagePack;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Multiplayer.Countdown
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a countdown was stopped in the <see cref="MultiplayerRoom"/>.
|
||||||
|
/// </summary>
|
||||||
|
[MessagePackObject]
|
||||||
|
public class CountdownStoppedEvent : MatchServerEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The identifier of the countdown that was stopped.
|
||||||
|
/// </summary>
|
||||||
|
[Key(0)]
|
||||||
|
public readonly int ID;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
[SerializationConstructor]
|
||||||
|
public CountdownStoppedEvent(int id)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer.Countdown
|
namespace osu.Game.Online.Multiplayer.Countdown
|
||||||
{
|
{
|
||||||
@ -11,5 +12,14 @@ namespace osu.Game.Online.Multiplayer.Countdown
|
|||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public class StopCountdownRequest : MatchUserRequest
|
public class StopCountdownRequest : MatchUserRequest
|
||||||
{
|
{
|
||||||
|
[Key(0)]
|
||||||
|
public readonly int ID;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
[SerializationConstructor]
|
||||||
|
public StopCountdownRequest(int id)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// and forcing progression of any clients that are blocking load due to user interaction.
|
/// and forcing progression of any clients that are blocking load due to user interaction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public class ForceGameplayStartCountdown : MultiplayerCountdown
|
public sealed class ForceGameplayStartCountdown : MultiplayerCountdown
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
// IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
// IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||||
[Union(0, typeof(CountdownChangedEvent))]
|
[Union(0, typeof(CountdownStartedEvent))]
|
||||||
|
[Union(1, typeof(CountdownStoppedEvent))]
|
||||||
public abstract class MatchServerEvent
|
public abstract class MatchServerEvent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// A <see cref="MultiplayerCountdown"/> which will start the match after ending.
|
/// A <see cref="MultiplayerCountdown"/> which will start the match after ending.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public class MatchStartCountdown : MultiplayerCountdown
|
public sealed class MatchStartCountdown : MultiplayerCountdown
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -552,8 +552,14 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
switch (e)
|
switch (e)
|
||||||
{
|
{
|
||||||
case CountdownChangedEvent countdownChangedEvent:
|
case CountdownStartedEvent countdownStartedEvent:
|
||||||
Room.Countdown = countdownChangedEvent.Countdown;
|
Room.ActiveCountdowns.Add(countdownStartedEvent.Countdown);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CountdownStoppedEvent countdownStoppedEvent:
|
||||||
|
MultiplayerCountdown? countdown = Room.ActiveCountdowns.FirstOrDefault(countdown => countdown.ID == countdownStoppedEvent.ID);
|
||||||
|
if (countdown != null)
|
||||||
|
Room.ActiveCountdowns.Remove(countdown);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,13 +15,24 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
[Union(1, typeof(ForceGameplayStartCountdown))]
|
[Union(1, typeof(ForceGameplayStartCountdown))]
|
||||||
public abstract class MultiplayerCountdown
|
public abstract class MultiplayerCountdown
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A unique identifier for this countdown.
|
||||||
|
/// </summary>
|
||||||
|
[Key(0)]
|
||||||
|
public int ID { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of time remaining in the countdown.
|
/// The amount of time remaining in the countdown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is only sent once from the server upon initial retrieval of the <see cref="MultiplayerRoom"/> or via a <see cref="CountdownChangedEvent"/>.
|
/// This is only sent once from the server upon initial retrieval of the <see cref="MultiplayerRoom"/> or via a <see cref="CountdownStartedEvent"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Key(0)]
|
[Key(1)]
|
||||||
public TimeSpan TimeRemaining { get; set; }
|
public TimeSpan TimeRemaining { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether only a single instance of this <see cref="MultiplayerCountdown"/> type may be active at any one time.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool IsExclusive => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,10 +53,10 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
public IList<MultiplayerPlaylistItem> Playlist { get; set; } = new List<MultiplayerPlaylistItem>();
|
public IList<MultiplayerPlaylistItem> Playlist { get; set; } = new List<MultiplayerPlaylistItem>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently-running countdown.
|
/// The currently running countdowns.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Key(7)]
|
[Key(7)]
|
||||||
public MultiplayerCountdown? Countdown { get; set; }
|
public IList<MultiplayerCountdown> ActiveCountdowns { get; set; } = new List<MultiplayerCountdown>();
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
[SerializationConstructor]
|
[SerializationConstructor]
|
||||||
|
@ -114,6 +114,7 @@ namespace osu.Game.Online.Rooms
|
|||||||
|
|
||||||
switch (downloadTracker.State.Value)
|
switch (downloadTracker.State.Value)
|
||||||
{
|
{
|
||||||
|
case DownloadState.Unknown:
|
||||||
case DownloadState.NotDownloaded:
|
case DownloadState.NotDownloaded:
|
||||||
availability.Value = BeatmapAvailability.NotDownloaded();
|
availability.Value = BeatmapAvailability.NotDownloaded();
|
||||||
break;
|
break;
|
||||||
|
@ -23,7 +23,8 @@ namespace osu.Game.Online
|
|||||||
(typeof(ChangeTeamRequest), typeof(MatchUserRequest)),
|
(typeof(ChangeTeamRequest), typeof(MatchUserRequest)),
|
||||||
(typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)),
|
(typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)),
|
||||||
(typeof(StopCountdownRequest), typeof(MatchUserRequest)),
|
(typeof(StopCountdownRequest), typeof(MatchUserRequest)),
|
||||||
(typeof(CountdownChangedEvent), typeof(MatchServerEvent)),
|
(typeof(CountdownStartedEvent), typeof(MatchServerEvent)),
|
||||||
|
(typeof(CountdownStoppedEvent), typeof(MatchServerEvent)),
|
||||||
(typeof(TeamVersusRoomState), typeof(MatchRoomState)),
|
(typeof(TeamVersusRoomState), typeof(MatchRoomState)),
|
||||||
(typeof(TeamVersusUserState), typeof(MatchUserState)),
|
(typeof(TeamVersusUserState), typeof(MatchUserState)),
|
||||||
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),
|
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),
|
||||||
|
@ -839,7 +839,9 @@ namespace osu.Game
|
|||||||
OnHome = delegate
|
OnHome = delegate
|
||||||
{
|
{
|
||||||
CloseAllOverlays(false);
|
CloseAllOverlays(false);
|
||||||
menuScreen?.MakeCurrent();
|
|
||||||
|
if (menuScreen?.GetChildScreen() != null)
|
||||||
|
menuScreen.MakeCurrent();
|
||||||
},
|
},
|
||||||
}, topMostOverlayContent.Add);
|
}, topMostOverlayContent.Add);
|
||||||
|
|
||||||
|
@ -390,11 +390,6 @@ namespace osu.Game
|
|||||||
var framedClock = new FramedClock(beatmap.Track);
|
var framedClock = new FramedClock(beatmap.Track);
|
||||||
|
|
||||||
beatmapClock.ChangeSource(framedClock);
|
beatmapClock.ChangeSource(framedClock);
|
||||||
|
|
||||||
// Normally the internal decoupled clock will seek the *track* to the decoupled time, but we blocked this.
|
|
||||||
// It won't behave nicely unless we also set it to the track's time.
|
|
||||||
// Probably another thing which should be fixed in the decoupled mess (or just replaced).
|
|
||||||
beatmapClock.Seek(beatmap.Track.CurrentTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void InitialiseFonts()
|
protected virtual void InitialiseFonts()
|
||||||
|
@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const double FINAL_RATE_PROGRESS = 0.75f;
|
public const double FINAL_RATE_PROGRESS = 0.75f;
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 0.5;
|
||||||
|
|
||||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||||
public abstract BindableNumber<double> InitialRate { get; }
|
public abstract BindableNumber<double> InitialRate { get; }
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override string Acronym => "WD";
|
public override string Acronym => "WD";
|
||||||
public override LocalisableString Description => "Sloooow doooown...";
|
public override LocalisableString Description => "Sloooow doooown...";
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown;
|
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown;
|
||||||
public override double ScoreMultiplier => 1.0;
|
|
||||||
|
|
||||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||||
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
|
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
|
||||||
|
@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override string Acronym => "WU";
|
public override string Acronym => "WU";
|
||||||
public override LocalisableString Description => "Can you keep up?";
|
public override LocalisableString Description => "Can you keep up?";
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp;
|
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp;
|
||||||
public override double ScoreMultiplier => 1.0;
|
|
||||||
|
|
||||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||||
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
|
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Scoring
|
namespace osu.Game.Rulesets.Scoring
|
||||||
@ -135,6 +135,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
public static class HitResultExtensions
|
public static class HitResultExtensions
|
||||||
{
|
{
|
||||||
|
private static readonly IList<HitResult> order = EnumExtensions.GetValuesInOrder<HitResult>().ToList();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether a <see cref="HitResult"/> increases the combo.
|
/// Whether a <see cref="HitResult"/> increases the combo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -282,6 +284,13 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
Debug.Assert(minResult <= maxResult);
|
Debug.Assert(minResult <= maxResult);
|
||||||
return result > minResult && result < maxResult;
|
return result > minResult && result < maxResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ordered index of a <see cref="HitResult"/>. Used for consistent order when displaying hit results to the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="result">The <see cref="HitResult"/> to get the index of.</param>
|
||||||
|
/// <returns>The index of <paramref name="result"/>.</returns>
|
||||||
|
public static int GetIndexForOrderedDisplay(this HitResult result) => order.IndexOf(result);
|
||||||
}
|
}
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// Displays an interactive ruleset gameplay instance.
|
/// Displays an interactive ruleset gameplay instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TObject">The type of HitObject contained by this DrawableRuleset.</typeparam>
|
/// <typeparam name="TObject">The type of HitObject contained by this DrawableRuleset.</typeparam>
|
||||||
public abstract class DrawableRuleset<TObject> : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter
|
public abstract class DrawableRuleset<TObject> : DrawableRuleset, IProvideCursor, ICanAttachHUDPieces
|
||||||
where TObject : HitObject
|
where TObject : HitObject
|
||||||
{
|
{
|
||||||
public override event Action<JudgementResult> NewResult;
|
public override event Action<JudgementResult> NewResult;
|
||||||
@ -338,7 +339,10 @@ namespace osu.Game.Rulesets.UI
|
|||||||
public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h);
|
public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h);
|
||||||
|
|
||||||
public void Attach(KeyCounterDisplay keyCounter) =>
|
public void Attach(KeyCounterDisplay keyCounter) =>
|
||||||
(KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter);
|
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter);
|
||||||
|
|
||||||
|
public void Attach(ClicksPerSecondCalculator calculator) =>
|
||||||
|
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.
|
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.
|
||||||
|
@ -2,15 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
@ -22,7 +20,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Cached(typeof(IGameplayClock))]
|
[Cached(typeof(IGameplayClock))]
|
||||||
[Cached(typeof(IFrameStableClock))]
|
[Cached(typeof(IFrameStableClock))]
|
||||||
public sealed class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock, IGameplayClock
|
public sealed class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock
|
||||||
{
|
{
|
||||||
public ReplayInputHandler? ReplayInputHandler { get; set; }
|
public ReplayInputHandler? ReplayInputHandler { get; set; }
|
||||||
|
|
||||||
@ -263,27 +261,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public FrameTimeInfo TimeInfo => framedClock.TimeInfo;
|
public FrameTimeInfo TimeInfo => framedClock.TimeInfo;
|
||||||
|
|
||||||
public double TrueGameplayRate
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
double baseRate = Rate;
|
|
||||||
|
|
||||||
foreach (double adjustment in NonGameplayAdjustments)
|
|
||||||
{
|
|
||||||
if (Precision.AlmostEquals(adjustment, 0))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
baseRate /= adjustment;
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public double StartTime => parentGameplayClock?.StartTime ?? 0;
|
public double StartTime => parentGameplayClock?.StartTime ?? 0;
|
||||||
|
|
||||||
public IEnumerable<double> NonGameplayAdjustments => parentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<double>();
|
private readonly AudioAdjustments gameplayAdjustments = new AudioAdjustments();
|
||||||
|
|
||||||
|
public IAdjustableAudioComponent AdjustmentsFromMods => parentGameplayClock?.AdjustmentsFromMods ?? gameplayAdjustments;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Timing;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public interface IFrameStableClock : IFrameBasedClock
|
public interface IFrameStableClock : IGameplayClock
|
||||||
{
|
{
|
||||||
IBindable<bool> IsCatchingUp { get; }
|
IBindable<bool> IsCatchingUp { get; }
|
||||||
|
|
||||||
|
@ -20,11 +20,12 @@ using osu.Game.Input.Bindings;
|
|||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public abstract class RulesetInputManager<T> : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler
|
public abstract class RulesetInputManager<T> : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
||||||
@ -168,7 +169,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
.Select(action => new KeyCounterAction<T>(action)));
|
.Select(action => new KeyCounterAction<T>(action)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
|
private class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
|
||||||
{
|
{
|
||||||
public ActionReceptor(KeyCounterDisplay target)
|
public ActionReceptor(KeyCounterDisplay target)
|
||||||
: base(target)
|
: base(target)
|
||||||
@ -186,6 +187,37 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Keys per second Counter Attachment
|
||||||
|
|
||||||
|
public void Attach(ClicksPerSecondCalculator calculator)
|
||||||
|
{
|
||||||
|
var listener = new ActionListener(calculator);
|
||||||
|
|
||||||
|
KeyBindingContainer.Add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ActionListener : Component, IKeyBindingHandler<T>
|
||||||
|
{
|
||||||
|
private readonly ClicksPerSecondCalculator calculator;
|
||||||
|
|
||||||
|
public ActionListener(ClicksPerSecondCalculator calculator)
|
||||||
|
{
|
||||||
|
this.calculator = calculator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<T> e)
|
||||||
|
{
|
||||||
|
calculator.AddInputTimestamp();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
protected virtual KeyBindingContainer<T> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
protected virtual KeyBindingContainer<T> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||||
=> new RulesetKeyBindingContainer(ruleset, variant, unique);
|
=> new RulesetKeyBindingContainer(ruleset, variant, unique);
|
||||||
|
|
||||||
@ -221,12 +253,13 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Supports attaching a <see cref="KeyCounterDisplay"/>.
|
/// Supports attaching various HUD pieces.
|
||||||
/// Keys will be populated automatically and a receptor will be injected inside.
|
/// Keys will be populated automatically and a receptor will be injected inside.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICanAttachKeyCounter
|
public interface ICanAttachHUDPieces
|
||||||
{
|
{
|
||||||
void Attach(KeyCounterDisplay keyCounter);
|
void Attach(KeyCounterDisplay keyCounter);
|
||||||
|
void Attach(ClicksPerSecondCalculator calculator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RulesetInputManagerInputState<T> : InputState
|
public class RulesetInputManagerInputState<T> : InputState
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -16,6 +17,8 @@ using osu.Game.Scoring.Legacy;
|
|||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
@ -71,6 +74,8 @@ namespace osu.Game.Scoring
|
|||||||
if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo));
|
if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo));
|
||||||
if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset));
|
if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset));
|
||||||
|
|
||||||
|
PopulateMaximumStatistics(model);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(model.StatisticsJson))
|
if (string.IsNullOrEmpty(model.StatisticsJson))
|
||||||
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
|
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
|
||||||
|
|
||||||
@ -78,6 +83,68 @@ namespace osu.Game.Scoring
|
|||||||
model.MaximumStatisticsJson = JsonConvert.SerializeObject(model.MaximumStatistics);
|
model.MaximumStatisticsJson = JsonConvert.SerializeObject(model.MaximumStatistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The score to populate the statistics of.</param>
|
||||||
|
public void PopulateMaximumStatistics(ScoreInfo score)
|
||||||
|
{
|
||||||
|
if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var beatmap = score.BeatmapInfo.Detach();
|
||||||
|
var ruleset = score.Ruleset.Detach();
|
||||||
|
var rulesetInstance = ruleset.CreateInstance();
|
||||||
|
|
||||||
|
Debug.Assert(rulesetInstance != null);
|
||||||
|
|
||||||
|
// Populate the maximum statistics.
|
||||||
|
HitResult maxBasicResult = rulesetInstance.GetHitResults()
|
||||||
|
.Select(h => h.result)
|
||||||
|
.Where(h => h.IsBasic())
|
||||||
|
.OrderByDescending(Judgement.ToNumericResult).First();
|
||||||
|
|
||||||
|
foreach ((HitResult result, int count) in score.Statistics)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
case HitResult.LargeTickMiss:
|
||||||
|
score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
case HitResult.SmallTickMiss:
|
||||||
|
score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.IgnoreHit:
|
||||||
|
case HitResult.IgnoreMiss:
|
||||||
|
case HitResult.SmallBonus:
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!score.IsLegacyScore)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
// In osu! and osu!mania, some judgements affect combo but aren't stored to scores.
|
||||||
|
// A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes.
|
||||||
|
var calculator = rulesetInstance.CreateDifficultyCalculator(beatmaps().GetWorkingBeatmap(beatmap));
|
||||||
|
var attributes = calculator.Calculate(score.Mods);
|
||||||
|
|
||||||
|
int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum();
|
||||||
|
if (attributes.MaxCombo > maxComboFromStatistics)
|
||||||
|
score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics;
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
}
|
||||||
|
|
||||||
protected override void PostImport(ScoreInfo model, Realm realm, bool batchImport)
|
protected override void PostImport(ScoreInfo model, Realm realm, bool batchImport)
|
||||||
{
|
{
|
||||||
base.PostImport(model, realm, batchImport);
|
base.PostImport(model, realm, batchImport);
|
||||||
|
@ -28,7 +28,8 @@ namespace osu.Game.Scoring
|
|||||||
private readonly OsuConfigManager configManager;
|
private readonly OsuConfigManager configManager;
|
||||||
private readonly ScoreImporter scoreImporter;
|
private readonly ScoreImporter scoreImporter;
|
||||||
|
|
||||||
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, OsuConfigManager configManager = null)
|
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api,
|
||||||
|
OsuConfigManager configManager = null)
|
||||||
: base(storage, realm)
|
: base(storage, realm)
|
||||||
{
|
{
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
@ -178,6 +179,12 @@ namespace osu.Game.Scoring
|
|||||||
public Live<ScoreInfo> Import(ScoreInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) =>
|
public Live<ScoreInfo> Import(ScoreInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) =>
|
||||||
scoreImporter.ImportModel(item, archive, batchImport, cancellationToken);
|
scoreImporter.ImportModel(item, archive, batchImport, cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The score to populate the statistics of.</param>
|
||||||
|
public void PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score);
|
||||||
|
|
||||||
#region Implementation of IPresentImports<ScoreInfo>
|
#region Implementation of IPresentImports<ScoreInfo>
|
||||||
|
|
||||||
public Action<IEnumerable<Live<ScoreInfo>>> PresentImport
|
public Action<IEnumerable<Live<ScoreInfo>>> PresentImport
|
||||||
|
@ -561,6 +561,10 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
{
|
{
|
||||||
switch (state.NewValue)
|
switch (state.NewValue)
|
||||||
{
|
{
|
||||||
|
case DownloadState.Unknown:
|
||||||
|
// Ignore initial state to ensure the button doesn't briefly appear.
|
||||||
|
break;
|
||||||
|
|
||||||
case DownloadState.LocallyAvailable:
|
case DownloadState.LocallyAvailable:
|
||||||
// Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching.
|
// Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching.
|
||||||
if (beatmapManager.QueryBeatmap(b => b.MD5Hash == beatmap.MD5Hash) == null)
|
if (beatmapManager.QueryBeatmap(b => b.MD5Hash == beatmap.MD5Hash) == null)
|
||||||
|
@ -433,6 +433,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
|
|
||||||
private void updateWorkingBeatmap()
|
private void updateWorkingBeatmap()
|
||||||
{
|
{
|
||||||
|
if (SelectedItem.Value == null || !this.IsCurrentScreen())
|
||||||
|
return;
|
||||||
|
|
||||||
var beatmap = SelectedItem.Value?.Beatmap;
|
var beatmap = SelectedItem.Value?.Beatmap;
|
||||||
|
|
||||||
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
|
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
|
||||||
|
@ -109,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Debug.Assert(clickOperation == null);
|
Debug.Assert(clickOperation == null);
|
||||||
clickOperation = ongoingOperationTracker.BeginOperation();
|
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
if (isReady() && Client.IsHost && Room.Countdown == null)
|
if (isReady() && Client.IsHost && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
|
||||||
startMatch();
|
startMatch();
|
||||||
else
|
else
|
||||||
toggleReady();
|
toggleReady();
|
||||||
@ -140,10 +140,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
private void cancelCountdown()
|
private void cancelCountdown()
|
||||||
{
|
{
|
||||||
|
if (Client.Room == null)
|
||||||
|
return;
|
||||||
|
|
||||||
Debug.Assert(clickOperation == null);
|
Debug.Assert(clickOperation == null);
|
||||||
clickOperation = ongoingOperationTracker.BeginOperation();
|
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
Client.SendMatchRequest(new StopCountdownRequest()).ContinueWith(_ => endOperation());
|
MultiplayerCountdown countdown = Client.Room.ActiveCountdowns.Single(c => c is MatchStartCountdown);
|
||||||
|
Client.SendMatchRequest(new StopCountdownRequest(countdown.ID)).ContinueWith(_ => endOperation());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endOperation()
|
private void endOperation()
|
||||||
@ -192,7 +196,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
// When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready.
|
// When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready.
|
||||||
if (localUser?.State == MultiplayerUserState.Spectating)
|
if (localUser?.State == MultiplayerUserState.Spectating)
|
||||||
readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && Room.Countdown == null;
|
readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown);
|
||||||
|
|
||||||
if (newCountReady == countReady)
|
if (newCountReady == countReady)
|
||||||
return;
|
return;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -79,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
||||||
{
|
{
|
||||||
bool countdownActive = multiplayerClient.Room?.Countdown is MatchStartCountdown;
|
bool countdownActive = multiplayerClient.Room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true;
|
||||||
|
|
||||||
if (countdownActive)
|
if (countdownActive)
|
||||||
{
|
{
|
||||||
@ -121,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multiplayerClient.Room?.Countdown != null && multiplayerClient.IsHost)
|
if (multiplayerClient.Room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true && multiplayerClient.IsHost)
|
||||||
{
|
{
|
||||||
flow.Add(new OsuButton
|
flow.Add(new OsuButton
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -30,12 +27,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
public class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay
|
public class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay
|
||||||
{
|
{
|
||||||
private MatchSettings settings;
|
private MatchSettings settings = null!;
|
||||||
|
|
||||||
protected override OsuButton SubmitButton => settings.ApplyButton;
|
protected override OsuButton SubmitButton => settings.ApplyButton;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
|
||||||
|
|
||||||
protected override bool IsLoading => ongoingOperationTracker.InProgress.Value;
|
protected override bool IsLoading => ongoingOperationTracker.InProgress.Value;
|
||||||
|
|
||||||
@ -57,20 +54,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
private const float disabled_alpha = 0.2f;
|
private const float disabled_alpha = 0.2f;
|
||||||
|
|
||||||
public Action SettingsApplied;
|
public Action? SettingsApplied;
|
||||||
|
|
||||||
public OsuTextBox NameField, MaxParticipantsField;
|
public OsuTextBox NameField = null!;
|
||||||
public MatchTypePicker TypePicker;
|
public OsuTextBox MaxParticipantsField = null!;
|
||||||
public OsuEnumDropdown<QueueMode> QueueModeDropdown;
|
public MatchTypePicker TypePicker = null!;
|
||||||
public OsuTextBox PasswordTextBox;
|
public OsuEnumDropdown<QueueMode> QueueModeDropdown = null!;
|
||||||
public OsuCheckbox AutoSkipCheckbox;
|
public OsuTextBox PasswordTextBox = null!;
|
||||||
public TriangleButton ApplyButton;
|
public OsuCheckbox AutoSkipCheckbox = null!;
|
||||||
|
public TriangleButton ApplyButton = null!;
|
||||||
|
|
||||||
public OsuSpriteText ErrorText;
|
public OsuSpriteText ErrorText = null!;
|
||||||
|
|
||||||
private OsuEnumDropdown<StartMode> startModeDropdown;
|
private OsuEnumDropdown<StartMode> startModeDropdown = null!;
|
||||||
private OsuSpriteText typeLabel;
|
private OsuSpriteText typeLabel = null!;
|
||||||
private LoadingLayer loadingLayer;
|
private LoadingLayer loadingLayer = null!;
|
||||||
|
|
||||||
public void SelectBeatmap()
|
public void SelectBeatmap()
|
||||||
{
|
{
|
||||||
@ -79,26 +77,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerMatchSubScreen matchSubScreen { get; set; }
|
private MultiplayerMatchSubScreen matchSubScreen { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IRoomManager manager { get; set; }
|
private IRoomManager manager { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerClient client { get; set; }
|
private MultiplayerClient client { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
|
||||||
|
|
||||||
private readonly IBindable<bool> operationInProgress = new BindableBool();
|
private readonly IBindable<bool> operationInProgress = new BindableBool();
|
||||||
|
|
||||||
[CanBeNull]
|
|
||||||
private IDisposable applyingSettingsOperation;
|
|
||||||
|
|
||||||
private readonly Room room;
|
private readonly Room room;
|
||||||
|
|
||||||
private Drawable playlistContainer;
|
private IDisposable? applyingSettingsOperation;
|
||||||
private DrawableRoomPlaylist drawablePlaylist;
|
private Drawable playlistContainer = null!;
|
||||||
|
private DrawableRoomPlaylist drawablePlaylist = null!;
|
||||||
|
|
||||||
public MatchSettings(Room room)
|
public MatchSettings(Room room)
|
||||||
{
|
{
|
||||||
@ -423,7 +418,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
else
|
else
|
||||||
room.MaxParticipants.Value = null;
|
room.MaxParticipants.Value = null;
|
||||||
|
|
||||||
manager?.CreateRoom(room, onSuccess, onError);
|
manager.CreateRoom(room, onSuccess, onError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,7 +461,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
public class CreateOrUpdateButton : TriangleButton
|
public class CreateOrUpdateButton : TriangleButton
|
||||||
{
|
{
|
||||||
[Resolved(typeof(Room), nameof(Room.RoomID))]
|
[Resolved(typeof(Room), nameof(Room.RoomID))]
|
||||||
private Bindable<long?> roomId { get; set; }
|
private Bindable<long?> roomId { get; set; } = null!;
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
@ -57,23 +57,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
||||||
{
|
{
|
||||||
MultiplayerCountdown newCountdown;
|
MultiplayerCountdown newCountdown = room?.ActiveCountdowns.SingleOrDefault(c => c is MatchStartCountdown);
|
||||||
|
|
||||||
switch (room?.Countdown)
|
|
||||||
{
|
|
||||||
case MatchStartCountdown:
|
|
||||||
newCountdown = room.Countdown;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Clear the countdown with any other (including non-null) countdown values.
|
|
||||||
default:
|
|
||||||
newCountdown = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newCountdown != countdown)
|
if (newCountdown != countdown)
|
||||||
{
|
{
|
||||||
countdown = room?.Countdown;
|
countdown = newCountdown;
|
||||||
countdownChangeTime = Time.Current;
|
countdownChangeTime = Time.Current;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +201,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
case MultiplayerUserState.Ready:
|
case MultiplayerUserState.Ready:
|
||||||
if (room?.Host?.Equals(localUser) == true && room.Countdown == null)
|
if (room?.Host?.Equals(localUser) == true && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
|
||||||
setGreen();
|
setGreen();
|
||||||
else
|
else
|
||||||
setYellow();
|
setYellow();
|
||||||
@ -248,8 +236,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready && !room.Settings.AutoStartEnabled)
|
if (room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true
|
||||||
|
&& multiplayerClient.IsHost
|
||||||
|
&& multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready
|
||||||
|
&& !room.Settings.AutoStartEnabled)
|
||||||
|
{
|
||||||
return "Cancel countdown";
|
return "Cancel countdown";
|
||||||
|
}
|
||||||
|
|
||||||
return base.TooltipText;
|
return base.TooltipText;
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,9 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
private OngoingOperationTracker operationTracker { get; set; } = null!;
|
private OngoingOperationTracker operationTracker { get; set; } = null!;
|
||||||
|
|
||||||
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
||||||
private readonly long? itemToEdit;
|
private readonly PlaylistItem? itemToEdit;
|
||||||
|
|
||||||
private LoadingLayer loadingLayer = null!;
|
private LoadingLayer loadingLayer = null!;
|
||||||
private IDisposable? selectionOperation;
|
private IDisposable? selectionOperation;
|
||||||
@ -37,21 +35,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="room">The room.</param>
|
/// <param name="room">The room.</param>
|
||||||
/// <param name="itemToEdit">The item to be edited. May be null, in which case a new item will be added to the playlist.</param>
|
/// <param name="itemToEdit">The item to be edited. May be null, in which case a new item will be added to the playlist.</param>
|
||||||
/// <param name="beatmap">An optional initial beatmap selection to perform.</param>
|
public MultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null)
|
||||||
/// <param name="ruleset">An optional initial ruleset selection to perform.</param>
|
: base(room, itemToEdit)
|
||||||
public MultiplayerMatchSongSelect(Room room, long? itemToEdit = null, WorkingBeatmap? beatmap = null, RulesetInfo? ruleset = null)
|
|
||||||
: base(room)
|
|
||||||
{
|
{
|
||||||
this.itemToEdit = itemToEdit;
|
this.itemToEdit = itemToEdit;
|
||||||
|
|
||||||
if (beatmap != null || ruleset != null)
|
|
||||||
{
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
|
||||||
if (beatmap != null) Beatmap.Value = beatmap;
|
|
||||||
if (ruleset != null) Ruleset.Value = ruleset;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -80,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
if (operationInProgress.Value)
|
if (operationInProgress.Value)
|
||||||
{
|
{
|
||||||
Logger.Log($"{nameof(SelectedItem)} aborted due to {nameof(operationInProgress)}");
|
Logger.Log($"{nameof(SelectItem)} aborted due to {nameof(operationInProgress)}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
var multiplayerItem = new MultiplayerPlaylistItem
|
var multiplayerItem = new MultiplayerPlaylistItem
|
||||||
{
|
{
|
||||||
ID = itemToEdit ?? 0,
|
ID = itemToEdit?.ID ?? 0,
|
||||||
BeatmapID = item.Beatmap.OnlineID,
|
BeatmapID = item.Beatmap.OnlineID,
|
||||||
BeatmapChecksum = item.Beatmap.MD5Hash,
|
BeatmapChecksum = item.Beatmap.MD5Hash,
|
||||||
RulesetID = item.RulesetID,
|
RulesetID = item.RulesetID,
|
||||||
|
@ -49,11 +49,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerClient client { get; set; }
|
private MultiplayerClient client { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private BeatmapManager beatmapManager { get; set; }
|
|
||||||
|
|
||||||
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
|
||||||
|
|
||||||
private AddItemButton addItemButton;
|
private AddItemButton addItemButton;
|
||||||
|
|
||||||
public MultiplayerMatchSubScreen(Room room)
|
public MultiplayerMatchSubScreen(Room room)
|
||||||
@ -227,12 +222,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
if (!this.IsCurrentScreen())
|
if (!this.IsCurrentScreen())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.Last().Beatmap.OnlineID;
|
this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit));
|
||||||
var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id);
|
|
||||||
|
|
||||||
var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap);
|
|
||||||
|
|
||||||
this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit?.ID, workingBeatmap));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateFooter() => new MultiplayerMatchFooter();
|
protected override Drawable CreateFooter() => new MultiplayerMatchFooter();
|
||||||
@ -424,7 +414,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Push(new MultiplayerMatchSongSelect(Room, client.Room.Settings.PlaylistItemId, beatmap, ruleset));
|
this.Push(new MultiplayerMatchSongSelect(Room, Room.Playlist.Single(item => item.ID == client.Room.Settings.PlaylistItemId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -13,6 +14,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class MultiSpectatorPlayer : SpectatorPlayer
|
public class MultiSpectatorPlayer : SpectatorPlayer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All adjustments applied to the clock of this <see cref="MultiSpectatorPlayer"/> which come from mods.
|
||||||
|
/// </summary>
|
||||||
|
public IAggregateAudioAdjustment ClockAdjustmentsFromMods => clockAdjustmentsFromMods;
|
||||||
|
|
||||||
|
private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments();
|
||||||
private readonly SpectatorPlayerClock spectatorPlayerClock;
|
private readonly SpectatorPlayerClock spectatorPlayerClock;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -53,6 +60,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
|
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
|
||||||
=> new GameplayClockContainer(spectatorPlayerClock);
|
{
|
||||||
|
var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock);
|
||||||
|
clockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods);
|
||||||
|
return gameplayClockContainer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -43,6 +44,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerClient multiplayerClient { get; set; } = null!;
|
private MultiplayerClient multiplayerClient { get; set; } = null!;
|
||||||
|
|
||||||
|
private IAggregateAudioAdjustment? boundAdjustments;
|
||||||
|
|
||||||
private readonly PlayerArea[] instances;
|
private readonly PlayerArea[] instances;
|
||||||
private MasterGameplayClockContainer masterClockContainer = null!;
|
private MasterGameplayClockContainer masterClockContainer = null!;
|
||||||
private SpectatorSyncManager syncManager = null!;
|
private SpectatorSyncManager syncManager = null!;
|
||||||
@ -157,6 +160,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
masterClockContainer.Reset();
|
masterClockContainer.Reset();
|
||||||
|
|
||||||
|
// Start with adjustments from the first player to keep a sane state.
|
||||||
|
bindAudioAdjustments(instances.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -169,11 +175,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
.OrderBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime))
|
.OrderBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
// Only bind adjustments if there's actually a valid source, else just use the previous ones to ensure no sudden changes to audio.
|
||||||
|
if (currentAudioSource != null)
|
||||||
|
bindAudioAdjustments(currentAudioSource);
|
||||||
|
|
||||||
foreach (var instance in instances)
|
foreach (var instance in instances)
|
||||||
instance.Mute = instance != currentAudioSource;
|
instance.Mute = instance != currentAudioSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void bindAudioAdjustments(PlayerArea first)
|
||||||
|
{
|
||||||
|
if (boundAdjustments != null)
|
||||||
|
masterClockContainer.AdjustmentsFromMods.UnbindAdjustments(boundAdjustments);
|
||||||
|
|
||||||
|
boundAdjustments = first.ClockAdjustmentsFromMods;
|
||||||
|
masterClockContainer.AdjustmentsFromMods.BindAdjustments(boundAdjustments);
|
||||||
|
}
|
||||||
|
|
||||||
private bool isCandidateAudioSource(SpectatorPlayerClock? clock)
|
private bool isCandidateAudioSource(SpectatorPlayerClock? clock)
|
||||||
=> clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames;
|
=> clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames;
|
||||||
|
|
||||||
|
@ -42,6 +42,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly SpectatorPlayerClock SpectatorPlayerClock;
|
public readonly SpectatorPlayerClock SpectatorPlayerClock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The clock adjustments applied by the <see cref="Player"/> loaded in this area.
|
||||||
|
/// </summary>
|
||||||
|
public IAggregateAudioAdjustment ClockAdjustmentsFromMods => clockAdjustmentsFromMods;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently-loaded score.
|
/// The currently-loaded score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -50,6 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments();
|
||||||
private readonly BindableDouble volumeAdjustment = new BindableDouble();
|
private readonly BindableDouble volumeAdjustment = new BindableDouble();
|
||||||
private readonly Container gameplayContent;
|
private readonly Container gameplayContent;
|
||||||
private readonly LoadingLayer loadingLayer;
|
private readonly LoadingLayer loadingLayer;
|
||||||
@ -97,6 +103,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
{
|
{
|
||||||
var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock);
|
var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock);
|
||||||
player.OnGameplayStarted += () => OnGameplayStarted?.Invoke();
|
player.OnGameplayStarted += () => OnGameplayStarted?.Invoke();
|
||||||
|
|
||||||
|
clockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods);
|
||||||
|
|
||||||
return player;
|
return player;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -35,32 +33,34 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
public override bool AllowEditing => false;
|
public override bool AllowEditing => false;
|
||||||
|
|
||||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
protected BindableList<PlaylistItem> Playlist { get; private set; } = null!;
|
||||||
|
|
||||||
[CanBeNull]
|
|
||||||
[Resolved(CanBeNull = true)]
|
|
||||||
protected IBindable<PlaylistItem> SelectedItem { get; private set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private RulesetStore rulesets { get; set; }
|
private RulesetStore rulesets { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.InLobby(room);
|
protected override UserActivity InitialActivity => new UserActivity.InLobby(room);
|
||||||
|
|
||||||
protected readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
protected readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
private readonly Room room;
|
private readonly Room room;
|
||||||
|
private readonly PlaylistItem? initialItem;
|
||||||
private WorkingBeatmap initialBeatmap;
|
|
||||||
private RulesetInfo initialRuleset;
|
|
||||||
private IReadOnlyList<Mod> initialMods;
|
|
||||||
private bool itemSelected;
|
|
||||||
|
|
||||||
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
||||||
private IDisposable freeModSelectOverlayRegistration;
|
|
||||||
|
|
||||||
protected OnlinePlaySongSelect(Room room)
|
private IDisposable? freeModSelectOverlayRegistration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="OnlinePlaySongSelect"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="room">The room.</param>
|
||||||
|
/// <param name="initialItem">An optional initial <see cref="PlaylistItem"/> to use for the initial beatmap/ruleset/mods.
|
||||||
|
/// If <c>null</c>, the last <see cref="PlaylistItem"/> in the room will be used.</param>
|
||||||
|
protected OnlinePlaySongSelect(Room room, PlaylistItem? initialItem = null)
|
||||||
{
|
{
|
||||||
this.room = room;
|
this.room = room;
|
||||||
|
this.initialItem = initialItem ?? room.Playlist.LastOrDefault();
|
||||||
|
|
||||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
||||||
|
|
||||||
@ -75,11 +75,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
|
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||||
|
|
||||||
initialBeatmap = Beatmap.Value;
|
|
||||||
initialRuleset = Ruleset.Value;
|
|
||||||
initialMods = Mods.Value.ToList();
|
|
||||||
|
|
||||||
LoadComponent(freeModSelectOverlay);
|
LoadComponent(freeModSelectOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,14 +82,35 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var rulesetInstance = SelectedItem?.Value?.RulesetID == null ? null : rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
|
if (initialItem != null)
|
||||||
|
|
||||||
if (rulesetInstance != null)
|
|
||||||
{
|
{
|
||||||
// At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods.
|
// Prefer using a local databased beatmap lookup since OnlineId may be -1 for an invalid beatmap selection.
|
||||||
// Similarly, freeMods is currently empty but should only contain the allowed mods.
|
BeatmapInfo? beatmapInfo = initialItem.Beatmap as BeatmapInfo;
|
||||||
Mods.Value = SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
|
||||||
FreeMods.Value = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
// And in the case that this isn't a local databased beatmap, query by online ID.
|
||||||
|
if (beatmapInfo == null)
|
||||||
|
{
|
||||||
|
int onlineId = initialItem.Beatmap.OnlineID;
|
||||||
|
beatmapInfo = beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beatmapInfo != null)
|
||||||
|
Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo);
|
||||||
|
|
||||||
|
RulesetInfo? ruleset = rulesets.GetRuleset(initialItem.RulesetID);
|
||||||
|
|
||||||
|
if (ruleset != null)
|
||||||
|
{
|
||||||
|
Ruleset.Value = ruleset;
|
||||||
|
|
||||||
|
var rulesetInstance = ruleset.CreateInstance();
|
||||||
|
Debug.Assert(rulesetInstance != null);
|
||||||
|
|
||||||
|
// At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods.
|
||||||
|
// Similarly, freeMods is currently empty but should only contain the allowed mods.
|
||||||
|
Mods.Value = initialItem.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||||
|
FreeMods.Value = initialItem.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mods.BindValueChanged(onModsChanged);
|
Mods.BindValueChanged(onModsChanged);
|
||||||
@ -125,13 +141,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray()
|
AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (SelectItem(item))
|
return SelectItem(item);
|
||||||
{
|
|
||||||
itemSelected = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -154,15 +164,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
public override bool OnExiting(ScreenExitEvent e)
|
public override bool OnExiting(ScreenExitEvent e)
|
||||||
{
|
{
|
||||||
if (!itemSelected)
|
|
||||||
{
|
|
||||||
Beatmap.Value = initialBeatmap;
|
|
||||||
Ruleset.Value = initialRuleset;
|
|
||||||
Mods.Value = initialMods;
|
|
||||||
}
|
|
||||||
|
|
||||||
freeModSelectOverlay.Hide();
|
freeModSelectOverlay.Hide();
|
||||||
|
|
||||||
return base.OnExiting(e);
|
return base.OnExiting(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +201,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
freeModSelectOverlayRegistration?.Dispose();
|
freeModSelectOverlayRegistration?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
@ -2,15 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
@ -46,7 +44,7 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public double StartTime { get; protected set; }
|
public double StartTime { get; protected set; }
|
||||||
|
|
||||||
public virtual IEnumerable<double> NonGameplayAdjustments => Enumerable.Empty<double>();
|
public IAdjustableAudioComponent AdjustmentsFromMods { get; } = new AudioAdjustments();
|
||||||
|
|
||||||
private readonly BindableBool isPaused = new BindableBool(true);
|
private readonly BindableBool isPaused = new BindableBool(true);
|
||||||
|
|
||||||
@ -196,7 +194,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
void IAdjustableClock.Reset() => Reset();
|
void IAdjustableClock.Reset() => Reset();
|
||||||
|
|
||||||
public void ResetSpeedAdjustments() => throw new NotImplementedException();
|
public virtual void ResetSpeedAdjustments()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
double IAdjustableClock.Rate
|
double IAdjustableClock.Rate
|
||||||
{
|
{
|
||||||
@ -222,23 +222,5 @@ namespace osu.Game.Screens.Play
|
|||||||
public double FramesPerSecond => GameplayClock.FramesPerSecond;
|
public double FramesPerSecond => GameplayClock.FramesPerSecond;
|
||||||
|
|
||||||
public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo;
|
public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo;
|
||||||
|
|
||||||
public double TrueGameplayRate
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
double baseRate = Rate;
|
|
||||||
|
|
||||||
foreach (double adjustment in NonGameplayAdjustments)
|
|
||||||
{
|
|
||||||
if (Precision.AlmostEquals(adjustment, 0))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
baseRate /= adjustment;
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
osu.Game/Screens/Play/GameplayClockExtensions.cs
Normal file
24
osu.Game/Screens/Play/GameplayClockExtensions.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play
|
||||||
|
{
|
||||||
|
public static class GameplayClockExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The rate of gameplay when playback is at 100%.
|
||||||
|
/// This excludes any seeking / user adjustments.
|
||||||
|
/// </summary>
|
||||||
|
public static double GetTrueGameplayRate(this IGameplayClock clock)
|
||||||
|
{
|
||||||
|
// To handle rewind, we still want to maintain the same direction as the underlying clock.
|
||||||
|
double rate = clock.Rate == 0 ? 1 : Math.Sign(clock.Rate);
|
||||||
|
|
||||||
|
return rate
|
||||||
|
* clock.AdjustmentsFromMods.AggregateFrequency.Value
|
||||||
|
* clock.AdjustmentsFromMods.AggregateTempo.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||||
|
{
|
||||||
|
public class ClicksPerSecondCalculator : Component
|
||||||
|
{
|
||||||
|
private readonly List<double> timestamps = new List<double>();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IGameplayClock gameplayClock { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DrawableRuleset? drawableRuleset { get; set; }
|
||||||
|
|
||||||
|
public int Value { get; private set; }
|
||||||
|
|
||||||
|
// Even though `FrameStabilityContainer` caches as a `GameplayClock`, we need to check it directly via `drawableRuleset`
|
||||||
|
// as this calculator is not contained within the `FrameStabilityContainer` and won't see the dependency.
|
||||||
|
private IGameplayClock clock => drawableRuleset?.FrameStableClock ?? gameplayClock;
|
||||||
|
|
||||||
|
public ClicksPerSecondCalculator()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddInputTimestamp() => timestamps.Add(clock.CurrentTime);
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
double latestValidTime = clock.CurrentTime;
|
||||||
|
double earliestTimeValid = latestValidTime - 1000 * gameplayClock.GetTrueGameplayRate();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = timestamps.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
// handle rewinding by removing future timestamps as we go
|
||||||
|
if (timestamps[i] > latestValidTime)
|
||||||
|
{
|
||||||
|
timestamps.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamps[i] >= earliestTimeValid)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||||
|
{
|
||||||
|
public class ClicksPerSecondCounter : RollingCounter<int>, ISkinnableDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private ClicksPerSecondCalculator calculator { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override double RollingDuration => 350;
|
||||||
|
|
||||||
|
public bool UsesFixedAnchor { get; set; }
|
||||||
|
|
||||||
|
public ClicksPerSecondCounter()
|
||||||
|
{
|
||||||
|
Current.Value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Colour = colours.BlueLighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
Current.Value = calculator.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IHasText CreateText() => new TextComponent();
|
||||||
|
|
||||||
|
private class TextComponent : CompositeDrawable, IHasText
|
||||||
|
{
|
||||||
|
public LocalisableString Text
|
||||||
|
{
|
||||||
|
get => text.Text;
|
||||||
|
set => text.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly OsuSpriteText text;
|
||||||
|
|
||||||
|
public TextComponent()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Spacing = new Vector2(2),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Font = OsuFont.Numeric.With(size: 16, fixedWidth: true)
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Font = OsuFont.Numeric.With(size: 6, fixedWidth: false),
|
||||||
|
Text = @"clicks",
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Font = OsuFont.Numeric.With(size: 6, fixedWidth: false),
|
||||||
|
Text = @"/sec",
|
||||||
|
Padding = new MarginPadding { Bottom = 3f }, // align baseline better
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
public class DefaultSongProgress : SongProgress
|
public class DefaultSongProgress : SongProgress
|
||||||
{
|
{
|
||||||
private const float info_height = 20;
|
|
||||||
private const float bottom_bar_height = 5;
|
private const float bottom_bar_height = 5;
|
||||||
private const float graph_height = SquareGraph.Column.WIDTH * 6;
|
private const float graph_height = SquareGraph.Column.WIDTH * 6;
|
||||||
private const float handle_height = 18;
|
private const float handle_height = 18;
|
||||||
@ -65,7 +64,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = info_height,
|
|
||||||
},
|
},
|
||||||
graph = new SongProgressGraph
|
graph = new SongProgressGraph
|
||||||
{
|
{
|
||||||
@ -178,7 +176,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
Height = bottom_bar_height + graph_height + handle_size.Y + info_height - graph.Y;
|
Height = bottom_bar_height + graph_height + handle_size.Y + info.Height - graph.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBarVisibility()
|
private void updateBarVisibility()
|
||||||
|
@ -59,30 +59,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
|
|
||||||
protected Color4 GetColourForHitResult(HitResult result)
|
protected Color4 GetColourForHitResult(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
return colours.ForHitResult(result);
|
||||||
{
|
|
||||||
case HitResult.SmallTickMiss:
|
|
||||||
case HitResult.LargeTickMiss:
|
|
||||||
case HitResult.Miss:
|
|
||||||
return colours.Red;
|
|
||||||
|
|
||||||
case HitResult.Meh:
|
|
||||||
return colours.Yellow;
|
|
||||||
|
|
||||||
case HitResult.Ok:
|
|
||||||
return colours.Green;
|
|
||||||
|
|
||||||
case HitResult.Good:
|
|
||||||
return colours.GreenLight;
|
|
||||||
|
|
||||||
case HitResult.SmallTickHit:
|
|
||||||
case HitResult.LargeTickHit:
|
|
||||||
case HitResult.Great:
|
|
||||||
return colours.Blue;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return colours.BlueLight;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
@ -14,9 +15,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
public class SongProgressInfo : Container
|
public class SongProgressInfo : Container
|
||||||
{
|
{
|
||||||
private OsuSpriteText timeCurrent;
|
private SizePreservingSpriteText timeCurrent;
|
||||||
private OsuSpriteText timeLeft;
|
private SizePreservingSpriteText timeLeft;
|
||||||
private OsuSpriteText progress;
|
private SizePreservingSpriteText progress;
|
||||||
|
|
||||||
private double startTime;
|
private double startTime;
|
||||||
private double endTime;
|
private double endTime;
|
||||||
@ -46,36 +47,71 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
if (clock != null)
|
if (clock != null)
|
||||||
gameplayClock = clock;
|
gameplayClock = clock;
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
timeCurrent = new OsuSpriteText
|
new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.BottomLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Colour = colours.BlueLighter,
|
AutoSizeAxes = Axes.Both,
|
||||||
Font = OsuFont.Numeric,
|
Child = new UprightAspectMaintainingContainer
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
{
|
||||||
Left = margin,
|
Origin = Anchor.Centre,
|
||||||
},
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scaling = ScaleMode.Vertical,
|
||||||
|
ScalingFactor = 0.5f,
|
||||||
|
Child = timeCurrent = new SizePreservingSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Colour = colours.BlueLighter,
|
||||||
|
Font = OsuFont.Numeric,
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
progress = new OsuSpriteText
|
new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.Centre,
|
||||||
Colour = colours.BlueLighter,
|
AutoSizeAxes = Axes.Both,
|
||||||
Font = OsuFont.Numeric,
|
Child = new UprightAspectMaintainingContainer
|
||||||
},
|
|
||||||
timeLeft = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Colour = colours.BlueLighter,
|
|
||||||
Font = OsuFont.Numeric,
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
{
|
||||||
Right = margin,
|
Origin = Anchor.Centre,
|
||||||
},
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scaling = ScaleMode.Vertical,
|
||||||
|
ScalingFactor = 0.5f,
|
||||||
|
Child = progress = new SizePreservingSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Colour = colours.BlueLighter,
|
||||||
|
Font = OsuFont.Numeric,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Child = new UprightAspectMaintainingContainer
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scaling = ScaleMode.Vertical,
|
||||||
|
ScalingFactor = 0.5f,
|
||||||
|
Child = timeLeft = new SizePreservingSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Colour = colours.BlueLighter,
|
||||||
|
Font = OsuFont.Numeric,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -49,6 +50,9 @@ namespace osu.Game.Screens.Play
|
|||||||
public readonly HoldForMenuButton HoldToQuit;
|
public readonly HoldForMenuButton HoldToQuit;
|
||||||
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
|
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly ClicksPerSecondCalculator clicksPerSecondCalculator;
|
||||||
|
|
||||||
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
|
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
|
||||||
|
|
||||||
private readonly DrawableRuleset drawableRuleset;
|
private readonly DrawableRuleset drawableRuleset;
|
||||||
@ -122,7 +126,8 @@ namespace osu.Game.Screens.Play
|
|||||||
KeyCounter = CreateKeyCounter(),
|
KeyCounter = CreateKeyCounter(),
|
||||||
HoldToQuit = CreateHoldForMenuButton(),
|
HoldToQuit = CreateHoldForMenuButton(),
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
clicksPerSecondCalculator = new ClicksPerSecondCalculator()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +264,11 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset)
|
protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset)
|
||||||
{
|
{
|
||||||
(drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter);
|
if (drawableRuleset is ICanAttachHUDPieces attachTarget)
|
||||||
|
{
|
||||||
|
attachTarget.Attach(KeyCounter);
|
||||||
|
attachTarget.Attach(clicksPerSecondCalculator);
|
||||||
|
}
|
||||||
|
|
||||||
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
|
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
|
||||||
@ -9,12 +9,6 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
public interface IGameplayClock : IFrameBasedClock
|
public interface IGameplayClock : IFrameBasedClock
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The rate of gameplay when playback is at 100%.
|
|
||||||
/// This excludes any seeking / user adjustments.
|
|
||||||
/// </summary>
|
|
||||||
double TrueGameplayRate { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time from which the clock should start. Will be seeked to on calling <see cref="GameplayClockContainer.Reset"/>.
|
/// The time from which the clock should start. Will be seeked to on calling <see cref="GameplayClockContainer.Reset"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -25,9 +19,9 @@ namespace osu.Game.Screens.Play
|
|||||||
double StartTime { get; }
|
double StartTime { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All adjustments applied to this clock which don't come from gameplay or mods.
|
/// All adjustments applied to this clock which come from mods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IEnumerable<double> NonGameplayAdjustments { get; }
|
IAdjustableAudioComponent AdjustmentsFromMods { get; }
|
||||||
|
|
||||||
IBindable<bool> IsPaused { get; }
|
IBindable<bool> IsPaused { get; }
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -11,6 +11,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
@ -41,9 +42,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private readonly WorkingBeatmap beatmap;
|
private readonly WorkingBeatmap beatmap;
|
||||||
|
|
||||||
private readonly double skipTargetTime;
|
private readonly Track track;
|
||||||
|
|
||||||
private readonly List<Bindable<double>> nonGameplayAdjustments = new List<Bindable<double>>();
|
private readonly double skipTargetTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores the time at which the last <see cref="StopGameplayClock"/> call was triggered.
|
/// Stores the time at which the last <see cref="StopGameplayClock"/> call was triggered.
|
||||||
@ -56,7 +57,8 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private double? actualStopTime;
|
private double? actualStopTime;
|
||||||
|
|
||||||
public override IEnumerable<double> NonGameplayAdjustments => nonGameplayAdjustments.Select(b => b.Value);
|
[Resolved]
|
||||||
|
private MusicController musicController { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new master gameplay clock container.
|
/// Create a new master gameplay clock container.
|
||||||
@ -69,6 +71,8 @@ namespace osu.Game.Screens.Play
|
|||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
this.skipTargetTime = skipTargetTime;
|
this.skipTargetTime = skipTargetTime;
|
||||||
|
|
||||||
|
track = beatmap.Track;
|
||||||
|
|
||||||
StartTime = findEarliestStartTime();
|
StartTime = findEarliestStartTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,15 +199,12 @@ namespace osu.Game.Screens.Play
|
|||||||
if (speedAdjustmentsApplied)
|
if (speedAdjustmentsApplied)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (SourceClock is not Track track)
|
musicController.ResetTrackAdjustments();
|
||||||
return;
|
|
||||||
|
|
||||||
|
track.BindAdjustments(AdjustmentsFromMods);
|
||||||
track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
|
track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
|
||||||
track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
|
track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
|
||||||
|
|
||||||
nonGameplayAdjustments.Add(GameplayClock.ExternalPauseFrequencyAdjust);
|
|
||||||
nonGameplayAdjustments.Add(UserPlaybackRate);
|
|
||||||
|
|
||||||
speedAdjustmentsApplied = true;
|
speedAdjustmentsApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,15 +213,10 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!speedAdjustmentsApplied)
|
if (!speedAdjustmentsApplied)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (SourceClock is not Track track)
|
track.UnbindAdjustments(AdjustmentsFromMods);
|
||||||
return;
|
|
||||||
|
|
||||||
track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
|
track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
|
||||||
track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
|
track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
|
||||||
|
|
||||||
nonGameplayAdjustments.Remove(GameplayClock.ExternalPauseFrequencyAdjust);
|
|
||||||
nonGameplayAdjustments.Remove(UserPlaybackRate);
|
|
||||||
|
|
||||||
speedAdjustmentsApplied = false;
|
speedAdjustmentsApplied = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -996,12 +996,8 @@ namespace osu.Game.Screens.Play
|
|||||||
foreach (var mod in GameplayState.Mods.OfType<IApplicableToHUD>())
|
foreach (var mod in GameplayState.Mods.OfType<IApplicableToHUD>())
|
||||||
mod.ApplyToHUD(HUDOverlay);
|
mod.ApplyToHUD(HUDOverlay);
|
||||||
|
|
||||||
// Our mods are local copies of the global mods so they need to be re-applied to the track.
|
|
||||||
// This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack.
|
|
||||||
// Todo: In the future, player will receive in a track and will probably not have to worry about this...
|
|
||||||
musicController.ResetTrackAdjustments();
|
|
||||||
foreach (var mod in GameplayState.Mods.OfType<IApplicableToTrack>())
|
foreach (var mod in GameplayState.Mods.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToTrack(musicController.CurrentTrack);
|
mod.ApplyToTrack(GameplayClockContainer.AdjustmentsFromMods);
|
||||||
|
|
||||||
updateGameplayState();
|
updateGameplayState();
|
||||||
|
|
||||||
@ -1053,6 +1049,7 @@ namespace osu.Game.Screens.Play
|
|||||||
musicController.ResetTrackAdjustments();
|
musicController.ResetTrackAdjustments();
|
||||||
|
|
||||||
fadeOut();
|
fadeOut();
|
||||||
|
|
||||||
return base.OnExiting(e);
|
return base.OnExiting(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,8 +63,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (player != null)
|
if (player != null)
|
||||||
{
|
{
|
||||||
importedScore = realm.Run(r => r.Find<ScoreInfo>(player.Score.ScoreInfo.ID)?.Detach());
|
importedScore = realm.Run(r => r.Find<ScoreInfo>(player.Score.ScoreInfo.ID)?.Detach());
|
||||||
if (importedScore != null)
|
state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded;
|
||||||
state.Value = DownloadState.LocallyAvailable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.BindValueChanged(state =>
|
state.BindValueChanged(state =>
|
||||||
|
@ -7,7 +7,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -57,7 +56,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList();
|
this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] bins;
|
private IDictionary<HitResult, int>[] bins;
|
||||||
private double binSize;
|
private double binSize;
|
||||||
private double hitOffset;
|
private double hitOffset;
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
if (hitEvents == null || hitEvents.Count == 0)
|
if (hitEvents == null || hitEvents.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bins = new int[total_timing_distribution_bins];
|
bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary<HitResult, int>()).ToArray<IDictionary<HitResult, int>>();
|
||||||
|
|
||||||
binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins);
|
binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins);
|
||||||
|
|
||||||
@ -89,7 +88,8 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
{
|
{
|
||||||
bool roundUp = true;
|
bool roundUp = true;
|
||||||
|
|
||||||
Array.Clear(bins, 0, bins.Length);
|
foreach (var bin in bins)
|
||||||
|
bin.Clear();
|
||||||
|
|
||||||
foreach (var e in hitEvents)
|
foreach (var e in hitEvents)
|
||||||
{
|
{
|
||||||
@ -110,23 +110,23 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
|
|
||||||
// may be out of range when applying an offset. for such cases we can just drop the results.
|
// may be out of range when applying an offset. for such cases we can just drop the results.
|
||||||
if (index >= 0 && index < bins.Length)
|
if (index >= 0 && index < bins.Length)
|
||||||
bins[index]++;
|
{
|
||||||
|
bins[index].TryGetValue(e.Result, out int value);
|
||||||
|
bins[index][e.Result] = ++value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (barDrawables != null)
|
if (barDrawables != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < barDrawables.Length; i++)
|
for (int i = 0; i < barDrawables.Length; i++)
|
||||||
{
|
{
|
||||||
barDrawables[i].UpdateOffset(bins[i]);
|
barDrawables[i].UpdateOffset(bins[i].Sum(b => b.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int maxCount = bins.Max();
|
int maxCount = bins.Max(b => b.Values.Sum());
|
||||||
barDrawables = new Bar[total_timing_distribution_bins];
|
barDrawables = bins.Select((bin, i) => new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index)).ToArray();
|
||||||
|
|
||||||
for (int i = 0; i < barDrawables.Length; i++)
|
|
||||||
barDrawables[i] = new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index);
|
|
||||||
|
|
||||||
Container axisFlow;
|
Container axisFlow;
|
||||||
|
|
||||||
@ -209,50 +209,97 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
|
|
||||||
private class Bar : CompositeDrawable
|
private class Bar : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly float value;
|
private float totalValue => values.Sum(v => v.Value);
|
||||||
private readonly float maxValue;
|
private float basalHeight => BoundingBox.Width / BoundingBox.Height;
|
||||||
|
private float availableHeight => 1 - basalHeight;
|
||||||
|
|
||||||
private readonly Circle boxOriginal;
|
private readonly IReadOnlyList<KeyValuePair<HitResult, int>> values;
|
||||||
|
private readonly float maxValue;
|
||||||
|
private readonly bool isCentre;
|
||||||
|
|
||||||
|
private Circle[] boxOriginals;
|
||||||
private Circle boxAdjustment;
|
private Circle boxAdjustment;
|
||||||
|
|
||||||
private const float minimum_height = 0.05f;
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
public Bar(float value, float maxValue, bool isCentre)
|
public Bar(IDictionary<HitResult, int> values, float maxValue, bool isCentre)
|
||||||
{
|
{
|
||||||
this.value = value;
|
this.values = values.OrderBy(v => v.Key.GetIndexForOrderedDisplay()).ToList();
|
||||||
this.maxValue = maxValue;
|
this.maxValue = maxValue;
|
||||||
|
this.isCentre = isCentre;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
}
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
if (values.Any())
|
||||||
{
|
{
|
||||||
boxOriginal = new Circle
|
boxOriginals = values.Select((v, i) => new Circle
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"),
|
Colour = isCentre && i == 0 ? Color4.White : colours.ForHitResult(v.Key),
|
||||||
Height = minimum_height,
|
Height = 0,
|
||||||
},
|
}).ToArray();
|
||||||
};
|
// The bars of the stacked bar graph will be processed (stacked) from the bottom, which is the base position,
|
||||||
|
// to the top, and the bottom bar should be drawn more toward the front by design,
|
||||||
|
// while the drawing order is from the back to the front, so the order passed to `InternalChildren` is the opposite.
|
||||||
|
InternalChildren = boxOriginals.Reverse().ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// A bin with no value draws a grey dot instead.
|
||||||
|
InternalChildren = boxOriginals = new[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Colour = isCentre ? Color4.White : Color4.Gray,
|
||||||
|
Height = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const double duration = 300;
|
private const double duration = 300;
|
||||||
|
|
||||||
|
private float offsetForValue(float value)
|
||||||
|
{
|
||||||
|
return availableHeight * value / maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float heightForValue(float value)
|
||||||
|
{
|
||||||
|
return basalHeight + offsetForValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
float height = Math.Clamp(value / maxValue, minimum_height, 1);
|
foreach (var boxOriginal in boxOriginals)
|
||||||
|
boxOriginal.Height = basalHeight;
|
||||||
|
|
||||||
if (height > minimum_height)
|
float offsetValue = 0;
|
||||||
boxOriginal.ResizeHeightTo(height, duration, Easing.OutQuint);
|
|
||||||
|
for (int i = 0; i < values.Count; i++)
|
||||||
|
{
|
||||||
|
boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint);
|
||||||
|
boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint);
|
||||||
|
offsetValue -= values[i].Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateOffset(float adjustment)
|
public void UpdateOffset(float adjustment)
|
||||||
{
|
{
|
||||||
bool hasAdjustment = adjustment != value && adjustment / maxValue >= minimum_height;
|
bool hasAdjustment = adjustment != totalValue;
|
||||||
|
|
||||||
if (boxAdjustment == null)
|
if (boxAdjustment == null)
|
||||||
{
|
{
|
||||||
@ -271,7 +318,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, minimum_height, 1), duration, Easing.OutQuint);
|
boxAdjustment.ResizeHeightTo(heightForValue(adjustment), duration, Easing.OutQuint);
|
||||||
boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint);
|
boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,13 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private class MinimumStarsSlider : StarsSlider
|
private class MinimumStarsSlider : StarsSlider
|
||||||
{
|
{
|
||||||
|
public MinimumStarsSlider()
|
||||||
|
: base("0")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars");
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -82,6 +89,11 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private class MaximumStarsSlider : StarsSlider
|
private class MaximumStarsSlider : StarsSlider
|
||||||
{
|
{
|
||||||
|
public MaximumStarsSlider()
|
||||||
|
: base("∞")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -96,10 +108,17 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private class StarsSlider : OsuSliderBar<double>
|
private class StarsSlider : OsuSliderBar<double>
|
||||||
{
|
{
|
||||||
|
private readonly string defaultString;
|
||||||
|
|
||||||
public override LocalisableString TooltipText => Current.IsDefault
|
public override LocalisableString TooltipText => Current.IsDefault
|
||||||
? UserInterfaceStrings.NoLimit
|
? UserInterfaceStrings.NoLimit
|
||||||
: Current.Value.ToString(@"0.## stars");
|
: Current.Value.ToString(@"0.## stars");
|
||||||
|
|
||||||
|
protected StarsSlider(string defaultString)
|
||||||
|
{
|
||||||
|
this.defaultString = defaultString;
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
base.OnHover(e);
|
base.OnHover(e);
|
||||||
@ -125,7 +144,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
Current.BindValueChanged(current =>
|
Current.BindValueChanged(current =>
|
||||||
{
|
{
|
||||||
currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : "∞";
|
currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : defaultString;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,10 +127,10 @@ namespace osu.Game.Screens.Select
|
|||||||
config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1);
|
config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1);
|
||||||
});
|
});
|
||||||
|
|
||||||
string lowerStar = filter.UserStarDifficulty.Min == null ? "∞" : $"{filter.UserStarDifficulty.Min:N1}";
|
string lowerStar = $"{filter.UserStarDifficulty.Min ?? 0:N1}";
|
||||||
string upperStar = filter.UserStarDifficulty.Max == null ? "∞" : $"{filter.UserStarDifficulty.Max:N1}";
|
string upperStar = filter.UserStarDifficulty.Max == null ? "∞" : $"{filter.UserStarDifficulty.Max:N1}";
|
||||||
|
|
||||||
textFlow.AddText($" the {lowerStar}-{upperStar} star difficulty filter.");
|
textFlow.AddText($" the {lowerStar} - {upperStar} star difficulty filter.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch).
|
// TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch).
|
||||||
|
@ -11,9 +11,12 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Skinning.Editor
|
namespace osu.Game.Skinning.Editor
|
||||||
{
|
{
|
||||||
@ -90,6 +93,47 @@ namespace osu.Game.Skinning.Editor
|
|||||||
base.AddBlueprintFor(item);
|
base.AddBlueprintFor(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case Key.Left:
|
||||||
|
moveSelection(new Vector2(-1, 0));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Key.Right:
|
||||||
|
moveSelection(new Vector2(1, 0));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Key.Up:
|
||||||
|
moveSelection(new Vector2(0, -1));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Key.Down:
|
||||||
|
moveSelection(new Vector2(0, 1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move the current selection spatially by the specified delta, in screen coordinates (ie. the same coordinates as the blueprints).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delta"></param>
|
||||||
|
private void moveSelection(Vector2 delta)
|
||||||
|
{
|
||||||
|
var firstBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault();
|
||||||
|
|
||||||
|
if (firstBlueprint == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// convert to game space coordinates
|
||||||
|
delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero);
|
||||||
|
|
||||||
|
SelectionHandler.HandleMovement(new MoveSelectionEvent<ISkinnableDrawable>(firstBlueprint, delta));
|
||||||
|
}
|
||||||
|
|
||||||
protected override SelectionHandler<ISkinnableDrawable> CreateSelectionHandler() => new SkinSelectionHandler();
|
protected override SelectionHandler<ISkinnableDrawable> CreateSelectionHandler() => new SkinSelectionHandler();
|
||||||
|
|
||||||
protected override SelectionBlueprint<ISkinnableDrawable> CreateBlueprintFor(ISkinnableDrawable component)
|
protected override SelectionBlueprint<ISkinnableDrawable> CreateBlueprintFor(ISkinnableDrawable component)
|
||||||
|
@ -81,13 +81,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
public void Disconnect() => isConnected.Value = false;
|
public void Disconnect() => isConnected.Value = false;
|
||||||
|
|
||||||
public MultiplayerRoomUser AddUser(APIUser user, bool markAsPlaying = false)
|
public MultiplayerRoomUser AddUser(APIUser user, bool markAsPlaying = false)
|
||||||
{
|
=> AddUser(new MultiplayerRoomUser(user.Id) { User = user }, markAsPlaying);
|
||||||
var roomUser = new MultiplayerRoomUser(user.Id) { User = user };
|
|
||||||
|
|
||||||
|
public MultiplayerRoomUser AddUser(MultiplayerRoomUser roomUser, bool markAsPlaying = false)
|
||||||
|
{
|
||||||
addUser(roomUser);
|
addUser(roomUser);
|
||||||
|
|
||||||
if (markAsPlaying)
|
if (markAsPlaying)
|
||||||
PlayingUserIds.Add(user.Id);
|
PlayingUserIds.Add(roomUser.UserID);
|
||||||
|
|
||||||
return roomUser;
|
return roomUser;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -37,6 +38,7 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
private readonly Dictionary<int, ReplayFrame> lastReceivedUserFrames = new Dictionary<int, ReplayFrame>();
|
private readonly Dictionary<int, ReplayFrame> lastReceivedUserFrames = new Dictionary<int, ReplayFrame>();
|
||||||
|
|
||||||
private readonly Dictionary<int, int> userBeatmapDictionary = new Dictionary<int, int>();
|
private readonly Dictionary<int, int> userBeatmapDictionary = new Dictionary<int, int>();
|
||||||
|
private readonly Dictionary<int, APIMod[]> userModsDictionary = new Dictionary<int, APIMod[]>();
|
||||||
private readonly Dictionary<int, int> userNextFrameDictionary = new Dictionary<int, int>();
|
private readonly Dictionary<int, int> userNextFrameDictionary = new Dictionary<int, int>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -52,9 +54,11 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user to start play for.</param>
|
/// <param name="userId">The user to start play for.</param>
|
||||||
/// <param name="beatmapId">The playing beatmap id.</param>
|
/// <param name="beatmapId">The playing beatmap id.</param>
|
||||||
public void SendStartPlay(int userId, int beatmapId)
|
/// <param name="mods">The mods the user has applied.</param>
|
||||||
|
public void SendStartPlay(int userId, int beatmapId, APIMod[]? mods = null)
|
||||||
{
|
{
|
||||||
userBeatmapDictionary[userId] = beatmapId;
|
userBeatmapDictionary[userId] = beatmapId;
|
||||||
|
userModsDictionary[userId] = mods ?? Array.Empty<APIMod>();
|
||||||
userNextFrameDictionary[userId] = 0;
|
userNextFrameDictionary[userId] = 0;
|
||||||
sendPlayingState(userId);
|
sendPlayingState(userId);
|
||||||
}
|
}
|
||||||
@ -73,10 +77,12 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
{
|
{
|
||||||
BeatmapID = userBeatmapDictionary[userId],
|
BeatmapID = userBeatmapDictionary[userId],
|
||||||
RulesetID = 0,
|
RulesetID = 0,
|
||||||
|
Mods = userModsDictionary[userId],
|
||||||
State = state
|
State = state
|
||||||
});
|
});
|
||||||
|
|
||||||
userBeatmapDictionary.Remove(userId);
|
userBeatmapDictionary.Remove(userId);
|
||||||
|
userModsDictionary.Remove(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -125,6 +131,7 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
// Track the local user's playing beatmap ID.
|
// Track the local user's playing beatmap ID.
|
||||||
Debug.Assert(state.BeatmapID != null);
|
Debug.Assert(state.BeatmapID != null);
|
||||||
userBeatmapDictionary[api.LocalUser.Value.Id] = state.BeatmapID.Value;
|
userBeatmapDictionary[api.LocalUser.Value.Id] = state.BeatmapID.Value;
|
||||||
|
userModsDictionary[api.LocalUser.Value.Id] = state.Mods.ToArray();
|
||||||
|
|
||||||
return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state);
|
return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state);
|
||||||
}
|
}
|
||||||
@ -158,6 +165,7 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
{
|
{
|
||||||
BeatmapID = userBeatmapDictionary[userId],
|
BeatmapID = userBeatmapDictionary[userId],
|
||||||
RulesetID = 0,
|
RulesetID = 0,
|
||||||
|
Mods = userModsDictionary[userId],
|
||||||
State = SpectatedUserState.Playing
|
State = SpectatedUserState.Playing
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.15.1" />
|
<PackageReference Include="Realm" Version="10.15.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.901.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.908.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.20.1" />
|
<PackageReference Include="Sentry" Version="3.20.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.901.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.908.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.901.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.908.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user