mirror of
https://github.com/ppy/osu.git
synced 2026-05-22 22:20:53 +08:00
Compare commits
123 Commits
@@ -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:
|
||||
|
||||
- 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 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.
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1028.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1121.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestCase("mania-samples")]
|
||||
[TestCase("mania-slider")] // e.g. second and fourth notes of https://osu.ppy.sh/beatmapsets/73883#mania/216407
|
||||
[TestCase("slider-convert-samples")]
|
||||
[TestCase("spinner-convert-samples")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<SampleConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
||||
+10
@@ -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:
|
||||
+16
@@ -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);
|
||||
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,
|
||||
Column = column,
|
||||
Samples = HitObject.Samples,
|
||||
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
|
||||
NodeSamples =
|
||||
[
|
||||
HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_NORMAL).ToList(),
|
||||
HitObject.Samples
|
||||
]
|
||||
};
|
||||
}
|
||||
else
|
||||
|
||||
@@ -64,11 +64,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
private readonly Lazy<bool> hasKeyTexture;
|
||||
|
||||
private readonly ManiaBeatmap beatmap;
|
||||
private readonly bool isBeatmapConverted;
|
||||
|
||||
public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap)
|
||||
: base(skin)
|
||||
{
|
||||
this.beatmap = (ManiaBeatmap)beatmap;
|
||||
isBeatmapConverted = !beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
|
||||
|
||||
isLegacySkin = new Lazy<bool>(() => GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version) != null);
|
||||
hasKeyTexture = new Lazy<bool>(() =>
|
||||
@@ -196,8 +198,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
|
||||
public override ISample GetSample(ISampleInfo sampleInfo)
|
||||
{
|
||||
// layered hit sounds never play in mania
|
||||
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered)
|
||||
// layered hit sounds never play in mania-native beatmaps (but do play on converts)
|
||||
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered && !isBeatmapConverted)
|
||||
return new SampleVirtual();
|
||||
|
||||
return base.GetSample(sampleInfo);
|
||||
|
||||
@@ -245,13 +245,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddAssert("grid spacing is distance to slider tail", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
|
||||
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01)
|
||||
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.1)
|
||||
&& Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y);
|
||||
});
|
||||
AddAssert("grid rotation points to slider tail", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
|
||||
return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01);
|
||||
return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.1);
|
||||
});
|
||||
|
||||
AddStep("start grid placement", () => InputManager.Key(Key.Number5));
|
||||
@@ -280,9 +280,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddAssert("grid spacing and rotation unchanged", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
|
||||
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01)
|
||||
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.1)
|
||||
&& Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y)
|
||||
&& Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01);
|
||||
&& Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
@@ -10,6 +11,7 @@ using osu.Framework.Input.States;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Testing.Input;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
@@ -58,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
foreach (var smokeContainer in smokeContainers)
|
||||
{
|
||||
if (smokeContainer.Children.Count != 0)
|
||||
if (smokeContainer.Children.OfType<SkinnableDrawable>().Any())
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
{
|
||||
this.gridToolboxGroup = gridToolboxGroup;
|
||||
originalOrigin = gridToolboxGroup.StartPosition.Value;
|
||||
originalSpacing = gridToolboxGroup.Spacing.Value;
|
||||
originalSpacing = gridToolboxGroup.GridLineSpacing.Value;
|
||||
originalRotation = gridToolboxGroup.GridLinesRotation.Value;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
{
|
||||
// Reset the grid to the default values.
|
||||
gridToolboxGroup.StartPosition.Value = gridToolboxGroup.StartPosition.Default;
|
||||
gridToolboxGroup.Spacing.Value = gridToolboxGroup.Spacing.Default;
|
||||
gridToolboxGroup.GridLineSpacing.Value = gridToolboxGroup.GridLineSpacing.Default;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = gridToolboxGroup.GridLinesRotation.Default;
|
||||
EndPlacement(true);
|
||||
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
// Default to the original spacing and rotation if the distance is too small.
|
||||
if (Vector2.Distance(gridToolboxGroup.StartPosition.Value, pos) < 2)
|
||||
{
|
||||
gridToolboxGroup.Spacing.Value = originalSpacing;
|
||||
gridToolboxGroup.GridLineSpacing.Value = originalSpacing;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
|
||||
}
|
||||
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
private void resetGridState()
|
||||
{
|
||||
gridToolboxGroup.StartPosition.Value = originalOrigin;
|
||||
gridToolboxGroup.Spacing.Value = originalSpacing;
|
||||
gridToolboxGroup.GridLineSpacing.Value = originalSpacing;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
@@ -42,25 +44,31 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private readonly BindableInt displayTolerance = new BindableInt(90)
|
||||
{
|
||||
MinValue = 5,
|
||||
MaxValue = 100
|
||||
MaxValue = 100,
|
||||
Precision = 1,
|
||||
};
|
||||
|
||||
private readonly BindableInt displayCornerThreshold = new BindableInt(40)
|
||||
{
|
||||
MinValue = 5,
|
||||
MaxValue = 100
|
||||
MaxValue = 100,
|
||||
Precision = 1,
|
||||
};
|
||||
|
||||
private readonly BindableInt displayCircleThreshold = new BindableInt(30)
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 100
|
||||
MaxValue = 100,
|
||||
Precision = 1,
|
||||
};
|
||||
|
||||
private ExpandableSlider<int> toleranceSlider = null!;
|
||||
private ExpandableSlider<int> cornerThresholdSlider = null!;
|
||||
private ExpandableSlider<int> circleThresholdSlider = null!;
|
||||
|
||||
[Resolved]
|
||||
private IExpandingContainer? expandingContainer { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -68,15 +76,18 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
toleranceSlider = new ExpandableSlider<int>
|
||||
{
|
||||
Current = displayTolerance
|
||||
Current = displayTolerance,
|
||||
ExpandedLabelText = "Control point spacing",
|
||||
},
|
||||
cornerThresholdSlider = new ExpandableSlider<int>
|
||||
{
|
||||
Current = displayCornerThreshold
|
||||
Current = displayCornerThreshold,
|
||||
ExpandedLabelText = "Corner bias",
|
||||
},
|
||||
circleThresholdSlider = new ExpandableSlider<int>
|
||||
{
|
||||
Current = displayCircleThreshold
|
||||
Current = displayCircleThreshold,
|
||||
ExpandedLabelText = "Perfect curve bias"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -88,24 +99,18 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
displayTolerance.BindValueChanged(tolerance =>
|
||||
{
|
||||
toleranceSlider.ContractedLabelText = $"C. P. S.: {tolerance.NewValue:N0}";
|
||||
toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {tolerance.NewValue:N0}";
|
||||
|
||||
Tolerance.Value = displayToInternalTolerance(tolerance.NewValue);
|
||||
}, true);
|
||||
|
||||
displayCornerThreshold.BindValueChanged(threshold =>
|
||||
{
|
||||
cornerThresholdSlider.ContractedLabelText = $"C. T.: {threshold.NewValue:N0}";
|
||||
cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {threshold.NewValue:N0}";
|
||||
|
||||
cornerThresholdSlider.ContractedLabelText = $"C. B.: {threshold.NewValue:N0}";
|
||||
CornerThreshold.Value = displayToInternalCornerThreshold(threshold.NewValue);
|
||||
}, true);
|
||||
|
||||
displayCircleThreshold.BindValueChanged(threshold =>
|
||||
{
|
||||
circleThresholdSlider.ContractedLabelText = $"P. C. T.: {threshold.NewValue:N0}";
|
||||
circleThresholdSlider.ExpandedLabelText = $"Perfect Curve Threshold: {threshold.NewValue:N0}";
|
||||
|
||||
circleThresholdSlider.ContractedLabelText = $"P. C. B.: {threshold.NewValue:N0}";
|
||||
CircleThreshold.Value = displayToInternalCircleThreshold(threshold.NewValue);
|
||||
}, true);
|
||||
|
||||
@@ -119,6 +124,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
displayCircleThreshold.Value = internalToDisplayCircleThreshold(threshold.NewValue)
|
||||
);
|
||||
|
||||
expandingContainer?.Expanded.BindValueChanged(v =>
|
||||
{
|
||||
Spacing = v.NewValue ? new Vector2(5) : new Vector2(15);
|
||||
}, true);
|
||||
|
||||
float displayToInternalTolerance(float v) => v / 50f;
|
||||
int internalToDisplayTolerance(float v) => (int)Math.Round(v * 50f);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = OsuPlayfield.BASE_SIZE.X,
|
||||
Precision = 0.01f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -48,17 +48,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = OsuPlayfield.BASE_SIZE.Y,
|
||||
Precision = 0.01f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The spacing between grid lines.
|
||||
/// </summary>
|
||||
public BindableFloat Spacing { get; } = new BindableFloat(4f)
|
||||
public BindableFloat GridLineSpacing { get; } = new BindableFloat(4f)
|
||||
{
|
||||
MinValue = 4f,
|
||||
MaxValue = 256f,
|
||||
Precision = 0.01f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = -180f,
|
||||
MaxValue = 180f,
|
||||
Precision = 0.01f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
float dist = Vector2.Distance(point1, point2);
|
||||
while (dist >= max_automatic_spacing)
|
||||
dist /= 2;
|
||||
Spacing.Value = dist;
|
||||
GridLineSpacing.Value = dist;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -127,21 +127,25 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Current = StartPositionX,
|
||||
KeyboardStep = 1,
|
||||
ExpandedLabelText = "X offset",
|
||||
},
|
||||
startPositionYSlider = new ExpandableSlider<float>
|
||||
{
|
||||
Current = StartPositionY,
|
||||
KeyboardStep = 1,
|
||||
ExpandedLabelText = "Y offset",
|
||||
},
|
||||
spacingSlider = new ExpandableSlider<float>
|
||||
{
|
||||
Current = Spacing,
|
||||
Current = GridLineSpacing,
|
||||
KeyboardStep = 1,
|
||||
ExpandedLabelText = "Spacing",
|
||||
},
|
||||
gridLinesRotationSlider = new ExpandableSlider<float>
|
||||
{
|
||||
Current = GridLinesRotation,
|
||||
KeyboardStep = 1,
|
||||
ExpandedLabelText = "Rotation",
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
@@ -170,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
},
|
||||
};
|
||||
|
||||
Spacing.Value = editorBeatmap.GridSize;
|
||||
GridLineSpacing.Value = editorBeatmap.GridSize;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -182,14 +186,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
StartPositionX.BindValueChanged(x =>
|
||||
{
|
||||
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:#,0.##}";
|
||||
startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:#,0.##}";
|
||||
StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y);
|
||||
}, true);
|
||||
|
||||
StartPositionY.BindValueChanged(y =>
|
||||
{
|
||||
startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:#,0.##}";
|
||||
startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:#,0.##}";
|
||||
StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue);
|
||||
}, true);
|
||||
|
||||
@@ -199,10 +201,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
StartPositionY.Value = pos.NewValue.Y;
|
||||
});
|
||||
|
||||
Spacing.BindValueChanged(spacing =>
|
||||
GridLineSpacing.BindValueChanged(spacing =>
|
||||
{
|
||||
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}";
|
||||
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}";
|
||||
SpacingVector.Value = new Vector2(spacing.NewValue);
|
||||
editorBeatmap.GridSize = (int)spacing.NewValue;
|
||||
}, true);
|
||||
@@ -210,7 +211,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
GridLinesRotation.BindValueChanged(rotation =>
|
||||
{
|
||||
gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}";
|
||||
gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}";
|
||||
}, true);
|
||||
|
||||
GridType.BindValueChanged(v =>
|
||||
@@ -239,6 +239,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
|
||||
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
|
||||
|
||||
Spacing = v.NewValue ? new Vector2(5) : new Vector2(15);
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -252,7 +254,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.EditorCycleGridSpacing:
|
||||
Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2;
|
||||
GridLineSpacing.Value = GridLineSpacing.Value * 2 >= max_automatic_spacing ? GridLineSpacing.Value / 8 : GridLineSpacing.Value * 2;
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorCycleGridType:
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
case PositionSnapGridType.Triangle:
|
||||
var triangularPositionSnapGrid = new TriangularPositionSnapGrid();
|
||||
|
||||
triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
|
||||
triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.GridLineSpacing);
|
||||
triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||
|
||||
positionSnapGrid = triangularPositionSnapGrid;
|
||||
@@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
case PositionSnapGridType.Circle:
|
||||
var circularPositionSnapGrid = new CircularPositionSnapGrid();
|
||||
|
||||
circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
|
||||
circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.GridLineSpacing);
|
||||
|
||||
positionSnapGrid = circularPositionSnapGrid;
|
||||
break;
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
if (LastAcceptedAction != null && nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||
LastAcceptedAction = null;
|
||||
|
||||
if (LastAcceptedAction != null && gameplayClock.IsRewinding)
|
||||
LastAcceptedAction = null;
|
||||
}
|
||||
|
||||
protected abstract bool CheckValidNewAction(OsuAction action);
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => @"Don't use the same key twice in a row!";
|
||||
public override IconUsage? Icon => OsuIcon.ModAlternate;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
||||
public override bool Ranked => true;
|
||||
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModSingleTap;
|
||||
public override LocalisableString Description => @"You must only use one key!";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
||||
public override bool Ranked => true;
|
||||
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
|
||||
}
|
||||
|
||||
@@ -77,9 +77,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
base.LoadComplete();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Game.Graphics;
|
||||
@@ -12,7 +11,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis
|
||||
{
|
||||
public partial class CursorPathContainer : Path
|
||||
public partial class CursorPathContainer : SmoothPath
|
||||
{
|
||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||
private readonly SortedSet<AnalysisFrameEntry> aliveEntries = new SortedSet<AnalysisFrameEntry>(new AimLinePointComparator());
|
||||
@@ -22,14 +21,13 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis
|
||||
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
||||
lifetimeManager.EntryBecameDead += entryBecameDead;
|
||||
|
||||
PathRadius = 0.5f;
|
||||
PathRadius = 1f;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Colour = colours.Pink2;
|
||||
BackgroundColour = colours.Pink2.Opacity(0);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
@@ -19,17 +19,24 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
/// </summary>
|
||||
public partial class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
private DrawablePool<SmokeSkinnableDrawable> segmentPool = null!;
|
||||
private SmokeSkinnableDrawable? currentSegmentSkinnable;
|
||||
|
||||
private Vector2 lastMousePosition;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 _) => true;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(segmentPool = new DrawablePool<SmokeSkinnableDrawable>(10));
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
{
|
||||
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.
|
||||
addPosition();
|
||||
@@ -59,17 +66,19 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
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
|
||||
{
|
||||
public SmokeSegment? Segment => Drawable as SmokeSegment;
|
||||
|
||||
public override bool RemoveWhenNotAlive => true;
|
||||
|
||||
public override double LifetimeStart => Drawable.LifetimeStart;
|
||||
public override double LifetimeEnd => Drawable.LifetimeEnd;
|
||||
|
||||
public SmokeSkinnableDrawable(ISkinComponentLookup lookup, Func<ISkinComponentLookup, Drawable>? defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
|
||||
: base(lookup, defaultImplementation, confineMode)
|
||||
public SmokeSkinnableDrawable()
|
||||
: base(new OsuSkinComponentLookup(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModSingleTap;
|
||||
public override LocalisableString Description => @"One key for dons, one key for kats.";
|
||||
|
||||
public override bool Ranked => true;
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(TaikoModCinema) };
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
private int rollingHits;
|
||||
|
||||
private readonly Container tickContainer;
|
||||
private SkinnableDrawable headPiece;
|
||||
|
||||
private Color4 colourIdle;
|
||||
private Color4 colourEngaged;
|
||||
@@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
Content.Add(tickContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MinValue
|
||||
Depth = -1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,7 +80,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
protected override void RecreatePieces()
|
||||
{
|
||||
if (headPiece != null)
|
||||
Content.Remove(headPiece, true);
|
||||
|
||||
base.RecreatePieces();
|
||||
|
||||
Content.Add(headPiece = createHeadPiece());
|
||||
|
||||
updateColour();
|
||||
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),
|
||||
_ => 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;
|
||||
|
||||
private void onNewResult(DrawableHitObject obj, JudgementResult result)
|
||||
@@ -174,7 +187,23 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
private void updateColour(double fadeDuration = 0)
|
||||
{
|
||||
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
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -21,14 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
get
|
||||
{
|
||||
// the reason why this calculation is so involved is that the head & tail sprites have different sizes/radii.
|
||||
// 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 headCentre = (body.ScreenSpaceDrawQuad.TopLeft + body.ScreenSpaceDrawQuad.BottomLeft) / 2;
|
||||
var tailCentre = (tailCircle.ScreenSpaceDrawQuad.TopLeft + tailCircle.ScreenSpaceDrawQuad.BottomLeft) / 2;
|
||||
|
||||
float headRadius = headCircle.ScreenSpaceDrawQuad.Height / 2;
|
||||
float tailRadius = tailCircle.ScreenSpaceDrawQuad.Height / 2;
|
||||
float radius = Math.Max(headRadius, tailRadius);
|
||||
float radius = body.ScreenSpaceDrawQuad.Height / 2;
|
||||
|
||||
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);
|
||||
@@ -37,8 +32,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => ScreenSpaceDrawQuad.Contains(screenSpacePos);
|
||||
|
||||
private LegacyCirclePiece headCircle = null!;
|
||||
|
||||
private Sprite body = null!;
|
||||
|
||||
private Sprite tailCircle = null!;
|
||||
@@ -66,10 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Texture = skin.GetTexture("taiko-roll-middle", WrapMode.ClampToEdge, WrapMode.ClampToEdge),
|
||||
},
|
||||
headCircle = new LegacyCirclePiece
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
};
|
||||
|
||||
AccentColour = colours.YellowDark;
|
||||
@@ -101,7 +90,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
|
||||
|
||||
headCircle.AccentColour = colour;
|
||||
body.Colour = colour;
|
||||
tailCircle.Colour = colour;
|
||||
}
|
||||
|
||||
@@ -103,6 +103,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
switch (taikoComponent.Component)
|
||||
{
|
||||
case TaikoSkinComponents.DrumRollHead:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyCirclePiece();
|
||||
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.DrumRollBody:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyDrumRoll();
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
InputDrum,
|
||||
CentreHit,
|
||||
RimHit,
|
||||
DrumRollHead,
|
||||
DrumRollBody,
|
||||
DrumRollTick,
|
||||
Swell,
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace osu.Game.Tests.Chat
|
||||
sentMessages = new List<Message>();
|
||||
silencedUserIds = new List<int>();
|
||||
|
||||
((DummyAPIAccess)API).LocalUserState.Blocks.Clear();
|
||||
((DummyAPIAccess)API).HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
@@ -63,6 +64,10 @@ namespace osu.Game.Tests.Chat
|
||||
silencedUserIds.Clear();
|
||||
return true;
|
||||
|
||||
case GetMessagesRequest getMessages:
|
||||
getMessages.TriggerSuccess(sentMessages);
|
||||
return true;
|
||||
|
||||
case GetUpdatesRequest updatesRequest:
|
||||
updatesRequest.TriggerSuccess(new GetUpdatesResponse
|
||||
{
|
||||
@@ -161,6 +166,60 @@ namespace osu.Game.Tests.Chat
|
||||
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)
|
||||
{
|
||||
var message = new Message(++currentMessageId)
|
||||
|
||||
@@ -126,6 +126,22 @@ namespace osu.Game.Tests.Gameplay
|
||||
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>
|
||||
/// Tests that a default hitobject and control point causes <see cref="TestDefaultSampleFromUserSkin"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -738,6 +738,16 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
new object[] { "submitted=99999", false },
|
||||
new object[] { "submitted>=2012-03-05-04", false },
|
||||
new object[] { "submitted>=2012/03.05-04", false },
|
||||
|
||||
new object[] { "created<2012", true },
|
||||
new object[] { "created<2012.03", true },
|
||||
new object[] { "created<2012/03/05", true },
|
||||
new object[] { "created<2012-3-5", true },
|
||||
|
||||
new object[] { "created<0", false },
|
||||
new object[] { "created=99999", false },
|
||||
new object[] { "created>=2012-03-05-04", false },
|
||||
new object[] { "created>=2012/03.05-04", false },
|
||||
};
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -220,10 +220,13 @@ namespace osu.Game.Tests.Visual.Components
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
if (registerAsOwner)
|
||||
dependencies.CacheAs<IPreviewTrackOwner>(this);
|
||||
return dependencies;
|
||||
{
|
||||
// Automatically handled by interface caching.
|
||||
return base.CreateChildDependencies(parent);
|
||||
}
|
||||
|
||||
return new DependencyContainer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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.Game.Overlays;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public abstract partial class MatchmakingTestScene : MultiplayerTestScene
|
||||
{
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
protected MatchmakingTestScene()
|
||||
{
|
||||
BackgroundScreenStack backgroundStack;
|
||||
|
||||
base.Content.AddRange(new Drawable[]
|
||||
{
|
||||
backgroundStack = new BackgroundScreenStack(),
|
||||
Content = new Container { RelativeSizeAxes = Axes.Both }
|
||||
});
|
||||
|
||||
backgroundStack.Push(new MatchmakingBackgroundScreen(colourProvider));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -13,15 +14,15 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneBeatmapSelectGrid : OnlinePlayTestScene
|
||||
public partial class TestSceneBeatmapSelectGrid : MatchmakingTestScene
|
||||
{
|
||||
private MultiplayerPlaylistItem[] items = null!;
|
||||
private MatchmakingPlaylistItem[] items = null!;
|
||||
|
||||
private BeatmapSelectGrid grid = null!;
|
||||
|
||||
@@ -36,24 +37,44 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
.Take(50)
|
||||
.ToArray();
|
||||
|
||||
IEnumerable<MatchmakingPlaylistItem> playlistItems;
|
||||
|
||||
if (beatmaps.Length > 0)
|
||||
{
|
||||
items = Enumerable.Range(1, 50).Select(i => new MultiplayerPlaylistItem
|
||||
playlistItems = Enumerable.Range(1, 50).Select(i =>
|
||||
{
|
||||
ID = i,
|
||||
BeatmapID = beatmaps[i % beatmaps.Length].OnlineID,
|
||||
StarRating = i / 10.0,
|
||||
}).ToArray();
|
||||
var beatmap = beatmaps[i % beatmaps.Length];
|
||||
|
||||
return new MatchmakingPlaylistItem(
|
||||
new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = i,
|
||||
BeatmapID = beatmap.OnlineID,
|
||||
StarRating = i / 10.0,
|
||||
},
|
||||
CreateAPIBeatmap(beatmap),
|
||||
Array.Empty<Mod>()
|
||||
);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
items = Enumerable.Range(1, 50).Select(i => new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = i,
|
||||
BeatmapID = i,
|
||||
StarRating = i / 10.0,
|
||||
}).ToArray();
|
||||
playlistItems = Enumerable.Range(1, 50).Select(i => new MatchmakingPlaylistItem(
|
||||
new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = i,
|
||||
BeatmapID = i,
|
||||
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()
|
||||
@@ -70,8 +91,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
AddStep("add items", () =>
|
||||
{
|
||||
foreach (var item in items)
|
||||
grid.AddItem(item);
|
||||
grid.AddItems(items);
|
||||
});
|
||||
|
||||
AddWaitStep("wait for panels", 3);
|
||||
@@ -85,17 +105,17 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
// 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,
|
||||
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,
|
||||
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,
|
||||
Username = "smoogipoo",
|
||||
@@ -109,7 +129,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
var (candidateItems, finalItem) = pickRandomItems(5);
|
||||
|
||||
grid.RollAndDisplayFinalBeatmap(candidateItems, finalItem);
|
||||
grid.RollAndDisplayFinalBeatmap(candidateItems, finalItem, finalItem);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -138,7 +158,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
|
||||
grid.PlayRollAnimation(finalItem, duration: 0);
|
||||
|
||||
Scheduler.AddDelayed(() => grid.PresentRolledBeatmap(finalItem), 500);
|
||||
Scheduler.AddDelayed(() => grid.PresentRolledBeatmap(finalItem, finalItem), 500);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,7 +173,25 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
|
||||
grid.PlayRollAnimation(finalItem, duration: 0);
|
||||
|
||||
Scheduler.AddDelayed(() => grid.PresentUnanimouslyChosenBeatmap(finalItem), 500);
|
||||
Scheduler.AddDelayed(() => grid.PresentUnanimouslyChosenBeatmap(finalItem, finalItem), 500);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPresentRandomItem()
|
||||
{
|
||||
AddStep("present random item panel", () =>
|
||||
{
|
||||
var (candidateItems, finalItem) = pickRandomItems(4);
|
||||
|
||||
grid.TransferCandidatePanelsToRollContainer(candidateItems.Append(-1).ToArray(), duration: 0);
|
||||
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
|
||||
grid.PlayRollAnimation(-1, duration: 0);
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
grid.PresentRolledBeatmap(-1, finalItem);
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -180,7 +218,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
AddStep("display roll order", () =>
|
||||
{
|
||||
var panels = grid.ChildrenOfType<BeatmapSelectPanel>().ToArray();
|
||||
var panels = grid.ChildrenOfType<MatchmakingSelectPanel>().ToArray();
|
||||
|
||||
for (int i = 0; i < panels.Length; i++)
|
||||
{
|
||||
|
||||
@@ -1,37 +1,54 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneBeatmapSelectPanel : MultiplayerTestScene
|
||||
public partial class TestSceneBeatmapSelectPanel : MatchmakingTestScene
|
||||
{
|
||||
[Cached]
|
||||
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]
|
||||
public void TestBeatmapPanel()
|
||||
{
|
||||
BeatmapSelectPanel? panel = null;
|
||||
MatchmakingSelectPanel? panel = null;
|
||||
|
||||
AddStep("add panel", () =>
|
||||
{
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = panel = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
|
||||
Child = panel = new MatchmakingSelectPanelBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), []))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -58,47 +75,55 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
AddStep("remove peppy", () => panel!.RemoveUser(new APIUser { Id = 2 }));
|
||||
AddStep("remove maarvin", () => panel!.RemoveUser(new APIUser { Id = 6411631 }));
|
||||
|
||||
AddToggleStep("allow selection", value =>
|
||||
{
|
||||
if (panel != null)
|
||||
panel.AllowSelection = value;
|
||||
});
|
||||
AddToggleStep("allow selection", value => panel!.AllowSelection = value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFailedBeatmapLookup()
|
||||
public void TestRandomPanel()
|
||||
{
|
||||
AddStep("setup request handle", () =>
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
});
|
||||
MatchmakingSelectPanelRandom? panel = null;
|
||||
|
||||
AddStep("add panel", () =>
|
||||
{
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
|
||||
Child = panel = new MatchmakingSelectPanelRandom(new MultiplayerPlaylistItem { ID = -1 })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddToggleStep("allow selection", value => panel!.AllowSelection = value);
|
||||
|
||||
AddStep("reveal beatmap", () => panel!.PresentAsChosenBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), 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",
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneMatchmakingChatDisplay : ScreenTestScene
|
||||
public partial class TestSceneMatchmakingChatDisplay : MatchmakingTestScene
|
||||
{
|
||||
private MatchmakingChatDisplay? chat;
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
state.CandidateItems = beatmaps.Select(b => b.ID).ToArray();
|
||||
state.CandidateItem = beatmaps[0].ID;
|
||||
state.GameplayItem = beatmaps[0].ID;
|
||||
}, waitTime: 35);
|
||||
|
||||
changeStage(MatchmakingStage.WaitingForClientsBeatmapDownload);
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePanelRoomAward : MultiplayerTestScene
|
||||
public partial class TestScenePanelRoomAward : MatchmakingTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
|
||||
@@ -6,16 +6,16 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePickScreen : MultiplayerTestScene
|
||||
public partial class TestScenePickScreen : MatchmakingTestScene
|
||||
{
|
||||
private readonly IReadOnlyList<APIUser> users = new[]
|
||||
{
|
||||
@@ -104,8 +104,28 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
long[] candidateItems = selectedItems.ToArray();
|
||||
long finalItem = candidateItems[Random.Shared.Next(candidateItems.Length)];
|
||||
|
||||
screen.RollFinalBeatmap(candidateItems, finalItem);
|
||||
screen.RollFinalBeatmap(candidateItems, finalItem, finalItem);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpiredBeatmapNotShown()
|
||||
{
|
||||
SubScreenBeatmapSelect screen = null!;
|
||||
|
||||
AddStep("add screen with expired items", () =>
|
||||
{
|
||||
MultiplayerClient.ClientRoom!.Playlist =
|
||||
[
|
||||
new MultiplayerPlaylistItem(items[0]) { Expired = true },
|
||||
new MultiplayerPlaylistItem(items[1])
|
||||
];
|
||||
|
||||
Child = new ScreenStack(screen = new SubScreenBeatmapSelect());
|
||||
});
|
||||
|
||||
AddUntilStep("items displayed", () => screen.ChildrenOfType<MatchmakingSelectPanelBeatmap>().Any());
|
||||
AddAssert("expired item not shown", () => screen.ChildrenOfType<MatchmakingSelectPanelBeatmap>().Count(), () => Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,11 @@ using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePlayerPanel : MultiplayerTestScene
|
||||
public partial class TestScenePlayerPanel : MatchmakingTestScene
|
||||
{
|
||||
private PlayerPanel panel = null!;
|
||||
|
||||
|
||||
@@ -8,17 +8,18 @@ using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Matchmaking.Events;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePlayerPanelOverlay : MultiplayerTestScene
|
||||
public partial class TestScenePlayerPanelOverlay : MatchmakingTestScene
|
||||
{
|
||||
private PlayerPanelOverlay list = null!;
|
||||
|
||||
@@ -158,5 +159,64 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,11 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneResultsScreen : MultiplayerTestScene
|
||||
public partial class TestSceneResultsScreen : MatchmakingTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
|
||||
@@ -14,12 +14,11 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneRoundResultsScreen : MultiplayerTestScene
|
||||
public partial class TestSceneRoundResultsScreen : MatchmakingTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
|
||||
@@ -9,11 +9,10 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneStageDisplay : MultiplayerTestScene
|
||||
public partial class TestSceneStageDisplay : MatchmakingTestScene
|
||||
{
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Direction = FillDirection.Full,
|
||||
Padding = new MarginPadding(20),
|
||||
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
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
@@ -19,9 +19,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private TestExpandingContainer container;
|
||||
private SettingsToolboxGroup toolboxGroup;
|
||||
|
||||
private ExpandableSlider<float, SizeSlider<float>> slider1;
|
||||
private ExpandableSlider<float> slider1;
|
||||
private ExpandableSlider<double> slider2;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
@@ -36,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Width = 1,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
slider1 = new ExpandableSlider<float, SizeSlider<float>>
|
||||
slider1 = new ExpandableSlider<float>
|
||||
{
|
||||
Current = new BindableFloat
|
||||
{
|
||||
@@ -62,13 +65,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
slider1.Current.BindValueChanged(v =>
|
||||
{
|
||||
slider1.ExpandedLabelText = $"Slider One ({v.NewValue:0.##x})";
|
||||
slider1.ExpandedLabelText = "Slider One";
|
||||
slider1.ContractedLabelText = $"S. 1. ({v.NewValue:0.##x})";
|
||||
}, true);
|
||||
|
||||
slider2.Current.BindValueChanged(v =>
|
||||
{
|
||||
slider2.ExpandedLabelText = $"Slider Two ({v.NewValue:N2})";
|
||||
slider2.ExpandedLabelText = "Slider Two";
|
||||
slider2.ContractedLabelText = $"S. 2. ({v.NewValue:N2})";
|
||||
}, true);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,8 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.Chat;
|
||||
@@ -61,14 +63,33 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
chatDisplay.Channel.Value = testChannel;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddStep("set up API", () =>
|
||||
{
|
||||
((DummyAPIAccess)API).HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case JoinChannelRequest joinChannelRequest:
|
||||
joinChannelRequest.TriggerSuccess();
|
||||
return true;
|
||||
|
||||
case LeaveChannelRequest leaveChannelRequest:
|
||||
leaveChannelRequest.TriggerSuccess();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
AddStep("set channel", () => chatDisplay.Channel.Value = testChannel);
|
||||
|
||||
AddStep("message from admin", () => testChannel.AddNewMessages(new Message(nextMessageId())
|
||||
{
|
||||
Sender = admin,
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
public partial class TournamentMatchChatDisplay : StandAloneChatDisplay
|
||||
{
|
||||
private readonly Bindable<string> chatChannel = new Bindable<string>();
|
||||
private readonly Bindable<string> channelName = new Bindable<string>();
|
||||
|
||||
private ChannelManager? manager;
|
||||
|
||||
@@ -34,39 +34,33 @@ namespace osu.Game.Tournament.Components
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(MatchIPCInfo? ipc, IAPIProvider api)
|
||||
private void load(MatchIPCInfo ipc, IAPIProvider api)
|
||||
{
|
||||
if (ipc != null)
|
||||
AddInternal(manager = new ChannelManager(api));
|
||||
Channel.BindTo(manager.CurrentChannel);
|
||||
|
||||
channelName.BindTo(ipc.ChatChannel);
|
||||
channelName.BindValueChanged(c =>
|
||||
{
|
||||
chatChannel.BindTo(ipc.ChatChannel);
|
||||
chatChannel.BindValueChanged(c =>
|
||||
if (int.TryParse(c.OldValue, out int oldChannelId) && oldChannelId > 0)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(c.NewValue))
|
||||
return;
|
||||
|
||||
int id = int.Parse(c.NewValue);
|
||||
|
||||
if (id <= 0) return;
|
||||
|
||||
if (manager == null)
|
||||
{
|
||||
AddInternal(manager = new ChannelManager(api));
|
||||
Channel.BindTo(manager.CurrentChannel);
|
||||
}
|
||||
|
||||
foreach (var ch in manager.JoinedChannels.ToList())
|
||||
manager.LeaveChannel(ch);
|
||||
var joinedChannel = manager.JoinedChannels.SingleOrDefault(ch => ch.Id == oldChannelId);
|
||||
if (joinedChannel != null)
|
||||
manager.LeaveChannel(joinedChannel);
|
||||
}
|
||||
|
||||
if (int.TryParse(c.NewValue, out int newChannelId) && newChannelId > 0)
|
||||
{
|
||||
var channel = new Channel
|
||||
{
|
||||
Id = id,
|
||||
Id = newChannelId,
|
||||
Type = ChannelType.Public
|
||||
};
|
||||
|
||||
manager.JoinChannel(channel);
|
||||
manager.CurrentChannel.Value = channel;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void Expand() => this.FadeIn(300);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Audio
|
||||
{
|
||||
/// <summary>
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Audio
|
||||
/// <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"/>.
|
||||
/// </remarks>
|
||||
[Cached]
|
||||
public interface IPreviewTrackOwner
|
||||
{
|
||||
}
|
||||
|
||||
@@ -157,6 +157,12 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
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
|
||||
&& BeatmapSet != null
|
||||
&& other.BeatmapSet != null
|
||||
|
||||
@@ -544,7 +544,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (!banksOnly)
|
||||
{
|
||||
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;
|
||||
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@@ -263,11 +264,11 @@ namespace osu.Game.Collections
|
||||
{
|
||||
Debug.Assert(collection != null);
|
||||
|
||||
collection.PerformWrite(c =>
|
||||
Task.Run(() => collection.PerformWrite(c =>
|
||||
{
|
||||
if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash))
|
||||
c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => (Content)base.CreateContent();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Threading.Tasks;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@@ -10,7 +11,7 @@ namespace osu.Game.Collections
|
||||
public class CollectionToggleMenuItem : ToggleMenuItem
|
||||
{
|
||||
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 =>
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace osu.Game.Collections
|
||||
else
|
||||
c.BeatmapMD5Hashes.Remove(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:
|
||||
// - 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.
|
||||
FindCarouselItemsForSelection(ref currentKeyboardSelection, ref currentSelection, carouselItems);
|
||||
|
||||
for (int i = 0; i < count; 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);
|
||||
|
||||
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
|
||||
// most items are not displayed / loaded).
|
||||
Scroll.SetLayoutHeight(yPos + visibleHalfHeight);
|
||||
@@ -821,6 +809,27 @@ namespace osu.Game.Graphics.Carousel
|
||||
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
|
||||
|
||||
#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="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>
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Threading;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
@@ -58,6 +58,19 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected virtual OsuScrollContainer CreateScrollContainer() => new OsuScrollContainer();
|
||||
|
||||
private InputManager inputManager = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks whether the mouse was in bounds of this expanding container in the last frame.
|
||||
/// </summary>
|
||||
private bool? lastMouseInBounds;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks whether the last expansion of the container was caused by the mouse moving into its bounds
|
||||
/// (as opposed to an external set of `Expanded`, in which case moving the mouse outside of its bounds should not contract).
|
||||
/// </summary>
|
||||
private bool? expandedByMouse;
|
||||
|
||||
private ScheduledDelegate? hoverExpandEvent;
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -68,37 +81,43 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
this.ResizeWidthTo(v.NewValue ? expandedWidth : contractedWidth, TRANSITION_DURATION, Easing.OutQuint);
|
||||
}, true);
|
||||
|
||||
inputManager = GetContainingInputManager()!;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
protected override void Update()
|
||||
{
|
||||
updateHoverExpansion();
|
||||
return true;
|
||||
base.Update();
|
||||
|
||||
bool mouseInBounds = Contains(inputManager.CurrentState.Mouse.Position);
|
||||
|
||||
if (lastMouseInBounds != mouseInBounds)
|
||||
updateExpansionState(mouseInBounds);
|
||||
|
||||
lastMouseInBounds = mouseInBounds;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
if (hoverExpandEvent != null)
|
||||
{
|
||||
hoverExpandEvent?.Cancel();
|
||||
hoverExpandEvent = null;
|
||||
|
||||
Expanded.Value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private void updateHoverExpansion()
|
||||
private void updateExpansionState(bool mouseInBounds)
|
||||
{
|
||||
if (!ExpandOnHover)
|
||||
return;
|
||||
|
||||
hoverExpandEvent?.Cancel();
|
||||
hoverExpandEvent = null;
|
||||
|
||||
if (IsHovered && !Expanded.Value)
|
||||
if (mouseInBounds && !Expanded.Value)
|
||||
{
|
||||
hoverExpandEvent = Scheduler.AddDelayed(() => Expanded.Value = true, HoverExpansionDelay);
|
||||
expandedByMouse = true;
|
||||
}
|
||||
|
||||
if (!mouseInBounds && Expanded.Value)
|
||||
{
|
||||
if (expandedByMouse == true)
|
||||
Expanded.Value = false;
|
||||
|
||||
expandedByMouse = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
[Cached(typeof(IPreviewTrackOwner))]
|
||||
public abstract partial class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using Vector2 = osuTK.Vector2;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
@@ -19,49 +20,27 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public partial class ExpandableSlider<T, TSlider> : CompositeDrawable, IExpandable, IHasCurrentValue<T>
|
||||
where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
where TSlider : RoundedSliderBar<T>, new()
|
||||
where TSlider : FormSliderBar<T>, new()
|
||||
{
|
||||
private readonly OsuSpriteText label;
|
||||
private readonly OsuSpriteText contractedLabel;
|
||||
private readonly TSlider slider;
|
||||
|
||||
private LocalisableString contractedLabelText;
|
||||
|
||||
/// <summary>
|
||||
/// The label text to display when this slider is in a contracted state.
|
||||
/// </summary>
|
||||
public LocalisableString ContractedLabelText
|
||||
{
|
||||
get => contractedLabelText;
|
||||
set
|
||||
{
|
||||
if (value == contractedLabelText)
|
||||
return;
|
||||
|
||||
contractedLabelText = value;
|
||||
|
||||
if (!Expanded.Value)
|
||||
label.Text = value;
|
||||
}
|
||||
get => contractedLabel.Text;
|
||||
set => contractedLabel.Text = value;
|
||||
}
|
||||
|
||||
private LocalisableString expandedLabelText;
|
||||
|
||||
/// <summary>
|
||||
/// The label text to display when this slider is in an expanded state.
|
||||
/// </summary>
|
||||
public LocalisableString ExpandedLabelText
|
||||
{
|
||||
get => expandedLabelText;
|
||||
set
|
||||
{
|
||||
if (value == expandedLabelText)
|
||||
return;
|
||||
|
||||
expandedLabelText = value;
|
||||
|
||||
if (Expanded.Value)
|
||||
label.Text = value;
|
||||
}
|
||||
get => slider.Caption;
|
||||
set => slider.Caption = value;
|
||||
}
|
||||
|
||||
public Bindable<T> Current
|
||||
@@ -95,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Spacing = new Vector2(0f, 10f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
label = new OsuSpriteText(),
|
||||
contractedLabel = new OsuSpriteText(),
|
||||
slider = new TSlider
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -118,7 +97,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
Expanded.BindValueChanged(v =>
|
||||
{
|
||||
label.Text = v.NewValue ? expandedLabelText : contractedLabelText;
|
||||
contractedLabel.FadeTo(v.NewValue ? 0 : 1);
|
||||
|
||||
slider.FadeTo(v.NewValue ? Current.Disabled ? 0.3f : 1f : 0f, 500, Easing.OutQuint);
|
||||
slider.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
|
||||
}, true);
|
||||
@@ -133,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <summary>
|
||||
/// An <see cref="IExpandable"/> implementation for the UI slider bar control.
|
||||
/// </summary>
|
||||
public partial class ExpandableSlider<T> : ExpandableSlider<T, RoundedSliderBar<T>>
|
||||
public partial class ExpandableSlider<T> : ExpandableSlider<T, FormSliderBar<T>>
|
||||
where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class ProgressBar : SliderBar<double>
|
||||
{
|
||||
public bool Seeking { get; private set; }
|
||||
|
||||
public Action<double> OnSeek;
|
||||
public Action<double> OnCommit;
|
||||
|
||||
private readonly Box fill;
|
||||
private readonly Box background;
|
||||
@@ -75,6 +78,18 @@ namespace osu.Game.Graphics.UserInterface
|
||||
fill.Width = value * UsableWidth;
|
||||
}
|
||||
|
||||
protected override void OnUserChange(double value) => OnSeek?.Invoke(value);
|
||||
protected override void OnUserChange(double value)
|
||||
{
|
||||
Seeking = true;
|
||||
OnSeek?.Invoke(value);
|
||||
base.OnUserChange(value);
|
||||
}
|
||||
|
||||
protected override bool Commit()
|
||||
{
|
||||
Seeking = false;
|
||||
OnCommit?.Invoke(CurrentNumber.Value);
|
||||
return base.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
case Key.KeypadEnter:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,26 +58,49 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
}
|
||||
|
||||
private LocalisableString caption;
|
||||
|
||||
/// <summary>
|
||||
/// Caption describing this slider bar, displayed on top of the controls.
|
||||
/// </summary>
|
||||
public LocalisableString Caption { get; init; }
|
||||
public LocalisableString Caption
|
||||
{
|
||||
get => caption;
|
||||
set
|
||||
{
|
||||
caption = value;
|
||||
|
||||
if (IsLoaded)
|
||||
captionText.Caption = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
|
||||
/// </summary>
|
||||
public LocalisableString HintText { get; init; }
|
||||
|
||||
private float keyboardStep;
|
||||
|
||||
/// <summary>
|
||||
/// A custom step value for each key press which actuates a change on this control.
|
||||
/// </summary>
|
||||
public float KeyboardStep { get; init; }
|
||||
public float KeyboardStep
|
||||
{
|
||||
get => keyboardStep;
|
||||
set
|
||||
{
|
||||
keyboardStep = value;
|
||||
if (IsLoaded)
|
||||
slider.KeyboardStep = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Box background = null!;
|
||||
private Box flashLayer = null!;
|
||||
private FormTextBox.InnerTextBox textBox = null!;
|
||||
private InnerSlider slider = null!;
|
||||
private FormFieldCaption caption = null!;
|
||||
private FormFieldCaption captionText = null!;
|
||||
private IFocusManager focusManager = null!;
|
||||
|
||||
[Resolved]
|
||||
@@ -117,11 +140,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
captionText = new FormFieldCaption
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
textBox = new FormNumberBox.InnerNumberBox(allowDecimals: true)
|
||||
@@ -145,7 +167,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
KeyboardStep = KeyboardStep,
|
||||
Current = currentNumberInstantaneous,
|
||||
OnCommit = () => current.Value = currentNumberInstantaneous.Value,
|
||||
}
|
||||
@@ -161,6 +182,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
slider.KeyboardStep = keyboardStep;
|
||||
captionText.Caption = caption;
|
||||
|
||||
focusManager = GetContainingFocusManager()!;
|
||||
|
||||
textBox.Focused.BindValueChanged(_ => updateState());
|
||||
@@ -270,7 +294,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
textBox.Alpha = 1;
|
||||
|
||||
background.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background4 : colourProvider.Background5;
|
||||
caption.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||
captionText.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||
|
||||
BorderThickness = childHasFocus || IsHovered || slider.IsDragging.Value ? 2 : 0;
|
||||
|
||||
@@ -99,6 +99,21 @@ namespace osu.Game.Localisation
|
||||
/// </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.");
|
||||
|
||||
/// <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}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +199,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Mapper => new TranslatableString(getKey(@"mapper"), @"Mapper");
|
||||
|
||||
/// <summary>
|
||||
/// "Delete..."
|
||||
/// </summary>
|
||||
public static LocalisableString DeleteWithConfirmation => new TranslatableString(getKey(@"delete_with_confrmation"), @"Delete...");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString LearnMoreAboutLazerTooltip => new TranslatableString(getKey(@"check_out_the_feature_comparison"), @"Check out the feature comparison and FAQ");
|
||||
|
||||
/// <summary>
|
||||
/// "Report an issue"
|
||||
/// </summary>
|
||||
public static LocalisableString ReportIssue => new TranslatableString(getKey(@"report_issue"), @"Report an issue");
|
||||
|
||||
/// <summary>
|
||||
/// "Report a problem with the game to the developers."
|
||||
/// </summary>
|
||||
public static LocalisableString ReportIssueTooltip => new TranslatableString(getKey(@"report_issue_tooltip"), @"Report a problem with the game to the developers.");
|
||||
|
||||
/// <summary>
|
||||
/// "Check with your package manager / provider for other release streams."
|
||||
/// </summary>
|
||||
|
||||
@@ -139,11 +139,6 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString ClearAllLocalScores => new TranslatableString(getKey(@"clear_all_local_scores"), @"Clear all local scores");
|
||||
|
||||
/// <summary>
|
||||
/// "Delete beatmap"
|
||||
/// </summary>
|
||||
public static LocalisableString DeleteBeatmap => new TranslatableString(getKey(@"delete_beatmap"), @"Delete beatmap");
|
||||
|
||||
/// <summary>
|
||||
/// "Restore all hidden"
|
||||
/// </summary>
|
||||
|
||||
@@ -62,6 +62,10 @@ namespace osu.Game.Online.API
|
||||
localUser.Value = me;
|
||||
configSupporter.Value = me.IsSupporter;
|
||||
|
||||
// `last_visit` is assumed to be `null` if and only if the web-side "hide online presence toggle" is enabled
|
||||
if (me.LastVisit == null)
|
||||
configStatus.Value = UserStatus.Offline;
|
||||
|
||||
UpdateFriends();
|
||||
UpdateBlocks();
|
||||
UpdateFavouriteBeatmapSets();
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace osu.Game.Online.API.Requests
|
||||
private class VerificationFailureResponse
|
||||
{
|
||||
[JsonProperty("method")]
|
||||
public SessionVerificationMethod RequiredSessionVerificationMethod { get; set; }
|
||||
public SessionVerificationMethod? RequiredSessionVerificationMethod { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -70,6 +71,7 @@ namespace osu.Game.Online.Chat
|
||||
private UserLookupCache users { get; set; }
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
private readonly IBindableList<APIRelation> localUserBlocks = new BindableList<APIRelation>();
|
||||
private ScheduledDelegate scheduledAck;
|
||||
|
||||
private IChatClient chatClient = null!;
|
||||
@@ -95,6 +97,9 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(_ => SendAck(), true);
|
||||
|
||||
localUserBlocks.BindTo(api.LocalUserState.Blocks);
|
||||
localUserBlocks.BindCollectionChanged((_, args) => Schedule(() => onBlocksChanged(args)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -311,8 +316,9 @@ namespace osu.Game.Online.Chat
|
||||
private void addMessages(List<Message> messages)
|
||||
{
|
||||
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());
|
||||
|
||||
lastSilenceMessageId ??= messages.LastOrDefault()?.Id;
|
||||
@@ -641,6 +647,18 @@ namespace osu.Game.Online.Chat
|
||||
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)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
@@ -43,11 +43,15 @@ namespace osu.Game.Online.Matchmaking
|
||||
/// <summary>
|
||||
/// The user has raised a candidate playlist item to be played.
|
||||
/// </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);
|
||||
|
||||
/// <summary>
|
||||
/// The user has removed a candidate playlist item.
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Online.Matchmaking
|
||||
/// <summary>
|
||||
/// Raise a candidate playlist item to be played in the current round.
|
||||
/// </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);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -28,14 +28,20 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
public int CurrentRound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The playlist items that were picked as gameplay candidates.
|
||||
/// The playlist items that were picked as candidates by user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// May contain <c>-1</c> when any users picked the "random" playlist item.
|
||||
/// </remarks>
|
||||
[Key(2)]
|
||||
public long[] CandidateItems { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The final gameplay candidate.
|
||||
/// A playlist item from <see cref="CandidateItems"/> that was randomly picked by the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// May be <c>-1</c> to indicate the "random" playlist item was chosen.
|
||||
/// </remarks>
|
||||
[Key(3)]
|
||||
public long CandidateItem { get; set; }
|
||||
|
||||
@@ -45,6 +51,15 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
[Key(4)]
|
||||
public MatchmakingUserList Users { get; set; } = new MatchmakingUserList();
|
||||
|
||||
/// <summary>
|
||||
/// A playlist item from the room's playlist that will be played in the current round.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value of this property may not equal <see cref="CandidateItem"/> or exist in <see cref="CandidateItems"/>.
|
||||
/// </remarks>
|
||||
[Key(5)]
|
||||
public long GameplayItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Advances to the next round.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
@@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Notifications
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
IconContent.Width = IconContent.DrawHeight;
|
||||
IconContent.Width = Math.Min(78, IconContent.DrawHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
|
||||
{
|
||||
public const double TRACK_DRAG_SEEK_DEBOUNCE = 200;
|
||||
|
||||
public IconUsage Icon => OsuIcon.Music;
|
||||
public LocalisableString Title => NowPlayingStrings.HeaderTitle;
|
||||
public LocalisableString Description => NowPlayingStrings.HeaderDescription;
|
||||
@@ -207,7 +209,8 @@ namespace osu.Game.Overlays
|
||||
Height = progress_height / 2,
|
||||
FillColour = colours.Yellow,
|
||||
BackgroundColour = colours.YellowDarker.Opacity(0.5f),
|
||||
OnSeek = musicController.SeekTo
|
||||
OnSeek = onSeek,
|
||||
OnCommit = onCommit,
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -221,6 +224,23 @@ namespace osu.Game.Overlays
|
||||
};
|
||||
}
|
||||
|
||||
private double? lastSeekTime;
|
||||
|
||||
private void onSeek(double progress)
|
||||
{
|
||||
if (lastSeekTime == null || Time.Current - lastSeekTime > TRACK_DRAG_SEEK_DEBOUNCE)
|
||||
{
|
||||
musicController.SeekTo(progress);
|
||||
lastSeekTime = Time.Current;
|
||||
}
|
||||
}
|
||||
|
||||
private void onCommit(double progress)
|
||||
{
|
||||
musicController.SeekTo(progress);
|
||||
lastSeekTime = null;
|
||||
}
|
||||
|
||||
private void togglePlaylist()
|
||||
{
|
||||
if (playlist == null)
|
||||
@@ -304,18 +324,21 @@ namespace osu.Game.Overlays
|
||||
|
||||
var track = musicController.CurrentTrack;
|
||||
|
||||
if (!track.IsDummyDevice)
|
||||
if (!progressBar.Seeking)
|
||||
{
|
||||
progressBar.EndTime = track.Length;
|
||||
progressBar.CurrentTime = track.CurrentTime;
|
||||
if (!track.IsDummyDevice)
|
||||
{
|
||||
progressBar.EndTime = track.Length;
|
||||
progressBar.CurrentTime = track.CurrentTime;
|
||||
|
||||
playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
||||
}
|
||||
else
|
||||
{
|
||||
progressBar.CurrentTime = 0;
|
||||
progressBar.EndTime = 1;
|
||||
playButton.Icon = FontAwesome.Regular.PlayCircle;
|
||||
playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
||||
}
|
||||
else
|
||||
{
|
||||
progressBar.CurrentTime = 0;
|
||||
progressBar.EndTime = 1;
|
||||
playButton.Icon = FontAwesome.Regular.PlayCircle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@@ -14,6 +15,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@@ -36,6 +38,7 @@ namespace osu.Game.Overlays
|
||||
public ScrollBackButton Button { get; private set; }
|
||||
|
||||
private readonly Bindable<double?> lastScrollTarget = new Bindable<double?>();
|
||||
private readonly Bindable<double> progress = new Bindable<double>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -46,7 +49,8 @@ namespace osu.Game.Overlays
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding(20),
|
||||
Action = scrollBack,
|
||||
LastScrollTarget = { BindTarget = lastScrollTarget }
|
||||
LastScrollTarget = { BindTarget = lastScrollTarget },
|
||||
Progress = { BindTarget = progress },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,6 +58,10 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
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)
|
||||
{
|
||||
Button.State = Visibility.Hidden;
|
||||
@@ -110,9 +118,11 @@ namespace osu.Game.Overlays
|
||||
|
||||
private readonly Container content;
|
||||
private readonly Box background;
|
||||
private readonly CircularProgress currentCircularProgress;
|
||||
private readonly SpriteIcon spriteIcon;
|
||||
|
||||
public Bindable<double?> LastScrollTarget = new Bindable<double?>();
|
||||
public Bindable<double> Progress = new Bindable<double>();
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||
|
||||
@@ -145,6 +155,11 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
currentCircularProgress = new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
InnerRadius = 0.1f,
|
||||
},
|
||||
spriteIcon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@@ -164,6 +179,7 @@ namespace osu.Game.Overlays
|
||||
IdleColour = colourProvider.Background6;
|
||||
HoverColour = colourProvider.Background5;
|
||||
flashColour = colourProvider.Light1;
|
||||
currentCircularProgress.Colour = colourProvider.Highlight1;
|
||||
|
||||
scrollToTopSample = audio.Samples.Get(@"UI/scroll-to-top");
|
||||
scrollToPreviousSample = audio.Samples.Get(@"UI/scroll-to-previous");
|
||||
@@ -173,6 +189,8 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Progress.BindValueChanged(p => currentCircularProgress.Progress = p.NewValue, true);
|
||||
|
||||
LastScrollTarget.BindValueChanged(target =>
|
||||
{
|
||||
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)
|
||||
return RankingTier.Radiant;
|
||||
|
||||
if (percent < 0.0025)
|
||||
if (percent < 0.0015)
|
||||
return RankingTier.Rhodium;
|
||||
|
||||
if (percent < 0.005)
|
||||
return RankingTier.Platinum;
|
||||
|
||||
if (percent < 0.025)
|
||||
if (percent < 0.015)
|
||||
return RankingTier.Gold;
|
||||
|
||||
if (percent < 0.05)
|
||||
return RankingTier.Silver;
|
||||
|
||||
if (percent < 0.25)
|
||||
if (percent < 0.15)
|
||||
return RankingTier.Bronze;
|
||||
|
||||
if (percent < 0.5)
|
||||
|
||||
@@ -41,8 +41,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
||||
{
|
||||
Add(wasapiExperimental = new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Use experimental audio mode",
|
||||
TooltipText = "This will attempt to initialise the audio engine in a lower latency mode.",
|
||||
LabelText = AudioSettingsStrings.WasapiLabel,
|
||||
TooltipText = AudioSettingsStrings.WasapiTooltip,
|
||||
Current = audio.UseExperimentalWasapi,
|
||||
Keywords = new[] { "wasapi", "latency", "exclusive" }
|
||||
});
|
||||
@@ -64,10 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
||||
if (wasapiExperimental != null)
|
||||
{
|
||||
if (wasapiExperimental.Current.Value)
|
||||
{
|
||||
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);
|
||||
}
|
||||
wasapiExperimental.SetNoticeText(AudioSettingsStrings.WasapiNotice, true);
|
||||
else
|
||||
wasapiExperimental.ClearNoticeText();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.Settings.Sections.General;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections
|
||||
@@ -45,6 +46,13 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
BackgroundColour = colours.YellowDark,
|
||||
Action = () => game?.ShowWiki(@"Help_centre/Upgrading_to_lazer")
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
Text = GeneralSettingsStrings.ReportIssue,
|
||||
TooltipText = GeneralSettingsStrings.ReportIssueTooltip,
|
||||
BackgroundColour = colours.DarkOrange2,
|
||||
Action = () => game?.OpenUrlExternally(@"https://osu.ppy.sh/community/forums/topics/create?forum_id=5", LinkWarnMode.NeverWarn)
|
||||
},
|
||||
new LanguageSettings(),
|
||||
new UpdateSettings(),
|
||||
};
|
||||
|
||||
@@ -36,8 +36,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
|
||||
private Bindable<ScalingMode> scalingMode = null!;
|
||||
private Bindable<Size> sizeFullscreen = null!;
|
||||
private Bindable<Size> sizeWindowed = null!;
|
||||
|
||||
private readonly BindableList<Size> resolutions = new BindableList<Size>(new[] { new Size(9999, 9999) });
|
||||
private readonly BindableList<Size> resolutionsFullscreen = new BindableList<Size>(new[] { new Size(9999, 9999) });
|
||||
private readonly BindableList<Size> resolutionsWindowed = new BindableList<Size>();
|
||||
private readonly Bindable<Size> windowedResolution = new Bindable<Size>();
|
||||
private readonly IBindable<FullscreenCapability> fullscreenCapability = new Bindable<FullscreenCapability>(FullscreenCapability.Capable);
|
||||
|
||||
[Resolved]
|
||||
@@ -48,12 +51,15 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
|
||||
private IWindow? window;
|
||||
|
||||
private SettingsDropdown<Size> resolutionDropdown = null!;
|
||||
private SettingsDropdown<Size> resolutionFullscreenDropdown = null!;
|
||||
private SettingsDropdown<Size> resolutionWindowedDropdown = null!;
|
||||
private SettingsDropdown<Display> displayDropdown = null!;
|
||||
private SettingsDropdown<WindowMode> windowModeDropdown = null!;
|
||||
private SettingsCheckbox minimiseOnFocusLossCheckbox = null!;
|
||||
private SettingsCheckbox safeAreaConsiderationsCheckbox = null!;
|
||||
|
||||
private Bindable<double> windowedPositionX = null!;
|
||||
private Bindable<double> windowedPositionY = null!;
|
||||
private Bindable<float> scalingPositionX = null!;
|
||||
private Bindable<float> scalingPositionY = null!;
|
||||
private Bindable<float> scalingSizeX = null!;
|
||||
@@ -70,12 +76,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
|
||||
scalingMode = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling);
|
||||
sizeFullscreen = config.GetBindable<Size>(FrameworkSetting.SizeFullscreen);
|
||||
sizeWindowed = config.GetBindable<Size>(FrameworkSetting.WindowedSize);
|
||||
windowedPositionX = config.GetBindable<double>(FrameworkSetting.WindowedPositionX);
|
||||
windowedPositionY = config.GetBindable<double>(FrameworkSetting.WindowedPositionY);
|
||||
scalingSizeX = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeX);
|
||||
scalingSizeY = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeY);
|
||||
scalingPositionX = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionX);
|
||||
scalingPositionY = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY);
|
||||
scalingBackgroundDim = osuConfig.GetBindable<float>(OsuSetting.ScalingBackgroundDim);
|
||||
|
||||
windowedResolution.Value = sizeWindowed.Value;
|
||||
|
||||
if (window != null)
|
||||
{
|
||||
currentDisplay.BindTo(window.CurrentDisplayBindable);
|
||||
@@ -100,13 +111,20 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
Items = window?.Displays,
|
||||
Current = currentDisplay,
|
||||
},
|
||||
resolutionDropdown = new ResolutionSettingsDropdown
|
||||
resolutionFullscreenDropdown = new ResolutionSettingsDropdown
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.Resolution,
|
||||
ShowsDefaultIndicator = false,
|
||||
ItemSource = resolutions,
|
||||
ItemSource = resolutionsFullscreen,
|
||||
Current = sizeFullscreen
|
||||
},
|
||||
resolutionWindowedDropdown = new ResolutionSettingsDropdown
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.Resolution,
|
||||
ShowsDefaultIndicator = false,
|
||||
ItemSource = resolutionsWindowed,
|
||||
Current = windowedResolution
|
||||
},
|
||||
minimiseOnFocusLossCheckbox = new SettingsCheckbox
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.MinimiseOnFocusLoss,
|
||||
@@ -202,19 +220,68 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
{
|
||||
if (display.NewValue == null)
|
||||
{
|
||||
resolutions.Clear();
|
||||
resolutionsFullscreen.Clear();
|
||||
resolutionsWindowed.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
resolutions.ReplaceRange(1, resolutions.Count - 1, display.NewValue.DisplayModes
|
||||
.Where(m => m.Size.Width >= 800 && m.Size.Height >= 600)
|
||||
.OrderByDescending(m => Math.Max(m.Size.Height, m.Size.Width))
|
||||
.Select(m => m.Size)
|
||||
.Distinct());
|
||||
var buffer = new Bindable<Size>(windowedResolution.Value);
|
||||
resolutionWindowedDropdown.Current = buffer;
|
||||
|
||||
var fullscreenResolutions = display.NewValue.DisplayModes
|
||||
.Where(m => m.Size.Width >= 800 && m.Size.Height >= 600)
|
||||
.OrderByDescending(m => Math.Max(m.Size.Height, m.Size.Width))
|
||||
.Select(m => m.Size)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var windowedResolutions = fullscreenResolutions
|
||||
.Where(res => res.Width <= display.NewValue.UsableBounds.Width && res.Height <= display.NewValue.UsableBounds.Height)
|
||||
.ToList();
|
||||
|
||||
resolutionsFullscreen.ReplaceRange(1, resolutionsFullscreen.Count - 1, fullscreenResolutions);
|
||||
resolutionsWindowed.ReplaceRange(0, resolutionsWindowed.Count, windowedResolutions);
|
||||
|
||||
resolutionWindowedDropdown.Current = windowedResolution;
|
||||
|
||||
updateDisplaySettingsVisibility();
|
||||
}), true);
|
||||
|
||||
windowedResolution.BindValueChanged(size =>
|
||||
{
|
||||
if (size.NewValue == sizeWindowed.Value || windowModeDropdown.Current.Value != WindowMode.Windowed)
|
||||
return;
|
||||
|
||||
if (window?.WindowState == Framework.Platform.WindowState.Maximised)
|
||||
{
|
||||
window.WindowState = Framework.Platform.WindowState.Normal;
|
||||
}
|
||||
|
||||
// Adjust only for top decorations (assuming system titlebar).
|
||||
// Bottom/left/right borders are ignored as invisible padding, which don't align with the screen.
|
||||
var dBounds = currentDisplay.Value.Bounds;
|
||||
var dUsable = currentDisplay.Value.UsableBounds;
|
||||
float topBar = host.Window?.BorderSize.Value.Top ?? 0;
|
||||
|
||||
int w = Math.Min(size.NewValue.Width, dUsable.Width);
|
||||
int h = (int)Math.Min(size.NewValue.Height, dUsable.Height - topBar);
|
||||
|
||||
windowedResolution.Value = new Size(w, h);
|
||||
sizeWindowed.Value = windowedResolution.Value;
|
||||
|
||||
float adjustedY = Math.Max(
|
||||
dUsable.Y + (dUsable.Height - h) / 2f,
|
||||
dUsable.Y + topBar // titlebar adjustment
|
||||
);
|
||||
windowedPositionY.Value = dBounds.Height - h != 0 ? (adjustedY - dBounds.Y) / (dBounds.Height - h) : 0;
|
||||
windowedPositionX.Value = dBounds.Width - w != 0 ? (dUsable.X - dBounds.X + (dUsable.Width - w) / 2f) / (dBounds.Width - w) : 0;
|
||||
});
|
||||
|
||||
sizeWindowed.BindValueChanged(size =>
|
||||
{
|
||||
if (size.NewValue != windowedResolution.Value)
|
||||
windowedResolution.Value = size.NewValue;
|
||||
});
|
||||
|
||||
scalingMode.BindValueChanged(_ =>
|
||||
{
|
||||
scalingSettings.ClearTransforms();
|
||||
@@ -223,8 +290,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
|
||||
updateScalingModeVisibility();
|
||||
});
|
||||
|
||||
// initial update bypasses transforms
|
||||
updateScalingModeVisibility();
|
||||
|
||||
void updateScalingModeVisibility()
|
||||
@@ -260,7 +325,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
|
||||
private void updateDisplaySettingsVisibility()
|
||||
{
|
||||
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||
resolutionFullscreenDropdown.CanBeShown.Value = windowModeDropdown.Current.Value == WindowMode.Fullscreen && resolutionsFullscreen.Count > 1;
|
||||
resolutionWindowedDropdown.CanBeShown.Value = windowModeDropdown.Current.Value == WindowMode.Windowed && resolutionsWindowed.Count > 1;
|
||||
|
||||
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
||||
minimiseOnFocusLossCheckbox.CanBeShown.Value = RuntimeInfo.IsDesktop && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
||||
|
||||
@@ -46,6 +46,12 @@ namespace osu.Game.Overlays
|
||||
|
||||
public BindableBool Expanded { get; } = new BindableBool(true);
|
||||
|
||||
public Vector2 Spacing
|
||||
{
|
||||
get => content.Spacing;
|
||||
set => content.Spacing = value;
|
||||
}
|
||||
|
||||
private OsuSpriteText headerText = null!;
|
||||
|
||||
private Container headerContent = null!;
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")]
|
||||
[assembly: InternalsVisibleTo("osu.Game.Tests.iOS")]
|
||||
[assembly: InternalsVisibleTo("osu.Game.Tests.Android")]
|
||||
[assembly: InternalsVisibleTo("osu.Game.Tournament.Tests")]
|
||||
|
||||
// intended for Moq usage
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
|
||||
@@ -20,12 +20,12 @@ using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
Bindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
|
||||
|
||||
private ExpandableSlider<double, SizeSlider<double>> distanceSpacingSlider = null!;
|
||||
private ExpandableSlider<double> distanceSpacingSlider = null!;
|
||||
private ExpandableButton currentDistanceSpacingButton = null!;
|
||||
|
||||
[Resolved]
|
||||
@@ -75,14 +75,16 @@ namespace osu.Game.Rulesets.Edit
|
||||
toolboxContainer.Add(toolboxGroup = new EditorToolboxGroup("snapping")
|
||||
{
|
||||
Name = "snapping",
|
||||
Spacing = new Vector2(5),
|
||||
Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
distanceSpacingSlider = new ExpandableSlider<double, SizeSlider<double>>
|
||||
distanceSpacingSlider = new ExpandableSlider<double>
|
||||
{
|
||||
KeyboardStep = adjust_step,
|
||||
// Manual binding in LoadComplete to handle one-way event flow.
|
||||
Current = DistanceSpacingMultiplier.GetUnboundCopy(),
|
||||
ExpandedLabelText = "Distance spacing",
|
||||
},
|
||||
currentDistanceSpacingButton = new ExpandableButton
|
||||
{
|
||||
@@ -104,7 +106,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
DistanceSpacingMultiplier.BindValueChanged(multiplier =>
|
||||
{
|
||||
distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})";
|
||||
distanceSpacingSlider.ExpandedLabelText = $"Distance Spacing ({multiplier.NewValue:0.##x})";
|
||||
distanceSpacingSlider.Current.Value = multiplier.NewValue;
|
||||
|
||||
if (multiplier.NewValue != multiplier.OldValue)
|
||||
onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier));
|
||||
|
||||
@@ -550,7 +550,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -680,14 +679,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
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 FileHitSampleInfo(string filename, int volume)
|
||||
// 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(string.Empty, customSampleBank: 1, volume: volume)
|
||||
: base(HIT_NORMAL, SampleControlPoint.DEFAULT_BANK, customSampleBank: 1, volume: volume)
|
||||
{
|
||||
Filename = filename;
|
||||
}
|
||||
@@ -696,7 +694,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
Filename,
|
||||
Path.ChangeExtension(Filename, null)
|
||||
};
|
||||
}.Concat(base.LookupNames);
|
||||
|
||||
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)
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||
using osuTK;
|
||||
|
||||
@@ -34,39 +34,53 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||
});
|
||||
}
|
||||
|
||||
private double? lastSeekTime;
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e) => true;
|
||||
|
||||
protected override void OnDrag(DragEvent e)
|
||||
{
|
||||
seekToPosition(e.ScreenSpaceMousePosition);
|
||||
base.OnDrag(e);
|
||||
seekToPosition(e.ScreenSpaceMousePosition, instant: false);
|
||||
}
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
base.OnDragEnd(e);
|
||||
seekToPosition(e.ScreenSpaceMousePosition, instant: true);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
seekToPosition(e.ScreenSpaceMousePosition);
|
||||
seekToPosition(e.ScreenSpaceMousePosition, instant: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ScheduledDelegate? scheduledSeek;
|
||||
|
||||
/// <summary>
|
||||
/// Seeks the <see cref="SummaryTimeline"/> to the time closest to a position on the screen relative to the <see cref="SummaryTimeline"/>.
|
||||
/// </summary>
|
||||
/// <param name="screenPosition">The position in screen coordinates.</param>
|
||||
private void seekToPosition(Vector2 screenPosition)
|
||||
/// <param name="instant">Whether the seek should be instant (drag end, mouse button press) or debounced (drag in progress).</param>
|
||||
private void seekToPosition(Vector2 screenPosition, bool instant)
|
||||
{
|
||||
scheduledSeek?.Cancel();
|
||||
scheduledSeek = Schedule(() =>
|
||||
{
|
||||
float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth);
|
||||
editorClock.SeekSmoothlyTo(markerPos / DrawWidth * editorClock.TrackLength);
|
||||
});
|
||||
float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth);
|
||||
double seekDestination = markerPos / DrawWidth * editorClock.TrackLength;
|
||||
marker.X = (float)seekDestination;
|
||||
|
||||
if (!instant && lastSeekTime != null && Time.Current - lastSeekTime < NowPlayingOverlay.TRACK_DRAG_SEEK_DEBOUNCE)
|
||||
return;
|
||||
|
||||
editorClock.SeekSmoothlyTo(seekDestination);
|
||||
|
||||
lastSeekTime = instant ? null : Time.Current;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
marker.X = (float)editorClock.CurrentTime;
|
||||
|
||||
if (!IsDragged)
|
||||
marker.X = (float)editorClock.CurrentTime;
|
||||
}
|
||||
|
||||
protected override void LoadBeatmap(EditorBeatmap beatmap)
|
||||
|
||||
@@ -44,7 +44,6 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
{
|
||||
[Cached(typeof(IPreviewTrackOwner))]
|
||||
public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePresentBeatmap
|
||||
{
|
||||
private readonly Room room;
|
||||
|
||||
@@ -5,7 +5,6 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@@ -13,7 +12,7 @@ using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Queue;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Intro
|
||||
@@ -53,7 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Intro
|
||||
|
||||
private IDisposable? duckOperation;
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new MatchmakingIntroBackgroundScreen(colourProvider);
|
||||
protected override BackgroundScreen CreateBackground() => new MatchmakingBackgroundScreen(colourProvider);
|
||||
|
||||
public ScreenIntro()
|
||||
{
|
||||
@@ -240,27 +239,5 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Intro
|
||||
beatmapImpactChannel?.Stop();
|
||||
duckOperation?.Dispose();
|
||||
}
|
||||
|
||||
private partial class MatchmakingIntroBackgroundScreen : RoomBackgroundScreen
|
||||
{
|
||||
private readonly OverlayColourProvider colourProvider;
|
||||
|
||||
public MatchmakingIntroBackgroundScreen(OverlayColourProvider colourProvider)
|
||||
: base(null)
|
||||
{
|
||||
this.colourProvider = colourProvider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(new Box
|
||||
{
|
||||
Depth = float.MinValue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5.Opacity(0.6f),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-466
@@ -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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.HighPerformance;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@@ -33,17 +34,18 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
|
||||
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 Container<BeatmapSelectPanel> rollContainer;
|
||||
private readonly Container<MatchmakingSelectPanel> rollContainer;
|
||||
private readonly OsuScrollContainer scroll;
|
||||
|
||||
private bool allowSelection = true;
|
||||
|
||||
private readonly Sample?[] spinSamples = new Sample?[5];
|
||||
private static readonly int[] spin_sample_sequence = [0, 1, 2, 3, 4, 2, 3, 4];
|
||||
private Sample? resultSample;
|
||||
private Sample? swooshSample;
|
||||
private double? lastSamplePlayback;
|
||||
|
||||
@@ -63,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
Spacing = new Vector2(panel_spacing)
|
||||
},
|
||||
},
|
||||
rollContainer = new Container<BeatmapSelectPanel>
|
||||
rollContainer = new Container<MatchmakingSelectPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
@@ -77,13 +79,36 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
for (int i = 0; i < spinSamples.Length; i++)
|
||||
spinSamples[i] = audio.Samples.Get($@"Multiplayer/Matchmaking/Selection/roulette-{i}");
|
||||
|
||||
resultSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Selection/roulette-result");
|
||||
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;
|
||||
|
||||
@@ -99,32 +124,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
|
||||
panel.FadeInAndEnterFromBelow(duration: enter_duration, delay: delay);
|
||||
}
|
||||
|
||||
panelsLoaded.SetResult();
|
||||
});
|
||||
}
|
||||
|
||||
public void AddItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
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)
|
||||
public void SetUserSelection(APIUser user, long itemId, bool selected) => whenPanelsLoaded(() =>
|
||||
{
|
||||
if (!panelLookup.TryGetValue(itemId, out var panel))
|
||||
return;
|
||||
@@ -133,13 +138,13 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
panel.AddUser(user);
|
||||
else
|
||||
panel.RemoveUser(user);
|
||||
}
|
||||
});
|
||||
|
||||
public void RollAndDisplayFinalBeatmap(long[] candidateItemIds, long finalItemId)
|
||||
public void RollAndDisplayFinalBeatmap(long[] candidateItemIds, long candidateItemId, long gameplayItemId) => whenPanelsLoaded(() =>
|
||||
{
|
||||
Debug.Assert(candidateItemIds.Length >= 1);
|
||||
Debug.Assert(candidateItemIds.Contains(finalItemId));
|
||||
Debug.Assert(panelLookup.ContainsKey(finalItemId));
|
||||
Debug.Assert(candidateItemIds.Contains(candidateItemId));
|
||||
Debug.Assert(panelLookup.ContainsKey(candidateItemId));
|
||||
Debug.Assert(candidateItemIds.All(id => panelLookup.ContainsKey(id)));
|
||||
|
||||
allowSelection = false;
|
||||
@@ -151,18 +156,18 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
this.Delay(ARRANGE_DELAY)
|
||||
.Schedule(() => ArrangeItemsForRollAnimation())
|
||||
.Delay(arrange_duration + present_beatmap_delay)
|
||||
.Schedule(() => PresentUnanimouslyChosenBeatmap(finalItemId));
|
||||
.Schedule(() => PresentUnanimouslyChosenBeatmap(candidateItemId, gameplayItemId));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Delay(ARRANGE_DELAY)
|
||||
.Schedule(() => ArrangeItemsForRollAnimation())
|
||||
.Delay(arrange_duration)
|
||||
.Schedule(() => PlayRollAnimation(finalItemId, roll_duration))
|
||||
.Schedule(() => PlayRollAnimation(gameplayItemId, roll_duration))
|
||||
.Delay(roll_duration + present_beatmap_delay)
|
||||
.Schedule(() => PresentRolledBeatmap(finalItemId));
|
||||
.Schedule(() => PresentRolledBeatmap(candidateItemId, gameplayItemId));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
internal void TransferCandidatePanelsToRollContainer(long[] candidateItemIds, double duration = hide_duration)
|
||||
{
|
||||
@@ -171,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
|
||||
var rng = new Random();
|
||||
|
||||
var remainingPanels = new List<BeatmapSelectPanel>();
|
||||
var remainingPanels = new List<MatchmakingSelectPanel>();
|
||||
|
||||
foreach (var panel in panelGridContainer.Children.ToArray())
|
||||
{
|
||||
@@ -211,7 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
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));
|
||||
|
||||
@@ -280,7 +285,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
while ((numSteps - 1) % rollContainer.Children.Count != finalItemIndex)
|
||||
numSteps++;
|
||||
|
||||
BeatmapSelectPanel? lastPanel = null;
|
||||
MatchmakingSelectPanel? lastPanel = null;
|
||||
|
||||
for (int i = 0; i < numSteps; i++)
|
||||
{
|
||||
@@ -307,13 +312,14 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
}
|
||||
}
|
||||
|
||||
internal void PresentRolledBeatmap(long finalItem)
|
||||
internal void PresentRolledBeatmap(long candidateItem, long gameplayItem)
|
||||
{
|
||||
Debug.Assert(rollContainer.Children.Any(it => it.Item.ID == finalItem));
|
||||
Debug.Assert(rollContainer.Children.Any(it => it.Item.ID == candidateItem));
|
||||
Debug.Assert(playlistItems.ContainsKey(gameplayItem));
|
||||
|
||||
foreach (var panel in rollContainer.Children)
|
||||
{
|
||||
if (panel.Item.ID != finalItem)
|
||||
if (panel.Item.ID != candidateItem)
|
||||
{
|
||||
panel.FadeOut(200);
|
||||
panel.PopOutAndExpire(easing: Easing.InQuad);
|
||||
@@ -325,23 +331,29 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
rollContainer.ChangeChildDepth(panel, float.MinValue);
|
||||
|
||||
panel.ShowChosenBorder();
|
||||
panel.MoveTo(Vector2.Zero, 1000, Easing.OutExpo)
|
||||
.ScaleTo(1.5f, 1000, Easing.OutExpo);
|
||||
var item = playlistItems[gameplayItem];
|
||||
|
||||
resultSample?.Play();
|
||||
panel.PresentAsChosenBeatmap(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal void PresentUnanimouslyChosenBeatmap(long finalItem)
|
||||
internal void PresentUnanimouslyChosenBeatmap(long candidateItem, long gameplayItem)
|
||||
{
|
||||
// TODO: display special animation in this case
|
||||
|
||||
PresentRolledBeatmap(finalItem);
|
||||
PresentRolledBeatmap(candidateItem, gameplayItem);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+156
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+381
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// 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.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
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!;
|
||||
public SpriteIcon Dice { get; private set; } = null!;
|
||||
public OsuSpriteText Label { get; private set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Dark5,
|
||||
},
|
||||
new TrianglesV2
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.1f,
|
||||
},
|
||||
Label = new OsuSpriteText
|
||||
{
|
||||
Y = 20,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Random"
|
||||
},
|
||||
Dice = new SpriteIcon
|
||||
{
|
||||
Y = -10,
|
||||
Size = new Vector2(28),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = randomDiceIcon(),
|
||||
},
|
||||
selectionOverlay = new AvatarOverlay
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
}
|
||||
};
|
||||
|
||||
Dice.Spin(10_000, RotationDirection.Clockwise);
|
||||
}
|
||||
|
||||
public void RollDice()
|
||||
{
|
||||
var icon = randomDiceIcon();
|
||||
|
||||
while (icon.Equals(Dice.Icon))
|
||||
icon = randomDiceIcon();
|
||||
|
||||
Dice.ScaleTo(0.65f, 60, Easing.Out)
|
||||
.Then()
|
||||
.Schedule(() => Dice.Icon = icon)
|
||||
.ScaleTo(1f, 400, Easing.OutElasticHalf);
|
||||
}
|
||||
|
||||
private static IconUsage[] diceIcons => new[]
|
||||
{
|
||||
FontAwesome.Solid.DiceOne,
|
||||
FontAwesome.Solid.DiceTwo,
|
||||
FontAwesome.Solid.DiceThree,
|
||||
FontAwesome.Solid.DiceFour,
|
||||
FontAwesome.Solid.DiceFive,
|
||||
FontAwesome.Solid.DiceSix,
|
||||
};
|
||||
|
||||
private static IconUsage randomDiceIcon() => diceIcons[RNG.Next(diceIcons.Length)];
|
||||
}
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
// 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;
|
||||
|
||||
protected 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 abstract void PresentAsChosenBeatmap(MatchmakingPlaylistItem playlistItem);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
|
||||
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!;
|
||||
private Sample? resultSample;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
resultSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Selection/roulette-result");
|
||||
|
||||
Add(content = new CardContentBeatmap(beatmap, mods));
|
||||
}
|
||||
|
||||
public override void PresentAsChosenBeatmap(MatchmakingPlaylistItem playlistItem)
|
||||
{
|
||||
ShowChosenBorder();
|
||||
this.MoveTo(Vector2.Zero, 1000, Easing.OutExpo)
|
||||
.ScaleTo(1.5f, 1000, Easing.OutExpo);
|
||||
|
||||
resultSample?.Play();
|
||||
}
|
||||
|
||||
public override void AddUser(APIUser user)
|
||||
{
|
||||
content.SelectionOverlay.AddUser(user);
|
||||
}
|
||||
|
||||
public override void RemoveUser(APIUser user)
|
||||
{
|
||||
content.SelectionOverlay.RemoveUser(user.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
public partial class MatchmakingSelectPanelRandom : MatchmakingSelectPanel
|
||||
{
|
||||
public MatchmakingSelectPanelRandom(MultiplayerPlaylistItem item)
|
||||
: base(item)
|
||||
{
|
||||
}
|
||||
|
||||
private CardContentRandom content = null!;
|
||||
private Drawable diceProxy = null!;
|
||||
private readonly List<APIUser> users = new List<APIUser>();
|
||||
|
||||
private Sample? resultSample;
|
||||
private Sample? swooshSample;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
resultSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Selection/roulette-result");
|
||||
swooshSample = audio.Samples.Get(@"SongSelect/options-pop-out");
|
||||
|
||||
Add(content = new CardContentRandom());
|
||||
|
||||
AddInternal(diceProxy = content.Dice.CreateProxy());
|
||||
}
|
||||
|
||||
public override void PresentAsChosenBeatmap(MatchmakingPlaylistItem playlistItem)
|
||||
{
|
||||
const double duration = 800;
|
||||
|
||||
this.MoveTo(Vector2.Zero, 1000, Easing.OutExpo)
|
||||
.ScaleTo(1.5f, 1000, Easing.OutExpo);
|
||||
|
||||
content.Dice.MoveToY(-200, duration * 0.55, new CubicBezierEasingFunction(0.33, 1, 0.8, 1))
|
||||
.Then()
|
||||
.Schedule(() => ChangeInternalChildDepth(diceProxy, float.MaxValue))
|
||||
.MoveToY(-DrawHeight / 2, duration * 0.45, new CubicBezierEasingFunction(0.2, 0, 0.55, 0))
|
||||
.Then()
|
||||
.FadeOut()
|
||||
.Expire();
|
||||
|
||||
content.Dice.RotateTo(content.Dice.Rotation - 360 * 5, duration * 1.3f, Easing.Out);
|
||||
content.Label.FadeOut(200).Expire();
|
||||
|
||||
swooshSample?.Play();
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
content.Expire();
|
||||
|
||||
var flashLayer = new Box { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new CardContentBeatmap(playlistItem.Beatmap, playlistItem.Mods),
|
||||
flashLayer,
|
||||
});
|
||||
|
||||
foreach (var user in users)
|
||||
content.SelectionOverlay.AddUser(user);
|
||||
|
||||
flashLayer.FadeOutFromOne(1000, Easing.In);
|
||||
|
||||
ScaleContainer.ScaleTo(0.92f, 120, Easing.Out)
|
||||
.Then()
|
||||
.ScaleTo(1f, 600, Easing.OutElasticHalf);
|
||||
|
||||
resultSample?.Play();
|
||||
}, duration);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (AllowSelection && content is CardContentRandom randomContent)
|
||||
randomContent.RollDice();
|
||||
|
||||
return base.OnClick(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
-15
@@ -1,13 +1,22 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
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.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
@@ -17,10 +26,17 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
public override Drawable PlayersDisplayArea { get; }
|
||||
|
||||
private readonly BeatmapSelectGrid beatmapSelectGrid;
|
||||
private readonly LoadingSpinner loadingSpinner;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesetStore { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
|
||||
|
||||
public SubScreenBeatmapSelect()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
@@ -29,9 +45,19 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
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
|
||||
@@ -49,24 +75,52 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
client.ItemAdded += onItemAdded;
|
||||
|
||||
foreach (var item in client.Room!.Playlist)
|
||||
onItemAdded(item);
|
||||
|
||||
beatmapSelectGrid.ItemSelected += item => client.MatchmakingToggleSelection(item.ID);
|
||||
|
||||
client.MatchmakingItemSelected += onItemSelected;
|
||||
client.MatchmakingItemDeselected += onItemDeselected;
|
||||
|
||||
Debug.Assert(client.Room != null);
|
||||
|
||||
loadItems(client.Room.Playlist.Where(item => !item.Expired).ToArray()).FireAndForget();
|
||||
}
|
||||
|
||||
private void onItemAdded(MultiplayerPlaylistItem item) => Scheduler.Add(() =>
|
||||
private async Task loadItems(MultiplayerPlaylistItem[] items)
|
||||
{
|
||||
if (item.Expired)
|
||||
return;
|
||||
var beatmaps = await beatmapLookupCache.GetBeatmapsAsync(items.Select(it => it.BeatmapID).ToArray()).ConfigureAwait(false);
|
||||
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)
|
||||
{
|
||||
@@ -80,7 +134,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
beatmapSelectGrid.SetUserSelection(user, itemId, false);
|
||||
}
|
||||
|
||||
public void RollFinalBeatmap(long[] candidateItems, long finalItem) => beatmapSelectGrid.RollAndDisplayFinalBeatmap(candidateItems, finalItem);
|
||||
public void RollFinalBeatmap(long[] candidateItems, long candidateItem, long gameplayItem) =>
|
||||
beatmapSelectGrid.RollAndDisplayFinalBeatmap(candidateItems, candidateItem, gameplayItem);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
@@ -88,7 +143,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
|
||||
if (client.IsNotNull())
|
||||
{
|
||||
client.ItemAdded -= onItemAdded;
|
||||
client.MatchmakingItemSelected -= onItemSelected;
|
||||
client.MatchmakingItemDeselected -= onItemDeselected;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
{
|
||||
public partial class MatchmakingBackgroundScreen : BackgroundScreen
|
||||
{
|
||||
private readonly OverlayColourProvider colourProvider;
|
||||
|
||||
public MatchmakingBackgroundScreen(OverlayColourProvider colourProvider)
|
||||
{
|
||||
this.colourProvider = colourProvider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures)
|
||||
{
|
||||
InternalChild = new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Texture = textures.Get("Backgrounds/bg1"),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fill,
|
||||
Colour = colourProvider.Dark2
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -15,6 +17,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@@ -114,6 +117,18 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
private PlayerPanelDisplayMode displayMode = PlayerPanelDisplayMode.Horizontal;
|
||||
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)
|
||||
: base(HoverSampleSet.Button)
|
||||
{
|
||||
@@ -130,7 +145,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
Content.Masking = true;
|
||||
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.
|
||||
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()
|
||||
@@ -272,6 +294,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
avatar.ScaleTo(0)
|
||||
.ScaleTo(1, 500, Easing.OutElasticHalf)
|
||||
.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
|
||||
@@ -481,6 +506,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
scale.Then().ScaleTo(new Vector2(1, 1.05f), 200, Easing.Out)
|
||||
.Then().ScaleTo(new Vector2(1, 0.95f), 200, Easing.In)
|
||||
.Then().ScaleTo(Vector2.One, 800, Easing.OutElastic);
|
||||
|
||||
// only play jump sample if panel is visible
|
||||
if (Alpha > 0)
|
||||
playJumpSample(isConsecutive);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -498,6 +528,44 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
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)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
+40
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
|
||||
case MatchmakingStage.ServerBeatmapFinalised:
|
||||
Debug.Assert(screenStack.CurrentScreen is SubScreenBeatmapSelect);
|
||||
((SubScreenBeatmapSelect)screenStack.CurrentScreen).RollFinalBeatmap(matchmakingState.CandidateItems, matchmakingState.CandidateItem);
|
||||
((SubScreenBeatmapSelect)screenStack.CurrentScreen).RollFinalBeatmap(matchmakingState.CandidateItems, matchmakingState.CandidateItem, matchmakingState.GameplayItem);
|
||||
break;
|
||||
|
||||
case MatchmakingStage.ResultsDisplaying:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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 System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -29,6 +30,7 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Gameplay;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Users;
|
||||
@@ -47,12 +49,19 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
/// </summary>
|
||||
private const float row_padding = 10;
|
||||
|
||||
private static readonly Vector2 chat_size = new Vector2(550, 130);
|
||||
|
||||
public override bool? ApplyModTrackAdjustments => true;
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new MatchmakingBackgroundScreen(colourProvider);
|
||||
|
||||
[Cached(typeof(OnlinePlayBeatmapAvailabilityTracker))]
|
||||
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new MultiplayerBeatmapAvailabilityTracker();
|
||||
|
||||
@@ -104,8 +113,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Size = new Vector2(700, 130),
|
||||
Margin = new MarginPadding { Bottom = 10, Right = WaveOverlayContainer.WIDTH_PADDING - HORIZONTAL_OVERFLOW_PADDING },
|
||||
Size = chat_size,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = WaveOverlayContainer.WIDTH_PADDING - HORIZONTAL_OVERFLOW_PADDING,
|
||||
Bottom = row_padding
|
||||
},
|
||||
Alpha = 0
|
||||
};
|
||||
}
|
||||
@@ -162,9 +175,10 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
[
|
||||
new Container
|
||||
{
|
||||
Name = "Chat Area Space",
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Size = new Vector2(700, 130),
|
||||
Size = new Vector2(550, 130),
|
||||
Margin = new MarginPadding { Bottom = row_padding }
|
||||
}
|
||||
]
|
||||
@@ -326,6 +340,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
return false;
|
||||
}
|
||||
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() =>
|
||||
[
|
||||
new HistoryFooterButton(room)
|
||||
];
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnEntering(e);
|
||||
@@ -463,7 +482,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
|
||||
// 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.
|
||||
Size = new Vector2(700);
|
||||
Size = new Vector2(chat_size.X);
|
||||
|
||||
InternalChild = new OsuContextMenuContainer
|
||||
{
|
||||
|
||||
@@ -165,7 +165,6 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private void updateDisplay(ValueChangedEvent<Period?> period)
|
||||
{
|
||||
FinishTransforms(true);
|
||||
Scheduler.CancelDelayedTasks();
|
||||
|
||||
if (period.NewValue == null)
|
||||
@@ -180,12 +179,12 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
remainingTimeAdjustmentBox
|
||||
.ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint)
|
||||
.Delay(b.Duration - BREAK_FADE_DURATION)
|
||||
.Delay(b.Duration)
|
||||
.ResizeWidthTo(0);
|
||||
|
||||
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)
|
||||
.MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint);
|
||||
@@ -193,7 +192,7 @@ namespace osu.Game.Screens.Play
|
||||
info.MoveToX(50)
|
||||
.MoveToX(0, BREAK_FADE_DURATION, Easing.OutQuint);
|
||||
|
||||
using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION))
|
||||
using (BeginDelayedSequence(b.Duration))
|
||||
{
|
||||
fadeContainer.FadeOut(BREAK_FADE_DURATION);
|
||||
breakArrows.Hide(BREAK_FADE_DURATION);
|
||||
|
||||
@@ -22,6 +22,7 @@ using osu.Game.Input.Bindings;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
@@ -236,7 +237,7 @@ namespace osu.Game.Screens.Play
|
||||
if (gameplayState != null)
|
||||
{
|
||||
playInfoText.NewLine();
|
||||
playInfoText.AddText(SongSelectStrings.Accuracy);
|
||||
playInfoText.AddText(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy);
|
||||
playInfoText.AddText(": ");
|
||||
playInfoText.AddText(gameplayState!.ScoreProcessor.Accuracy.Value.FormatAccuracy(), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user