1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 06:57:39 +08:00

Merge branch 'master' into move-incompatibility-icon

This commit is contained in:
Salman Ahmed 2021-08-31 19:22:16 +03:00
commit 9afc482598
80 changed files with 734 additions and 354 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.828.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.830.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -6,7 +6,6 @@ using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.Mania.UI;
@ -29,11 +28,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
[Resolved(canBeNull: true)]
private ManiaPlayfield playfield { get; set; }
/// <summary>
/// Gets the samples that are played by this object during gameplay.
/// </summary>
public ISampleInfo[] GetGameplaySamples() => Samples.Samples;
protected override float SamplePlaybackPosition
{
get

View File

@ -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.Linq;
using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -19,6 +18,7 @@ using osuTK;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.UI
{
@ -28,12 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI
public const float COLUMN_WIDTH = 80;
public const float SPECIAL_COLUMN_WIDTH = 70;
/// <summary>
/// For hitsounds played by this <see cref="Column"/> (i.e. not as a result of hitting a hitobject),
/// a certain number of samples are allowed to be played concurrently so that it feels better when spam-pressing the key.
/// </summary>
private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY;
/// <summary>
/// The index of this column as part of the whole playfield.
/// </summary>
@ -45,10 +39,10 @@ namespace osu.Game.Rulesets.Mania.UI
internal readonly Container TopLevelContainer;
private readonly DrawablePool<PoolableHitExplosion> hitExplosionPool;
private readonly OrderedHitPolicy hitPolicy;
private readonly Container<SkinnableSound> hitSounds;
public Container UnderlayElements => HitObjectArea.UnderlayElements;
private readonly GameplaySampleTriggerSource sampleTriggerSource;
public Column(int index)
{
Index = index;
@ -64,6 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
InternalChildren = new[]
{
hitExplosionPool = new DrawablePool<PoolableHitExplosion>(5),
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
@ -72,12 +67,6 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both
},
background,
hitSounds = new Container<SkinnableSound>
{
Name = "Column samples pool",
RelativeSizeAxes = Axes.Both,
Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray()
},
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
@ -133,29 +122,12 @@ namespace osu.Game.Rulesets.Mania.UI
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
}
private int nextHitSoundIndex;
public bool OnPressed(ManiaAction action)
{
if (action != Action.Value)
return false;
var nextObject =
HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
// fallback to non-alive objects to find next off-screen object
HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
HitObjectContainer.Objects.LastOrDefault();
if (nextObject is DrawableManiaHitObject maniaObject)
{
var hitSound = hitSounds[nextHitSoundIndex];
hitSound.Samples = maniaObject.GetGameplaySamples();
hitSound.Play();
nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds;
}
sampleTriggerSource.Play();
return true;
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
Origin = Anchor.Centre,
Children = new Drawable[]
{
new TaikoPlayfield(new ControlPointInfo()),
new TaikoPlayfield(),
hoc = new ScrollingHitObjectContainer()
}
};
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
Origin = Anchor.Centre,
Children = new Drawable[]
{
new TaikoPlayfield(new ControlPointInfo()),
new TaikoPlayfield(),
hoc = new ScrollingHitObjectContainer()
}
};

View File

@ -5,7 +5,6 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.UI;
using osuTK;
@ -17,6 +16,13 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[BackgroundDependencyLoader]
private void load()
{
var playfield = new TaikoPlayfield();
var beatmap = CreateWorkingBeatmap(new TaikoRuleset().RulesetInfo).GetPlayableBeatmap(new TaikoRuleset().RulesetInfo);
foreach (var h in beatmap.HitObjects)
playfield.Add(h);
SetContents(_ => new TaikoInputManager(new TaikoRuleset().RulesetInfo)
{
RelativeSizeAxes = Axes.Both,
@ -25,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200),
Child = new InputDrum(new ControlPointInfo())
Child = new InputDrum(playfield.HitObjectContainer)
}
});
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
Beatmap.Value.Track.Start();
});
AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield(new ControlPointInfo())
AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,

View File

@ -1,104 +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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Audio
{
/// <summary>
/// Stores samples for the input drum.
/// The lifetime of the samples is adjusted so that they are only alive during the appropriate sample control point.
/// </summary>
public class DrumSampleContainer : LifetimeManagementContainer
{
private readonly ControlPointInfo controlPoints;
private readonly Dictionary<double, DrumSample> mappings = new Dictionary<double, DrumSample>();
private readonly IBindableList<SampleControlPoint> samplePoints = new BindableList<SampleControlPoint>();
public DrumSampleContainer(ControlPointInfo controlPoints)
{
this.controlPoints = controlPoints;
}
[BackgroundDependencyLoader]
private void load()
{
samplePoints.BindTo(controlPoints.SamplePoints);
samplePoints.BindCollectionChanged((_, __) => recreateMappings(), true);
}
private void recreateMappings()
{
mappings.Clear();
ClearInternal();
SampleControlPoint[] points = samplePoints.Count == 0
? new[] { controlPoints.SamplePointAt(double.MinValue) }
: samplePoints.ToArray();
for (int i = 0; i < points.Length; i++)
{
var samplePoint = points[i];
var lifetimeStart = i > 0 ? samplePoint.Time : double.MinValue;
var lifetimeEnd = i + 1 < points.Length ? points[i + 1].Time : double.MaxValue;
AddInternal(mappings[samplePoint.Time] = new DrumSample(samplePoint)
{
LifetimeStart = lifetimeStart,
LifetimeEnd = lifetimeEnd
});
}
}
public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time];
public class DrumSample : CompositeDrawable
{
public override bool RemoveWhenNotAlive => false;
public PausableSkinnableSound Centre { get; private set; }
public PausableSkinnableSound Rim { get; private set; }
private readonly SampleControlPoint samplePoint;
private Bindable<string> sampleBank;
private BindableNumber<int> sampleVolume;
public DrumSample(SampleControlPoint samplePoint)
{
this.samplePoint = samplePoint;
}
[BackgroundDependencyLoader]
private void load()
{
sampleBank = samplePoint.SampleBankBindable.GetBoundCopy();
sampleBank.BindValueChanged(_ => recreate());
sampleVolume = samplePoint.SampleVolumeBindable.GetBoundCopy();
sampleVolume.BindValueChanged(_ => recreate());
recreate();
}
private void recreate()
{
InternalChildren = new Drawable[]
{
Centre = new PausableSkinnableSound(samplePoint.GetSampleInfo()),
Rim = new PausableSkinnableSound(samplePoint.GetSampleInfo(HitSampleInfo.HIT_CLAP))
};
}
}
}
}

