Compare commits
58 Commits
2025.1118.
...
2025.1119.
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bd996ee43 | |||
| 37b9f91d42 | |||
| 1a5a5606dc | |||
| 87ff1051e9 | |||
|
|
80474565fc | ||
|
|
89f2c7160d | ||
|
|
f0ca079fe6 | ||
|
|
19b6761697 | ||
|
|
edf08b176a | ||
|
|
7b952b83bf | ||
|
|
214122f633 | ||
|
|
76c0bd4750 | ||
|
|
4bf3d9397f | ||
|
|
8b778e8106 | ||
|
|
ce5e54c9d2 | ||
|
|
45e8df7af2 | ||
|
|
1c30cb8371 | ||
|
|
bd4ed49c06 | ||
|
|
a593a40429 | ||
|
|
02b88de76e | ||
|
|
b64abbf1f5 | ||
|
|
4265e72180 | ||
|
|
cb9d9734d6 | ||
|
|
5763b7dbe9 | ||
|
|
e1baa03622 | ||
|
|
4f783f8c41 | ||
|
|
4c72a60ee2 | ||
|
|
013de9f85d | ||
|
|
cd6c9405fe | ||
|
|
822cb9e2fb | ||
|
|
680614fbee | ||
|
|
cb8ddc706f | ||
|
|
04d2ce150a | ||
|
|
eaffb89b4c | ||
|
|
650a61539b | ||
|
|
75bc934aa5 | ||
|
|
8d80e2bd2c | ||
|
|
34a3b1ba78 | ||
|
|
b354fa4472 | ||
|
|
1fbe1bd6c9 | ||
|
|
3c215f6574 | ||
|
|
8c28d26130 | ||
|
|
933fbd274d | ||
|
|
55ae7e8bb8 | ||
|
|
4a22ef88ce | ||
|
|
43ca046f9b | ||
|
|
dbefba57ce | ||
|
|
20904de276 | ||
|
|
fb2fe65a77 | ||
|
|
4662c5d678 | ||
|
|
d98cb9ca45 | ||
|
|
a7e4aa8b12 | ||
|
|
78f639d760 | ||
|
|
4ea03d0e07 | ||
|
|
cf0e5edf34 | ||
|
|
a825104688 | ||
|
|
fadcb9882c | ||
|
|
0558f9f2d9 |
@@ -73,6 +73,9 @@ Aside from the above, below is a brief checklist of things to watch out when you
|
|||||||
After you're done with your changes and you wish to open the PR, please observe the following recommendations:
|
After you're done with your changes and you wish to open the PR, please observe the following recommendations:
|
||||||
|
|
||||||
- Please submit the pull request from a [topic branch](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#_topic_branch) (not `master`), and keep the *Allow edits from maintainers* check box selected, so that we can push fixes to your PR if necessary.
|
- Please submit the pull request from a [topic branch](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#_topic_branch) (not `master`), and keep the *Allow edits from maintainers* check box selected, so that we can push fixes to your PR if necessary.
|
||||||
|
- Please pick the following target branch for your pull request:
|
||||||
|
- `pp-dev`, if the change impacts star rating or performance points calculations for any of the rulesets,
|
||||||
|
- `master`, otherwise.
|
||||||
- Please avoid pushing untested or incomplete code.
|
- Please avoid pushing untested or incomplete code.
|
||||||
- Please do not force-push or rebase unless we ask you to.
|
- Please do not force-push or rebase unless we ask you to.
|
||||||
- Please do not merge `master` continually if there are no conflicts to resolve. We will do this for you when the change is ready for merge.
|
- Please do not merge `master` continually if there are no conflicts to resolve. We will do this for you when the change is ready for merge.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1028.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1118.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
internal partial class DiscordRichPresence : Component
|
internal partial class DiscordRichPresence : Component
|
||||||
{
|
{
|
||||||
private const string client_id = "1216669957799018608";
|
private const string client_id = "1440647613358800918";
|
||||||
|
|
||||||
private DiscordRpcClient client = null!;
|
private DiscordRpcClient client = null!;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[TestCase("mania-samples")]
|
[TestCase("mania-samples")]
|
||||||
[TestCase("mania-slider")] // e.g. second and fourth notes of https://osu.ppy.sh/beatmapsets/73883#mania/216407
|
[TestCase("mania-slider")] // e.g. second and fourth notes of https://osu.ppy.sh/beatmapsets/73883#mania/216407
|
||||||
[TestCase("slider-convert-samples")]
|
[TestCase("slider-convert-samples")]
|
||||||
|
[TestCase("spinner-convert-samples")]
|
||||||
public void Test(string name) => base.Test(name);
|
public void Test(string name) => base.Test(name);
|
||||||
|
|
||||||
protected override IEnumerable<SampleConvertValue> CreateConvertValue(HitObject hitObject)
|
protected override IEnumerable<SampleConvertValue> CreateConvertValue(HitObject hitObject)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
Mode: 0
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
0,300,4,0,2,100,1,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
444,320,1000,5,2,0:0:0:0:
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"Mappings": [{
|
||||||
|
"StartTime": 1000.0,
|
||||||
|
"Objects": [{
|
||||||
|
"StartTime": 1000.0,
|
||||||
|
"EndTime": 8000.0,
|
||||||
|
"Column": 0,
|
||||||
|
"PlaySlidingSamples": false,
|
||||||
|
"NodeSamples": [
|
||||||
|
["Gameplay/soft-hitnormal"],
|
||||||
|
["Gameplay/soft-hitnormal", "Gameplay/soft-hitfinish"]
|
||||||
|
],
|
||||||
|
"Samples": ["Gameplay/soft-hitnormal", "Gameplay/soft-hitfinish"],
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
Mode: 0
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:5
|
||||||
|
CircleSize:5
|
||||||
|
OverallDifficulty:5
|
||||||
|
ApproachRate:5
|
||||||
|
SliderMultiplier:1.4
|
||||||
|
SliderTickRate:1
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
0,500,4,2,0,100,1,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
256,192,1000,8,4,8000,0:2:0:0:
|
||||||
@@ -45,5 +45,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AssertBeatmapLookup(expected_sample);
|
AssertBeatmapLookup(expected_sample);
|
||||||
AssertNoLookup(unwanted_sample);
|
AssertNoLookup(unwanted_sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestConvertHitObjectCustomSampleBank()
|
||||||
|
{
|
||||||
|
const string beatmap_sample = "normal-hitwhistle2";
|
||||||
|
const string user_skin_sample = "normal-hitnormal";
|
||||||
|
|
||||||
|
SetupSkins(beatmap_sample, user_skin_sample);
|
||||||
|
|
||||||
|
CreateTestWithBeatmap("convert-beatmap-custom-sample-bank.osu");
|
||||||
|
|
||||||
|
AssertBeatmapLookup(beatmap_sample);
|
||||||
|
AssertUserLookup(user_skin_sample);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
Duration = endTime - HitObject.StartTime,
|
Duration = endTime - HitObject.StartTime,
|
||||||
Column = column,
|
Column = column,
|
||||||
Samples = HitObject.Samples,
|
Samples = HitObject.Samples,
|
||||||
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
|
NodeSamples =
|
||||||
|
[
|
||||||
|
HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_NORMAL).ToList(),
|
||||||
|
HitObject.Samples
|
||||||
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -64,11 +64,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
private readonly Lazy<bool> hasKeyTexture;
|
private readonly Lazy<bool> hasKeyTexture;
|
||||||
|
|
||||||
private readonly ManiaBeatmap beatmap;
|
private readonly ManiaBeatmap beatmap;
|
||||||
|
private readonly bool isBeatmapConverted;
|
||||||
|
|
||||||
public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap)
|
public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap)
|
||||||
: base(skin)
|
: base(skin)
|
||||||
{
|
{
|
||||||
this.beatmap = (ManiaBeatmap)beatmap;
|
this.beatmap = (ManiaBeatmap)beatmap;
|
||||||
|
isBeatmapConverted = !beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
|
||||||
|
|
||||||
isLegacySkin = new Lazy<bool>(() => GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version) != null);
|
isLegacySkin = new Lazy<bool>(() => GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version) != null);
|
||||||
hasKeyTexture = new Lazy<bool>(() =>
|
hasKeyTexture = new Lazy<bool>(() =>
|
||||||
@@ -196,8 +198,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
|
|
||||||
public override ISample GetSample(ISampleInfo sampleInfo)
|
public override ISample GetSample(ISampleInfo sampleInfo)
|
||||||
{
|
{
|
||||||
// layered hit sounds never play in mania
|
// layered hit sounds never play in mania-native beatmaps (but do play on converts)
|
||||||
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered)
|
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered && !isBeatmapConverted)
|
||||||
return new SampleVirtual();
|
return new SampleVirtual();
|
||||||
|
|
||||||
return base.GetSample(sampleInfo);
|
return base.GetSample(sampleInfo);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@@ -10,6 +11,7 @@ using osu.Framework.Input.States;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Testing.Input;
|
using osu.Framework.Testing.Input;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
@@ -58,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
foreach (var smokeContainer in smokeContainers)
|
foreach (var smokeContainer in smokeContainers)
|
||||||
{
|
{
|
||||||
if (smokeContainer.Children.Count != 0)
|
if (smokeContainer.Children.OfType<SkinnableDrawable>().Any())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
if (LastAcceptedAction != null && nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
if (LastAcceptedAction != null && nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||||
LastAcceptedAction = null;
|
LastAcceptedAction = null;
|
||||||
|
|
||||||
|
if (LastAcceptedAction != null && gameplayClock.IsRewinding)
|
||||||
|
LastAcceptedAction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract bool CheckValidNewAction(OsuAction action);
|
protected abstract bool CheckValidNewAction(OsuAction action);
|
||||||
|
|||||||
@@ -77,9 +77,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
LifetimeStart = smokeStartTime = Time.Current;
|
public void StartDrawing(double time)
|
||||||
|
{
|
||||||
|
LifetimeStart = smokeStartTime = time;
|
||||||
|
LifetimeEnd = smokeEndTime = double.MaxValue;
|
||||||
|
SmokePoints.Clear();
|
||||||
|
lastPosition = null;
|
||||||
totalDistance = pointInterval;
|
totalDistance = pointInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@@ -19,17 +19,24 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler<OsuAction>
|
public partial class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler<OsuAction>
|
||||||
{
|
{
|
||||||
|
private DrawablePool<SmokeSkinnableDrawable> segmentPool = null!;
|
||||||
private SmokeSkinnableDrawable? currentSegmentSkinnable;
|
private SmokeSkinnableDrawable? currentSegmentSkinnable;
|
||||||
|
|
||||||
private Vector2 lastMousePosition;
|
private Vector2 lastMousePosition;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 _) => true;
|
public override bool ReceivePositionalInputAt(Vector2 _) => true;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(segmentPool = new DrawablePool<SmokeSkinnableDrawable>(10));
|
||||||
|
}
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
{
|
{
|
||||||
if (e.Action == OsuAction.Smoke)
|
if (e.Action == OsuAction.Smoke)
|
||||||
{
|
{
|
||||||
AddInternal(currentSegmentSkinnable = new SmokeSkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment()));
|
AddInternal(currentSegmentSkinnable = segmentPool.Get(segment => segment.Segment?.StartDrawing(Time.Current)));
|
||||||
|
|
||||||
// Add initial position immediately.
|
// Add initial position immediately.
|
||||||
addPosition();
|
addPosition();
|
||||||
@@ -59,17 +66,19 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPosition() => (currentSegmentSkinnable?.Drawable as SmokeSegment)?.AddPosition(lastMousePosition, Time.Current);
|
private void addPosition() => currentSegmentSkinnable?.Segment?.AddPosition(lastMousePosition, Time.Current);
|
||||||
|
|
||||||
private partial class SmokeSkinnableDrawable : SkinnableDrawable
|
private partial class SmokeSkinnableDrawable : SkinnableDrawable
|
||||||
{
|
{
|
||||||
|
public SmokeSegment? Segment => Drawable as SmokeSegment;
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => true;
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
public override double LifetimeStart => Drawable.LifetimeStart;
|
public override double LifetimeStart => Drawable.LifetimeStart;
|
||||||
public override double LifetimeEnd => Drawable.LifetimeEnd;
|
public override double LifetimeEnd => Drawable.LifetimeEnd;
|
||||||
|
|
||||||
public SmokeSkinnableDrawable(ISkinComponentLookup lookup, Func<ISkinComponentLookup, Drawable>? defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
|
public SmokeSkinnableDrawable()
|
||||||
: base(lookup, defaultImplementation, confineMode)
|
: base(new OsuSkinComponentLookup(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
private int rollingHits;
|
private int rollingHits;
|
||||||
|
|
||||||
private readonly Container tickContainer;
|
private readonly Container tickContainer;
|
||||||
|
private SkinnableDrawable headPiece;
|
||||||
|
|
||||||
private Color4 colourIdle;
|
private Color4 colourIdle;
|
||||||
private Color4 colourEngaged;
|
private Color4 colourEngaged;
|
||||||
@@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
Content.Add(tickContainer = new Container
|
Content.Add(tickContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = float.MinValue
|
Depth = -1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +80,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
protected override void RecreatePieces()
|
protected override void RecreatePieces()
|
||||||
{
|
{
|
||||||
|
if (headPiece != null)
|
||||||
|
Content.Remove(headPiece, true);
|
||||||
|
|
||||||
base.RecreatePieces();
|
base.RecreatePieces();
|
||||||
|
|
||||||
|
Content.Add(headPiece = createHeadPiece());
|
||||||
|
|
||||||
updateColour();
|
updateColour();
|
||||||
Height = HitObject.IsStrong ? TaikoStrongableHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE;
|
Height = HitObject.IsStrong ? TaikoStrongableHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE;
|
||||||
}
|
}
|
||||||
@@ -122,6 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollBody),
|
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollBody),
|
||||||
_ => new ElongatedCirclePiece());
|
_ => new ElongatedCirclePiece());
|
||||||
|
|
||||||
|
private SkinnableDrawable createHeadPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollHead), _ => Empty())
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Depth = -2,
|
||||||
|
};
|
||||||
|
|
||||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject obj, JudgementResult result)
|
private void onNewResult(DrawableHitObject obj, JudgementResult result)
|
||||||
@@ -174,7 +187,23 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
private void updateColour(double fadeDuration = 0)
|
private void updateColour(double fadeDuration = 0)
|
||||||
{
|
{
|
||||||
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
|
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
|
||||||
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, fadeDuration);
|
|
||||||
|
if (fadeDuration == 0)
|
||||||
|
{
|
||||||
|
// fade duration is 0 when calling via `RecreatePieces()`.
|
||||||
|
// in this case we want to apply the colour *without* using transforms.
|
||||||
|
// using transforms may result in the application of colour being undone via `DrawableHitObject.UpdateState()` clearing transforms.
|
||||||
|
if (MainPiece.Drawable is IHasAccentColour mainPieceWithAccentColour)
|
||||||
|
mainPieceWithAccentColour.AccentColour = newColour;
|
||||||
|
|
||||||
|
if (headPiece.Drawable is IHasAccentColour headPieceWithAccentColour)
|
||||||
|
headPieceWithAccentColour.AccentColour = newColour;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, fadeDuration);
|
||||||
|
(headPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, fadeDuration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class StrongNestedHit : DrawableStrongNestedHit
|
public partial class StrongNestedHit : DrawableStrongNestedHit
|
||||||
|
|||||||
@@ -1,7 +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.
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@@ -21,14 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// the reason why this calculation is so involved is that the head & tail sprites have different sizes/radii.
|
var headCentre = (body.ScreenSpaceDrawQuad.TopLeft + body.ScreenSpaceDrawQuad.BottomLeft) / 2;
|
||||||
// therefore naively taking the SSDQs of them and making a quad out of them results in a trapezoid shape and not a box.
|
|
||||||
var headCentre = headCircle.ScreenSpaceDrawQuad.Centre;
|
|
||||||
var tailCentre = (tailCircle.ScreenSpaceDrawQuad.TopLeft + tailCircle.ScreenSpaceDrawQuad.BottomLeft) / 2;
|
var tailCentre = (tailCircle.ScreenSpaceDrawQuad.TopLeft + tailCircle.ScreenSpaceDrawQuad.BottomLeft) / 2;
|
||||||
|
|
||||||
float headRadius = headCircle.ScreenSpaceDrawQuad.Height / 2;
|
float radius = body.ScreenSpaceDrawQuad.Height / 2;
|
||||||
float tailRadius = tailCircle.ScreenSpaceDrawQuad.Height / 2;
|
|
||||||
float radius = Math.Max(headRadius, tailRadius);
|
|
||||||
|
|
||||||
var rectangle = new RectangleF(headCentre.X, headCentre.Y, tailCentre.X - headCentre.X, 0).Inflate(radius);
|
var rectangle = new RectangleF(headCentre.X, headCentre.Y, tailCentre.X - headCentre.X, 0).Inflate(radius);
|
||||||
return new Quad(rectangle.TopLeft, rectangle.TopRight, rectangle.BottomLeft, rectangle.BottomRight);
|
return new Quad(rectangle.TopLeft, rectangle.TopRight, rectangle.BottomLeft, rectangle.BottomRight);
|
||||||
@@ -37,8 +32,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => ScreenSpaceDrawQuad.Contains(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => ScreenSpaceDrawQuad.Contains(screenSpacePos);
|
||||||
|
|
||||||
private LegacyCirclePiece headCircle = null!;
|
|
||||||
|
|
||||||
private Sprite body = null!;
|
private Sprite body = null!;
|
||||||
|
|
||||||
private Sprite tailCircle = null!;
|
private Sprite tailCircle = null!;
|
||||||
@@ -66,10 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Texture = skin.GetTexture("taiko-roll-middle", WrapMode.ClampToEdge, WrapMode.ClampToEdge),
|
Texture = skin.GetTexture("taiko-roll-middle", WrapMode.ClampToEdge, WrapMode.ClampToEdge),
|
||||||
},
|
},
|
||||||
headCircle = new LegacyCirclePiece
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AccentColour = colours.YellowDark;
|
AccentColour = colours.YellowDark;
|
||||||
@@ -101,7 +90,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
|
var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
|
||||||
|
|
||||||
headCircle.AccentColour = colour;
|
|
||||||
body.Colour = colour;
|
body.Colour = colour;
|
||||||
tailCircle.Colour = colour;
|
tailCircle.Colour = colour;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
switch (taikoComponent.Component)
|
switch (taikoComponent.Component)
|
||||||
{
|
{
|
||||||
|
case TaikoSkinComponents.DrumRollHead:
|
||||||
|
if (GetTexture("taiko-roll-middle") != null)
|
||||||
|
return new LegacyCirclePiece();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.DrumRollBody:
|
case TaikoSkinComponents.DrumRollBody:
|
||||||
if (GetTexture("taiko-roll-middle") != null)
|
if (GetTexture("taiko-roll-middle") != null)
|
||||||
return new LegacyDrumRoll();
|
return new LegacyDrumRoll();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
InputDrum,
|
InputDrum,
|
||||||
CentreHit,
|
CentreHit,
|
||||||
RimHit,
|
RimHit,
|
||||||
|
DrumRollHead,
|
||||||
DrumRollBody,
|
DrumRollBody,
|
||||||
DrumRollTick,
|
DrumRollTick,
|
||||||
Swell,
|
Swell,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
sentMessages = new List<Message>();
|
sentMessages = new List<Message>();
|
||||||
silencedUserIds = new List<int>();
|
silencedUserIds = new List<int>();
|
||||||
|
|
||||||
|
((DummyAPIAccess)API).LocalUserState.Blocks.Clear();
|
||||||
((DummyAPIAccess)API).HandleRequest = req =>
|
((DummyAPIAccess)API).HandleRequest = req =>
|
||||||
{
|
{
|
||||||
switch (req)
|
switch (req)
|
||||||
@@ -63,6 +64,10 @@ namespace osu.Game.Tests.Chat
|
|||||||
silencedUserIds.Clear();
|
silencedUserIds.Clear();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case GetMessagesRequest getMessages:
|
||||||
|
getMessages.TriggerSuccess(sentMessages);
|
||||||
|
return true;
|
||||||
|
|
||||||
case GetUpdatesRequest updatesRequest:
|
case GetUpdatesRequest updatesRequest:
|
||||||
updatesRequest.TriggerSuccess(new GetUpdatesResponse
|
updatesRequest.TriggerSuccess(new GetUpdatesResponse
|
||||||
{
|
{
|
||||||
@@ -161,6 +166,60 @@ namespace osu.Game.Tests.Chat
|
|||||||
AddUntilStep("/help command received", () => channel.Messages.Last().Content.Contains("Supported commands"));
|
AddUntilStep("/help command received", () => channel.Messages.Last().Content.Contains("Supported commands"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBlockedUserMessagesAreDeletedFromInitialMessageBatch()
|
||||||
|
{
|
||||||
|
Channel channel = null;
|
||||||
|
|
||||||
|
AddStep("create channel", () => channel = createChannel(1, ChannelType.Public));
|
||||||
|
AddStep("post a message from blocked user", () => sentMessages.Add(new Message
|
||||||
|
{
|
||||||
|
ChannelId = channel.Id,
|
||||||
|
Content = "i am blocked",
|
||||||
|
SenderId = 1234
|
||||||
|
}));
|
||||||
|
AddStep("mark user as blocked", () => ((DummyAPIAccess)API).LocalUserState.Blocks.Add(new APIRelation
|
||||||
|
{
|
||||||
|
TargetUser = new APIUser { Username = "blocked", Id = 1234 },
|
||||||
|
TargetID = 1234,
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("join channel and select it", () =>
|
||||||
|
{
|
||||||
|
channelManager.JoinChannel(channel);
|
||||||
|
channelManager.CurrentChannel.Value = channel;
|
||||||
|
});
|
||||||
|
AddAssert("channel has no messages", () => channel.Messages, () => Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBlockedUserMessagesAreDeletedImmediatelyOnBlock()
|
||||||
|
{
|
||||||
|
Channel channel = null;
|
||||||
|
|
||||||
|
AddStep("create channel", () => channel = createChannel(1, ChannelType.Public));
|
||||||
|
|
||||||
|
AddStep("join channel and select it", () =>
|
||||||
|
{
|
||||||
|
channelManager.JoinChannel(channel);
|
||||||
|
channelManager.CurrentChannel.Value = channel;
|
||||||
|
});
|
||||||
|
AddStep("post a message from blocked user", () => sentMessages.Add(new Message
|
||||||
|
{
|
||||||
|
ChannelId = channel.Id,
|
||||||
|
Content = "i am blocked",
|
||||||
|
SenderId = 1234
|
||||||
|
}));
|
||||||
|
AddUntilStep("channel has message", () => channel.Messages, () => Is.Not.Empty);
|
||||||
|
|
||||||
|
AddStep("block user", () => ((DummyAPIAccess)API).LocalUserState.Blocks.Add(new APIRelation
|
||||||
|
{
|
||||||
|
TargetUser = new APIUser { Username = "blocked", Id = 1234 },
|
||||||
|
TargetID = 1234,
|
||||||
|
}));
|
||||||
|
AddAssert("channel has no messages", () => channel.Messages, () => Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
private void handlePostMessageRequest(PostMessageRequest request)
|
private void handlePostMessageRequest(PostMessageRequest request)
|
||||||
{
|
{
|
||||||
var message = new Message(++currentMessageId)
|
var message = new Message(++currentMessageId)
|
||||||
|
|||||||
@@ -126,6 +126,22 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AssertBeatmapLookup(expected_sample);
|
AssertBeatmapLookup(expected_sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that a hitobject which specifies a specific sample file which doesn't exist (or isn't allowed to be looked up)
|
||||||
|
/// falls back to a normal sample.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestFileSampleFallsBackToNormal()
|
||||||
|
{
|
||||||
|
const string expected_sample = "normal-hitnormal";
|
||||||
|
|
||||||
|
SetupSkins(null, expected_sample);
|
||||||
|
|
||||||
|
CreateTestWithBeatmap("file-beatmap-sample.osu");
|
||||||
|
|
||||||
|
AssertUserLookup(expected_sample);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests that a default hitobject and control point causes <see cref="TestDefaultSampleFromUserSkin"/>.
|
/// Tests that a default hitobject and control point causes <see cref="TestDefaultSampleFromUserSkin"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -220,10 +220,13 @@ namespace osu.Game.Tests.Visual.Components
|
|||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
{
|
{
|
||||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
|
||||||
if (registerAsOwner)
|
if (registerAsOwner)
|
||||||
dependencies.CacheAs<IPreviewTrackOwner>(this);
|
{
|
||||||
return dependencies;
|
// Automatically handled by interface caching.
|
||||||
|
return base.CreateChildDependencies(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DependencyContainer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,20 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||||
using osu.Game.Tests.Visual.OnlinePlay;
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@@ -21,7 +24,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapSelectGrid : OnlinePlayTestScene
|
public partial class TestSceneBeatmapSelectGrid : OnlinePlayTestScene
|
||||||
{
|
{
|
||||||
private MultiplayerPlaylistItem[] items = null!;
|
private MatchmakingPlaylistItem[] items = null!;
|
||||||
|
|
||||||
private BeatmapSelectGrid grid = null!;
|
private BeatmapSelectGrid grid = null!;
|
||||||
|
|
||||||
@@ -36,24 +39,44 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
.Take(50)
|
.Take(50)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
IEnumerable<MatchmakingPlaylistItem> playlistItems;
|
||||||
|
|
||||||
if (beatmaps.Length > 0)
|
if (beatmaps.Length > 0)
|
||||||
{
|
{
|
||||||
items = Enumerable.Range(1, 50).Select(i => new MultiplayerPlaylistItem
|
playlistItems = Enumerable.Range(1, 50).Select(i =>
|
||||||
{
|
{
|
||||||
ID = i,
|
var beatmap = beatmaps[i % beatmaps.Length];
|
||||||
BeatmapID = beatmaps[i % beatmaps.Length].OnlineID,
|
|
||||||
StarRating = i / 10.0,
|
return new MatchmakingPlaylistItem(
|
||||||
}).ToArray();
|
new MultiplayerPlaylistItem
|
||||||
|
{
|
||||||
|
ID = i,
|
||||||
|
BeatmapID = beatmap.OnlineID,
|
||||||
|
StarRating = i / 10.0,
|
||||||
|
},
|
||||||
|
CreateAPIBeatmap(beatmap),
|
||||||
|
Array.Empty<Mod>()
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
items = Enumerable.Range(1, 50).Select(i => new MultiplayerPlaylistItem
|
playlistItems = Enumerable.Range(1, 50).Select(i => new MatchmakingPlaylistItem(
|
||||||
{
|
new MultiplayerPlaylistItem
|
||||||
ID = i,
|
{
|
||||||
BeatmapID = i,
|
ID = i,
|
||||||
StarRating = i / 10.0,
|
BeatmapID = i,
|
||||||
}).ToArray();
|
StarRating = i / 10.0,
|
||||||
|
},
|
||||||
|
CreateAPIBeatmap(),
|
||||||
|
Array.Empty<Mod>()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var item in playlistItems)
|
||||||
|
item.Beatmap.StarRating = item.PlaylistItem.StarRating;
|
||||||
|
|
||||||
|
items = playlistItems.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
@@ -70,8 +93,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
|
|
||||||
AddStep("add items", () =>
|
AddStep("add items", () =>
|
||||||
{
|
{
|
||||||
foreach (var item in items)
|
grid.AddItems(items);
|
||||||
grid.AddItem(item);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AddWaitStep("wait for panels", 3);
|
AddWaitStep("wait for panels", 3);
|
||||||
@@ -85,17 +107,17 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
// test scene is weird.
|
// test scene is weird.
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add selection 1", () => grid.ChildrenOfType<BeatmapSelectPanel>().First().AddUser(new APIUser
|
AddStep("add selection 1", () => grid.ChildrenOfType<MatchmakingSelectPanel>().First().AddUser(new APIUser
|
||||||
{
|
{
|
||||||
Id = DummyAPIAccess.DUMMY_USER_ID,
|
Id = DummyAPIAccess.DUMMY_USER_ID,
|
||||||
Username = "Maarvin",
|
Username = "Maarvin",
|
||||||
}));
|
}));
|
||||||
AddStep("add selection 2", () => grid.ChildrenOfType<BeatmapSelectPanel>().Skip(5).First().AddUser(new APIUser
|
AddStep("add selection 2", () => grid.ChildrenOfType<MatchmakingSelectPanel>().Skip(5).First().AddUser(new APIUser
|
||||||
{
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Username = "peppy",
|
Username = "peppy",
|
||||||
}));
|
}));
|
||||||
AddStep("add selection 3", () => grid.ChildrenOfType<BeatmapSelectPanel>().Skip(10).First().AddUser(new APIUser
|
AddStep("add selection 3", () => grid.ChildrenOfType<MatchmakingSelectPanel>().Skip(10).First().AddUser(new APIUser
|
||||||
{
|
{
|
||||||
Id = 1040328,
|
Id = 1040328,
|
||||||
Username = "smoogipoo",
|
Username = "smoogipoo",
|
||||||
@@ -180,7 +202,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
|
|
||||||
AddStep("display roll order", () =>
|
AddStep("display roll order", () =>
|
||||||
{
|
{
|
||||||
var panels = grid.ChildrenOfType<BeatmapSelectPanel>().ToArray();
|
var panels = grid.ChildrenOfType<MatchmakingSelectPanel>().ToArray();
|
||||||
|
|
||||||
for (int i = 0; i < panels.Length; i++)
|
for (int i = 0; i < panels.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -197,6 +219,23 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPresentRandomItem()
|
||||||
|
{
|
||||||
|
AddStep("present random item panel", () =>
|
||||||
|
{
|
||||||
|
grid.TransferCandidatePanelsToRollContainer(pickRandomItems(4).candidateItems.Append(-1).ToArray(), duration: 0);
|
||||||
|
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
|
||||||
|
grid.PlayRollAnimation(-1, duration: 0);
|
||||||
|
|
||||||
|
Scheduler.AddDelayed(() => grid.PresentUnanimouslyChosenBeatmap(-1), 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddWaitStep("wait for animation", 5);
|
||||||
|
|
||||||
|
AddStep("reveal beatmap", () => grid.RevealRandomItem(items[RNG.Next(items.Length)].PlaylistItem));
|
||||||
|
}
|
||||||
|
|
||||||
private (long[] candidateItems, long finalItem) pickRandomItems(int count)
|
private (long[] candidateItems, long finalItem) pickRandomItems(int count)
|
||||||
{
|
{
|
||||||
long[] candidateItems = items.Select(it => it.ID).ToArray();
|
long[] candidateItems = items.Select(it => it.ID).ToArray();
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||||
using osu.Game.Tests.Visual.Multiplayer;
|
using osu.Game.Tests.Visual.Multiplayer;
|
||||||
|
|
||||||
@@ -21,17 +21,35 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("join room", () =>
|
||||||
|
{
|
||||||
|
var room = CreateDefaultRoom(MatchType.Matchmaking);
|
||||||
|
room.Playlist = Enumerable.Range(1, 50).Select(i => new PlaylistItem(new MultiplayerPlaylistItem
|
||||||
|
{
|
||||||
|
ID = i,
|
||||||
|
BeatmapID = 0,
|
||||||
|
StarRating = i / 10.0,
|
||||||
|
})).ToArray();
|
||||||
|
|
||||||
|
JoinRoom(room);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBeatmapPanel()
|
public void TestBeatmapPanel()
|
||||||
{
|
{
|
||||||
BeatmapSelectPanel? panel = null;
|
MatchmakingSelectPanel? panel = null;
|
||||||
|
|
||||||
AddStep("add panel", () =>
|
AddStep("add panel", () =>
|
||||||
{
|
{
|
||||||
Child = new OsuContextMenuContainer
|
Child = new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = panel = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
|
Child = panel = new MatchmakingSelectPanelBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), []))
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@@ -58,47 +76,55 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
AddStep("remove peppy", () => panel!.RemoveUser(new APIUser { Id = 2 }));
|
AddStep("remove peppy", () => panel!.RemoveUser(new APIUser { Id = 2 }));
|
||||||
AddStep("remove maarvin", () => panel!.RemoveUser(new APIUser { Id = 6411631 }));
|
AddStep("remove maarvin", () => panel!.RemoveUser(new APIUser { Id = 6411631 }));
|
||||||
|
|
||||||
AddToggleStep("allow selection", value =>
|
AddToggleStep("allow selection", value => panel!.AllowSelection = value);
|
||||||
{
|
|
||||||
if (panel != null)
|
|
||||||
panel.AllowSelection = value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFailedBeatmapLookup()
|
public void TestRandomPanel()
|
||||||
{
|
{
|
||||||
AddStep("setup request handle", () =>
|
MatchmakingSelectPanelRandom? panel = null;
|
||||||
{
|
|
||||||
var api = (DummyAPIAccess)API;
|
|
||||||
var handler = api.HandleRequest;
|
|
||||||
api.HandleRequest = req =>
|
|
||||||
{
|
|
||||||
switch (req)
|
|
||||||
{
|
|
||||||
case GetBeatmapRequest:
|
|
||||||
case GetBeatmapsRequest:
|
|
||||||
req.TriggerFailure(new InvalidOperationException());
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return handler?.Invoke(req) ?? false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("add panel", () =>
|
AddStep("add panel", () =>
|
||||||
{
|
{
|
||||||
Child = new OsuContextMenuContainer
|
Child = new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
|
Child = panel = new MatchmakingSelectPanelRandom(new MultiplayerPlaylistItem { ID = -1 })
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddToggleStep("allow selection", value => panel!.AllowSelection = value);
|
||||||
|
|
||||||
|
AddStep("reveal beatmap", () => panel!.RevealBeatmap(CreateAPIBeatmap(), []));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapWithMods()
|
||||||
|
{
|
||||||
|
AddStep("add panel", () =>
|
||||||
|
{
|
||||||
|
MatchmakingSelectPanel? panel;
|
||||||
|
|
||||||
|
Child = new OsuContextMenuContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = panel = new MatchmakingSelectPanelBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), [new OsuModHardRock(), new OsuModDoubleTime()]))
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
panel.AddUser(new APIUser
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Username = "peppy",
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ using osu.Framework.Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Matchmaking.Events;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
@@ -158,5 +160,64 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
|||||||
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
|
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void InteractionSpam()
|
||||||
|
{
|
||||||
|
AddStep("join users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 7; i++)
|
||||||
|
{
|
||||||
|
MultiplayerClient.AddUser(new MultiplayerRoomUser(i)
|
||||||
|
{
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Username = $"User {i}"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddStep("change to grid mode", () => list.DisplayStyle = PanelDisplayStyle.Grid);
|
||||||
|
AddStep("player jump", () => { MultiplayerClient.SendUserMatchRequest(1001, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely(); });
|
||||||
|
AddStep("local jumping", () => jumpSpam(false));
|
||||||
|
AddWaitStep("wait", 25);
|
||||||
|
AddStep("group jumping spam", () => jumpSpam(true));
|
||||||
|
AddWaitStep("wait", 25);
|
||||||
|
|
||||||
|
AddStep("change to split mode", () => list.DisplayStyle = PanelDisplayStyle.Split);
|
||||||
|
AddStep("local jumping", () => jumpSpam(false));
|
||||||
|
AddWaitStep("wait", 25);
|
||||||
|
AddStep("group jumping spam", () => jumpSpam(true));
|
||||||
|
AddWaitStep("wait", 25);
|
||||||
|
|
||||||
|
AddStep("change to hidden mode", () => list.DisplayStyle = PanelDisplayStyle.Hidden);
|
||||||
|
AddStep("local jumping", () => jumpSpam(false));
|
||||||
|
AddWaitStep("wait", 25);
|
||||||
|
AddStep("group jumping spam", () => jumpSpam(true));
|
||||||
|
AddWaitStep("wait", 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void jumpSpam(bool everyone)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 30; i++)
|
||||||
|
{
|
||||||
|
Scheduler.AddDelayed(() =>
|
||||||
|
{
|
||||||
|
MultiplayerClient.SendUserMatchRequest(1001, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely();
|
||||||
|
}, i * 150 + RNG.NextDouble(0, 140));
|
||||||
|
|
||||||
|
if (!everyone)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (int ii = 0; ii < 7; ii++)
|
||||||
|
{
|
||||||
|
int iii = ii;
|
||||||
|
Scheduler.AddDelayed(() =>
|
||||||
|
{
|
||||||
|
MultiplayerClient.SendUserMatchRequest(iii, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely();
|
||||||
|
}, i * 150 + RNG.NextDouble(0, 140));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Direction = FillDirection.Full,
|
Direction = FillDirection.Full,
|
||||||
Padding = new MarginPadding(20),
|
Padding = new MarginPadding(20),
|
||||||
Spacing = new Vector2(40),
|
Spacing = new Vector2(40),
|
||||||
ChildrenEnumerable = new int?[] { 64, 423, 1453, 3468, 18_367, 48_342, 178_432, 375_231, 897_783, null }.Select(createDisplay)
|
ChildrenEnumerable = new int?[] { 64, 423, 1_453, 3_468, 8_367, 48_342, 78_432, 375_231, 897_783, null }.Select(createDisplay)
|
||||||
};
|
};
|
||||||
|
|
||||||
private GlobalRankDisplay createDisplay(int? rank) => new GlobalRankDisplay
|
private GlobalRankDisplay createDisplay(int? rank) => new GlobalRankDisplay
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
||||||
|
|
||||||
namespace osu.Game.Audio
|
namespace osu.Game.Audio
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -10,6 +12,7 @@ namespace osu.Game.Audio
|
|||||||
/// <see cref="IPreviewTrackOwner"/>s can cancel the currently playing <see cref="PreviewTrack"/> through the
|
/// <see cref="IPreviewTrackOwner"/>s can cancel the currently playing <see cref="PreviewTrack"/> through the
|
||||||
/// global <see cref="PreviewTrackManager"/> if they're the owner of the playing <see cref="PreviewTrack"/>.
|
/// global <see cref="PreviewTrackManager"/> if they're the owner of the playing <see cref="PreviewTrack"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
[Cached]
|
||||||
public interface IPreviewTrackOwner
|
public interface IPreviewTrackOwner
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,6 +157,12 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public bool Equals(IBeatmapInfo? other) => other is BeatmapInfo b && Equals(b);
|
public bool Equals(IBeatmapInfo? other) => other is BeatmapInfo b && Equals(b);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||||
|
return ID.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
public bool AudioEquals(BeatmapInfo? other) => other != null
|
public bool AudioEquals(BeatmapInfo? other) => other != null
|
||||||
&& BeatmapSet != null
|
&& BeatmapSet != null
|
||||||
&& other.BeatmapSet != null
|
&& other.BeatmapSet != null
|
||||||
|
|||||||
@@ -544,7 +544,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
if (!banksOnly)
|
if (!banksOnly)
|
||||||
{
|
{
|
||||||
int customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name)));
|
int customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name)));
|
||||||
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
|
string sampleFilename = samples.FirstOrDefault(s => s is ConvertHitObjectParser.FileHitSampleInfo)?.LookupNames.First() ?? string.Empty;
|
||||||
int volume = samples.FirstOrDefault()?.Volume ?? 100;
|
int volume = samples.FirstOrDefault()?.Volume ?? 100;
|
||||||
|
|
||||||
// We want to ignore custom sample banks and volume when not encoding to the mania game mode,
|
// We want to ignore custom sample banks and volume when not encoding to the mania game mode,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
@@ -263,11 +264,11 @@ namespace osu.Game.Collections
|
|||||||
{
|
{
|
||||||
Debug.Assert(collection != null);
|
Debug.Assert(collection != null);
|
||||||
|
|
||||||
collection.PerformWrite(c =>
|
Task.Run(() => collection.PerformWrite(c =>
|
||||||
{
|
{
|
||||||
if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash))
|
if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash))
|
||||||
c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash);
|
c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash);
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateContent() => (Content)base.CreateContent();
|
protected override Drawable CreateContent() => (Content)base.CreateContent();
|
||||||
|
|||||||
@@ -1,6 +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.Threading.Tasks;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@@ -10,7 +11,7 @@ namespace osu.Game.Collections
|
|||||||
public class CollectionToggleMenuItem : ToggleMenuItem
|
public class CollectionToggleMenuItem : ToggleMenuItem
|
||||||
{
|
{
|
||||||
public CollectionToggleMenuItem(Live<BeatmapCollection> collection, IBeatmapInfo beatmap)
|
public CollectionToggleMenuItem(Live<BeatmapCollection> collection, IBeatmapInfo beatmap)
|
||||||
: base(collection.PerformRead(c => c.Name), MenuItemType.Standard, state =>
|
: base(collection.PerformRead(c => c.Name), MenuItemType.Standard, state => Task.Run(() =>
|
||||||
{
|
{
|
||||||
collection.PerformWrite(c =>
|
collection.PerformWrite(c =>
|
||||||
{
|
{
|
||||||
@@ -19,7 +20,7 @@ namespace osu.Game.Collections
|
|||||||
else
|
else
|
||||||
c.BeatmapMD5Hashes.Remove(beatmap.MD5Hash);
|
c.BeatmapMD5Hashes.Remove(beatmap.MD5Hash);
|
||||||
});
|
});
|
||||||
})
|
}))
|
||||||
{
|
{
|
||||||
State.Value = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.MD5Hash));
|
State.Value = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.MD5Hash));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -785,32 +785,20 @@ namespace osu.Game.Graphics.Carousel
|
|||||||
// We are performing two important operations here:
|
// We are performing two important operations here:
|
||||||
// - Update all Y positions. After a selection occurs, panels may have changed visibility state and therefore Y positions.
|
// - Update all Y positions. After a selection occurs, panels may have changed visibility state and therefore Y positions.
|
||||||
// - Link selected models to CarouselItems. If a selection changed, this is where we find the relevant CarouselItems for further use.
|
// - Link selected models to CarouselItems. If a selection changed, this is where we find the relevant CarouselItems for further use.
|
||||||
|
FindCarouselItemsForSelection(ref currentKeyboardSelection, ref currentSelection, carouselItems);
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var item = carouselItems[i];
|
var item = carouselItems[i];
|
||||||
|
|
||||||
bool isKeyboardSelection = CheckModelEquality(item.Model, currentKeyboardSelection.Model!);
|
|
||||||
bool isSelection = CheckModelEquality(item.Model, currentSelection.Model!);
|
|
||||||
|
|
||||||
// while we don't know the Y position of the item yet, as it's about to be updated,
|
|
||||||
// consumers (specifically `BeatmapCarousel.GetSpacingBetweenPanels()`) benefit from `CurrentSelectionItem` already pointing
|
|
||||||
// at the correct item to avoid redundant local equality checks.
|
|
||||||
// the Y positions will be filled in after they're computed.
|
|
||||||
if (isKeyboardSelection)
|
|
||||||
currentKeyboardSelection = new Selection(currentKeyboardSelection.Model, item, null, i);
|
|
||||||
|
|
||||||
if (isSelection)
|
|
||||||
currentSelection = new Selection(currentSelection.Model, item, null, i);
|
|
||||||
|
|
||||||
updateItemYPosition(item, ref lastVisible, ref yPos);
|
updateItemYPosition(item, ref lastVisible, ref yPos);
|
||||||
|
|
||||||
if (isKeyboardSelection)
|
|
||||||
currentKeyboardSelection = currentKeyboardSelection with { YPosition = item.CarouselYPosition + item.DrawHeight / 2 };
|
|
||||||
|
|
||||||
if (isSelection)
|
|
||||||
currentSelection = currentSelection with { YPosition = item.CarouselYPosition + item.DrawHeight / 2 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentKeyboardSelection.CarouselItem is CarouselItem currentKeyboardSelectionItem)
|
||||||
|
currentKeyboardSelection = currentKeyboardSelection with { YPosition = currentKeyboardSelectionItem.CarouselYPosition + currentKeyboardSelectionItem.DrawHeight / 2 };
|
||||||
|
|
||||||
|
if (currentSelection.CarouselItem is CarouselItem currentSelectionItem)
|
||||||
|
currentSelection = currentSelection with { YPosition = currentSelectionItem.CarouselYPosition + currentSelectionItem.DrawHeight / 2 };
|
||||||
|
|
||||||
// Update the total height of all items (to make the scroll container scrollable through the full height even though
|
// Update the total height of all items (to make the scroll container scrollable through the full height even though
|
||||||
// most items are not displayed / loaded).
|
// most items are not displayed / loaded).
|
||||||
Scroll.SetLayoutHeight(yPos + visibleHalfHeight);
|
Scroll.SetLayoutHeight(yPos + visibleHalfHeight);
|
||||||
@@ -821,6 +809,27 @@ namespace osu.Game.Graphics.Carousel
|
|||||||
Scroll.OffsetScrollPosition((float)(currentKeyboardSelection.YPosition!.Value - prevKeyboard.YPosition.Value));
|
Scroll.OffsetScrollPosition((float)(currentKeyboardSelection.YPosition!.Value - prevKeyboard.YPosition.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void FindCarouselItemsForSelection(ref Selection keyboardSelection, ref Selection selection, IList<CarouselItem> items)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
var item = items[i];
|
||||||
|
|
||||||
|
bool isKeyboardSelection = CheckModelEquality(item.Model, keyboardSelection.Model!);
|
||||||
|
bool isSelection = CheckModelEquality(item.Model, selection.Model!);
|
||||||
|
|
||||||
|
// while we don't know the Y position of the item yet, as it's about to be updated,
|
||||||
|
// consumers (specifically `BeatmapCarousel.GetSpacingBetweenPanels()`) benefit from `CurrentSelectionItem` already pointing
|
||||||
|
// at the correct item to avoid redundant local equality checks.
|
||||||
|
// the Y positions will be filled in after they're computed.
|
||||||
|
if (isKeyboardSelection)
|
||||||
|
keyboardSelection = new Selection(keyboardSelection.Model, item, null, i);
|
||||||
|
|
||||||
|
if (isSelection)
|
||||||
|
selection = new Selection(selection.Model, item, null, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Display handling
|
#region Display handling
|
||||||
@@ -1081,7 +1090,7 @@ namespace osu.Game.Graphics.Carousel
|
|||||||
/// <param name="CarouselItem">A related carousel item representation for the model. May be null if selection is not present as an item, or if <see cref="Carousel{T}.refreshAfterSelection"/> has not been run yet.</param>
|
/// <param name="CarouselItem">A related carousel item representation for the model. May be null if selection is not present as an item, or if <see cref="Carousel{T}.refreshAfterSelection"/> has not been run yet.</param>
|
||||||
/// <param name="YPosition">The Y position of the selection as of the last run of <see cref="Carousel{T}.refreshAfterSelection"/>. May be null if selection is not present as an item, or if <see cref="Carousel{T}.refreshAfterSelection"/> has not been run yet.</param>
|
/// <param name="YPosition">The Y position of the selection as of the last run of <see cref="Carousel{T}.refreshAfterSelection"/>. May be null if selection is not present as an item, or if <see cref="Carousel{T}.refreshAfterSelection"/> has not been run yet.</param>
|
||||||
/// <param name="Index">The index of the selection as of the last run of <see cref="Carousel{T}.refreshAfterSelection"/>. May be null if selection is not present as an item, or if <see cref="Carousel{T}.refreshAfterSelection"/> has not been run yet.</param>
|
/// <param name="Index">The index of the selection as of the last run of <see cref="Carousel{T}.refreshAfterSelection"/>. May be null if selection is not present as an item, or if <see cref="Carousel{T}.refreshAfterSelection"/> has not been run yet.</param>
|
||||||
private record Selection(object? Model = null, CarouselItem? CarouselItem = null, double? YPosition = null, int? Index = null);
|
protected record Selection(object? Model = null, CarouselItem? CarouselItem = null, double? YPosition = null, int? Index = null);
|
||||||
|
|
||||||
private record DisplayRange(int First, int Last)
|
private record DisplayRange(int First, int Last)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using osu.Game.Overlays;
|
|||||||
|
|
||||||
namespace osu.Game.Graphics.Containers
|
namespace osu.Game.Graphics.Containers
|
||||||
{
|
{
|
||||||
[Cached(typeof(IPreviewTrackOwner))]
|
|
||||||
public abstract partial class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler<GlobalAction>
|
public abstract partial class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
public partial class ProgressBar : SliderBar<double>
|
public partial class ProgressBar : SliderBar<double>
|
||||||
{
|
{
|
||||||
|
public bool Seeking { get; private set; }
|
||||||
|
|
||||||
public Action<double> OnSeek;
|
public Action<double> OnSeek;
|
||||||
|
|
||||||
private readonly Box fill;
|
private readonly Box fill;
|
||||||
@@ -75,6 +77,16 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
fill.Width = value * UsableWidth;
|
fill.Width = value * UsableWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUserChange(double value) => OnSeek?.Invoke(value);
|
protected override void OnUserChange(double value)
|
||||||
|
{
|
||||||
|
Seeking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool Commit()
|
||||||
|
{
|
||||||
|
OnSeek?.Invoke(CurrentNumber.Value);
|
||||||
|
Seeking = false;
|
||||||
|
return base.Commit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,13 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
case Key.KeypadEnter:
|
case Key.KeypadEnter:
|
||||||
case Key.Enter:
|
case Key.Enter:
|
||||||
return false;
|
// even if committing per se is not allowed for this textbox,
|
||||||
|
// the commit flow is also responsible for terminating any active IME.
|
||||||
|
// ensure that the Enter press terminates IME correctly
|
||||||
|
// and is also handled if it needs to be, so that it doesn't leak to some other non-focused drawable and cause breakage.
|
||||||
|
bool wasImeComposing = ImeCompositionActive;
|
||||||
|
FinalizeImeComposition(true);
|
||||||
|
return wasImeComposing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,21 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString AdjustBeatmapOffsetAutomaticallyTooltip => new TranslatableString(getKey(@"adjust_beatmap_offset_automatically_tooltip"), @"If enabled, the offset suggested from last play on a beatmap is automatically applied.");
|
public static LocalisableString AdjustBeatmapOffsetAutomaticallyTooltip => new TranslatableString(getKey(@"adjust_beatmap_offset_automatically_tooltip"), @"If enabled, the offset suggested from last play on a beatmap is automatically applied.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Use experimental audio mode"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString WasapiLabel => new TranslatableString(getKey(@"wasapi_label"), @"Use experimental audio mode");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "This will attempt to initialise the audio engine in a lower latency mode."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString WasapiTooltip => new TranslatableString(getKey(@"wasapi_tooltip"), @"This will attempt to initialise the audio engine in a lower latency mode.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Due to reduced latency, your audio offset will need to be adjusted when enabling this setting. Generally expect to subtract 20 - 60 ms from your known value."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString WasapiNotice => new TranslatableString(getKey(@"wasapi_notice"), @"Due to reduced latency, your audio offset will need to be adjusted when enabling this setting. Generally expect to subtract 20 - 60 ms from your known value.");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace osu.Game.Online.API.Requests
|
|||||||
private class VerificationFailureResponse
|
private class VerificationFailureResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("method")]
|
[JsonProperty("method")]
|
||||||
public SessionVerificationMethod RequiredSessionVerificationMethod { get; set; }
|
public SessionVerificationMethod? RequiredSessionVerificationMethod { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@@ -70,6 +71,7 @@ namespace osu.Game.Online.Chat
|
|||||||
private UserLookupCache users { get; set; }
|
private UserLookupCache users { get; set; }
|
||||||
|
|
||||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||||
|
private readonly IBindableList<APIRelation> localUserBlocks = new BindableList<APIRelation>();
|
||||||
private ScheduledDelegate scheduledAck;
|
private ScheduledDelegate scheduledAck;
|
||||||
|
|
||||||
private IChatClient chatClient = null!;
|
private IChatClient chatClient = null!;
|
||||||
@@ -95,6 +97,9 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
apiState.BindTo(api.State);
|
apiState.BindTo(api.State);
|
||||||
apiState.BindValueChanged(_ => SendAck(), true);
|
apiState.BindValueChanged(_ => SendAck(), true);
|
||||||
|
|
||||||
|
localUserBlocks.BindTo(api.LocalUserState.Blocks);
|
||||||
|
localUserBlocks.BindCollectionChanged((_, args) => Schedule(() => onBlocksChanged(args)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -311,8 +316,9 @@ namespace osu.Game.Online.Chat
|
|||||||
private void addMessages(List<Message> messages)
|
private void addMessages(List<Message> messages)
|
||||||
{
|
{
|
||||||
var channels = JoinedChannels.ToList();
|
var channels = JoinedChannels.ToList();
|
||||||
|
var blockedUserIds = localUserBlocks.Select(b => b.TargetID).ToList();
|
||||||
|
|
||||||
foreach (var group in messages.GroupBy(m => m.ChannelId))
|
foreach (var group in messages.Where(m => !blockedUserIds.Contains(m.SenderId)).GroupBy(m => m.ChannelId))
|
||||||
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
||||||
|
|
||||||
lastSilenceMessageId ??= messages.LastOrDefault()?.Id;
|
lastSilenceMessageId ??= messages.LastOrDefault()?.Id;
|
||||||
@@ -641,6 +647,18 @@ namespace osu.Game.Online.Chat
|
|||||||
api.Queue(req);
|
api.Queue(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onBlocksChanged(NotifyCollectionChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Action != NotifyCollectionChangedAction.Add)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (APIRelation newBlock in args.NewItems!)
|
||||||
|
{
|
||||||
|
foreach (var channel in joinedChannels)
|
||||||
|
channel.RemoveMessagesFromUser(newBlock.TargetID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|||||||
@@ -43,11 +43,15 @@ namespace osu.Game.Online.Matchmaking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user has raised a candidate playlist item to be played.
|
/// The user has raised a candidate playlist item to be played.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="userId">The notifying user.</param>
|
||||||
|
/// <param name="playlistItemId">The playlist item candidate raised, or -1 as a special value that indicates a random selection.</param>
|
||||||
Task MatchmakingItemSelected(int userId, long playlistItemId);
|
Task MatchmakingItemSelected(int userId, long playlistItemId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user has removed a candidate playlist item.
|
/// The user has removed a candidate playlist item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="userId">The notifying user.</param>
|
||||||
|
/// <param name="playlistItemId">The playlist item candidate removed, or -1 as a special value that indicates a random selection.</param>
|
||||||
Task MatchmakingItemDeselected(int userId, long playlistItemId);
|
Task MatchmakingItemDeselected(int userId, long playlistItemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace osu.Game.Online.Matchmaking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raise a candidate playlist item to be played in the current round.
|
/// Raise a candidate playlist item to be played in the current round.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playlistItemId">The playlist item.</param>
|
/// <param name="playlistItemId">The playlist item, or -1 to indicate a random selection.</param>
|
||||||
Task MatchmakingToggleSelection(long playlistItemId);
|
Task MatchmakingToggleSelection(long playlistItemId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,6 +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;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
IconContent.Width = IconContent.DrawHeight;
|
IconContent.Width = Math.Min(78, IconContent.DrawHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,18 +304,21 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
var track = musicController.CurrentTrack;
|
var track = musicController.CurrentTrack;
|
||||||
|
|
||||||
if (!track.IsDummyDevice)
|
if (!progressBar.Seeking)
|
||||||
{
|
{
|
||||||
progressBar.EndTime = track.Length;
|
if (!track.IsDummyDevice)
|
||||||
progressBar.CurrentTime = track.CurrentTime;
|
{
|
||||||
|
progressBar.EndTime = track.Length;
|
||||||
|
progressBar.CurrentTime = track.CurrentTime;
|
||||||
|
|
||||||
playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
progressBar.CurrentTime = 0;
|
progressBar.CurrentTime = 0;
|
||||||
progressBar.EndTime = 1;
|
progressBar.EndTime = 1;
|
||||||
playButton.Icon = FontAwesome.Regular.PlayCircle;
|
playButton.Icon = FontAwesome.Regular.PlayCircle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@@ -14,6 +15,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@@ -36,6 +38,7 @@ namespace osu.Game.Overlays
|
|||||||
public ScrollBackButton Button { get; private set; }
|
public ScrollBackButton Button { get; private set; }
|
||||||
|
|
||||||
private readonly Bindable<double?> lastScrollTarget = new Bindable<double?>();
|
private readonly Bindable<double?> lastScrollTarget = new Bindable<double?>();
|
||||||
|
private readonly Bindable<double> progress = new Bindable<double>();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@@ -46,7 +49,8 @@ namespace osu.Game.Overlays
|
|||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
Margin = new MarginPadding(20),
|
Margin = new MarginPadding(20),
|
||||||
Action = scrollBack,
|
Action = scrollBack,
|
||||||
LastScrollTarget = { BindTarget = lastScrollTarget }
|
LastScrollTarget = { BindTarget = lastScrollTarget },
|
||||||
|
Progress = { BindTarget = progress },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +58,10 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
|
// Map current position to standardized progress
|
||||||
|
float height = AvailableContent - DrawHeight;
|
||||||
|
progress.Value = height == 0 ? 1 : Math.Round(Math.Clamp(Current / height, 0, 1), 3);
|
||||||
|
|
||||||
if (ScrollContent.DrawHeight + button_scroll_position < DrawHeight)
|
if (ScrollContent.DrawHeight + button_scroll_position < DrawHeight)
|
||||||
{
|
{
|
||||||
Button.State = Visibility.Hidden;
|
Button.State = Visibility.Hidden;
|
||||||
@@ -110,9 +118,11 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
|
private readonly CircularProgress currentCircularProgress;
|
||||||
private readonly SpriteIcon spriteIcon;
|
private readonly SpriteIcon spriteIcon;
|
||||||
|
|
||||||
public Bindable<double?> LastScrollTarget = new Bindable<double?>();
|
public Bindable<double?> LastScrollTarget = new Bindable<double?>();
|
||||||
|
public Bindable<double> Progress = new Bindable<double>();
|
||||||
|
|
||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||||
|
|
||||||
@@ -145,6 +155,11 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
|
currentCircularProgress = new CircularProgress
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
InnerRadius = 0.1f,
|
||||||
|
},
|
||||||
spriteIcon = new SpriteIcon
|
spriteIcon = new SpriteIcon
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@@ -164,6 +179,7 @@ namespace osu.Game.Overlays
|
|||||||
IdleColour = colourProvider.Background6;
|
IdleColour = colourProvider.Background6;
|
||||||
HoverColour = colourProvider.Background5;
|
HoverColour = colourProvider.Background5;
|
||||||
flashColour = colourProvider.Light1;
|
flashColour = colourProvider.Light1;
|
||||||
|
currentCircularProgress.Colour = colourProvider.Highlight1;
|
||||||
|
|
||||||
scrollToTopSample = audio.Samples.Get(@"UI/scroll-to-top");
|
scrollToTopSample = audio.Samples.Get(@"UI/scroll-to-top");
|
||||||
scrollToPreviousSample = audio.Samples.Get(@"UI/scroll-to-previous");
|
scrollToPreviousSample = audio.Samples.Get(@"UI/scroll-to-previous");
|
||||||
@@ -173,6 +189,8 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Progress.BindValueChanged(p => currentCircularProgress.Progress = p.NewValue, true);
|
||||||
|
|
||||||
LastScrollTarget.BindValueChanged(target =>
|
LastScrollTarget.BindValueChanged(target =>
|
||||||
{
|
{
|
||||||
spriteIcon.ScaleTo(target.NewValue != null ? new Vector2(1f, -1f) : Vector2.One, fade_duration, Easing.OutQuint);
|
spriteIcon.ScaleTo(target.NewValue != null ? new Vector2(1f, -1f) : Vector2.One, fade_duration, Easing.OutQuint);
|
||||||
|
|||||||
@@ -75,19 +75,19 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
if (percent < 0.0005)
|
if (percent < 0.0005)
|
||||||
return RankingTier.Radiant;
|
return RankingTier.Radiant;
|
||||||
|
|
||||||
if (percent < 0.0025)
|
if (percent < 0.0015)
|
||||||
return RankingTier.Rhodium;
|
return RankingTier.Rhodium;
|
||||||
|
|
||||||
if (percent < 0.005)
|
if (percent < 0.005)
|
||||||
return RankingTier.Platinum;
|
return RankingTier.Platinum;
|
||||||
|
|
||||||
if (percent < 0.025)
|
if (percent < 0.015)
|
||||||
return RankingTier.Gold;
|
return RankingTier.Gold;
|
||||||
|
|
||||||
if (percent < 0.05)
|
if (percent < 0.05)
|
||||||
return RankingTier.Silver;
|
return RankingTier.Silver;
|
||||||
|
|
||||||
if (percent < 0.25)
|
if (percent < 0.15)
|
||||||
return RankingTier.Bronze;
|
return RankingTier.Bronze;
|
||||||
|
|
||||||
if (percent < 0.5)
|
if (percent < 0.5)
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
{
|
{
|
||||||
Add(wasapiExperimental = new SettingsCheckbox
|
Add(wasapiExperimental = new SettingsCheckbox
|
||||||
{
|
{
|
||||||
LabelText = "Use experimental audio mode",
|
LabelText = AudioSettingsStrings.WasapiLabel,
|
||||||
TooltipText = "This will attempt to initialise the audio engine in a lower latency mode.",
|
TooltipText = AudioSettingsStrings.WasapiTooltip,
|
||||||
Current = audio.UseExperimentalWasapi,
|
Current = audio.UseExperimentalWasapi,
|
||||||
Keywords = new[] { "wasapi", "latency", "exclusive" }
|
Keywords = new[] { "wasapi", "latency", "exclusive" }
|
||||||
});
|
});
|
||||||
@@ -64,10 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
if (wasapiExperimental != null)
|
if (wasapiExperimental != null)
|
||||||
{
|
{
|
||||||
if (wasapiExperimental.Current.Value)
|
if (wasapiExperimental.Current.Value)
|
||||||
{
|
wasapiExperimental.SetNoticeText(AudioSettingsStrings.WasapiNotice, true);
|
||||||
wasapiExperimental.SetNoticeText(
|
|
||||||
"Due to reduced latency, your audio offset will need to be adjusted when enabling this setting. Generally expect to subtract 20 - 60 ms from your known value.", true);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
wasapiExperimental.ClearNoticeText();
|
wasapiExperimental.ClearNoticeText();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -550,7 +550,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Todo: This should set the normal SampleInfo if the specified sample file isn't found, but that's a pretty edge-case scenario
|
|
||||||
soundTypes.Add(new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume));
|
soundTypes.Add(new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,14 +679,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered);
|
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable<FileHitSampleInfo>
|
public class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable<FileHitSampleInfo>
|
||||||
{
|
{
|
||||||
public readonly string Filename;
|
public readonly string Filename;
|
||||||
|
|
||||||
public FileHitSampleInfo(string filename, int volume)
|
public FileHitSampleInfo(string filename, int volume)
|
||||||
// Force CSS=1 to make sure that the LegacyBeatmapSkin does not fall back to the user skin.
|
// Force CSS=1 to make sure that the LegacyBeatmapSkin does not fall back to the user skin.
|
||||||
// Note that this does not change the lookup names, as they are overridden locally.
|
: base(HIT_NORMAL, SampleControlPoint.DEFAULT_BANK, customSampleBank: 1, volume: volume)
|
||||||
: base(string.Empty, customSampleBank: 1, volume: volume)
|
|
||||||
{
|
{
|
||||||
Filename = filename;
|
Filename = filename;
|
||||||
}
|
}
|
||||||
@@ -696,7 +694,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
{
|
{
|
||||||
Filename,
|
Filename,
|
||||||
Path.ChangeExtension(Filename, null)
|
Path.ChangeExtension(Filename, null)
|
||||||
};
|
}.Concat(base.LookupNames);
|
||||||
|
|
||||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
|
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
|
||||||
Optional<bool> newEditorAutoBank = default, Optional<int> newCustomSampleBank = default, Optional<bool> newIsLayered = default)
|
Optional<bool> newEditorAutoBank = default, Optional<int> newCustomSampleBank = default, Optional<bool> newIsLayered = default)
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||||
{
|
{
|
||||||
[Cached(typeof(IPreviewTrackOwner))]
|
|
||||||
public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePresentBeatmap
|
public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePresentBeatmap
|
||||||
{
|
{
|
||||||
private readonly Room room;
|
private readonly Room room;
|
||||||
|
|||||||
@@ -1,466 +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 System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Audio;
|
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.Drawables;
|
|
||||||
using osu.Game.Beatmaps.Drawables.Cards;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Localisation;
|
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osu.Game.Overlays.BeatmapSet;
|
|
||||||
using osu.Game.Resources.Localisation.Web;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|
||||||
{
|
|
||||||
public partial class BeatmapCardMatchmaking : BeatmapCard
|
|
||||||
{
|
|
||||||
private readonly APIBeatmap beatmap;
|
|
||||||
|
|
||||||
protected override Drawable IdleContent => idleBottomContent;
|
|
||||||
protected override Drawable DownloadInProgressContent => downloadProgressBar;
|
|
||||||
|
|
||||||
public const float HEIGHT = 80;
|
|
||||||
|
|
||||||
[Cached]
|
|
||||||
private readonly BeatmapCardContent content;
|
|
||||||
|
|
||||||
private BeatmapCardThumbnail thumbnail = null!;
|
|
||||||
private CollapsibleButtonContainer buttonContainer = null!;
|
|
||||||
|
|
||||||
private FillFlowContainer idleBottomContent = null!;
|
|
||||||
private BeatmapCardDownloadProgressBar downloadProgressBar = null!;
|
|
||||||
|
|
||||||
public AvatarOverlay SelectionOverlay = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private BeatmapSetOverlay? beatmapSetOverlay { get; set; }
|
|
||||||
|
|
||||||
public BeatmapCardMatchmaking(APIBeatmap beatmap)
|
|
||||||
: base(beatmap.BeatmapSet!, false)
|
|
||||||
{
|
|
||||||
this.beatmap = beatmap;
|
|
||||||
content = new BeatmapCardContent(HEIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
Width = WIDTH;
|
|
||||||
Height = HEIGHT;
|
|
||||||
|
|
||||||
FillFlowContainer leftIconArea = null!;
|
|
||||||
FillFlowContainer titleBadgeArea = null!;
|
|
||||||
GridContainer artistContainer = null!;
|
|
||||||
|
|
||||||
Child = content.With(c =>
|
|
||||||
{
|
|
||||||
c.MainContent = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet, keepLoaded: true)
|
|
||||||
{
|
|
||||||
Name = @"Left (icon) area",
|
|
||||||
Size = new Vector2(HEIGHT),
|
|
||||||
Padding = new MarginPadding { Right = CORNER_RADIUS },
|
|
||||||
Child = leftIconArea = new FillFlowContainer
|
|
||||||
{
|
|
||||||
Margin = new MarginPadding(4),
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
buttonContainer = new CollapsibleButtonContainer(BeatmapSet, allowNavigationToBeatmap: false, keepBackgroundLoaded: true)
|
|
||||||
{
|
|
||||||
X = HEIGHT - CORNER_RADIUS,
|
|
||||||
Width = WIDTH - HEIGHT + CORNER_RADIUS,
|
|
||||||
FavouriteState = { BindTarget = FavouriteState },
|
|
||||||
ButtonsCollapsedWidth = 0,
|
|
||||||
ButtonsExpandedWidth = 24,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
},
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize)
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new TruncatingSpriteText
|
|
||||||
{
|
|
||||||
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
|
||||||
Font = OsuFont.Default.With(size: 18f, weight: FontWeight.SemiBold),
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
},
|
|
||||||
titleBadgeArea = new FillFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
artistContainer = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.AutoSize)
|
|
||||||
},
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize)
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new TruncatingSpriteText
|
|
||||||
{
|
|
||||||
Text = createArtistText(),
|
|
||||||
Font = OsuFont.Default.With(size: 14f, weight: FontWeight.SemiBold),
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
},
|
|
||||||
Empty()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new LinkFlowContainer(s =>
|
|
||||||
{
|
|
||||||
s.Shadow = false;
|
|
||||||
s.Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold);
|
|
||||||
}).With(d =>
|
|
||||||
{
|
|
||||||
d.AutoSizeAxes = Axes.Both;
|
|
||||||
d.Margin = new MarginPadding { Top = 1 };
|
|
||||||
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
|
|
||||||
d.AddUserLink(BeatmapSet.Author);
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
Name = @"Bottom content",
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
idleBottomContent = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(0, 2),
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = CORNER_RADIUS,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = colours.ForStarDifficulty(beatmap.StarRating).Darken(0.8f),
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
Padding = new MarginPadding(4),
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(6, 0),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new StarRatingDisplay(new StarDifficulty(beatmap.StarRating, 0), StarRatingDisplaySize.Small, animated: true)
|
|
||||||
{
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Scale = new Vector2(0.9f),
|
|
||||||
},
|
|
||||||
new TruncatingSpriteText
|
|
||||||
{
|
|
||||||
Text = beatmap.DifficultyName,
|
|
||||||
Font = OsuFont.Style.Caption1.With(weight: FontWeight.Bold),
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
downloadProgressBar = new BeatmapCardDownloadProgressBar
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 5,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
State = { BindTarget = DownloadTracker.State },
|
|
||||||
Progress = { BindTarget = DownloadTracker.Progress }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SelectionOverlay = new AvatarOverlay
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
c.Expanded.BindTarget = Expanded;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (BeatmapSet.HasVideo)
|
|
||||||
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) });
|
|
||||||
|
|
||||||
if (BeatmapSet.HasStoryboard)
|
|
||||||
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) });
|
|
||||||
|
|
||||||
if (BeatmapSet.FeaturedInSpotlight)
|
|
||||||
{
|
|
||||||
titleBadgeArea.Add(new SpotlightBeatmapBadge
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Margin = new MarginPadding { Left = 4 }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BeatmapSet.HasExplicitContent)
|
|
||||||
{
|
|
||||||
titleBadgeArea.Add(new ExplicitContentBeatmapBadge
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Margin = new MarginPadding { Left = 4 }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BeatmapSet.TrackId != null)
|
|
||||||
{
|
|
||||||
artistContainer.Content[0][1] = new FeaturedArtistBeatmapBadge
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Margin = new MarginPadding { Left = 4 }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalisableString createArtistText()
|
|
||||||
{
|
|
||||||
var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist);
|
|
||||||
return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateState()
|
|
||||||
{
|
|
||||||
base.UpdateState();
|
|
||||||
|
|
||||||
bool showDetails = IsHovered;
|
|
||||||
|
|
||||||
buttonContainer.ShowDetails.Value = showDetails;
|
|
||||||
thumbnail.Dimmed.Value = showDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override MenuItem[] ContextMenuItems
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
List<MenuItem> items = new List<MenuItem>
|
|
||||||
{
|
|
||||||
new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, () => beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineID))
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var button in buttonContainer.Buttons)
|
|
||||||
{
|
|
||||||
if (button.Enabled.Value)
|
|
||||||
items.Add(new OsuMenuItem(button.TooltipText.ToSentence(), MenuItemType.Standard, () => button.TriggerClick()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return items.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class AvatarOverlay : CompositeDrawable
|
|
||||||
{
|
|
||||||
private readonly Container<SelectionAvatar> avatars;
|
|
||||||
|
|
||||||
private Sample? userAddedSample;
|
|
||||||
private double? lastSamplePlayback;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IAPIProvider api { get; set; } = null!;
|
|
||||||
|
|
||||||
public AvatarOverlay()
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
InternalChild = avatars = new Container<SelectionAvatar>
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
Height = SelectionAvatar.AVATAR_SIZE,
|
|
||||||
};
|
|
||||||
|
|
||||||
Padding = new MarginPadding { Vertical = 5 };
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(AudioManager audio)
|
|
||||||
{
|
|
||||||
userAddedSample = audio.Samples.Get(@"Multiplayer/player-ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AddUser(APIUser user)
|
|
||||||
{
|
|
||||||
if (avatars.Any(a => a.User.Id == user.Id))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var avatar = new SelectionAvatar(user, user.Equals(api.LocalUser.Value));
|
|
||||||
|
|
||||||
avatars.Add(avatar);
|
|
||||||
|
|
||||||
if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > OsuGameBase.SAMPLE_DEBOUNCE_TIME)
|
|
||||||
{
|
|
||||||
userAddedSample?.Play();
|
|
||||||
lastSamplePlayback = Time.Current;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAvatarLayout();
|
|
||||||
|
|
||||||
avatar.FinishTransforms();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RemoveUser(int id)
|
|
||||||
{
|
|
||||||
if (avatars.SingleOrDefault(a => a.User.Id == id) is not SelectionAvatar avatar)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
avatar.PopOutAndExpire();
|
|
||||||
avatars.ChangeChildDepth(avatar, float.MaxValue);
|
|
||||||
|
|
||||||
updateAvatarLayout();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateAvatarLayout()
|
|
||||||
{
|
|
||||||
const double stagger = 30;
|
|
||||||
const float spacing = 4;
|
|
||||||
|
|
||||||
double delay = 0;
|
|
||||||
float x = 0;
|
|
||||||
|
|
||||||
for (int i = avatars.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var avatar = avatars[i];
|
|
||||||
|
|
||||||
if (avatar.Expired)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
avatar.Delay(delay).MoveToX(x, 500, Easing.OutElasticQuarter);
|
|
||||||
|
|
||||||
x -= avatar.LayoutSize.X + spacing;
|
|
||||||
|
|
||||||
delay += stagger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class SelectionAvatar : CompositeDrawable
|
|
||||||
{
|
|
||||||
public const float AVATAR_SIZE = 30;
|
|
||||||
|
|
||||||
public APIUser User { get; }
|
|
||||||
|
|
||||||
public bool Expired { get; private set; }
|
|
||||||
|
|
||||||
private readonly MatchmakingAvatar avatar;
|
|
||||||
|
|
||||||
public SelectionAvatar(APIUser user, bool isOwnUser)
|
|
||||||
{
|
|
||||||
User = user;
|
|
||||||
Size = new Vector2(AVATAR_SIZE);
|
|
||||||
|
|
||||||
InternalChild = avatar = new MatchmakingAvatar(user, isOwnUser)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
avatar.ScaleTo(0)
|
|
||||||
.ScaleTo(1, 500, Easing.OutElasticHalf)
|
|
||||||
.FadeIn(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PopOutAndExpire()
|
|
||||||
{
|
|
||||||
avatar.ScaleTo(0, 400, Easing.OutExpo);
|
|
||||||
|
|
||||||
this.FadeOut(100).Expire();
|
|
||||||
Expired = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Toolkit.HighPerformance;
|
using Microsoft.Toolkit.HighPerformance;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@@ -33,16 +34,19 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
|
|
||||||
public event Action<MultiplayerPlaylistItem>? ItemSelected;
|
public event Action<MultiplayerPlaylistItem>? ItemSelected;
|
||||||
|
|
||||||
private readonly Dictionary<long, BeatmapSelectPanel> panelLookup = new Dictionary<long, BeatmapSelectPanel>();
|
private readonly Dictionary<long, MatchmakingSelectPanel> panelLookup = new Dictionary<long, MatchmakingSelectPanel>();
|
||||||
|
private readonly Dictionary<long, MatchmakingPlaylistItem> playlistItems = new Dictionary<long, MatchmakingPlaylistItem>();
|
||||||
|
private MatchmakingSelectPanelRandom randomPanel = null!;
|
||||||
|
|
||||||
private readonly PanelGridContainer panelGridContainer;
|
private readonly PanelGridContainer panelGridContainer;
|
||||||
private readonly Container<BeatmapSelectPanel> rollContainer;
|
private readonly Container<MatchmakingSelectPanel> rollContainer;
|
||||||
private readonly OsuScrollContainer scroll;
|
private readonly OsuScrollContainer scroll;
|
||||||
|
|
||||||
private bool allowSelection = true;
|
private bool allowSelection = true;
|
||||||
|
|
||||||
private readonly Sample?[] spinSamples = new Sample?[5];
|
private readonly Sample?[] spinSamples = new Sample?[5];
|
||||||
private static readonly int[] spin_sample_sequence = [0, 1, 2, 3, 4, 2, 3, 4];
|
private static readonly int[] spin_sample_sequence = [0, 1, 2, 3, 4, 2, 3, 4];
|
||||||
|
private Sample? randomRevealSample;
|
||||||
private Sample? resultSample;
|
private Sample? resultSample;
|
||||||
private Sample? swooshSample;
|
private Sample? swooshSample;
|
||||||
private double? lastSamplePlayback;
|
private double? lastSamplePlayback;
|
||||||
@@ -63,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
Spacing = new Vector2(panel_spacing)
|
Spacing = new Vector2(panel_spacing)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rollContainer = new Container<BeatmapSelectPanel>
|
rollContainer = new Container<MatchmakingSelectPanel>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
@@ -77,13 +81,38 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
for (int i = 0; i < spinSamples.Length; i++)
|
for (int i = 0; i < spinSamples.Length; i++)
|
||||||
spinSamples[i] = audio.Samples.Get($@"Multiplayer/Matchmaking/Selection/roulette-{i}");
|
spinSamples[i] = audio.Samples.Get($@"Multiplayer/Matchmaking/Selection/roulette-{i}");
|
||||||
|
|
||||||
|
randomRevealSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Selection/random-reveal");
|
||||||
resultSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Selection/roulette-result");
|
resultSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Selection/roulette-result");
|
||||||
swooshSample = audio.Samples.Get(@"SongSelect/options-pop-out");
|
swooshSample = audio.Samples.Get(@"SongSelect/options-pop-out");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
public void AddItems(IEnumerable<MatchmakingPlaylistItem> items)
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
playlistItems[item.ID] = item;
|
||||||
|
|
||||||
|
var panel = panelLookup[item.ID] = new MatchmakingSelectPanelBeatmap(item)
|
||||||
|
{
|
||||||
|
AllowSelection = allowSelection,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Action = i => ItemSelected?.Invoke(i),
|
||||||
|
};
|
||||||
|
|
||||||
|
panelGridContainer.Add(panel);
|
||||||
|
panelGridContainer.SetLayoutPosition(panel, (float)panel.Item.StarRating);
|
||||||
|
}
|
||||||
|
|
||||||
|
panelLookup[-1] = randomPanel = new MatchmakingSelectPanelRandom(new MultiplayerPlaylistItem { ID = -1 })
|
||||||
|
{
|
||||||
|
AllowSelection = allowSelection,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Action = i => ItemSelected?.Invoke(i),
|
||||||
|
};
|
||||||
|
panelGridContainer.Add(randomPanel);
|
||||||
|
panelGridContainer.SetLayoutPosition(randomPanel, float.MinValue);
|
||||||
|
|
||||||
const double enter_duration = 500;
|
const double enter_duration = 500;
|
||||||
|
|
||||||
@@ -99,32 +128,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
|
|
||||||
panel.FadeInAndEnterFromBelow(duration: enter_duration, delay: delay);
|
panel.FadeInAndEnterFromBelow(duration: enter_duration, delay: delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
panelsLoaded.SetResult();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddItem(MultiplayerPlaylistItem item)
|
public void SetUserSelection(APIUser user, long itemId, bool selected) => whenPanelsLoaded(() =>
|
||||||
{
|
|
||||||
var panel = panelLookup[item.ID] = new BeatmapSelectPanel(item)
|
|
||||||
{
|
|
||||||
AllowSelection = allowSelection,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Action = ItemSelected,
|
|
||||||
};
|
|
||||||
|
|
||||||
panelGridContainer.Add(panel);
|
|
||||||
panelGridContainer.SetLayoutPosition(panel, (float)item.StarRating);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveItem(long id)
|
|
||||||
{
|
|
||||||
if (!panelLookup.Remove(id, out var panel))
|
|
||||||
return;
|
|
||||||
|
|
||||||
panel.Expire();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetUserSelection(APIUser user, long itemId, bool selected)
|
|
||||||
{
|
{
|
||||||
if (!panelLookup.TryGetValue(itemId, out var panel))
|
if (!panelLookup.TryGetValue(itemId, out var panel))
|
||||||
return;
|
return;
|
||||||
@@ -133,9 +142,19 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
panel.AddUser(user);
|
panel.AddUser(user);
|
||||||
else
|
else
|
||||||
panel.RemoveUser(user);
|
panel.RemoveUser(user);
|
||||||
}
|
});
|
||||||
|
|
||||||
public void RollAndDisplayFinalBeatmap(long[] candidateItemIds, long finalItemId)
|
public void RevealRandomItem(MultiplayerPlaylistItem item) => whenPanelsLoaded(() =>
|
||||||
|
{
|
||||||
|
playlistItems.TryGetValue(item.ID, out var playlistItem);
|
||||||
|
|
||||||
|
Debug.Assert(playlistItem != null);
|
||||||
|
|
||||||
|
randomRevealSample?.Play();
|
||||||
|
randomPanel.RevealBeatmap(playlistItem.Beatmap, playlistItem.Mods);
|
||||||
|
});
|
||||||
|
|
||||||
|
public void RollAndDisplayFinalBeatmap(long[] candidateItemIds, long finalItemId) => whenPanelsLoaded(() =>
|
||||||
{
|
{
|
||||||
Debug.Assert(candidateItemIds.Length >= 1);
|
Debug.Assert(candidateItemIds.Length >= 1);
|
||||||
Debug.Assert(candidateItemIds.Contains(finalItemId));
|
Debug.Assert(candidateItemIds.Contains(finalItemId));
|
||||||
@@ -162,7 +181,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
.Delay(roll_duration + present_beatmap_delay)
|
.Delay(roll_duration + present_beatmap_delay)
|
||||||
.Schedule(() => PresentRolledBeatmap(finalItemId));
|
.Schedule(() => PresentRolledBeatmap(finalItemId));
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
internal void TransferCandidatePanelsToRollContainer(long[] candidateItemIds, double duration = hide_duration)
|
internal void TransferCandidatePanelsToRollContainer(long[] candidateItemIds, double duration = hide_duration)
|
||||||
{
|
{
|
||||||
@@ -171,7 +190,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
|
|
||||||
var rng = new Random();
|
var rng = new Random();
|
||||||
|
|
||||||
var remainingPanels = new List<BeatmapSelectPanel>();
|
var remainingPanels = new List<MatchmakingSelectPanel>();
|
||||||
|
|
||||||
foreach (var panel in panelGridContainer.Children.ToArray())
|
foreach (var panel in panelGridContainer.Children.ToArray())
|
||||||
{
|
{
|
||||||
@@ -211,7 +230,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
{
|
{
|
||||||
var panel = rollContainer.Children[i];
|
var panel = rollContainer.Children[i];
|
||||||
|
|
||||||
var position = positions[i] * (BeatmapSelectPanel.SIZE + new Vector2(panel_spacing));
|
var position = positions[i] * (MatchmakingSelectPanel.SIZE + new Vector2(panel_spacing));
|
||||||
|
|
||||||
panel.MoveTo(position, duration + stagger * i, new SplitEasingFunction(Easing.InCubic, Easing.OutExpo, 0.3f));
|
panel.MoveTo(position, duration + stagger * i, new SplitEasingFunction(Easing.InCubic, Easing.OutExpo, 0.3f));
|
||||||
|
|
||||||
@@ -280,7 +299,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
while ((numSteps - 1) % rollContainer.Children.Count != finalItemIndex)
|
while ((numSteps - 1) % rollContainer.Children.Count != finalItemIndex)
|
||||||
numSteps++;
|
numSteps++;
|
||||||
|
|
||||||
BeatmapSelectPanel? lastPanel = null;
|
MatchmakingSelectPanel? lastPanel = null;
|
||||||
|
|
||||||
for (int i = 0; i < numSteps; i++)
|
for (int i = 0; i < numSteps; i++)
|
||||||
{
|
{
|
||||||
@@ -341,7 +360,15 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
PresentRolledBeatmap(finalItem);
|
PresentRolledBeatmap(finalItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class PanelGridContainer : FillFlowContainer<BeatmapSelectPanel>
|
private readonly TaskCompletionSource panelsLoaded = new TaskCompletionSource();
|
||||||
|
|
||||||
|
private void whenPanelsLoaded(Action action) => Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await panelsLoaded.Task.ConfigureAwait(false);
|
||||||
|
Schedule(action);
|
||||||
|
});
|
||||||
|
|
||||||
|
private partial class PanelGridContainer : FillFlowContainer<MatchmakingSelectPanel>
|
||||||
{
|
{
|
||||||
public bool LayoutDisabled;
|
public bool LayoutDisabled;
|
||||||
|
|
||||||
|
|||||||
@@ -1,241 +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 System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Effects;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Beatmaps.Drawables.Cards;
|
|
||||||
using osu.Game.Database;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|
||||||
{
|
|
||||||
public partial class BeatmapSelectPanel : Container
|
|
||||||
{
|
|
||||||
public static readonly Vector2 SIZE = new Vector2(BeatmapCard.WIDTH, BeatmapCardNormal.HEIGHT);
|
|
||||||
|
|
||||||
public bool AllowSelection { get; set; }
|
|
||||||
|
|
||||||
public readonly MultiplayerPlaylistItem Item;
|
|
||||||
|
|
||||||
public Action<MultiplayerPlaylistItem>? Action { private get; init; }
|
|
||||||
|
|
||||||
private const float border_width = 3;
|
|
||||||
|
|
||||||
private Container scaleContainer = null!;
|
|
||||||
private Drawable lighting = null!;
|
|
||||||
|
|
||||||
private Container border = null!;
|
|
||||||
private Container mainContent = null!;
|
|
||||||
|
|
||||||
private readonly List<APIUser> users = new List<APIUser>();
|
|
||||||
|
|
||||||
private BeatmapCardMatchmaking? card;
|
|
||||||
|
|
||||||
public BeatmapSelectPanel(MultiplayerPlaylistItem item)
|
|
||||||
{
|
|
||||||
Item = item;
|
|
||||||
Size = SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(BeatmapLookupCache lookupCache, OverlayColourProvider colourProvider)
|
|
||||||
{
|
|
||||||
InternalChild = scaleContainer = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
mainContent = new Container
|
|
||||||
{
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
|
||||||
CornerExponent = 10,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
lighting = new Box
|
|
||||||
{
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
border = new Container
|
|
||||||
{
|
|
||||||
Alpha = 0,
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
|
||||||
CornerExponent = 10,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
BorderThickness = border_width,
|
|
||||||
BorderColour = colourProvider.Light1,
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Radius = 40,
|
|
||||||
Roundness = 300,
|
|
||||||
Colour = colourProvider.Light3.Opacity(0.1f),
|
|
||||||
},
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Alpha = 0,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lookupCache.GetBeatmapAsync(Item.BeatmapID).ContinueWith(b => Schedule(() =>
|
|
||||||
{
|
|
||||||
Debug.Assert(card == null);
|
|
||||||
|
|
||||||
APIBeatmap beatmap = b.GetResultSafely() ?? new APIBeatmap
|
|
||||||
{
|
|
||||||
BeatmapSet = new APIBeatmapSet
|
|
||||||
{
|
|
||||||
Title = "unknown beatmap",
|
|
||||||
TitleUnicode = "unknown beatmap",
|
|
||||||
Artist = "unknown artist",
|
|
||||||
ArtistUnicode = "unknown artist",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
beatmap.StarRating = Item.StarRating;
|
|
||||||
|
|
||||||
mainContent.Add(card = new BeatmapCardMatchmaking(beatmap)
|
|
||||||
{
|
|
||||||
Depth = float.MaxValue,
|
|
||||||
Action = () =>
|
|
||||||
{
|
|
||||||
if (AllowSelection)
|
|
||||||
Action?.Invoke(Item);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var user in users)
|
|
||||||
card.SelectionOverlay.AddUser(user);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddUser(APIUser user)
|
|
||||||
{
|
|
||||||
users.Add(user);
|
|
||||||
card?.SelectionOverlay.AddUser(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveUser(APIUser user)
|
|
||||||
{
|
|
||||||
users.Remove(user);
|
|
||||||
card?.SelectionOverlay.RemoveUser(user.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
|
||||||
{
|
|
||||||
if (AllowSelection)
|
|
||||||
{
|
|
||||||
lighting.FadeTo(0.2f, 50)
|
|
||||||
.Then()
|
|
||||||
.FadeTo(0.1f, 300);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnHover(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
|
||||||
{
|
|
||||||
base.OnHoverLost(e);
|
|
||||||
|
|
||||||
lighting.FadeOut(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
|
||||||
{
|
|
||||||
if (AllowSelection && e.Button == MouseButton.Left)
|
|
||||||
scaleContainer.ScaleTo(0.95f, 400, Easing.OutExpo);
|
|
||||||
|
|
||||||
return base.OnMouseDown(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
|
||||||
{
|
|
||||||
base.OnMouseUp(e);
|
|
||||||
|
|
||||||
if (e.Button == MouseButton.Left)
|
|
||||||
scaleContainer.ScaleTo(1f, 500, Easing.OutElasticHalf);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
if (AllowSelection)
|
|
||||||
{
|
|
||||||
lighting.FadeTo(0.5f, 50)
|
|
||||||
.Then()
|
|
||||||
.FadeTo(0.1f, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass through to let the beatmap card handle actual click.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowChosenBorder()
|
|
||||||
{
|
|
||||||
border.FadeTo(1, 1000, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowBorder()
|
|
||||||
{
|
|
||||||
border.FadeTo(1, 80, Easing.OutQuint)
|
|
||||||
.Then()
|
|
||||||
.FadeTo(0.7f, 800, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideBorder()
|
|
||||||
{
|
|
||||||
border.FadeOut(500, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FadeInAndEnterFromBelow(double duration = 500, double delay = 0, float distance = 200)
|
|
||||||
{
|
|
||||||
scaleContainer
|
|
||||||
.FadeOut()
|
|
||||||
.MoveToY(distance)
|
|
||||||
.Delay(delay)
|
|
||||||
.FadeIn(duration / 2)
|
|
||||||
.MoveToY(0, duration, Easing.OutExpo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PopOutAndExpire(double duration = 400, double delay = 0, Easing easing = Easing.InCubic)
|
|
||||||
{
|
|
||||||
AllowSelection = false;
|
|
||||||
|
|
||||||
scaleContainer.Delay(delay)
|
|
||||||
.ScaleTo(0, duration, easing)
|
|
||||||
.FadeOut(duration);
|
|
||||||
|
|
||||||
this.Delay(delay + duration).FadeOut().Expire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 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.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||||
|
{
|
||||||
|
public record MatchmakingPlaylistItem(MultiplayerPlaylistItem PlaylistItem, APIBeatmap Beatmap, Mod[] Mods)
|
||||||
|
{
|
||||||
|
public long ID => PlaylistItem.ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||||
|
{
|
||||||
|
public partial class MatchmakingSelectPanel
|
||||||
|
{
|
||||||
|
public abstract partial class CardContent : CompositeDrawable
|
||||||
|
{
|
||||||
|
public abstract AvatarOverlay SelectionOverlay { get; }
|
||||||
|
|
||||||
|
protected CardContent()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class AvatarOverlay : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Container<SelectionAvatar> avatars;
|
||||||
|
|
||||||
|
private Sample? userAddedSample;
|
||||||
|
private double? lastSamplePlayback;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
|
public AvatarOverlay()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = avatars = new Container<SelectionAvatar>
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Height = SelectionAvatar.AVATAR_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
Padding = new MarginPadding { Vertical = 5 };
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio)
|
||||||
|
{
|
||||||
|
userAddedSample = audio.Samples.Get(@"Multiplayer/player-ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddUser(APIUser user)
|
||||||
|
{
|
||||||
|
if (avatars.Any(a => a.User.Id == user.Id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var avatar = new SelectionAvatar(user, user.Equals(api.LocalUser.Value));
|
||||||
|
|
||||||
|
avatars.Add(avatar);
|
||||||
|
|
||||||
|
if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > OsuGameBase.SAMPLE_DEBOUNCE_TIME)
|
||||||
|
{
|
||||||
|
userAddedSample?.Play();
|
||||||
|
lastSamplePlayback = Time.Current;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAvatarLayout();
|
||||||
|
|
||||||
|
avatar.FinishTransforms();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveUser(int id)
|
||||||
|
{
|
||||||
|
if (avatars.SingleOrDefault(a => a.User.Id == id) is not SelectionAvatar avatar)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
avatar.PopOutAndExpire();
|
||||||
|
avatars.ChangeChildDepth(avatar, float.MaxValue);
|
||||||
|
|
||||||
|
updateAvatarLayout();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAvatarLayout()
|
||||||
|
{
|
||||||
|
const double stagger = 30;
|
||||||
|
const float spacing = 4;
|
||||||
|
|
||||||
|
double delay = 0;
|
||||||
|
float x = 0;
|
||||||
|
|
||||||
|
for (int i = avatars.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var avatar = avatars[i];
|
||||||
|
|
||||||
|
if (avatar.Expired)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
avatar.Delay(delay).MoveToX(x, 500, Easing.OutElasticQuarter);
|
||||||
|
|
||||||
|
x -= avatar.LayoutSize.X + spacing;
|
||||||
|
|
||||||
|
delay += stagger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class SelectionAvatar : CompositeDrawable
|
||||||
|
{
|
||||||
|
public const float AVATAR_SIZE = 30;
|
||||||
|
|
||||||
|
public APIUser User { get; }
|
||||||
|
|
||||||
|
public bool Expired { get; private set; }
|
||||||
|
|
||||||
|
private readonly MatchmakingAvatar avatar;
|
||||||
|
|
||||||
|
public SelectionAvatar(APIUser user, bool isOwnUser)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
Size = new Vector2(AVATAR_SIZE);
|
||||||
|
|
||||||
|
InternalChild = avatar = new MatchmakingAvatar(user, isOwnUser)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
avatar.ScaleTo(0)
|
||||||
|
.ScaleTo(1, 500, Easing.OutElasticHalf)
|
||||||
|
.FadeIn(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopOutAndExpire()
|
||||||
|
{
|
||||||
|
avatar.ScaleTo(0, 400, Easing.OutExpo);
|
||||||
|
|
||||||
|
this.FadeOut(100).Expire();
|
||||||
|
Expired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,381 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Online;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.BeatmapSet;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||||
|
{
|
||||||
|
public partial class MatchmakingSelectPanel
|
||||||
|
{
|
||||||
|
public partial class CardContentBeatmap : CardContent, IHasContextMenu
|
||||||
|
{
|
||||||
|
public override AvatarOverlay SelectionOverlay => selectionOverlay;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapSetOverlay? beatmapSetOverlay { get; set; }
|
||||||
|
|
||||||
|
private readonly IBindable<DownloadState> downloadState = new Bindable<DownloadState>();
|
||||||
|
private readonly IBindableNumber<double> downloadProgress = new BindableDouble();
|
||||||
|
private readonly Bindable<BeatmapSetFavouriteState> favouriteState = new Bindable<BeatmapSetFavouriteState>();
|
||||||
|
private readonly APIBeatmapSet beatmapSet;
|
||||||
|
private readonly APIBeatmap beatmap;
|
||||||
|
private readonly Mod[] mods;
|
||||||
|
|
||||||
|
private BeatmapCardThumbnail thumbnail = null!;
|
||||||
|
private CollapsibleButtonContainer buttonContainer = null!;
|
||||||
|
private FillFlowContainer idleBottomContent = null!;
|
||||||
|
private BeatmapCardDownloadProgressBar downloadProgressBar = null!;
|
||||||
|
private AvatarOverlay selectionOverlay = null!;
|
||||||
|
|
||||||
|
public CardContentBeatmap(APIBeatmap beatmap, Mod[] mods)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
this.mods = mods;
|
||||||
|
|
||||||
|
beatmapSet = beatmap.BeatmapSet!;
|
||||||
|
favouriteState.Value = new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
FillFlowContainer leftIconArea;
|
||||||
|
FillFlowContainer titleBadgeArea;
|
||||||
|
GridContainer artistContainer;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new BeatmapDownloadTracker(beatmap.BeatmapSet!)
|
||||||
|
{
|
||||||
|
State = { BindTarget = downloadState },
|
||||||
|
Progress = { BindTarget = downloadProgress },
|
||||||
|
},
|
||||||
|
thumbnail = new BeatmapCardThumbnail(beatmapSet, beatmapSet, keepLoaded: true)
|
||||||
|
{
|
||||||
|
Name = @"Left (icon) area",
|
||||||
|
Size = new Vector2(MatchmakingSelectPanel.HEIGHT),
|
||||||
|
Padding = new MarginPadding { Right = BeatmapCard.CORNER_RADIUS },
|
||||||
|
Child = leftIconArea = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding(4),
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttonContainer = new CollapsibleButtonContainer(beatmapSet, allowNavigationToBeatmap: false, keepBackgroundLoaded: true)
|
||||||
|
{
|
||||||
|
X = MatchmakingSelectPanel.HEIGHT - BeatmapCard.CORNER_RADIUS,
|
||||||
|
Width = BeatmapCard.WIDTH - MatchmakingSelectPanel.HEIGHT + BeatmapCard.CORNER_RADIUS,
|
||||||
|
FavouriteState = { BindTarget = favouriteState },
|
||||||
|
ButtonsCollapsedWidth = 0,
|
||||||
|
ButtonsExpandedWidth = 24,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new TruncatingSpriteText
|
||||||
|
{
|
||||||
|
Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title),
|
||||||
|
Font = OsuFont.Default.With(size: 18f, weight: FontWeight.SemiBold),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
},
|
||||||
|
titleBadgeArea = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
artistContainer = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new TruncatingSpriteText
|
||||||
|
{
|
||||||
|
Text = BeatmapsetsStrings.ShowDetailsByArtist(new RomanisableString(beatmapSet.ArtistUnicode, beatmapSet.Artist)),
|
||||||
|
Font = OsuFont.Default.With(size: 14f, weight: FontWeight.SemiBold),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
},
|
||||||
|
Empty()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new LinkFlowContainer(s =>
|
||||||
|
{
|
||||||
|
s.Shadow = false;
|
||||||
|
s.Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold);
|
||||||
|
}).With(d =>
|
||||||
|
{
|
||||||
|
d.AutoSizeAxes = Axes.Both;
|
||||||
|
d.Margin = new MarginPadding { Top = 1 };
|
||||||
|
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
|
||||||
|
d.AddUserLink(beatmapSet.Author);
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Name = @"Bottom content",
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
idleBottomContent = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 2),
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.ForStarDifficulty(beatmap.StarRating).Darken(0.8f),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(4),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(6, 0),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new StarRatingDisplay(new StarDifficulty(beatmap.StarRating, 0), StarRatingDisplaySize.Small, animated: true)
|
||||||
|
{
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Scale = new Vector2(0.9f),
|
||||||
|
},
|
||||||
|
new TruncatingSpriteText
|
||||||
|
{
|
||||||
|
Text = beatmap.DifficultyName,
|
||||||
|
Font = OsuFont.Style.Caption1.With(weight: FontWeight.Bold),
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ModFlowDisplay
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scale = new Vector2(0.5f),
|
||||||
|
Margin = new MarginPadding { Left = 5 },
|
||||||
|
Current = { Value = mods }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
downloadProgressBar = new BeatmapCardDownloadProgressBar
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 5,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
State = { BindTarget = downloadState },
|
||||||
|
Progress = { BindTarget = downloadProgress }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectionOverlay = new AvatarOverlay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (beatmapSet.HasVideo)
|
||||||
|
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) });
|
||||||
|
|
||||||
|
if (beatmapSet.HasStoryboard)
|
||||||
|
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) });
|
||||||
|
|
||||||
|
if (beatmapSet.FeaturedInSpotlight)
|
||||||
|
{
|
||||||
|
titleBadgeArea.Add(new SpotlightBeatmapBadge
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Margin = new MarginPadding { Left = 4 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beatmapSet.HasExplicitContent)
|
||||||
|
{
|
||||||
|
titleBadgeArea.Add(new ExplicitContentBeatmapBadge
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Margin = new MarginPadding { Left = 4 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beatmapSet.TrackId != null)
|
||||||
|
{
|
||||||
|
artistContainer.Content[0][1] = new FeaturedArtistBeatmapBadge
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Margin = new MarginPadding { Left = 4 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
downloadState.BindValueChanged(_ => updateState(), true);
|
||||||
|
|
||||||
|
FinishTransforms(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
bool showDetails = IsHovered;
|
||||||
|
|
||||||
|
buttonContainer.ShowDetails.Value = showDetails;
|
||||||
|
thumbnail.Dimmed.Value = showDetails;
|
||||||
|
|
||||||
|
bool showProgress = downloadState.Value == DownloadState.Downloading || downloadState.Value == DownloadState.Importing;
|
||||||
|
|
||||||
|
idleBottomContent.FadeTo(showProgress ? 0 : 1, 340, Easing.OutQuint);
|
||||||
|
downloadProgressBar.FadeTo(showProgress ? 1 : 0, 340, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MenuItem[] ContextMenuItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<MenuItem> items = new List<MenuItem>
|
||||||
|
{
|
||||||
|
new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, () => beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineID))
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var button in buttonContainer.Buttons)
|
||||||
|
{
|
||||||
|
if (button.Enabled.Value)
|
||||||
|
items.Add(new OsuMenuItem(button.TooltipText.ToSentence(), MenuItemType.Standard, () => button.TriggerClick()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// 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.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||||
|
{
|
||||||
|
public partial class MatchmakingSelectPanel
|
||||||
|
{
|
||||||
|
public partial class CardContentRandom : CardContent
|
||||||
|
{
|
||||||
|
public override AvatarOverlay SelectionOverlay => selectionOverlay;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
private AvatarOverlay selectionOverlay = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background2,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 10,
|
||||||
|
Vertical = 4
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children =
|
||||||
|
[
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Size = new Vector2(32),
|
||||||
|
Icon = FontAwesome.Solid.Random,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = "Random",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
selectionOverlay = new AvatarOverlay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||||
|
{
|
||||||
|
public abstract partial class MatchmakingSelectPanel : Container
|
||||||
|
{
|
||||||
|
public const float WIDTH = 345;
|
||||||
|
public const float HEIGHT = 80;
|
||||||
|
|
||||||
|
public static readonly Vector2 SIZE = new Vector2(WIDTH, HEIGHT);
|
||||||
|
|
||||||
|
public bool AllowSelection { get; set; }
|
||||||
|
|
||||||
|
public readonly MultiplayerPlaylistItem Item;
|
||||||
|
|
||||||
|
public Action<MultiplayerPlaylistItem>? Action { private get; init; }
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
|
private const float border_width = 3;
|
||||||
|
|
||||||
|
private Container scaleContainer = null!;
|
||||||
|
private Drawable lighting = null!;
|
||||||
|
private Container border = null!;
|
||||||
|
|
||||||
|
protected MatchmakingSelectPanel(MultiplayerPlaylistItem item)
|
||||||
|
{
|
||||||
|
Item = item;
|
||||||
|
Size = SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
scaleContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||||
|
CornerExponent = 10,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
Content,
|
||||||
|
lighting = new Box
|
||||||
|
{
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
border = new Container
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||||
|
CornerExponent = 10,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
BorderThickness = border_width,
|
||||||
|
BorderColour = colourProvider.Light1,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Radius = 40,
|
||||||
|
Roundness = 300,
|
||||||
|
Colour = colourProvider.Light3.Opacity(0.1f),
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new HoverClickSounds(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: making these abstract for now but avatar overlay should really be owned by the top level class
|
||||||
|
public abstract void AddUser(APIUser user);
|
||||||
|
|
||||||
|
public abstract void RemoveUser(APIUser user);
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
if (AllowSelection)
|
||||||
|
{
|
||||||
|
lighting.FadeTo(0.2f, 50)
|
||||||
|
.Then()
|
||||||
|
.FadeTo(0.1f, 300);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
|
||||||
|
lighting.FadeOut(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (AllowSelection && e.Button == MouseButton.Left)
|
||||||
|
scaleContainer.ScaleTo(0.95f, 400, Easing.OutExpo);
|
||||||
|
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
|
||||||
|
if (e.Button == MouseButton.Left)
|
||||||
|
scaleContainer.ScaleTo(1f, 500, Easing.OutElasticHalf);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
if (AllowSelection)
|
||||||
|
{
|
||||||
|
lighting.FadeTo(0.5f, 50)
|
||||||
|
.Then()
|
||||||
|
.FadeTo(0.1f, 400);
|
||||||
|
|
||||||
|
Action?.Invoke(Item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowChosenBorder()
|
||||||
|
{
|
||||||
|
border.FadeTo(1, 1000, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowBorder()
|
||||||
|
{
|
||||||
|
border.FadeTo(1, 80, Easing.OutQuint)
|
||||||
|
.Then()
|
||||||
|
.FadeTo(0.7f, 800, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideBorder()
|
||||||
|
{
|
||||||
|
border.FadeOut(500, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FadeInAndEnterFromBelow(double duration = 500, double delay = 0, float distance = 200)
|
||||||
|
{
|
||||||
|
scaleContainer
|
||||||
|
.FadeOut()
|
||||||
|
.MoveToY(distance)
|
||||||
|
.Delay(delay)
|
||||||
|
.FadeIn(duration / 2)
|
||||||
|
.MoveToY(0, duration, Easing.OutExpo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopOutAndExpire(double duration = 400, double delay = 0, Easing easing = Easing.InCubic)
|
||||||
|
{
|
||||||
|
AllowSelection = false;
|
||||||
|
|
||||||
|
scaleContainer.Delay(delay)
|
||||||
|
.ScaleTo(0, duration, easing)
|
||||||
|
.FadeOut(duration);
|
||||||
|
|
||||||
|
this.Delay(delay + duration).FadeOut().Expire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||||
|
{
|
||||||
|
public partial class MatchmakingSelectPanelBeatmap : MatchmakingSelectPanel
|
||||||
|
{
|
||||||
|
private readonly APIBeatmap beatmap;
|
||||||
|
private readonly Mod[] mods;
|
||||||
|
|
||||||
|
public MatchmakingSelectPanelBeatmap(MatchmakingPlaylistItem item)
|
||||||
|
: base(item.PlaylistItem)
|
||||||
|
{
|
||||||
|
beatmap = item.Beatmap;
|
||||||
|
mods = item.Mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CardContent content = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Add(content = new CardContentBeatmap(beatmap, mods));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddUser(APIUser user)
|
||||||
|
{
|
||||||
|
content.SelectionOverlay.AddUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RemoveUser(APIUser user)
|
||||||
|
{
|
||||||
|
content.SelectionOverlay.RemoveUser(user.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// 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.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||||
|
{
|
||||||
|
public partial class MatchmakingSelectPanelRandom : MatchmakingSelectPanel
|
||||||
|
{
|
||||||
|
public MatchmakingSelectPanelRandom(MultiplayerPlaylistItem item)
|
||||||
|
: base(item)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private CardContent content = null!;
|
||||||
|
private readonly List<APIUser> users = new List<APIUser>();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Add(content = new CardContentRandom());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RevealBeatmap(APIBeatmap beatmap, Mod[] mods)
|
||||||
|
{
|
||||||
|
content.Expire();
|
||||||
|
|
||||||
|
var flashLayer = new Box { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
content = new CardContentBeatmap(beatmap, mods),
|
||||||
|
flashLayer,
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
content.SelectionOverlay.AddUser(user);
|
||||||
|
|
||||||
|
flashLayer.FadeOutFromOne(1000, Easing.In);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddUser(APIUser user)
|
||||||
|
{
|
||||||
|
users.Add(user);
|
||||||
|
content.SelectionOverlay.AddUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RemoveUser(APIUser user)
|
||||||
|
{
|
||||||
|
users.Remove(user);
|
||||||
|
content.SelectionOverlay.RemoveUser(user.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,23 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
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;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||||
{
|
{
|
||||||
@@ -17,10 +27,17 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
public override Drawable PlayersDisplayArea { get; }
|
public override Drawable PlayersDisplayArea { get; }
|
||||||
|
|
||||||
private readonly BeatmapSelectGrid beatmapSelectGrid;
|
private readonly BeatmapSelectGrid beatmapSelectGrid;
|
||||||
|
private readonly LoadingSpinner loadingSpinner;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerClient client { get; set; } = null!;
|
private MultiplayerClient client { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RulesetStore rulesetStore { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
|
||||||
|
|
||||||
public SubScreenBeatmapSelect()
|
public SubScreenBeatmapSelect()
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
@@ -29,9 +46,19 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Horizontal = 200 },
|
Padding = new MarginPadding { Horizontal = 200 },
|
||||||
Child = beatmapSelectGrid = new BeatmapSelectGrid
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
beatmapSelectGrid = new BeatmapSelectGrid
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
loadingSpinner = new LoadingSpinner
|
||||||
|
{
|
||||||
|
Size = new Vector2(64),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
State = { Value = Visibility.Visible }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
@@ -49,24 +76,53 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
client.ItemAdded += onItemAdded;
|
|
||||||
|
|
||||||
foreach (var item in client.Room!.Playlist)
|
|
||||||
onItemAdded(item);
|
|
||||||
|
|
||||||
beatmapSelectGrid.ItemSelected += item => client.MatchmakingToggleSelection(item.ID);
|
beatmapSelectGrid.ItemSelected += item => client.MatchmakingToggleSelection(item.ID);
|
||||||
|
|
||||||
client.MatchmakingItemSelected += onItemSelected;
|
client.MatchmakingItemSelected += onItemSelected;
|
||||||
client.MatchmakingItemDeselected += onItemDeselected;
|
client.MatchmakingItemDeselected += onItemDeselected;
|
||||||
|
client.SettingsChanged += onSettingsChanged;
|
||||||
|
|
||||||
|
Debug.Assert(client.Room != null);
|
||||||
|
|
||||||
|
loadItems(client.Room.Playlist.ToArray()).FireAndForget();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onItemAdded(MultiplayerPlaylistItem item) => Scheduler.Add(() =>
|
private async Task loadItems(MultiplayerPlaylistItem[] items)
|
||||||
{
|
{
|
||||||
if (item.Expired)
|
var beatmaps = await beatmapLookupCache.GetBeatmapsAsync(items.Select(it => it.BeatmapID).ToArray()).ConfigureAwait(false);
|
||||||
return;
|
var matchmakingItems = new List<MatchmakingPlaylistItem>();
|
||||||
|
|
||||||
beatmapSelectGrid.AddItem(item);
|
foreach (var entry in items.Zip(beatmaps))
|
||||||
});
|
{
|
||||||
|
var (item, beatmap) = entry;
|
||||||
|
|
||||||
|
beatmap ??= new APIBeatmap
|
||||||
|
{
|
||||||
|
BeatmapSet = new APIBeatmapSet
|
||||||
|
{
|
||||||
|
Title = "unknown beatmap",
|
||||||
|
TitleUnicode = "unknown beatmap",
|
||||||
|
Artist = "unknown artist",
|
||||||
|
ArtistUnicode = "unknown artist",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beatmap.StarRating = item.StarRating;
|
||||||
|
|
||||||
|
Ruleset? ruleset = rulesetStore.GetRuleset(item.RulesetID)?.CreateInstance();
|
||||||
|
|
||||||
|
Debug.Assert(ruleset != null);
|
||||||
|
|
||||||
|
Mod[] mods = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray();
|
||||||
|
|
||||||
|
matchmakingItems.Add(new MatchmakingPlaylistItem(item, beatmap, mods));
|
||||||
|
}
|
||||||
|
|
||||||
|
Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
loadingSpinner.Hide();
|
||||||
|
beatmapSelectGrid.AddItems(matchmakingItems);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void onItemSelected(int userId, long itemId)
|
private void onItemSelected(int userId, long itemId)
|
||||||
{
|
{
|
||||||
@@ -80,6 +136,20 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
beatmapSelectGrid.SetUserSelection(user, itemId, false);
|
beatmapSelectGrid.SetUserSelection(user, itemId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onSettingsChanged(MultiplayerRoomSettings settings)
|
||||||
|
{
|
||||||
|
if (client.Room!.MatchState is not MatchmakingRoomState matchmakingState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (matchmakingState.Stage != MatchmakingStage.ServerBeatmapFinalised)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (matchmakingState.CandidateItem != -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
beatmapSelectGrid.RevealRandomItem(client.Room!.CurrentPlaylistItem);
|
||||||
|
}
|
||||||
|
|
||||||
public void RollFinalBeatmap(long[] candidateItems, long finalItem) => beatmapSelectGrid.RollAndDisplayFinalBeatmap(candidateItems, finalItem);
|
public void RollFinalBeatmap(long[] candidateItems, long finalItem) => beatmapSelectGrid.RollAndDisplayFinalBeatmap(candidateItems, finalItem);
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@@ -88,9 +158,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
|||||||
|
|
||||||
if (client.IsNotNull())
|
if (client.IsNotNull())
|
||||||
{
|
{
|
||||||
client.ItemAdded -= onItemAdded;
|
|
||||||
client.MatchmakingItemSelected -= onItemSelected;
|
client.MatchmakingItemSelected -= onItemSelected;
|
||||||
client.MatchmakingItemDeselected -= onItemDeselected;
|
client.MatchmakingItemDeselected -= onItemDeselected;
|
||||||
|
client.SettingsChanged -= onSettingsChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using System.Globalization;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
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;
|
||||||
@@ -15,6 +17,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@@ -114,6 +117,18 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
private PlayerPanelDisplayMode displayMode = PlayerPanelDisplayMode.Horizontal;
|
private PlayerPanelDisplayMode displayMode = PlayerPanelDisplayMode.Horizontal;
|
||||||
private bool hasQuit;
|
private bool hasQuit;
|
||||||
|
|
||||||
|
private enum InteractionSampleType
|
||||||
|
{
|
||||||
|
PlayerJump,
|
||||||
|
PlayerReJump,
|
||||||
|
OtherPlayerJump,
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<InteractionSampleType, Sample?> interactionSamples = new Dictionary<InteractionSampleType, Sample?>();
|
||||||
|
private readonly Dictionary<InteractionSampleType, SampleChannel?> interactionSampleChannels = new Dictionary<InteractionSampleType, SampleChannel?>();
|
||||||
|
private double samplePitch;
|
||||||
|
private double? lastSamplePlayback;
|
||||||
|
|
||||||
public PlayerPanel(MultiplayerRoomUser user)
|
public PlayerPanel(MultiplayerRoomUser user)
|
||||||
: base(HoverSampleSet.Button)
|
: base(HoverSampleSet.Button)
|
||||||
{
|
{
|
||||||
@@ -130,7 +145,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
Content.Masking = true;
|
Content.Masking = true;
|
||||||
Content.CornerRadius = 10;
|
Content.CornerRadius = 10;
|
||||||
@@ -255,6 +270,13 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
|
|
||||||
// Allow avatar to exist outside of masking for when it jumps around and stuff.
|
// Allow avatar to exist outside of masking for when it jumps around and stuff.
|
||||||
AddInternal(avatar.CreateProxy());
|
AddInternal(avatar.CreateProxy());
|
||||||
|
|
||||||
|
interactionSamples = new Dictionary<InteractionSampleType, Sample?>
|
||||||
|
{
|
||||||
|
{ InteractionSampleType.PlayerJump, audio.Samples.Get(@"Multiplayer/Matchmaking/player-jump") },
|
||||||
|
{ InteractionSampleType.PlayerReJump, audio.Samples.Get(@"Multiplayer/Matchmaking/player-rejump") },
|
||||||
|
{ InteractionSampleType.OtherPlayerJump, audio.Samples.Get(@"Multiplayer/Matchmaking/player-jump-other") }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@@ -272,6 +294,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
avatar.ScaleTo(0)
|
avatar.ScaleTo(0)
|
||||||
.ScaleTo(1, 500, Easing.OutElasticHalf)
|
.ScaleTo(1, 500, Easing.OutElasticHalf)
|
||||||
.FadeIn(200);
|
.FadeIn(200);
|
||||||
|
|
||||||
|
// pick a random pitch to be used by the player for duration of this session
|
||||||
|
samplePitch = 0.75f + RNG.NextDouble(0f, 0.75f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerPanelDisplayMode DisplayMode
|
public PlayerPanelDisplayMode DisplayMode
|
||||||
@@ -481,6 +506,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
scale.Then().ScaleTo(new Vector2(1, 1.05f), 200, Easing.Out)
|
scale.Then().ScaleTo(new Vector2(1, 1.05f), 200, Easing.Out)
|
||||||
.Then().ScaleTo(new Vector2(1, 0.95f), 200, Easing.In)
|
.Then().ScaleTo(new Vector2(1, 0.95f), 200, Easing.In)
|
||||||
.Then().ScaleTo(Vector2.One, 800, Easing.OutElastic);
|
.Then().ScaleTo(Vector2.One, 800, Easing.OutElastic);
|
||||||
|
|
||||||
|
// only play jump sample if panel is visible
|
||||||
|
if (Alpha > 0)
|
||||||
|
playJumpSample(isConsecutive);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,6 +528,44 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
downloadProgressBar.ResizeWidthTo(availability.DownloadProgress ?? 0, 200, Easing.OutPow10);
|
downloadProgressBar.ResizeWidthTo(availability.DownloadProgress ?? 0, 200, Easing.OutPow10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void playJumpSample(bool rejumping)
|
||||||
|
{
|
||||||
|
bool isLocalUser = User.OnlineID == client.LocalUser?.UserID;
|
||||||
|
|
||||||
|
if (isLocalUser)
|
||||||
|
playInteractionSample(rejumping ? InteractionSampleType.PlayerReJump : InteractionSampleType.PlayerJump);
|
||||||
|
else
|
||||||
|
playInteractionSample(InteractionSampleType.OtherPlayerJump);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playInteractionSample(InteractionSampleType sampleType)
|
||||||
|
{
|
||||||
|
bool enoughTimePassedSinceLastPlayback = lastSamplePlayback == null || Time.Current - lastSamplePlayback.Value >= OsuGameBase.SAMPLE_DEBOUNCE_TIME;
|
||||||
|
if (!enoughTimePassedSinceLastPlayback)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Sample? targetSample = interactionSamples[sampleType];
|
||||||
|
SampleChannel? targetChannel = interactionSampleChannels.GetValueOrDefault(sampleType);
|
||||||
|
|
||||||
|
targetChannel?.Stop();
|
||||||
|
targetChannel = targetSample?.GetChannel();
|
||||||
|
|
||||||
|
if (targetChannel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float horizontalPos = BoundingBox.Centre.X / Parent!.ToLocalSpace(Parent!.ScreenSpaceDrawQuad).Width;
|
||||||
|
// rescale balance from 0..1 to -1..1
|
||||||
|
float balance = -1f + horizontalPos * 2f;
|
||||||
|
|
||||||
|
targetChannel.Frequency.Value = samplePitch;
|
||||||
|
targetChannel.Balance.Value = balance * OsuGameBase.SFX_STEREO_STRENGTH;
|
||||||
|
targetChannel.Play();
|
||||||
|
|
||||||
|
interactionSampleChannels[sampleType] = targetChannel;
|
||||||
|
|
||||||
|
lastSamplePlayback = Time.Current;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||||
|
{
|
||||||
|
public partial class ScreenMatchmaking
|
||||||
|
{
|
||||||
|
private partial class HistoryFooterButton : ScreenFooterButton
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuGame? game { get; set; }
|
||||||
|
|
||||||
|
private readonly MultiplayerRoom room;
|
||||||
|
|
||||||
|
public HistoryFooterButton(MultiplayerRoom room)
|
||||||
|
{
|
||||||
|
this.room = room;
|
||||||
|
|
||||||
|
Action = openRoomHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Text = "History";
|
||||||
|
Icon = FontAwesome.Solid.Globe;
|
||||||
|
AccentColour = colours.Lime1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openRoomHistory()
|
||||||
|
=> game?.OpenUrlExternally($@"/multiplayer/rooms/{room.RoomID}/events");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@@ -29,6 +30,7 @@ using osu.Game.Online.Rooms;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Gameplay;
|
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Gameplay;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
@@ -47,6 +49,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float row_padding = 10;
|
private const float row_padding = 10;
|
||||||
|
|
||||||
|
private static readonly Vector2 chat_size = new Vector2(550, 130);
|
||||||
|
|
||||||
public override bool? ApplyModTrackAdjustments => true;
|
public override bool? ApplyModTrackAdjustments => true;
|
||||||
|
|
||||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||||
@@ -104,8 +108,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
Size = new Vector2(700, 130),
|
Size = chat_size,
|
||||||
Margin = new MarginPadding { Bottom = 10, Right = WaveOverlayContainer.WIDTH_PADDING - HORIZONTAL_OVERFLOW_PADDING },
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Right = WaveOverlayContainer.WIDTH_PADDING - HORIZONTAL_OVERFLOW_PADDING,
|
||||||
|
Bottom = row_padding
|
||||||
|
},
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -162,9 +170,10 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
[
|
[
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
Name = "Chat Area Space",
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Size = new Vector2(700, 130),
|
Size = new Vector2(550, 130),
|
||||||
Margin = new MarginPadding { Bottom = row_padding }
|
Margin = new MarginPadding { Bottom = row_padding }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -326,6 +335,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() =>
|
||||||
|
[
|
||||||
|
new HistoryFooterButton(room)
|
||||||
|
];
|
||||||
|
|
||||||
public override void OnEntering(ScreenTransitionEvent e)
|
public override void OnEntering(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
base.OnEntering(e);
|
base.OnEntering(e);
|
||||||
@@ -463,7 +477,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
|||||||
|
|
||||||
// This component is added to the screen footer which is only about 50px high.
|
// This component is added to the screen footer which is only about 50px high.
|
||||||
// Therefore, it's given a large absolute size to give the context menu enough space to display correctly.
|
// Therefore, it's given a large absolute size to give the context menu enough space to display correctly.
|
||||||
Size = new Vector2(700);
|
Size = new Vector2(chat_size.X);
|
||||||
|
|
||||||
InternalChild = new OsuContextMenuContainer
|
InternalChild = new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private void updateDisplay(ValueChangedEvent<Period?> period)
|
private void updateDisplay(ValueChangedEvent<Period?> period)
|
||||||
{
|
{
|
||||||
FinishTransforms(true);
|
|
||||||
Scheduler.CancelDelayedTasks();
|
Scheduler.CancelDelayedTasks();
|
||||||
|
|
||||||
if (period.NewValue == null)
|
if (period.NewValue == null)
|
||||||
@@ -180,12 +179,12 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
remainingTimeAdjustmentBox
|
remainingTimeAdjustmentBox
|
||||||
.ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint)
|
.ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint)
|
||||||
.Delay(b.Duration - BREAK_FADE_DURATION)
|
.Delay(b.Duration)
|
||||||
.ResizeWidthTo(0);
|
.ResizeWidthTo(0);
|
||||||
|
|
||||||
remainingTimeBox.ResizeWidthTo(remainingTimeForCurrentPeriod);
|
remainingTimeBox.ResizeWidthTo(remainingTimeForCurrentPeriod);
|
||||||
|
|
||||||
remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration);
|
remainingTimeCounter.CountTo(b.Duration + BREAK_FADE_DURATION).CountTo(0, b.Duration + BREAK_FADE_DURATION);
|
||||||
|
|
||||||
remainingTimeCounter.MoveToX(-50)
|
remainingTimeCounter.MoveToX(-50)
|
||||||
.MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint);
|
.MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint);
|
||||||
@@ -193,7 +192,7 @@ namespace osu.Game.Screens.Play
|
|||||||
info.MoveToX(50)
|
info.MoveToX(50)
|
||||||
.MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint);
|
.MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION))
|
using (BeginDelayedSequence(b.Duration))
|
||||||
{
|
{
|
||||||
fadeContainer.FadeOut(BREAK_FADE_DURATION);
|
fadeContainer.FadeOut(BREAK_FADE_DURATION);
|
||||||
breakArrows.Hide(BREAK_FADE_DURATION);
|
breakArrows.Hide(BREAK_FADE_DURATION);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ using osu.Game.Input.Bindings;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
@@ -256,7 +257,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (gameplayState != null)
|
if (gameplayState != null)
|
||||||
{
|
{
|
||||||
playInfoText.NewLine();
|
playInfoText.NewLine();
|
||||||
playInfoText.AddText(SongSelectStrings.Accuracy);
|
playInfoText.AddText(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy);
|
||||||
playInfoText.AddText(": ");
|
playInfoText.AddText(": ");
|
||||||
playInfoText.AddText(gameplayState!.ScoreProcessor.Accuracy.Value.FormatAccuracy(), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold));
|
playInfoText.AddText(gameplayState!.ScoreProcessor.Accuracy.Value.FormatAccuracy(), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private void updateDisplay(ValueChangedEvent<Period?> period)
|
private void updateDisplay(ValueChangedEvent<Period?> period)
|
||||||
{
|
{
|
||||||
FinishTransforms(true);
|
|
||||||
|
|
||||||
if (period.NewValue == null)
|
if (period.NewValue == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -71,7 +69,7 @@ namespace osu.Game.Screens.Play
|
|||||||
using (BeginAbsoluteSequence(b.Start))
|
using (BeginAbsoluteSequence(b.Start))
|
||||||
{
|
{
|
||||||
fadeContainer.FadeInFromZero(BreakOverlay.BREAK_FADE_DURATION);
|
fadeContainer.FadeInFromZero(BreakOverlay.BREAK_FADE_DURATION);
|
||||||
using (BeginDelayedSequence(b.Duration - BreakOverlay.BREAK_FADE_DURATION))
|
using (BeginDelayedSequence(b.Duration))
|
||||||
fadeContainer.FadeOut(BreakOverlay.BREAK_FADE_DURATION);
|
fadeContainer.FadeOut(BreakOverlay.BREAK_FADE_DURATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
[Cached(typeof(IPreviewTrackOwner))]
|
|
||||||
public partial class SoloSpectatorScreen : SpectatorScreen, IPreviewTrackOwner
|
public partial class SoloSpectatorScreen : SpectatorScreen, IPreviewTrackOwner
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
|
|
||||||
return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s =>
|
return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s =>
|
||||||
{
|
{
|
||||||
liveCollection.PerformWrite(c =>
|
Task.Run(() => liveCollection.PerformWrite(c =>
|
||||||
{
|
{
|
||||||
foreach (var b in beatmapSet.Beatmaps)
|
foreach (var b in beatmapSet.Beatmaps)
|
||||||
{
|
{
|
||||||
@@ -346,7 +346,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
State = { Value = state }
|
State = { Value = state }
|
||||||
|
|||||||
@@ -459,6 +459,8 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
// - Background user tag population runs and causes a realm update.
|
// - Background user tag population runs and causes a realm update.
|
||||||
// We don't display user tags so want to ignore this.
|
// We don't display user tags so want to ignore this.
|
||||||
bool equalForDisplayPurposes =
|
bool equalForDisplayPurposes =
|
||||||
|
// covers import-as-update flows, such as updating the beatmap with the latest online versions, or external editing inside editor
|
||||||
|
oldBeatmap.ID == newBeatmap.ID &&
|
||||||
// covers metadata changes
|
// covers metadata changes
|
||||||
oldBeatmap.Hash == newBeatmap.Hash &&
|
oldBeatmap.Hash == newBeatmap.Hash &&
|
||||||
// sanity check
|
// sanity check
|
||||||
@@ -483,6 +485,15 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void FindCarouselItemsForSelection(ref Selection keyboardSelection, ref Selection selection, IList<CarouselItem> items)
|
||||||
|
{
|
||||||
|
if (keyboardSelection.Model != null && grouping.ItemMap.TryGetValue(keyboardSelection.Model, out var keyboardSelectionItem))
|
||||||
|
keyboardSelection = keyboardSelection with { CarouselItem = keyboardSelectionItem.item, Index = keyboardSelectionItem.index };
|
||||||
|
|
||||||
|
if (selection.Model != null && grouping.ItemMap.TryGetValue(selection.Model, out var selectionItem))
|
||||||
|
selection = selection with { CarouselItem = selectionItem.item, Index = selectionItem.index };
|
||||||
|
}
|
||||||
|
|
||||||
protected override void HandleFilterCompleted()
|
protected override void HandleFilterCompleted()
|
||||||
{
|
{
|
||||||
base.HandleFilterCompleted();
|
base.HandleFilterCompleted();
|
||||||
@@ -497,14 +508,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
// The filter might have changed the set of available groups, which means that the current selection may point to a stale group.
|
// The filter might have changed the set of available groups, which means that the current selection may point to a stale group.
|
||||||
// Check whether that is the case.
|
// Check whether that is the case.
|
||||||
bool groupingRemainsOff = currentGroupedBeatmap?.Group == null && grouping.GroupItems.Count == 0;
|
bool groupingRemainsOff = currentGroupedBeatmap?.Group == null && grouping.GroupItems.Count == 0;
|
||||||
|
bool groupStillValid = currentGroupedBeatmap?.Group != null && grouping.ItemMap.ContainsKey(currentGroupedBeatmap);
|
||||||
bool groupStillValid = false;
|
|
||||||
|
|
||||||
if (currentGroupedBeatmap?.Group != null)
|
|
||||||
{
|
|
||||||
groupStillValid = grouping.GroupItems.TryGetValue(currentGroupedBeatmap.Group, out var items)
|
|
||||||
&& items.Any(i => CheckModelEquality(i.Model, currentGroupedBeatmap));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupingRemainsOff || groupStillValid)
|
if (groupingRemainsOff || groupStillValid)
|
||||||
{
|
{
|
||||||
@@ -697,9 +701,8 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
if (CheckModelEquality(ExpandedGroup, CurrentGroupedBeatmap.Group))
|
if (CheckModelEquality(ExpandedGroup, CurrentGroupedBeatmap.Group))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var groupItem = GetCarouselItems()?.FirstOrDefault(i => CheckModelEquality(i.Model, CurrentGroupedBeatmap.Group));
|
if (grouping.ItemMap.TryGetValue(CurrentGroupedBeatmap.Group, out var groupItem))
|
||||||
if (groupItem != null)
|
Activate(groupItem.item);
|
||||||
Activate(groupItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double? GetScrollTarget()
|
protected override double? GetScrollTarget()
|
||||||
@@ -710,9 +713,13 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
// attempt a fallback to other possibly expanded panels (set first, then group)
|
// attempt a fallback to other possibly expanded panels (set first, then group)
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
var items = GetCarouselItems();
|
CarouselItem? targetItem = null;
|
||||||
var targetItem = items?.FirstOrDefault(i => CheckModelEquality(i.Model, ExpandedBeatmapSet))
|
|
||||||
?? items?.FirstOrDefault(i => CheckModelEquality(i.Model, ExpandedGroup));
|
if (ExpandedBeatmapSet != null && grouping.ItemMap.TryGetValue(ExpandedBeatmapSet, out var setItem))
|
||||||
|
targetItem = setItem.item;
|
||||||
|
|
||||||
|
if (targetItem == null && ExpandedGroup != null && grouping.ItemMap.TryGetValue(ExpandedGroup, out var groupItem))
|
||||||
|
targetItem = groupItem.item;
|
||||||
|
|
||||||
target = targetItem?.CarouselYPosition;
|
target = targetItem?.CarouselYPosition;
|
||||||
}
|
}
|
||||||
@@ -922,9 +929,6 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
if (x is BeatmapInfo beatmapInfoX && y is BeatmapInfo beatmapInfoY)
|
if (x is BeatmapInfo beatmapInfoX && y is BeatmapInfo beatmapInfoY)
|
||||||
return beatmapInfoX.Equals(beatmapInfoY);
|
return beatmapInfoX.Equals(beatmapInfoY);
|
||||||
|
|
||||||
if (x is GroupDefinition groupX && y is GroupDefinition groupY)
|
|
||||||
return groupX.Equals(groupY);
|
|
||||||
|
|
||||||
if (x is StarDifficultyGroupDefinition starX && y is StarDifficultyGroupDefinition starY)
|
if (x is StarDifficultyGroupDefinition starX && y is StarDifficultyGroupDefinition starY)
|
||||||
return starX.Equals(starY);
|
return starX.Equals(starY);
|
||||||
|
|
||||||
@@ -934,6 +938,14 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
if (x is RankedStatusGroupDefinition statusX && y is RankedStatusGroupDefinition statusY)
|
if (x is RankedStatusGroupDefinition statusX && y is RankedStatusGroupDefinition statusY)
|
||||||
return statusX.Equals(statusY);
|
return statusX.Equals(statusY);
|
||||||
|
|
||||||
|
// NOTE: this branch must be AFTER all branches that compare `GroupDefinition` subtypes!
|
||||||
|
// this is an optimisation measure. any subclass of `GroupDefinition` will pass the `is GroupDefinition` check,
|
||||||
|
// and testing a subclass of `GroupDefinition` against any other `GroupDefinition` (or subclass thereof)
|
||||||
|
// will result in a casting cascade of `Equals(GroupDefinition) -> Equals(object) -> Equals(GroupDefinitionSubClass)`
|
||||||
|
// (that last one only if the type check passes)
|
||||||
|
if (x is GroupDefinition groupX && y is GroupDefinition groupY)
|
||||||
|
return groupX.Equals(groupY);
|
||||||
|
|
||||||
return base.CheckModelEquality(x, y);
|
return base.CheckModelEquality(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int BeatmapItemsCount { get; private set; }
|
public int BeatmapItemsCount { get; private set; }
|
||||||
|
|
||||||
|
public IDictionary<object, (CarouselItem item, int index)> ItemMap => itemMap;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Beatmap sets contain difficulties as related panels. This dictionary holds the relationships between set-difficulties to allow expanding them on selection.
|
/// Beatmap sets contain difficulties as related panels. This dictionary holds the relationships between set-difficulties to allow expanding them on selection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -36,6 +38,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<GroupDefinition, HashSet<CarouselItem>> GroupItems => groupMap;
|
public IDictionary<GroupDefinition, HashSet<CarouselItem>> GroupItems => groupMap;
|
||||||
|
|
||||||
|
private Dictionary<object, (CarouselItem, int)> itemMap = new Dictionary<object, (CarouselItem, int)>();
|
||||||
private Dictionary<GroupedBeatmapSet, HashSet<CarouselItem>> setMap = new Dictionary<GroupedBeatmapSet, HashSet<CarouselItem>>();
|
private Dictionary<GroupedBeatmapSet, HashSet<CarouselItem>> setMap = new Dictionary<GroupedBeatmapSet, HashSet<CarouselItem>>();
|
||||||
private Dictionary<GroupDefinition, HashSet<CarouselItem>> groupMap = new Dictionary<GroupDefinition, HashSet<CarouselItem>>();
|
private Dictionary<GroupDefinition, HashSet<CarouselItem>> groupMap = new Dictionary<GroupDefinition, HashSet<CarouselItem>>();
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
// preallocate space for the new mappings using last known estimates
|
// preallocate space for the new mappings using last known estimates
|
||||||
|
var newItemMap = new Dictionary<object, (CarouselItem, int)>(itemMap.Count);
|
||||||
var newSetMap = new Dictionary<GroupedBeatmapSet, HashSet<CarouselItem>>(setMap.Count);
|
var newSetMap = new Dictionary<GroupedBeatmapSet, HashSet<CarouselItem>>(setMap.Count);
|
||||||
var newGroupMap = new Dictionary<GroupDefinition, HashSet<CarouselItem>>(groupMap.Count);
|
var newGroupMap = new Dictionary<GroupDefinition, HashSet<CarouselItem>>(groupMap.Count);
|
||||||
|
|
||||||
@@ -127,6 +131,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
{
|
{
|
||||||
newItems.Add(i);
|
newItems.Add(i);
|
||||||
|
|
||||||
|
newItemMap[i.Model] = (i, newItems.Count - 1);
|
||||||
currentGroupItems?.Add(i);
|
currentGroupItems?.Add(i);
|
||||||
currentSetItems?.Add(i);
|
currentSetItems?.Add(i);
|
||||||
|
|
||||||
@@ -136,6 +141,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
Interlocked.Exchange(ref itemMap, newItemMap);
|
||||||
Interlocked.Exchange(ref setMap, newSetMap);
|
Interlocked.Exchange(ref setMap, newSetMap);
|
||||||
Interlocked.Exchange(ref groupMap, newGroupMap);
|
Interlocked.Exchange(ref groupMap, newGroupMap);
|
||||||
BeatmapItemsCount = displayedBeatmapsCount;
|
BeatmapItemsCount = displayedBeatmapsCount;
|
||||||
@@ -209,7 +215,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
case GroupMode.Collections:
|
case GroupMode.Collections:
|
||||||
{
|
{
|
||||||
var collections = GetCollections();
|
var collections = GetCollections();
|
||||||
return getGroupsBy(b => defineGroupByCollection(b, collections), items);
|
return defineGroupsByCollection(items, collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
case GroupMode.MyMaps:
|
case GroupMode.MyMaps:
|
||||||
@@ -396,29 +402,56 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
return new GroupDefinition(0, source).Yield();
|
return new GroupDefinition(0, source).Yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<GroupDefinition> defineGroupByCollection(BeatmapInfo beatmap, List<BeatmapCollection> collections)
|
private List<GroupMapping> defineGroupsByCollection(List<CarouselItem> carouselItems, List<BeatmapCollection> allCollections)
|
||||||
{
|
{
|
||||||
bool anyCollections = false;
|
Dictionary<GroupDefinition, GroupMapping> groupMappings = new Dictionary<GroupDefinition, GroupMapping>();
|
||||||
|
// this is a pre-built mapping of MD5s to a list of collections in which this MD5 is found in.
|
||||||
|
// the reason to pre-build this is that `BeatmapCollection.BeatmapMD5Hashes` is a list and therefore a naive implementation would be slow,
|
||||||
|
// particularly in edge cases where most beatmaps are in more than one collection.
|
||||||
|
Dictionary<string, List<GroupDefinition>> md5ToCollectionsMap = new Dictionary<string, List<GroupDefinition>>();
|
||||||
|
|
||||||
for (int i = 0; i < collections.Count; i++)
|
for (int i = 0; i < allCollections.Count; i++)
|
||||||
{
|
{
|
||||||
var collection = collections[i];
|
var collection = allCollections[i];
|
||||||
|
// NOTE: the ordering of the incoming collection list is significant and needs to be preserved.
|
||||||
|
// the fallback to ordering by name cannot be relied on.
|
||||||
|
// see xmldoc of `BeatmapCarousel.GetAllCollections()`.
|
||||||
|
var groupDefinition = new GroupDefinition(i, collection.Name);
|
||||||
|
groupMappings[groupDefinition] = new GroupMapping(groupDefinition, []);
|
||||||
|
|
||||||
if (collection.BeatmapMD5Hashes.Contains(beatmap.MD5Hash))
|
foreach (string md5 in collection.BeatmapMD5Hashes)
|
||||||
{
|
{
|
||||||
// NOTE: the ordering of the incoming collection list is significant and needs to be preserved.
|
if (!md5ToCollectionsMap.TryGetValue(md5, out var collections))
|
||||||
// the fallback to ordering by name cannot be relied on.
|
md5ToCollectionsMap[md5] = collections = new List<GroupDefinition>();
|
||||||
// see xmldoc of `BeatmapCarousel.GetAllCollections()`.
|
|
||||||
yield return new GroupDefinition(i, collection.Name);
|
|
||||||
|
|
||||||
anyCollections = true;
|
collections.Add(groupDefinition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anyCollections)
|
var notInCollection = new GroupDefinition(int.MaxValue, "Not in collection");
|
||||||
yield break;
|
groupMappings[notInCollection] = new GroupMapping(notInCollection, []);
|
||||||
|
|
||||||
yield return new GroupDefinition(int.MaxValue, "Not in collection");
|
foreach (var item in carouselItems)
|
||||||
|
{
|
||||||
|
var beatmap = (BeatmapInfo)item.Model;
|
||||||
|
|
||||||
|
// as a side note, even reading the `MD5Hash` off a realm model is slow if done enough times,
|
||||||
|
// so it definitely helps that thanks to the mapping it needs to only be retrieved once
|
||||||
|
if (md5ToCollectionsMap.TryGetValue(beatmap.MD5Hash, out var collections))
|
||||||
|
{
|
||||||
|
foreach (var collection in collections)
|
||||||
|
groupMappings[collection].ItemsInGroup.Add(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
groupMappings[notInCollection].ItemsInGroup.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupMappings.Values
|
||||||
|
// safety against potentially empty eagerly-initialised groups
|
||||||
|
// (could happen if user has a collection with MD5s of maps that aren't locally available)
|
||||||
|
.Where(mapping => mapping.ItemsInGroup.Count > 0)
|
||||||
|
.OrderBy(mapping => mapping.Group!.Order)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<GroupDefinition> defineGroupByOwnMaps(BeatmapInfo beatmap, int? localUserId, string? localUserUsername)
|
private IEnumerable<GroupDefinition> defineGroupByOwnMaps(BeatmapInfo beatmap, int? localUserId, string? localUserUsername)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
@@ -237,11 +238,11 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
{
|
{
|
||||||
Debug.Assert(collection != null);
|
Debug.Assert(collection != null);
|
||||||
|
|
||||||
collection.PerformWrite(c =>
|
Task.Run(() => collection.PerformWrite(c =>
|
||||||
{
|
{
|
||||||
if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash))
|
if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash))
|
||||||
c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash);
|
c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash);
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateContent() => (Content)base.CreateContent();
|
protected override Drawable CreateContent() => (Content)base.CreateContent();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
@@ -297,7 +298,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
|
|
||||||
return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s =>
|
return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s =>
|
||||||
{
|
{
|
||||||
liveCollection.PerformWrite(c =>
|
Task.Run(() => liveCollection.PerformWrite(c =>
|
||||||
{
|
{
|
||||||
foreach (var b in beatmapSet.Beatmaps)
|
foreach (var b in beatmapSet.Beatmaps)
|
||||||
{
|
{
|
||||||
@@ -315,7 +316,7 @@ namespace osu.Game.Screens.SelectV2
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
State = { Value = state }
|
State = { Value = state }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Textures;
|
|||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
@@ -40,6 +41,23 @@ namespace osu.Game.Skinning
|
|||||||
new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Retro")
|
new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Retro")
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
Configuration.ConfigDictionary[@"SliderBallFlip"] = "0";
|
||||||
|
Configuration.ConfigDictionary[@"SliderBallFrames"] = "10";
|
||||||
|
Configuration.ConfigDictionary[@"AllowSliderBallTint"] = "0";
|
||||||
|
Configuration.ConfigDictionary[@"CursorTrailRotate"] = "0";
|
||||||
|
Configuration.ConfigDictionary[@"Version"] = "1";
|
||||||
|
|
||||||
|
Configuration.CustomComboColours =
|
||||||
|
[
|
||||||
|
new Color4(255, 150, 0, 255),
|
||||||
|
new Color4(5, 240, 5, 255),
|
||||||
|
new Color4(5, 5, 240, 255),
|
||||||
|
new Color4(240, 5, 5, 255)
|
||||||
|
];
|
||||||
|
|
||||||
|
Configuration.ConfigDictionary[@"HitCircleOverlap"] = "3";
|
||||||
|
Configuration.ConfigDictionary[@"ScoreOverlap"] = "3";
|
||||||
|
Configuration.ConfigDictionary[@"ComboOverlap"] = "3";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
|
|||||||
@@ -228,8 +228,9 @@ namespace osu.Game.Skinning
|
|||||||
// First attempt to deserialise using the new SkinLayoutInfo format
|
// First attempt to deserialise using the new SkinLayoutInfo format
|
||||||
layout = JsonConvert.DeserializeObject<SkinLayoutInfo>(jsonContent);
|
layout = JsonConvert.DeserializeObject<SkinLayoutInfo>(jsonContent);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
Logger.Log($"Deserialising skin layout to {nameof(SkinLayoutInfo)} failed. Falling back to {nameof(SerialisedDrawableInfo)}[].\nDetails: {ex}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list.
|
// If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list.
|
||||||
|
|||||||
@@ -178,9 +178,10 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
if (existingFile == null)
|
if (existingFile == null)
|
||||||
{
|
{
|
||||||
// skins without a skin.ini are supposed to import using the "latest version" spec.
|
// skins without a skin.ini are supposed to import using the "latest version" spec, unless we're making a copy of the retro skin which specifies 1.0.
|
||||||
// see https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/Graphics/Skinning/SkinManager.cs#L297-L298
|
// see https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/Graphics/Skinning/SkinManager.cs#L297-L298
|
||||||
newLines.Add(FormattableString.Invariant($"Version: {SkinConfiguration.LATEST_VERSION}"));
|
decimal version = item.InstantiationInfo == typeof(RetroSkin).GetInvariantInstantiationInfo() ? 1.0M : SkinConfiguration.LATEST_VERSION;
|
||||||
|
newLines.Add(FormattableString.Invariant($"Version: {version}"));
|
||||||
|
|
||||||
// In the case a skin doesn't have a skin.ini yet, let's create one.
|
// In the case a skin doesn't have a skin.ini yet, let's create one.
|
||||||
writeNewSkinIni();
|
writeNewSkinIni();
|
||||||
|
|||||||
@@ -86,9 +86,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
currentTestBeatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
currentTestBeatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
||||||
|
|
||||||
// populate ruleset for beatmap converters that require it to be present.
|
// populate ruleset for beatmap converters that require it to be present.
|
||||||
var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.Ruleset.OnlineID);
|
var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.Ruleset.OnlineID) ?? new RulesetInfo { OnlineID = currentTestBeatmap.BeatmapInfo.Ruleset.OnlineID };
|
||||||
|
|
||||||
Debug.Assert(ruleset != null);
|
|
||||||
|
|
||||||
currentTestBeatmap.BeatmapInfo.Ruleset = ruleset;
|
currentTestBeatmap.BeatmapInfo.Ruleset = ruleset;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,17 +68,6 @@ namespace osu.Game.Updater
|
|||||||
// make sure the release stream setting matches the build which was just run.
|
// make sure the release stream setting matches the build which was just run.
|
||||||
if (FixedReleaseStream != null)
|
if (FixedReleaseStream != null)
|
||||||
config.SetValue(OsuSetting.ReleaseStream, FixedReleaseStream.Value);
|
config.SetValue(OsuSetting.ReleaseStream, FixedReleaseStream.Value);
|
||||||
|
|
||||||
// nope, doesn't matter, we're already not official
|
|
||||||
// // notify the user if they're using a build that is not officially sanctioned.
|
|
||||||
// if (RuntimeInfo.EntryAssembly.GetCustomAttribute<OfficialBuildAttribute>() == null)
|
|
||||||
// Notifications.Post(new SimpleNotification { Text = NotificationsStrings.NotOfficialBuild });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// log that this is not an official build, for if users build their own game without an assembly version.
|
|
||||||
// this is only logged because a notification would be too spammy in local test builds.
|
|
||||||
Logger.Log(NotificationsStrings.NotOfficialBuild.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug / local compilations will reset to a non-release string.
|
// debug / local compilations will reset to a non-release string.
|
||||||
|
|||||||
@@ -41,18 +41,17 @@ namespace osu.Game.Utils
|
|||||||
{
|
{
|
||||||
this.game = game;
|
this.game = game;
|
||||||
|
|
||||||
if (!game.IsDeployedBuild || !game.CreateEndpoints().WebsiteUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal))
|
if (!game.IsDeployedBuild || !game.CreateEndpoints().WebsiteUrl.EndsWith(@".jvnko.boats", StringComparison.Ordinal))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
sentrySession = SentrySdk.Init(options =>
|
sentrySession = SentrySdk.Init(options =>
|
||||||
{
|
{
|
||||||
options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";
|
options.Dsn = "https://8b67571746af4a07a09558574c7e2227@satellite.jvnko.boats/1";
|
||||||
options.AutoSessionTracking = true;
|
options.AutoSessionTracking = true;
|
||||||
options.IsEnvironmentUser = false;
|
options.IsEnvironmentUser = false;
|
||||||
options.IsGlobalModeEnabled = true;
|
options.IsGlobalModeEnabled = true;
|
||||||
options.CacheDirectoryPath = storage?.GetFullPath(string.Empty);
|
options.CacheDirectoryPath = storage?.GetFullPath(string.Empty);
|
||||||
// The reported release needs to match version as reported to Sentry in .github/workflows/sentry-release.yml
|
options.Release = $"jvnkosu@{game.Version.Split('-').First()}";
|
||||||
options.Release = $"osu@{game.Version.Split('-').First()}";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Logger.NewEntry += processLogEntry;
|
Logger.NewEntry += processLogEntry;
|
||||||
|
|||||||
@@ -35,9 +35,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="20.1.0" />
|
<PackageReference Include="Realm" Version="20.1.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2025.1028.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2025.1118.1" />
|
||||||
<PackageReference Include="jvnkosu.Resources" Version="2025.1103.0" />
|
<PackageReference Include="jvnkosu.Resources" Version="2025.1119.0" />
|
||||||
|
|
||||||
<PackageReference Include="Sentry" Version="5.1.1" />
|
<PackageReference Include="Sentry" Version="5.1.1" />
|
||||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||||
<PackageReference Include="SharpCompress" Version="0.39.0" />
|
<PackageReference Include="SharpCompress" Version="0.39.0" />
|
||||||
|
|||||||
@@ -17,6 +17,6 @@
|
|||||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.1028.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.1118.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Reference in New Issue
Block a user