View File

@ -18,12 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
internal class TaikoBeatmapConverter : BeatmapConverter<TaikoHitObject>
{
/// <summary>
/// osu! is generally slower than taiko, so a factor is added to increase
/// speed. This must be used everywhere slider length or beat length is used.
/// </summary>
public const float LEGACY_VELOCITY_MULTIPLIER = 1.4f;
/// <summary>
/// Because swells are easier in taiko than spinners are in osu!,
/// legacy taiko multiplies a factor when converting the number of required hits.
@ -52,10 +46,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
{
// Rewrite the beatmap info to add the slider velocity multiplier
original.BeatmapInfo = original.BeatmapInfo.Clone();
original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LEGACY_VELOCITY_MULTIPLIER;
if (!(original.BeatmapInfo.BaseDifficulty is TaikoMutliplierAppliedDifficulty))
{
// Rewrite the beatmap info to add the slider velocity multiplier
original.BeatmapInfo = original.BeatmapInfo.Clone();
original.BeatmapInfo.BaseDifficulty = new TaikoMutliplierAppliedDifficulty(original.BeatmapInfo.BaseDifficulty);
}
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
@ -155,7 +151,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
int spans = (obj as IHasRepeats)?.SpanCount() ?? 1;
double distance = distanceData.Distance * spans * LEGACY_VELOCITY_MULTIPLIER;
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
@ -194,5 +190,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
}
protected override Beatmap<TaikoHitObject> CreateBeatmap() => new TaikoBeatmap();
private class TaikoMutliplierAppliedDifficulty : BeatmapDifficulty
{
public TaikoMutliplierAppliedDifficulty(BeatmapDifficulty difficulty)
{
difficulty.CopyTo(this);
SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
}
}
}
}

View File

@ -6,10 +6,10 @@ using System;
using System.Threading;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Judgements;
using osuTK;
@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
double IHasDistance.Distance => Duration * Velocity;
SliderPath IHasPath.Path
=> new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER);
=> new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER);
#endregion
}

View File

@ -7,7 +7,8 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Taiko.Audio;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning;
using osuTK;
@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
public readonly Sprite Centre;
[Resolved]
private DrumSampleContainer sampleContainer { get; set; }
private DrumSampleTriggerSource sampleTriggerSource { get; set; }
public LegacyHalfDrum(bool flipped)
{
@ -143,17 +144,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
public bool OnPressed(TaikoAction action)
{
Drawable target = null;
var drumSample = sampleContainer.SampleAt(Time.Current);
if (action == CentreAction)
{
target = Centre;
drumSample.Centre?.Play();
sampleTriggerSource.Play(HitType.Centre);
}
else if (action == RimAction)
{
target = Rim;
drumSample.Rim?.Play();
sampleTriggerSource.Play(HitType.Rim);
}
if (target != null)

View File

@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo);
protected override Playfield CreatePlayfield() => new TaikoPlayfield();
public override DrawableHitObject<TaikoHitObject> CreateDrawableRepresentation(TaikoHitObject h) => null;

View File

@ -0,0 +1,30 @@
// 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.Game.Audio;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.UI
{
public class DrumSampleTriggerSource : GameplaySampleTriggerSource
{
public DrumSampleTriggerSource(HitObjectContainer hitObjectContainer)
: base(hitObjectContainer)
{
}
public void Play(HitType hitType)
{
var hitObject = GetMostValidObject();
if (hitObject == null)
return;
PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) });
}
public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead");
}
}

View File

@ -2,18 +2,18 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osuTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Audio;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Taiko.UI
{
@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Taiko.UI
private const float middle_split = 0.025f;
[Cached]
private DrumSampleContainer sampleContainer;
private DrumSampleTriggerSource sampleTriggerSource;
public InputDrum(ControlPointInfo controlPoints)
public InputDrum(HitObjectContainer hitObjectContainer)
{
sampleContainer = new DrumSampleContainer(controlPoints);
sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer);
RelativeSizeAxes = Axes.Both;
}
@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
}),
sampleContainer
sampleTriggerSource
};
}
@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Sprite centreHit;
[Resolved]
private DrumSampleContainer sampleContainer { get; set; }
private DrumSampleTriggerSource sampleTriggerSource { get; set; }
public TaikoHalfDrum(bool flipped)
{
@ -156,21 +156,19 @@ namespace osu.Game.Rulesets.Taiko.UI
Drawable target = null;
Drawable back = null;
var drumSample = sampleContainer.SampleAt(Time.Current);
if (action == CentreAction)
{
target = centreHit;
back = centre;
drumSample.Centre?.Play();
sampleTriggerSource.Play(HitType.Centre);
}
else if (action == RimAction)
{
target = rimHit;
back = rim;
drumSample.Rim?.Play();
sampleTriggerSource.Play(HitType.Rim);
}
if (target != null)

View File

@ -8,7 +8,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
@ -27,8 +26,6 @@ namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoPlayfield : ScrollingPlayfield
{
private readonly ControlPointInfo controlPoints;
/// <summary>
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
/// </summary>
@ -56,11 +53,6 @@ namespace osu.Game.Rulesets.Taiko.UI
private Container hitTargetOffsetContent;
public TaikoPlayfield(ControlPointInfo controlPoints)
{
this.controlPoints = controlPoints;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@ -131,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI
Children = new Drawable[]
{
new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()),
new InputDrum(controlPoints)
new InputDrum(HitObjectContainer)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,

View File

@ -49,6 +49,22 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
[TestCaseSource(nameof(allBeatmaps))]
public void TestEncodeDecodeStabilityDoubleConvert(string name)
{
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
// run an extra convert. this is expected to be stable.
decodedAfterEncode.beatmap = convert(decodedAfterEncode.beatmap);
sort(decoded.beatmap);
sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
[Test]
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
{

View File

@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
@ -30,7 +31,10 @@ namespace osu.Game.Tests.Visual.Editing
{
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new ComposeScreen();
Child = new ComposeScreen
{
State = { Value = Visibility.Visible },
};
}
}
}

View File

@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit;
@ -26,7 +27,11 @@ namespace osu.Game.Tests.Visual.Editing
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new SetupScreen();
Child = new SetupScreen
{
State = { Value = Visibility.Visible },
};
}
}
}

View File

@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
@ -30,7 +31,10 @@ namespace osu.Game.Tests.Visual.Editing
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Beatmap.Disabled = true;
Child = new TimingScreen();
Child = new TimingScreen
{
State = { Value = Visibility.Visible },
};
}
protected override void Dispose(bool isDisposing)

View File

@ -0,0 +1,135 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneGameplaySampleTriggerSource : PlayerTestScene
{
private TestGameplaySampleTriggerSource sampleTriggerSource;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
private Beatmap beatmap;
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
Ruleset = ruleset
}
};
const double start_offset = 8000;
const double spacing = 2000;
double t = start_offset;
beatmap.HitObjects.AddRange(new[]
{
new HitCircle
{
// intentionally start objects a bit late so we can test the case of no alive objects.
StartTime = t += spacing,
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
},
new HitCircle
{
StartTime = t += spacing,
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) }
},
new HitCircle
{
StartTime = t += spacing,
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
SampleControlPoint = new SampleControlPoint { SampleBank = "soft" },
},
new HitCircle
{
StartTime = t + spacing,
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) },
SampleControlPoint = new SampleControlPoint { SampleBank = "soft" },
},
});
return beatmap;
}
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("Add trigger source", () => Player.HUDOverlay.Add(sampleTriggerSource = new TestGameplaySampleTriggerSource(Player.DrawableRuleset.Playfield.HitObjectContainer)));
}
[Test]
public void TestCorrectHitObject()
{
HitObjectLifetimeEntry nextObjectEntry = null;
AddAssert("no alive objects", () => getNextAliveObject() == null);
AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]);
AddUntilStep("get next object", () =>
{
var nextDrawableObject = getNextAliveObject();
if (nextDrawableObject != null)
{
nextObjectEntry = nextDrawableObject.Entry;
InputManager.MoveMouseTo(nextDrawableObject.ScreenSpaceDrawQuad.Centre);
return true;
}
return false;
});
AddUntilStep("hit first hitobject", () =>
{
InputManager.Click(MouseButton.Left);
return nextObjectEntry.Result.HasResult;
});
AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]);
AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[2]);
AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]);
AddUntilStep("no alive objects", () => getNextAliveObject() == null);
AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]);
}
private DrawableHitObject getNextAliveObject() =>
Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault();
[Test]
public void TestSampleTriggering()
{
AddRepeatStep("trigger sample", () => sampleTriggerSource.Play(), 10);
}
public class TestGameplaySampleTriggerSource : GameplaySampleTriggerSource
{
public TestGameplaySampleTriggerSource(HitObjectContainer hitObjectContainer)
: base(hitObjectContainer)
{
}
public new HitObject GetMostValidObject() => base.GetMostValidObject();
}
}
}

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
@ -19,6 +20,7 @@ using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Users;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestGeneral()
{
int[] userIds = Enumerable.Range(0, 4).Select(i => PLAYER_1_ID + i).ToArray();
int[] userIds = getPlayerIds(4);
start(userIds);
loadSpectateScreen();
@ -314,6 +316,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType<DrawableRuleset>().Single().FrameStableClock.CurrentTime > 30000);
}
[Test]
public void TestPlayersLeaveWhileSpectating()
{
start(getPlayerIds(4));
sendFrames(getPlayerIds(4), 300);
loadSpectateScreen();
for (int count = 3; count >= 0; count--)
{
var id = PLAYER_1_ID + count;
end(id);
AddUntilStep($"{id} area grayed", () => getInstance(id).Colour != Color4.White);
AddUntilStep($"{id} score quit set", () => getLeaderboardScore(id).HasQuit.Value);
sendFrames(getPlayerIds(count), 300);
}
Player player = null;
AddStep($"get {PLAYER_1_ID} player instance", () => player = getInstance(PLAYER_1_ID).ChildrenOfType<Player>().Single());
start(new[] { PLAYER_1_ID });
sendFrames(PLAYER_1_ID, 300);
AddAssert($"{PLAYER_1_ID} player instance still same", () => getInstance(PLAYER_1_ID).ChildrenOfType<Player>().Single() == player);
AddAssert($"{PLAYER_1_ID} area still grayed", () => getInstance(PLAYER_1_ID).Colour != Color4.White);
AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value);
}
private void loadSpectateScreen(bool waitForPlayerLoad = true)
{
AddStep("load screen", () =>
@ -333,14 +365,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
foreach (int id in userIds)
{
OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true);
var user = new MultiplayerRoomUser(id)
{
User = new User { Id = id },
};
OnlinePlayDependencies.Client.AddUser(user.User, true);
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
playingUsers.Add(new MultiplayerRoomUser(id));
playingUsers.Add(user);
}
});
}
private void end(int userId)
{
AddStep($"end play for {userId}", () =>
{
var user = playingUsers.Single(u => u.UserID == userId);
OnlinePlayDependencies.Client.RemoveUser(user.User.AsNonNull());
SpectatorClient.EndPlay(userId);
playingUsers.Remove(user);
});
}
private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count);
private void sendFrames(int[] userIds, int count = 10)
@ -374,5 +424,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType<Player>().Single();
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType<PlayerArea>().Single(p => p.UserId == userId);
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId);
private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray();
}
}

View File

@ -5,6 +5,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.Cursor;
@ -55,20 +56,24 @@ namespace osu.Game.Tests.Visual.UserInterface
{
AddStep("create component", () =>
{
Child = new OsuContextMenuContainer
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = new Container
Child = new OsuContextMenuContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledColourPalette
RelativeSizeAxes = Axes.Both,
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ColourNamePrefix = "My colour #"
Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledColourPalette
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ColourNamePrefix = "My colour #"
}
}
}
};

View File

@ -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 NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneLabelledDropdown : OsuTestScene
{
[Test]
public void TestLabelledDropdown()
=> AddStep(@"create dropdown", () => Child = new LabelledDropdown<string>
{
Label = @"Countdown speed",
Items = new[]
{
@"Half",
@"Normal",
@"Double"
},
Description = @"This is a description"
});
[Test]
public void TestLabelledEnumDropdown()
=> AddStep(@"create dropdown", () => Child = new LabelledEnumDropdown<BeatmapSetOnlineStatus>
{
Label = @"Beatmap status",
Description = @"This is a description"
});
}
}

View File

@ -4,8 +4,10 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Screens.Gameplay;
using osu.Game.Tournament.Screens.Gameplay.Components;
@ -16,16 +18,26 @@ namespace osu.Game.Tournament.Tests.Screens
[Cached]
private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay { Width = 0.5f };
[BackgroundDependencyLoader]
private void load()
[Test]
public void TestStartupState([Values] TourneyState state)
{
Add(new GameplayScreen());
Add(chat);
AddStep("set state", () => IPCInfo.State.Value = state);
createScreen();
}
[Test]
public void TestStartupStateNoCurrentMatch([Values] TourneyState state)
{
AddStep("set null current", () => Ladder.CurrentMatch.Value = null);
AddStep("set state", () => IPCInfo.State.Value = state);
createScreen();
}
[Test]
public void TestWarmup()
{
createScreen();
checkScoreVisibility(false);
toggleWarmup();
@ -35,6 +47,20 @@ namespace osu.Game.Tournament.Tests.Screens
checkScoreVisibility(false);
}
private void createScreen()
{
AddStep("setup screen", () =>
{
Remove(chat);
Children = new Drawable[]
{
new GameplayScreen(),
chat,
};
});
}
private void checkScoreVisibility(bool visible)
=> AddUntilStep($"scores {(visible ? "shown" : "hidden")}",
() => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0)));

View File

@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Allocation;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Tournament.Screens.TeamWin;
@ -10,19 +10,22 @@ namespace osu.Game.Tournament.Tests.Screens
{
public class TestSceneTeamWinScreen : TournamentTestScene
{
[BackgroundDependencyLoader]
private void load()
[Test]
public void TestBasic()
{
var match = Ladder.CurrentMatch.Value;
AddStep("set up match", () =>
{
var match = Ladder.CurrentMatch.Value;
match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals");
match.Completed.Value = true;
match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals");
match.Completed.Value = true;
});
Add(new TeamWinScreen
AddStep("create screen", () => Add(new TeamWinScreen
{
FillMode = FillMode.Fit,
FillAspectRatio = 16 / 9f
});
}));
}
}
}

View File

@ -19,6 +19,8 @@ namespace osu.Game.Tournament.Tests
{
public abstract class TournamentTestScene : OsuTestScene
{
private TournamentMatch match;
[Cached]
protected LadderInfo Ladder { get; private set; } = new LadderInfo();
@ -33,19 +35,23 @@ namespace osu.Game.Tournament.Tests
{
Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First();
TournamentMatch match = CreateSampleMatch();
match = CreateSampleMatch();
Ladder.Rounds.Add(match.Round.Value);
Ladder.Matches.Add(match);
Ladder.Teams.Add(match.Team1.Value);
Ladder.Teams.Add(match.Team2.Value);
Ladder.CurrentMatch.Value = match;
Ruleset.BindTo(Ladder.Ruleset);
Dependencies.CacheAs(new StableInfo(storage));
}
[SetUpSteps]
public virtual void SetUpSteps()
{
AddStep("set current match", () => Ladder.CurrentMatch.Value = match);
}
public static TournamentMatch CreateSampleMatch() => new TournamentMatch
{
Team1 =

View File

@ -20,6 +20,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
}
private void matchChanged(ValueChangedEvent<TournamentMatch> match) =>
Text.Text = match.NewValue.Round.Value?.Name.Value ?? "Unknown Round";
Text.Text = match.NewValue?.Round.Value?.Name.Value ?? "Unknown Round";
}
}

View File

@ -124,9 +124,6 @@ namespace osu.Game.Tournament.Screens.Gameplay
}
});
State.BindTo(ipc.State);
State.BindValueChanged(stateChanged, true);
ladder.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true);
warmup.BindValueChanged(w =>
@ -136,6 +133,14 @@ namespace osu.Game.Tournament.Screens.Gameplay
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
State.BindTo(ipc.State);
State.BindValueChanged(stateChanged, true);
}
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
{
base.CurrentMatchChanged(match);
@ -159,7 +164,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
{
if (state.NewValue == TourneyState.Ranking)
{
if (warmup.Value) return;
if (warmup.Value || CurrentMatch.Value == null) return;
if (ipc.Score1.Value > ipc.Score2.Value)
CurrentMatch.Value.Team1Score.Value++;

View File

@ -1,14 +1,12 @@
// 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.Drawing;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online.API;
using osu.Game.Overlays;
@ -131,25 +129,5 @@ namespace osu.Game.Tournament.Screens.Setup
resolution.Value = $"{ScreenSpaceDrawQuad.Width:N0}x{ScreenSpaceDrawQuad.Height:N0}";
}
public class LabelledDropdown<T> : LabelledComponent<OsuDropdown<T>, T>
{
public LabelledDropdown()
: base(true)
{
}
public IEnumerable<T> Items
{
get => Component.Items;
set => Component.Items = value;
}
protected override OsuDropdown<T> CreateComponent() => new OsuDropdown<T>
{
RelativeSizeAxes = Axes.X,
Width = 0.5f,
};
}
}
}

View File

@ -32,7 +32,23 @@ namespace osu.Game.Beatmaps
/// <summary>
/// Returns a shallow-clone of this <see cref="BeatmapDifficulty"/>.
/// </summary>
public BeatmapDifficulty Clone() => (BeatmapDifficulty)MemberwiseClone();
public BeatmapDifficulty Clone()
{
var diff = new BeatmapDifficulty();
CopyTo(diff);
return diff;
}
public void CopyTo(BeatmapDifficulty difficulty)
{
difficulty.ApproachRate = ApproachRate;
difficulty.DrainRate = DrainRate;
difficulty.CircleSize = CircleSize;
difficulty.OverallDifficulty = OverallDifficulty;
difficulty.SliderMultiplier = SliderMultiplier;
difficulty.SliderTickRate = SliderTickRate;
}
/// <summary>
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.

View File

@ -259,10 +259,10 @@ namespace osu.Game.Beatmaps.Drawables
private readonly IBindable<StarDifficulty> starDifficulty = new Bindable<StarDifficulty>();
public bool SetContent(object content)
public void SetContent(object content)
{
if (!(content is DifficultyIconTooltipContent iconContent))
return false;
return;
difficultyName.Text = iconContent.Beatmap.Version;
@ -273,8 +273,6 @@ namespace osu.Game.Beatmaps.Drawables
starRating.Text = $"{difficulty.NewValue.Stars:0.##}";
difficultyFlow.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars);
}, true);
return true;
}
public void Move(Vector2 pos) => Position = pos;

View File

@ -4,12 +4,12 @@
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.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;

View File

@ -24,6 +24,12 @@ namespace osu.Game.Beatmaps.Formats
{
public const int LATEST_VERSION = 128;
/// <summary>
/// osu! is generally slower than taiko, so a factor is added to increase
/// speed. This must be used everywhere slider length or beat length is used.
/// </summary>
public const float LEGACY_TAIKO_VELOCITY_MULTIPLIER = 1.4f;
private readonly IBeatmap beatmap;
[CanBeNull]
@ -140,9 +146,9 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}"));
writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}"));
// Taiko adjusts the slider multiplier (see: TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER)
// Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER)
writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1
? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / 1.4f}")
? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}")
: FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}"));
writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}"));

View File

@ -31,12 +31,9 @@ namespace osu.Game.Graphics.Cursor
private readonly OsuSpriteText text;
private bool instantMovement = true;
public override bool SetContent(object content)
public override void SetContent(LocalisableString contentString)
{
if (!(content is LocalisableString contentString))
return false;
if (contentString == text.Text) return true;
if (contentString == text.Text) return;
text.Text = contentString;
@ -47,8 +44,6 @@ namespace osu.Game.Graphics.Cursor
}
else
AutoSizeDuration = 0;
return true;
}
public OsuTooltip()

View File

@ -12,7 +12,7 @@ using osuTK;
namespace osu.Game.Graphics
{
public class DateTooltip : VisibilityContainer, ITooltip
public class DateTooltip : VisibilityContainer, ITooltip<DateTimeOffset>
{
private readonly OsuSpriteText dateText, timeText;
private readonly Box background;
@ -63,14 +63,10 @@ namespace osu.Game.Graphics
protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
public bool SetContent(object content)
public void SetContent(DateTimeOffset date)
{
if (!(content is DateTimeOffset date))
return false;
dateText.Text = $"{date:d MMMM yyyy} ";
timeText.Text = $"{date:HH:mm:ss \"UTC\"z}";
return true;
}
public void Move(Vector2 pos) => Position = pos;

View File

@ -0,0 +1,31 @@
// 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.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class LabelledDropdown<TItem> : LabelledComponent<OsuDropdown<TItem>, TItem>
{
public LabelledDropdown()
: base(true)
{
}
public IEnumerable<TItem> Items
{
get => Component.Items;
set => Component.Items = value;
}
protected sealed override OsuDropdown<TItem> CreateComponent() => CreateDropdown().With(d =>
{
d.RelativeSizeAxes = Axes.X;
d.Width = 0.5f;
});
protected virtual OsuDropdown<TItem> CreateDropdown() => new OsuDropdown<TItem>();
}
}

View File

@ -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 System;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class LabelledEnumDropdown<TEnum> : LabelledDropdown<TEnum>
where TEnum : struct, Enum
{
protected override OsuDropdown<TEnum> CreateDropdown() => new OsuEnumDropdown<TEnum>();
}
}

View File

@ -13,7 +13,6 @@ using osu.Framework.Development;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
@ -342,11 +341,7 @@ namespace osu.Game
globalBindings = new GlobalActionContainer(this)
};
MenuCursorContainer.Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }
};
MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both };
base.Content.Add(CreateScalingContainer().WithChildren(mainContent));

View File

@ -4,6 +4,7 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;

View File

@ -6,12 +6,12 @@ using System.Linq;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;

View File

@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
@ -192,6 +193,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
if (showPerformancePoints)
{
Debug.Assert(score.PP != null);
content.Add(new OsuSpriteText
{
Text = score.PP.ToLocalisableString(@"N0"),

View File

@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0;
ppColumn.Text = value.PP.ToLocalisableString(@"N0");
ppColumn.Text = value.PP?.ToLocalisableString(@"N0");
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
modsColumn.Mods = value.Mods;

View File

@ -3,6 +3,7 @@
using System;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
@ -127,7 +128,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
public int? ScorePosition
{
set => rankText.Text = value == null ? (LocalisableString)"-" : value.ToLocalisableString(@"\##");
set => rankText.Text = value?.ToLocalisableString(@"\##") ?? (LocalisableString)"-";
}
/// <summary>

View File

@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;

View File

@ -102,8 +102,6 @@ namespace osu.Game.Overlays.Mods
incompatibleText.Colour = colours.BlueLight;
}
protected override Type TargetContentType => typeof(LocalPlayerModButton);
protected override void UpdateDisplay(Mod mod)
{
base.UpdateDisplay(mod);

View File

@ -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 System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -15,7 +14,7 @@ using osuTK;
namespace osu.Game.Overlays.Mods
{
public class ModButtonTooltip : VisibilityContainer, ITooltip
public class ModButtonTooltip : VisibilityContainer, ITooltip<ModButton>
{
private readonly OsuSpriteText descriptionText;
private readonly Box background;
@ -62,21 +61,15 @@ namespace osu.Game.Overlays.Mods
private Mod lastMod;
protected virtual Type TargetContentType => typeof(ModButton);
public virtual bool SetContent(object content)
public virtual void SetContent(ModButton button)
{
if (!(content is ModButton button) || content.GetType() != TargetContentType)
return false;
var mod = button.SelectedMod ?? button.Mods.First();
if (mod.Equals(lastMod)) return true;
if (mod.Equals(lastMod)) return;
lastMod = mod;
UpdateDisplay(mod);
return true;
}
protected virtual void UpdateDisplay(Mod mod)

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;

View File

@ -1,10 +1,10 @@
// 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.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Humanizer;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics;
@ -80,14 +81,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
{
}
public override bool SetContent(object content)
public override void SetContent(object content)
{
if (!(content is TooltipDisplayContent info))
return false;
return;
Counter.Text = info.Rank;
BottomText.Text = info.Time;
return true;
}
}

View File

@ -4,6 +4,7 @@
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;

View File

@ -4,6 +4,7 @@
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.Shapes;

View File

@ -8,7 +8,7 @@ using osu.Game.Graphics;
using osu.Framework.Bindables;
using osu.Game.Graphics.Sprites;
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Framework.Extensions.LocalisationExtensions;
namespace osu.Game.Overlays.Profile.Sections
{

View File

@ -9,6 +9,7 @@ using System.Linq;
using osu.Game.Graphics.Sprites;
using osu.Framework.Utils;
using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Graphics;
using osu.Framework.Graphics.Shapes;
using osuTK;

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Localisation;
using static osu.Game.Users.User;
@ -49,14 +50,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
this.tooltipCounterName = tooltipCounterName;
}
public override bool SetContent(object content)
public override void SetContent(object content)
{
if (!(content is TooltipDisplayContent info) || info.Name != tooltipCounterName)
return false;
return;
Counter.Text = info.Count;
BottomText.Text = info.Date;
return true;
}
}

View File

@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Localisation;

View File

@ -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 osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;

View File

@ -268,7 +268,7 @@ namespace osu.Game.Overlays.Profile
background.Colour = colours.Gray1;
}
public abstract bool SetContent(object content);
public abstract void SetContent(object content);
private bool instantMove = true;

View File

@ -16,6 +16,7 @@ using System.Collections.Generic;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Online.API.Requests;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;

View File

@ -9,8 +9,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Localisation;
namespace osu.Game.Overlays.Rankings.Tables
{

View File

@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{
new RowText { Text = item.PP.ToLocalisableString(@"N0"), }
new RowText { Text = item.PP?.ToLocalisableString(@"N0"), }
};
}
}

View File

@ -10,6 +10,7 @@ using osu.Framework.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;

View File

@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;

View File

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;

View File

@ -0,0 +1,104 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.UI
{
/// <summary>
/// A component which can trigger the most appropriate hit sound for a given point in time, based on the state of a <see cref="HitObjectContainer"/>
/// </summary>
public class GameplaySampleTriggerSource : CompositeDrawable
{
/// <summary>
/// The number of concurrent samples allowed to be played concurrently so that it feels better when spam-pressing a key.
/// </summary>
private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY;
private readonly HitObjectContainer hitObjectContainer;
private int nextHitSoundIndex;
private readonly Container<SkinnableSound> hitSounds;
public GameplaySampleTriggerSource(HitObjectContainer hitObjectContainer)
{
this.hitObjectContainer = hitObjectContainer;
InternalChild = hitSounds = new Container<SkinnableSound>
{
Name = "concurrent sample pool",
ChildrenEnumerable = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new PausableSkinnableSound())
};
}
private HitObjectLifetimeEntry fallbackObject;
/// <summary>
/// Play the most appropriate hit sound for the current point in time.
/// </summary>
public virtual void Play()
{
var nextObject = GetMostValidObject();
if (nextObject == null)
return;
var samples = nextObject.Samples
.Select(s => nextObject.SampleControlPoint.ApplyTo(s))
.Cast<ISampleInfo>()
.ToArray();
PlaySamples(samples);
}
protected void PlaySamples(ISampleInfo[] samples)
{
var hitSound = getNextSample();
hitSound.Samples = samples;
hitSound.Play();
}
protected HitObject GetMostValidObject()
{
// The most optimal lookup case we have is when an object is alive. There are usually very few alive objects so there's no drawbacks in attempting this lookup each time.
var hitObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.Result?.HasResult != true)?.HitObject;
// In the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play.
if (hitObject == null)
{
// This lookup can be skipped if the last entry is still valid (in the future and not yet hit).
if (fallbackObject == null || fallbackObject.Result?.HasResult == true)
{
// We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty).
// If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager.
fallbackObject = hitObjectContainer.Entries
.Where(e => e.Result?.HasResult != true)
.OrderBy(e => e.HitObject.StartTime)
.FirstOrDefault();
// In the case there are no unjudged objects, the last hit object should be used instead.
fallbackObject ??= hitObjectContainer.Entries.LastOrDefault();
}
hitObject = fallbackObject?.HitObject;
}
return hitObject;
}
private SkinnableSound getNextSample()
{
SkinnableSound hitSound = hitSounds[nextHitSoundIndex];
// round robin over available samples to allow for concurrent playback.
nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds;
return hitSound;
}
}
}

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Overlays;
namespace osu.Game.Screens.Edit
{
@ -17,9 +16,6 @@ namespace osu.Game.Screens.Edit
[Resolved]
private OsuColour colours { get; set; }
[Cached]
protected readonly OverlayColourProvider ColourProvider;
private Container roundedContent;
protected override Container<Drawable> Content => roundedContent;
@ -27,7 +23,6 @@ namespace osu.Game.Screens.Edit
public EditorRoundedScreen(EditorScreenMode mode)
: base(mode)
{
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
}
[BackgroundDependencyLoader]

View File

@ -4,6 +4,8 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Game.Overlays;
namespace osu.Game.Screens.Edit
{
@ -15,6 +17,9 @@ namespace osu.Game.Screens.Edit
[Resolved]
protected EditorBeatmap EditorBeatmap { get; private set; }
[Cached]
protected readonly OverlayColourProvider ColourProvider;
protected override Container<Drawable> Content => content;
private readonly Container content;
@ -28,7 +33,9 @@ namespace osu.Game.Screens.Edit
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both };
}
protected override void PopIn()

View File

@ -11,6 +11,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Logging;
@ -72,6 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private readonly Bindable<FilterCriteria> filter = new Bindable<FilterCriteria>(new FilterCriteria());
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
private readonly IBindable<bool> isIdle = new BindableBool();
private PopoverContainer popoverContainer;
private LoadingLayer loadingLayer;
private RoomsContainer roomsContainer;
private SearchTextBox searchTextBox;
@ -90,7 +92,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
InternalChildren = new Drawable[]
{
ListingPollingComponent = CreatePollingComponent().With(c => c.Filter.BindTarget = filter),
new Container
popoverContainer = new PopoverContainer
{
Name = @"Rooms area",
RelativeSizeAxes = Axes.Both,
@ -285,7 +287,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
searchTextBox.HoldFocus = false;
// ensure any password prompt is dismissed.
this.HidePopover();
popoverContainer.HidePopover();
}
public void Join(Room room, string password) => Schedule(() =>

View File

@ -61,7 +61,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
playerClocks.Add(clock);
}
public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock);
public void RemovePlayerClock(ISpectatorPlayerClock clock)
{
playerClocks.Remove(clock);
clock.Stop();
}
protected override void Update()
{

View File

@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Screens.Play;
@ -32,6 +33,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// </summary>
public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true);
[Resolved]
private OsuColour colours { get; set; }
[Resolved]
private SpectatorClient spectatorClient { get; set; }
@ -215,6 +219,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
protected override void EndGameplay(int userId)
{
RemoveUser(userId);
var instance = instances.Single(i => i.UserId == userId);
instance.FadeColour(colours.Gray4, 400, Easing.OutQuint);
syncManager.RemovePlayerClock(instance.GameplayClock);
leaderboard.RemoveClock(userId);
}

View File

@ -4,6 +4,7 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;

View File

@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play
Beatmap.Value = gameplayState.Beatmap;
Ruleset.Value = gameplayState.Ruleset.RulesetInfo;
this.Push(new SpectatorPlayerLoader(gameplayState.Score));
this.Push(new SpectatorPlayerLoader(gameplayState.Score, () => new SoloSpectatorPlayer(gameplayState.Score)));
}
}

View File

@ -0,0 +1,52 @@
// 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.Screens;
using osu.Game.Online.Spectator;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play
{
public class SoloSpectatorPlayer : SpectatorPlayer
{
private readonly Score score;
public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null)
: base(score, configuration)
{
this.score = score;
}
[BackgroundDependencyLoader]
private void load()
{
SpectatorClient.OnUserBeganPlaying += userBeganPlaying;
}
public override bool OnExiting(IScreen next)
{
SpectatorClient.OnUserBeganPlaying -= userBeganPlaying;
return base.OnExiting(next);
}
private void userBeganPlaying(int userId, SpectatorState state)
{
if (userId != score.ScoreInfo.UserID) return;
Schedule(() =>
{
if (this.IsCurrentScreen()) this.Exit();
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (SpectatorClient != null)
SpectatorClient.OnUserBeganPlaying -= userBeganPlaying;
}
}
}

View File

@ -14,16 +14,16 @@ using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Play
{
public class SpectatorPlayer : Player
public abstract class SpectatorPlayer : Player
{
[Resolved]
private SpectatorClient spectatorClient { get; set; }
protected SpectatorClient SpectatorClient { get; private set; }
private readonly Score score;
protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap
public SpectatorPlayer(Score score, PlayerConfiguration configuration = null)
protected SpectatorPlayer(Score score, PlayerConfiguration configuration = null)
: base(configuration)
{
this.score = score;
@ -32,8 +32,6 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader]
private void load()
{
spectatorClient.OnUserBeganPlaying += userBeganPlaying;
AddInternal(new OsuSpriteText
{
Text = $"Watching {score.ScoreInfo.User.Username} playing live!",
@ -50,7 +48,7 @@ namespace osu.Game.Screens.Play
// Start gameplay along with the very first arrival frame (the latest one).
score.Replay.Frames.Clear();
spectatorClient.OnNewFrames += userSentFrames;
SpectatorClient.OnNewFrames += userSentFrames;
}
private void userSentFrames(int userId, FrameDataBundle bundle)
@ -93,31 +91,17 @@ namespace osu.Game.Screens.Play
public override bool OnExiting(IScreen next)
{
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
spectatorClient.OnNewFrames -= userSentFrames;
SpectatorClient.OnNewFrames -= userSentFrames;
return base.OnExiting(next);
}
private void userBeganPlaying(int userId, SpectatorState state)
{
if (userId != score.ScoreInfo.UserID) return;
Schedule(() =>
{
if (this.IsCurrentScreen()) this.Exit();
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (spectatorClient != null)
{
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
spectatorClient.OnNewFrames -= userSentFrames;
}
if (SpectatorClient != null)
SpectatorClient.OnNewFrames -= userSentFrames;
}
}
}

View File

@ -11,12 +11,7 @@ namespace osu.Game.Screens.Play
{
public readonly ScoreInfo Score;
public SpectatorPlayerLoader(Score score)
: this(score, () => new SpectatorPlayer(score))
{
}
public SpectatorPlayerLoader(Score score, Func<Player> createPlayer)
public SpectatorPlayerLoader(Score score, Func<SpectatorPlayer> createPlayer)
: base(createPlayer)
{
if (score.Replay == null)

View File

@ -8,8 +8,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using System.Linq;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Beatmaps;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Screens.Select.Details

View File

@ -3,8 +3,8 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Screens.Select
@ -64,8 +64,7 @@ namespace osu.Game.Screens.Select
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
case "status":
return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value,
(string s, out BeatmapSetOnlineStatus val) => Enum.TryParse(value, true, out val));
return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, tryParseEnum);
case "creator":
return TryUpdateCriteriaText(ref criteria.Creator, op, value);
@ -120,6 +119,14 @@ namespace osu.Game.Screens.Select
private static bool tryParseInt(string value, out int result) =>
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
private static bool tryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct
{
if (Enum.TryParse(value, true, out result)) return true;
value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture));
return Enum.TryParse(value, true, out result);
}
/// <summary>
/// Attempts to parse a keyword filter with the specified <paramref name="op"/> and textual <paramref name="value"/>.
/// If the value indicates a valid textual filter, the function returns <c>true</c> and the resulting data is stored into

View File

@ -3,7 +3,6 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing.Input;
using osu.Game.Graphics.Cursor;
@ -35,11 +34,7 @@ namespace osu.Game.Tests.Visual
{
MenuCursorContainer cursorContainer;
CompositeDrawable mainContent = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both, }
};
CompositeDrawable mainContent = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both };
cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor)
{

View File

@ -3,6 +3,7 @@
using System;
using Humanizer;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Localisation;
namespace osu.Game.Utils

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.828.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.830.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" />
<PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpCompress" Version="0.28.3" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.828.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.830.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.828.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.830.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />