mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 12:42:54 +08:00
Merge remote-tracking branch 'OliBomby/fix-segment-ends' into fix-slider-reversing
This commit is contained in:
commit
47d787b359
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.801.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.815.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -75,7 +75,7 @@ namespace osu.Desktop.LegacyIpc
|
|||||||
case LegacyIpcDifficultyCalculationRequest req:
|
case LegacyIpcDifficultyCalculationRequest req:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile);
|
WorkingBeatmap beatmap = new FlatWorkingBeatmap(req.BeatmapFile);
|
||||||
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
|
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
|
||||||
Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
|
Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = !tournamentClient }))
|
||||||
{
|
{
|
||||||
if (!host.IsPrimaryInstance)
|
if (!host.IsPrimaryInstance)
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,6 @@ using NUnit.Framework;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
@ -24,21 +23,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
Assert.False(testBeatmap.HitObjects.OfType<HoldNote>().Any());
|
Assert.False(testBeatmap.HitObjects.OfType<HoldNote>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestCorrectNoteValues()
|
|
||||||
{
|
|
||||||
var testBeatmap = createRawBeatmap();
|
|
||||||
var noteValues = new List<double>(testBeatmap.HitObjects.OfType<HoldNote>().Count());
|
|
||||||
|
|
||||||
foreach (HoldNote h in testBeatmap.HitObjects.OfType<HoldNote>())
|
|
||||||
{
|
|
||||||
noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap));
|
|
||||||
}
|
|
||||||
|
|
||||||
noteValues.Sort();
|
|
||||||
Assert.AreEqual(noteValues, new List<double> { 0.125, 0.250, 0.500, 1.000, 2.000 });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCorrectObjectCount()
|
public void TestCorrectObjectCount()
|
||||||
{
|
{
|
||||||
@ -47,25 +31,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
var rawBeatmap = createRawBeatmap();
|
var rawBeatmap = createRawBeatmap();
|
||||||
var testBeatmap = createModdedBeatmap();
|
var testBeatmap = createModdedBeatmap();
|
||||||
|
|
||||||
// Calculate expected number of objects
|
// Both notes and hold notes account for at least one object
|
||||||
int expectedObjectCount = 0;
|
int expectedObjectCount = rawBeatmap.HitObjects.Count;
|
||||||
|
|
||||||
foreach (ManiaHitObject h in rawBeatmap.HitObjects)
|
|
||||||
{
|
|
||||||
// Both notes and hold notes account for at least one object
|
|
||||||
expectedObjectCount++;
|
|
||||||
|
|
||||||
if (h.GetType() == typeof(HoldNote))
|
|
||||||
{
|
|
||||||
double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap);
|
|
||||||
|
|
||||||
if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD)
|
|
||||||
{
|
|
||||||
// Should generate an end note if it's longer than the minimum note value
|
|
||||||
expectedObjectCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount);
|
Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount);
|
||||||
}
|
}
|
||||||
|
@ -117,18 +117,16 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
private void createBarLine(bool major)
|
private void createBarLine(bool major)
|
||||||
{
|
{
|
||||||
foreach (var stage in stages)
|
var obj = new BarLine
|
||||||
{
|
{
|
||||||
var obj = new BarLine
|
StartTime = Time.Current + 2000,
|
||||||
{
|
Major = major,
|
||||||
StartTime = Time.Current + 2000,
|
};
|
||||||
Major = major,
|
|
||||||
};
|
|
||||||
|
|
||||||
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
foreach (var stage in stages)
|
||||||
stage.Add(obj);
|
stage.Add(obj);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
{
|
{
|
||||||
private const double individual_decay_base = 0.125;
|
private const double individual_decay_base = 0.125;
|
||||||
private const double overall_decay_base = 0.30;
|
private const double overall_decay_base = 0.30;
|
||||||
private const double release_threshold = 24;
|
private const double release_threshold = 30;
|
||||||
|
|
||||||
protected override double SkillMultiplier => 1;
|
protected override double SkillMultiplier => 1;
|
||||||
protected override double StrainDecayBase => 1;
|
protected override double StrainDecayBase => 1;
|
||||||
@ -50,10 +50,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
for (int i = 0; i < endTimes.Length; ++i)
|
for (int i = 0; i < endTimes.Length; ++i)
|
||||||
{
|
{
|
||||||
// The current note is overlapped if a previous note or end is overlapping the current note body
|
// The current note is overlapped if a previous note or end is overlapping the current note body
|
||||||
isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1);
|
isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) &&
|
||||||
|
Precision.DefinitelyBigger(endTime, endTimes[i], 1) &&
|
||||||
|
Precision.DefinitelyBigger(startTime, startTimes[i], 1);
|
||||||
|
|
||||||
// We give a slight bonus to everything if something is held meanwhile
|
// We give a slight bonus to everything if something is held meanwhile
|
||||||
if (Precision.DefinitelyBigger(endTimes[i], endTime, 1))
|
if (Precision.DefinitelyBigger(endTimes[i], endTime, 1) &&
|
||||||
|
Precision.DefinitelyBigger(startTime, startTimes[i], 1))
|
||||||
holdFactor = 1.25;
|
holdFactor = 1.25;
|
||||||
|
|
||||||
closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i]));
|
closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i]));
|
||||||
@ -70,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
// 0.0 +--------+-+---------------> Release Difference / ms
|
// 0.0 +--------+-+---------------> Release Difference / ms
|
||||||
// release_threshold
|
// release_threshold
|
||||||
if (isOverlapping)
|
if (isOverlapping)
|
||||||
holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime)));
|
holdAddition = 1 / (1 + Math.Exp(0.27 * (release_threshold - closestEndTime)));
|
||||||
|
|
||||||
// Decay and increase individualStrains in own column
|
// Decay and increase individualStrains in own column
|
||||||
individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base);
|
individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base);
|
||||||
|
@ -33,5 +33,6 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
HitExplosion,
|
HitExplosion,
|
||||||
StageBackground,
|
StageBackground,
|
||||||
StageForeground,
|
StageForeground,
|
||||||
|
BarLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
|
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
|
||||||
|
|
||||||
public const double END_NOTE_ALLOW_THRESHOLD = 0.5;
|
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||||
@ -46,28 +44,9 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
StartTime = h.StartTime,
|
StartTime = h.StartTime,
|
||||||
Samples = h.GetNodeSamples(0)
|
Samples = h.GetNodeSamples(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Don't add an end note if the duration is shorter than the threshold
|
|
||||||
double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc.
|
|
||||||
|
|
||||||
if (noteValue >= END_NOTE_ALLOW_THRESHOLD)
|
|
||||||
{
|
|
||||||
newObjects.Add(new Note
|
|
||||||
{
|
|
||||||
Column = h.Column,
|
|
||||||
StartTime = h.EndTime,
|
|
||||||
Samples = h.GetNodeSamples((h.NodeSamples?.Count - 1) ?? 1)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType<Note>().Concat(newObjects).OrderBy(h => h.StartTime).ToList();
|
maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType<Note>().Concat(newObjects).OrderBy(h => h.StartTime).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap)
|
|
||||||
{
|
|
||||||
double beatLength = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BeatLength;
|
|
||||||
return holdNote.Duration / beatLength;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
@ -8,7 +9,15 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
{
|
{
|
||||||
public class BarLine : ManiaHitObject, IBarLine
|
public class BarLine : ManiaHitObject, IBarLine
|
||||||
{
|
{
|
||||||
public bool Major { get; set; }
|
private HitObjectProperty<bool> major;
|
||||||
|
|
||||||
|
public Bindable<bool> MajorBindable => major.Bindable;
|
||||||
|
|
||||||
|
public bool Major
|
||||||
|
{
|
||||||
|
get => major.Value;
|
||||||
|
set => major.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||||
using osuTK;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -13,45 +15,41 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class DrawableBarLine : DrawableManiaHitObject<BarLine>
|
public partial class DrawableBarLine : DrawableManiaHitObject<BarLine>
|
||||||
{
|
{
|
||||||
|
public readonly Bindable<bool> Major = new Bindable<bool>();
|
||||||
|
|
||||||
|
public DrawableBarLine()
|
||||||
|
: this(null!)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableBarLine(BarLine barLine)
|
public DrawableBarLine(BarLine barLine)
|
||||||
: base(barLine)
|
: base(barLine)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = barLine.Major ? 1.7f : 1.2f;
|
}
|
||||||
|
|
||||||
AddInternal(new Box
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.BarLine), _ => new DefaultBarLine())
|
||||||
{
|
{
|
||||||
Name = "Bar line",
|
Anchor = Anchor.Centre,
|
||||||
Anchor = Anchor.BottomCentre,
|
Origin = Anchor.Centre,
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = barLine.Major ? 0.5f : 0.2f
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (barLine.Major)
|
Major.BindValueChanged(major => Height = major.NewValue ? 1.7f : 1.2f, true);
|
||||||
{
|
}
|
||||||
Vector2 size = new Vector2(22, 6);
|
|
||||||
const float line_offset = 4;
|
|
||||||
|
|
||||||
AddInternal(new Circle
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
Name = "Left line",
|
base.OnApply();
|
||||||
Anchor = Anchor.CentreLeft,
|
Major.BindTo(HitObject.MajorBindable);
|
||||||
Origin = Anchor.CentreRight,
|
}
|
||||||
|
|
||||||
Size = size,
|
protected override void OnFree()
|
||||||
X = -line_offset,
|
{
|
||||||
});
|
base.OnFree();
|
||||||
|
Major.UnbindFrom(HitObject.MajorBindable);
|
||||||
AddInternal(new Circle
|
|
||||||
{
|
|
||||||
Name = "Right line",
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Size = size,
|
|
||||||
X = line_offset,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
|
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
|
||||||
|
72
osu.Game.Rulesets.Mania/Skinning/Default/DefaultBarLine.cs
Normal file
72
osu.Game.Rulesets.Mania/Skinning/Default/DefaultBarLine.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Default
|
||||||
|
{
|
||||||
|
public partial class DefaultBarLine : CompositeDrawable
|
||||||
|
{
|
||||||
|
private Bindable<bool> major = null!;
|
||||||
|
|
||||||
|
private Drawable mainLine = null!;
|
||||||
|
private Drawable leftAnchor = null!;
|
||||||
|
private Drawable rightAnchor = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
AddInternal(mainLine = new Box
|
||||||
|
{
|
||||||
|
Name = "Bar line",
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vector2 size = new Vector2(22, 6);
|
||||||
|
const float line_offset = 4;
|
||||||
|
|
||||||
|
AddInternal(leftAnchor = new Circle
|
||||||
|
{
|
||||||
|
Name = "Left anchor",
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Size = size,
|
||||||
|
X = -line_offset,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddInternal(rightAnchor = new Circle
|
||||||
|
{
|
||||||
|
Name = "Right anchor",
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Size = size,
|
||||||
|
X = line_offset,
|
||||||
|
});
|
||||||
|
|
||||||
|
major = ((DrawableBarLine)drawableHitObject).Major.GetBoundCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
major.BindValueChanged(updateMajor, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMajor(ValueChangedEvent<bool> major)
|
||||||
|
{
|
||||||
|
mainLine.Alpha = major.NewValue ? 0.5f : 0.2f;
|
||||||
|
leftAnchor.Alpha = rightAnchor.Alpha = major.NewValue ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -119,6 +119,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
case ManiaSkinComponents.StageForeground:
|
case ManiaSkinComponents.StageForeground:
|
||||||
return new LegacyStageForeground();
|
return new LegacyStageForeground();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.BarLine:
|
||||||
|
return null; // Not yet implemented.
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedSkinComponentException(lookup);
|
throw new UnsupportedSkinComponentException(lookup);
|
||||||
}
|
}
|
||||||
|
@ -30,15 +30,15 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Stages.Count == 1)
|
RectangleF totalArea = RectangleF.Empty;
|
||||||
return Stages.First().ScreenSpaceDrawQuad;
|
|
||||||
|
|
||||||
RectangleF area = RectangleF.Empty;
|
for (int i = 0; i < Stages.Count; ++i)
|
||||||
|
{
|
||||||
|
var stageArea = Stages[i].ScreenSpaceDrawQuad.AABBFloat;
|
||||||
|
totalArea = i == 0 ? stageArea : RectangleF.Union(totalArea, stageArea);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var stage in Stages)
|
return totalArea;
|
||||||
area = RectangleF.Union(area, stage.ScreenSpaceDrawQuad.AABBFloat);
|
|
||||||
|
|
||||||
return area;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +136,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
columnFlow.SetContentForColumn(i, column);
|
columnFlow.SetContentForColumn(i, column);
|
||||||
AddNested(column);
|
AddNested(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RegisterPool<BarLine, DrawableBarLine>(50, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ISkinSource currentSkin;
|
private ISkinSource currentSkin;
|
||||||
@ -186,7 +188,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h);
|
public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h);
|
||||||
|
|
||||||
public void Add(BarLine barLine) => base.Add(new DrawableBarLine(barLine));
|
public void Add(BarLine barLine) => base.Add(barLine);
|
||||||
|
|
||||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
|
@ -139,7 +139,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
Duration = taikoDuration,
|
Duration = taikoDuration,
|
||||||
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
@ -14,7 +13,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
{
|
{
|
||||||
public class DrumRoll : TaikoStrongableHitObject, IHasPath, IHasSliderVelocity
|
public class DrumRoll : TaikoStrongableHitObject, IHasPath
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
|
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
|
||||||
@ -34,19 +33,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double Velocity { get; private set; }
|
public double Velocity { get; private set; }
|
||||||
|
|
||||||
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
|
|
||||||
{
|
|
||||||
Precision = 0.01,
|
|
||||||
MinValue = 0.1,
|
|
||||||
MaxValue = 10
|
|
||||||
};
|
|
||||||
|
|
||||||
public double SliderVelocity
|
|
||||||
{
|
|
||||||
get => SliderVelocityBindable.Value;
|
|
||||||
set => SliderVelocityBindable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Numer of ticks per beat length.
|
/// Numer of ticks per beat length.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -63,8 +49,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
|
EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime);
|
||||||
|
|
||||||
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
|
double scoringDistance = base_distance * difficulty.SliderMultiplier * effectPoint.ScrollSpeed;
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
|
|
||||||
TickRate = difficulty.SliderTickRate == 3 ? 3 : 4;
|
TickRate = difficulty.SliderTickRate == 3 ? 3 : 4;
|
||||||
|
@ -92,25 +92,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[FlakyTest]
|
|
||||||
/*
|
|
||||||
* Fail rate around 1.2%.
|
|
||||||
*
|
|
||||||
* Failing with realm refetch occasionally being null.
|
|
||||||
* My only guess is that the WorkingBeatmap at SetupScreen is dummy instead of the true one.
|
|
||||||
* If it's something else, we have larger issues with realm, but I don't think that's the case.
|
|
||||||
*
|
|
||||||
* at osu.Framework.Logging.ThrowingTraceListener.Fail(String message1, String message2)
|
|
||||||
* at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage)
|
|
||||||
* at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage)
|
|
||||||
* at System.Diagnostics.Debug.Fail(String message, String detailMessage)
|
|
||||||
* at osu.Game.Database.ModelManager`1.<>c__DisplayClass8_0.<performFileOperation>b__0(Realm realm) ModelManager.cs:line 50
|
|
||||||
* at osu.Game.Database.RealmExtensions.Write(Realm realm, Action`1 function) RealmExtensions.cs:line 14
|
|
||||||
* at osu.Game.Database.ModelManager`1.performFileOperation(TModel item, Action`1 operation) ModelManager.cs:line 47
|
|
||||||
* at osu.Game.Database.ModelManager`1.AddFile(TModel item, Stream contents, String filename) ModelManager.cs:line 37
|
|
||||||
* at osu.Game.Screens.Edit.Setup.ResourcesSection.ChangeAudioTrack(FileInfo source) ResourcesSection.cs:line 115
|
|
||||||
* at osu.Game.Tests.Visual.Editing.TestSceneEditorBeatmapCreation.<TestAddAudioTrack>b__11_0() TestSceneEditorBeatmapCreation.cs:line 101
|
|
||||||
*/
|
|
||||||
public void TestAddAudioTrack()
|
public void TestAddAudioTrack()
|
||||||
{
|
{
|
||||||
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
|
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
|
||||||
|
@ -185,6 +185,34 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10);
|
AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGetSegmentEnds()
|
||||||
|
{
|
||||||
|
var positions = new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(100, 0),
|
||||||
|
new Vector2(100),
|
||||||
|
new Vector2(200, 100),
|
||||||
|
};
|
||||||
|
double[] distances = { 100d, 200d, 300d };
|
||||||
|
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(positions.Select(p => new PathControlPoint(p, PathType.Linear))));
|
||||||
|
AddAssert("segment ends are correct", () => path.GetSegmentEnds().SequenceEqual(distances.Select(d => d / 300)));
|
||||||
|
AddAssert("segment end positions recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)).SequenceEqual(positions.Skip(1)));
|
||||||
|
AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 400);
|
||||||
|
AddAssert("segment ends are correct", () => path.GetSegmentEnds().SequenceEqual(distances.Select(d => d / 400)));
|
||||||
|
AddAssert("segment end positions recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)).SequenceEqual(positions.Skip(1)));
|
||||||
|
AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150);
|
||||||
|
AddAssert("segment ends are correct", () => path.GetSegmentEnds().SequenceEqual(distances.Select(d => d / 150)));
|
||||||
|
AddAssert("segment end positions not recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)).SequenceEqual(new[]
|
||||||
|
{
|
||||||
|
positions[1],
|
||||||
|
new Vector2(100, 50),
|
||||||
|
new Vector2(100, 50),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
private List<PathControlPoint> createSegment(PathType type, params Vector2[] controlPoints)
|
private List<PathControlPoint> createSegment(PathType type, params Vector2[] controlPoints)
|
||||||
{
|
{
|
||||||
var points = controlPoints.Select(p => new PathControlPoint { Position = p }).ToList();
|
var points = controlPoints.Select(p => new PathControlPoint { Position = p }).ToList();
|
||||||
|
@ -84,12 +84,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFocusOnTabKeyWhenExpanded()
|
public void TestFocusOnEnterKeyWhenExpanded()
|
||||||
{
|
{
|
||||||
setLocalUserPlaying(true);
|
setLocalUserPlaying(true);
|
||||||
|
|
||||||
assertChatFocused(false);
|
assertChatFocused(false);
|
||||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||||
assertChatFocused(true);
|
assertChatFocused(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,19 +99,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
setLocalUserPlaying(true);
|
setLocalUserPlaying(true);
|
||||||
|
|
||||||
assertChatFocused(false);
|
assertChatFocused(false);
|
||||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||||
assertChatFocused(true);
|
assertChatFocused(true);
|
||||||
AddStep("press escape", () => InputManager.Key(Key.Escape));
|
AddStep("press escape", () => InputManager.Key(Key.Escape));
|
||||||
assertChatFocused(false);
|
assertChatFocused(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFocusOnTabKeyWhenNotExpanded()
|
public void TestFocusOnEnterKeyWhenNotExpanded()
|
||||||
{
|
{
|
||||||
AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
|
AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
|
||||||
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
|
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
|
||||||
|
|
||||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||||
assertChatFocused(true);
|
assertChatFocused(true);
|
||||||
AddUntilStep("is visible", () => chatDisplay.IsPresent);
|
AddUntilStep("is visible", () => chatDisplay.IsPresent);
|
||||||
|
|
||||||
@ -120,21 +120,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
|
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestFocusToggleViaAction()
|
|
||||||
{
|
|
||||||
AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
|
|
||||||
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
|
|
||||||
|
|
||||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
|
||||||
assertChatFocused(true);
|
|
||||||
AddUntilStep("is visible", () => chatDisplay.IsPresent);
|
|
||||||
|
|
||||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
|
||||||
assertChatFocused(false);
|
|
||||||
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertChatFocused(bool isFocused) =>
|
private void assertChatFocused(bool isFocused) =>
|
||||||
AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused);
|
AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused);
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray())
|
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray())
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
Expanded = { Value = true }
|
Expanded = { Value = true }
|
||||||
}, Add);
|
}, Add);
|
||||||
});
|
});
|
||||||
|
@ -79,6 +79,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddWaitStep("wait a bit", 20);
|
AddWaitStep("wait a bit", 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(2)]
|
||||||
|
[TestCase(16)]
|
||||||
|
public void TestTeams(int count)
|
||||||
|
{
|
||||||
|
int[] userIds = getPlayerIds(count);
|
||||||
|
|
||||||
|
start(userIds, teams: true);
|
||||||
|
loadSpectateScreen();
|
||||||
|
|
||||||
|
sendFrames(userIds, 1000);
|
||||||
|
AddWaitStep("wait a bit", 20);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMultipleStartRequests()
|
public void TestMultipleStartRequests()
|
||||||
{
|
{
|
||||||
@ -450,16 +463,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
|
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
|
||||||
|
|
||||||
private void start(int[] userIds, int? beatmapId = null, APIMod[]? mods = null)
|
private void start(int[] userIds, int? beatmapId = null, APIMod[]? mods = null, bool teams = false)
|
||||||
{
|
{
|
||||||
AddStep("start play", () =>
|
AddStep("start play", () =>
|
||||||
{
|
{
|
||||||
foreach (int id in userIds)
|
for (int i = 0; i < userIds.Length; i++)
|
||||||
{
|
{
|
||||||
|
int id = userIds[i];
|
||||||
var user = new MultiplayerRoomUser(id)
|
var user = new MultiplayerRoomUser(id)
|
||||||
{
|
{
|
||||||
User = new APIUser { Id = id },
|
User = new APIUser { Id = id },
|
||||||
Mods = mods ?? Array.Empty<APIMod>(),
|
Mods = mods ?? Array.Empty<APIMod>(),
|
||||||
|
MatchState = teams ? new TeamVersusUserState { TeamID = i % 2 } : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
OnlinePlayDependencies.MultiplayerClient.AddUser(user, true);
|
OnlinePlayDependencies.MultiplayerClient.AddUser(user, true);
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public partial class TestSceneSliderWithTextBoxInput : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private SliderWithTextBoxInput<float> sliderWithTextBoxInput = null!;
|
||||||
|
|
||||||
|
private OsuSliderBar<float> slider => sliderWithTextBoxInput.ChildrenOfType<OsuSliderBar<float>>().Single();
|
||||||
|
private Nub nub => sliderWithTextBoxInput.ChildrenOfType<Nub>().Single();
|
||||||
|
private OsuTextBox textBox => sliderWithTextBoxInput.ChildrenOfType<OsuTextBox>().Single();
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("create slider", () => Child = sliderWithTextBoxInput = new SliderWithTextBoxInput<float>("Test Slider")
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 0.5f,
|
||||||
|
Current = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = -5,
|
||||||
|
MaxValue = 5,
|
||||||
|
Precision = 0.2f
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNonInstantaneousMode()
|
||||||
|
{
|
||||||
|
AddStep("set instantaneous to false", () => sliderWithTextBoxInput.Instantaneous = false);
|
||||||
|
|
||||||
|
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
||||||
|
AddStep("change text", () => textBox.Text = "3");
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero);
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero);
|
||||||
|
|
||||||
|
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||||
|
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||||
|
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub));
|
||||||
|
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("3"));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||||
|
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
||||||
|
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||||
|
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
||||||
|
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("lose focus", () => InputManager.ChangeFocus(null));
|
||||||
|
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInstantaneousMode()
|
||||||
|
{
|
||||||
|
AddStep("set instantaneous to true", () => sliderWithTextBoxInput.Instantaneous = true);
|
||||||
|
|
||||||
|
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
||||||
|
AddStep("change text", () => textBox.Text = "3");
|
||||||
|
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||||
|
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub));
|
||||||
|
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||||
|
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
||||||
|
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||||
|
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
||||||
|
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
|
AddStep("lose focus", () => InputManager.ChangeFocus(null));
|
||||||
|
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||||
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osu.Game.Tournament.Components;
|
using osu.Game.Tournament.Components;
|
||||||
@ -17,11 +18,11 @@ namespace osu.Game.Tournament.Tests.Components
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly LadderInfo ladder = new LadderInfo();
|
private readonly LadderInfo ladder = new LadderInfo();
|
||||||
|
|
||||||
[Test]
|
private SongBar songBar = null!;
|
||||||
public void TestSongBar()
|
|
||||||
{
|
|
||||||
SongBar songBar = null!;
|
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
AddStep("create bar", () => Child = songBar = new SongBar
|
AddStep("create bar", () => Child = songBar = new SongBar
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -29,7 +30,11 @@ namespace osu.Game.Tournament.Tests.Components
|
|||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre
|
||||||
});
|
});
|
||||||
AddUntilStep("wait for loaded", () => songBar.IsLoaded);
|
AddUntilStep("wait for loaded", () => songBar.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSongBar()
|
||||||
|
{
|
||||||
AddStep("set beatmap", () =>
|
AddStep("set beatmap", () =>
|
||||||
{
|
{
|
||||||
var beatmap = CreateAPIBeatmap(Ruleset.Value);
|
var beatmap = CreateAPIBeatmap(Ruleset.Value);
|
||||||
|
@ -14,7 +14,6 @@ using osu.Game.Extensions;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Tournament.Models;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -22,14 +21,14 @@ namespace osu.Game.Tournament.Components
|
|||||||
{
|
{
|
||||||
public partial class SongBar : CompositeDrawable
|
public partial class SongBar : CompositeDrawable
|
||||||
{
|
{
|
||||||
private TournamentBeatmap? beatmap;
|
private IBeatmapInfo? beatmap;
|
||||||
|
|
||||||
public const float HEIGHT = 145 / 2f;
|
public const float HEIGHT = 145 / 2f;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||||
|
|
||||||
public TournamentBeatmap? Beatmap
|
public IBeatmapInfo? Beatmap
|
||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@ -37,7 +36,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
beatmap = value;
|
beatmap = value;
|
||||||
update();
|
refreshContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
mods = value;
|
mods = value;
|
||||||
update();
|
refreshContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,19 +70,25 @@ namespace osu.Game.Tournament.Components
|
|||||||
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Gray3,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
flow = new FillFlowContainer
|
flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
LayoutDuration = 500,
|
|
||||||
LayoutEasing = Easing.OutQuint,
|
|
||||||
Direction = FillDirection.Full,
|
Direction = FillDirection.Full,
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
@ -93,7 +98,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
Expanded = true;
|
Expanded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update()
|
private void refreshContent()
|
||||||
{
|
{
|
||||||
if (beatmap == null)
|
if (beatmap == null)
|
||||||
{
|
{
|
||||||
@ -229,7 +234,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new TournamentBeatmapPanel(beatmap)
|
new UnmaskedTournamentBeatmapPanel(beatmap)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
@ -272,4 +277,18 @@ namespace osu.Game.Tournament.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class UnmaskedTournamentBeatmapPanel : TournamentBeatmapPanel
|
||||||
|
{
|
||||||
|
public UnmaskedTournamentBeatmapPanel(IBeatmapInfo? beatmap, string mod = "")
|
||||||
|
: base(beatmap, mod)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Masking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
{
|
{
|
||||||
public partial class TournamentBeatmapPanel : CompositeDrawable
|
public partial class TournamentBeatmapPanel : CompositeDrawable
|
||||||
{
|
{
|
||||||
public readonly TournamentBeatmap? Beatmap;
|
public readonly IBeatmapInfo? Beatmap;
|
||||||
|
|
||||||
private readonly string mod;
|
private readonly string mod;
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
|
|
||||||
private Box flash = null!;
|
private Box flash = null!;
|
||||||
|
|
||||||
public TournamentBeatmapPanel(TournamentBeatmap? beatmap, string mod = "")
|
public TournamentBeatmapPanel(IBeatmapInfo? beatmap, string mod = "")
|
||||||
{
|
{
|
||||||
Beatmap = beatmap;
|
Beatmap = beatmap;
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.Gray(0.5f),
|
Colour = OsuColour.Gray(0.5f),
|
||||||
OnlineInfo = Beatmap,
|
OnlineInfo = (Beatmap as IBeatmapSetOnlineInfo),
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Tournament.IPC
|
|||||||
public Bindable<LegacyMods> Mods { get; } = new Bindable<LegacyMods>();
|
public Bindable<LegacyMods> Mods { get; } = new Bindable<LegacyMods>();
|
||||||
public Bindable<TourneyState> State { get; } = new Bindable<TourneyState>();
|
public Bindable<TourneyState> State { get; } = new Bindable<TourneyState>();
|
||||||
public Bindable<string> ChatChannel { get; } = new Bindable<string>();
|
public Bindable<string> ChatChannel { get; } = new Bindable<string>();
|
||||||
public BindableInt Score1 { get; } = new BindableInt();
|
public BindableLong Score1 { get; } = new BindableLong();
|
||||||
public BindableInt Score2 { get; } = new BindableInt();
|
public BindableLong Score2 { get; } = new BindableLong();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,181 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Tournament.IPC;
|
using osu.Game.Tournament.IPC;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Gameplay.Components
|
namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||||
{
|
{
|
||||||
// TODO: Update to derive from osu-side class?
|
public partial class TournamentMatchScoreDisplay : MatchScoreDisplay
|
||||||
public partial class TournamentMatchScoreDisplay : CompositeDrawable
|
|
||||||
{
|
{
|
||||||
private const float bar_height = 18;
|
|
||||||
|
|
||||||
private readonly BindableInt score1 = new BindableInt();
|
|
||||||
private readonly BindableInt score2 = new BindableInt();
|
|
||||||
|
|
||||||
private readonly MatchScoreCounter score1Text;
|
|
||||||
private readonly MatchScoreCounter score2Text;
|
|
||||||
|
|
||||||
private readonly MatchScoreDiffCounter scoreDiffText;
|
|
||||||
|
|
||||||
private readonly Drawable score1Bar;
|
|
||||||
private readonly Drawable score2Bar;
|
|
||||||
|
|
||||||
public TournamentMatchScoreDisplay()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
InternalChildren = new[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Name = "top bar red (static)",
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = bar_height / 4,
|
|
||||||
Width = 0.5f,
|
|
||||||
Colour = TournamentGame.COLOUR_RED,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopRight
|
|
||||||
},
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Name = "top bar blue (static)",
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = bar_height / 4,
|
|
||||||
Width = 0.5f,
|
|
||||||
Colour = TournamentGame.COLOUR_BLUE,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopLeft
|
|
||||||
},
|
|
||||||
score1Bar = new Box
|
|
||||||
{
|
|
||||||
Name = "top bar red",
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = bar_height,
|
|
||||||
Width = 0,
|
|
||||||
Colour = TournamentGame.COLOUR_RED,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopRight
|
|
||||||
},
|
|
||||||
score1Text = new MatchScoreCounter
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre
|
|
||||||
},
|
|
||||||
score2Bar = new Box
|
|
||||||
{
|
|
||||||
Name = "top bar blue",
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = bar_height,
|
|
||||||
Width = 0,
|
|
||||||
Colour = TournamentGame.COLOUR_BLUE,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopLeft
|
|
||||||
},
|
|
||||||
score2Text = new MatchScoreCounter
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre
|
|
||||||
},
|
|
||||||
scoreDiffText = new MatchScoreDiffCounter
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
|
||||||
Top = bar_height / 4,
|
|
||||||
Horizontal = 8
|
|
||||||
},
|
|
||||||
Alpha = 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(MatchIPCInfo ipc)
|
private void load(MatchIPCInfo ipc)
|
||||||
{
|
{
|
||||||
score1.BindValueChanged(_ => updateScores());
|
Team1Score.BindTo(ipc.Score1);
|
||||||
score1.BindTo(ipc.Score1);
|
Team2Score.BindTo(ipc.Score2);
|
||||||
|
|
||||||
score2.BindValueChanged(_ => updateScores());
|
|
||||||
score2.BindTo(ipc.Score2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateScores()
|
|
||||||
{
|
|
||||||
score1Text.Current.Value = score1.Value;
|
|
||||||
score2Text.Current.Value = score2.Value;
|
|
||||||
|
|
||||||
var winningText = score1.Value > score2.Value ? score1Text : score2Text;
|
|
||||||
var losingText = score1.Value <= score2.Value ? score1Text : score2Text;
|
|
||||||
|
|
||||||
winningText.Winning = true;
|
|
||||||
losingText.Winning = false;
|
|
||||||
|
|
||||||
var winningBar = score1.Value > score2.Value ? score1Bar : score2Bar;
|
|
||||||
var losingBar = score1.Value <= score2.Value ? score1Bar : score2Bar;
|
|
||||||
|
|
||||||
int diff = Math.Max(score1.Value, score2.Value) - Math.Min(score1.Value, score2.Value);
|
|
||||||
|
|
||||||
losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
|
|
||||||
winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint);
|
|
||||||
|
|
||||||
scoreDiffText.Alpha = diff != 0 ? 1 : 0;
|
|
||||||
scoreDiffText.Current.Value = -diff;
|
|
||||||
scoreDiffText.Origin = score1.Value > score2.Value ? Anchor.TopLeft : Anchor.TopRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
|
||||||
{
|
|
||||||
base.UpdateAfterChildren();
|
|
||||||
score1Text.X = -Math.Max(5 + score1Text.DrawWidth / 2, score1Bar.DrawWidth);
|
|
||||||
score2Text.X = Math.Max(5 + score2Text.DrawWidth / 2, score2Bar.DrawWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class MatchScoreCounter : CommaSeparatedScoreCounter
|
|
||||||
{
|
|
||||||
private OsuSpriteText displayedSpriteText = null!;
|
|
||||||
|
|
||||||
public MatchScoreCounter()
|
|
||||||
{
|
|
||||||
Margin = new MarginPadding { Top = bar_height, Horizontal = 10 };
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Winning
|
|
||||||
{
|
|
||||||
set => updateFont(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
|
|
||||||
{
|
|
||||||
displayedSpriteText = s;
|
|
||||||
displayedSpriteText.Spacing = new Vector2(-6);
|
|
||||||
updateFont(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
private void updateFont(bool winning)
|
|
||||||
=> displayedSpriteText.Font = winning
|
|
||||||
? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true)
|
|
||||||
: OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class MatchScoreDiffCounter : CommaSeparatedScoreCounter
|
|
||||||
{
|
|
||||||
protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
|
|
||||||
{
|
|
||||||
s.Spacing = new Vector2(-2);
|
|
||||||
s.Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: bar_height, fixedWidth: true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,25 +12,26 @@ using osu.Game.Skinning;
|
|||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="WorkingBeatmap"/> which can be constructed directly from a .osu file, providing an implementation for
|
/// A <see cref="WorkingBeatmap"/> which can be constructed directly from an .osu file (via <see cref="FlatWorkingBeatmap(string, int?)"/>)
|
||||||
|
/// or an <see cref="IBeatmap"/> instance (via <see cref="FlatWorkingBeatmap(IBeatmap)"/>,
|
||||||
|
/// providing an implementation for
|
||||||
/// <see cref="WorkingBeatmap.GetPlayableBeatmap(osu.Game.Rulesets.IRulesetInfo,System.Collections.Generic.IReadOnlyList{osu.Game.Rulesets.Mods.Mod})"/>.
|
/// <see cref="WorkingBeatmap.GetPlayableBeatmap(osu.Game.Rulesets.IRulesetInfo,System.Collections.Generic.IReadOnlyList{osu.Game.Rulesets.Mods.Mod})"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FlatFileWorkingBeatmap : WorkingBeatmap
|
public class FlatWorkingBeatmap : WorkingBeatmap
|
||||||
{
|
{
|
||||||
private readonly Beatmap beatmap;
|
private readonly IBeatmap beatmap;
|
||||||
|
|
||||||
public FlatFileWorkingBeatmap(string file, int? beatmapId = null)
|
public FlatWorkingBeatmap(string file, int? beatmapId = null)
|
||||||
: this(readFromFile(file), beatmapId)
|
: this(readFromFile(file))
|
||||||
{
|
{
|
||||||
|
if (beatmapId.HasValue)
|
||||||
|
beatmap.BeatmapInfo.OnlineID = beatmapId.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FlatFileWorkingBeatmap(Beatmap beatmap, int? beatmapId = null)
|
public FlatWorkingBeatmap(IBeatmap beatmap)
|
||||||
: base(beatmap.BeatmapInfo, null)
|
: base(beatmap.BeatmapInfo, null)
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
|
|
||||||
if (beatmapId.HasValue)
|
|
||||||
beatmap.BeatmapInfo.OnlineID = beatmapId.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Beatmap readFromFile(string filename)
|
private static Beatmap readFromFile(string filename)
|
@ -237,6 +237,12 @@ namespace osu.Game.Configuration
|
|||||||
value: disabledState ? CommonStrings.Disabled.ToLower() : CommonStrings.Enabled.ToLower(),
|
value: disabledState ? CommonStrings.Disabled.ToLower() : CommonStrings.Enabled.ToLower(),
|
||||||
shortcut: LookupKeyBindings(GlobalAction.ToggleGameplayMouseButtons))
|
shortcut: LookupKeyBindings(GlobalAction.ToggleGameplayMouseButtons))
|
||||||
),
|
),
|
||||||
|
new TrackedSetting<bool>(OsuSetting.GameplayLeaderboard, state => new SettingDescription(
|
||||||
|
rawValue: state,
|
||||||
|
name: GlobalActionKeyBindingStrings.ToggleInGameLeaderboard,
|
||||||
|
value: state ? CommonStrings.Enabled.ToLower() : CommonStrings.Disabled.ToLower(),
|
||||||
|
shortcut: LookupKeyBindings(GlobalAction.ToggleInGameLeaderboard))
|
||||||
|
),
|
||||||
new TrackedSetting<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode, visibilityMode => new SettingDescription(
|
new TrackedSetting<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode, visibilityMode => new SettingDescription(
|
||||||
rawValue: visibilityMode,
|
rawValue: visibilityMode,
|
||||||
name: GameplaySettingsStrings.HUDVisibilityMode,
|
name: GameplaySettingsStrings.HUDVisibilityMode,
|
||||||
|
@ -29,9 +29,9 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
protected override Stream? GetFileContents(BeatmapSetInfo model, INamedFileUsage file)
|
protected override Stream? GetFileContents(BeatmapSetInfo model, INamedFileUsage file)
|
||||||
{
|
{
|
||||||
bool isBeatmap = model.Beatmaps.Any(o => o.Hash == file.File.Hash);
|
var beatmapInfo = model.Beatmaps.SingleOrDefault(o => o.Hash == file.File.Hash);
|
||||||
|
|
||||||
if (!isBeatmap)
|
if (beatmapInfo == null)
|
||||||
return base.GetFileContents(model, file);
|
return base.GetFileContents(model, file);
|
||||||
|
|
||||||
// Read the beatmap contents and skin
|
// Read the beatmap contents and skin
|
||||||
@ -43,6 +43,9 @@ namespace osu.Game.Database
|
|||||||
using var contentStreamReader = new LineBufferedReader(contentStream);
|
using var contentStreamReader = new LineBufferedReader(contentStream);
|
||||||
var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader);
|
var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader);
|
||||||
|
|
||||||
|
var workingBeatmap = new FlatWorkingBeatmap(beatmapContent);
|
||||||
|
var playableBeatmap = workingBeatmap.GetPlayableBeatmap(beatmapInfo.Ruleset);
|
||||||
|
|
||||||
using var skinStream = base.GetFileContents(model, file);
|
using var skinStream = base.GetFileContents(model, file);
|
||||||
|
|
||||||
if (skinStream == null)
|
if (skinStream == null)
|
||||||
@ -56,10 +59,10 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
// Convert beatmap elements to be compatible with legacy format
|
// Convert beatmap elements to be compatible with legacy format
|
||||||
// So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves
|
// So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves
|
||||||
foreach (var controlPoint in beatmapContent.ControlPointInfo.AllControlPoints)
|
foreach (var controlPoint in playableBeatmap.ControlPointInfo.AllControlPoints)
|
||||||
controlPoint.Time = Math.Floor(controlPoint.Time);
|
controlPoint.Time = Math.Floor(controlPoint.Time);
|
||||||
|
|
||||||
foreach (var hitObject in beatmapContent.HitObjects)
|
foreach (var hitObject in playableBeatmap.HitObjects)
|
||||||
{
|
{
|
||||||
// Truncate end time before truncating start time because end time is dependent on start time
|
// Truncate end time before truncating start time because end time is dependent on start time
|
||||||
if (hitObject is IHasDuration hasDuration && hitObject is not IHasPath)
|
if (hitObject is IHasDuration hasDuration && hitObject is not IHasPath)
|
||||||
@ -86,7 +89,7 @@ namespace osu.Game.Database
|
|||||||
// Encode to legacy format
|
// Encode to legacy format
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
|
new LegacyBeatmapEncoder(playableBeatmap, beatmapSkin).Encode(sw);
|
||||||
|
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Database
|
|||||||
// (ie. if an async import finished very recently).
|
// (ie. if an async import finished very recently).
|
||||||
Realm.Realm.Write(realm =>
|
Realm.Realm.Write(realm =>
|
||||||
{
|
{
|
||||||
var managed = realm.Find<TModel>(item.ID);
|
var managed = realm.FindWithRefresh<TModel>(item.ID);
|
||||||
Debug.Assert(managed != null);
|
Debug.Assert(managed != null);
|
||||||
operation(managed);
|
operation(managed);
|
||||||
|
|
||||||
|
@ -82,8 +82,9 @@ namespace osu.Game.Database
|
|||||||
/// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations.
|
/// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations.
|
||||||
/// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores.
|
/// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores.
|
||||||
/// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files.
|
/// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files.
|
||||||
|
/// 33 2023-08-16 Reset default chat toggle key binding to avoid conflict with newly added leaderboard toggle key binding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 32;
|
private const int schema_version = 33;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
@ -771,6 +772,7 @@ namespace osu.Game.Database
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 8:
|
case 8:
|
||||||
|
{
|
||||||
// Ctrl -/+ now adjusts UI scale so let's clear any bindings which overlap these combinations.
|
// Ctrl -/+ now adjusts UI scale so let's clear any bindings which overlap these combinations.
|
||||||
// New defaults will be populated by the key store afterwards.
|
// New defaults will be populated by the key store afterwards.
|
||||||
var keyBindings = migration.NewRealm.All<RealmKeyBinding>();
|
var keyBindings = migration.NewRealm.All<RealmKeyBinding>();
|
||||||
@ -784,6 +786,7 @@ namespace osu.Game.Database
|
|||||||
migration.NewRealm.Remove(decreaseSpeedBinding);
|
migration.NewRealm.Remove(decreaseSpeedBinding);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 9:
|
case 9:
|
||||||
// Pretty pointless to do this as beatmaps aren't really loaded via realm yet, but oh well.
|
// Pretty pointless to do this as beatmaps aren't really loaded via realm yet, but oh well.
|
||||||
@ -838,6 +841,7 @@ namespace osu.Game.Database
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 11:
|
case 11:
|
||||||
|
{
|
||||||
string keyBindingClassName = getMappedOrOriginalName(typeof(RealmKeyBinding));
|
string keyBindingClassName = getMappedOrOriginalName(typeof(RealmKeyBinding));
|
||||||
|
|
||||||
if (!migration.OldRealm.Schema.TryFindObjectSchema(keyBindingClassName, out _))
|
if (!migration.OldRealm.Schema.TryFindObjectSchema(keyBindingClassName, out _))
|
||||||
@ -864,6 +868,7 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 14:
|
case 14:
|
||||||
foreach (var beatmap in migration.NewRealm.All<BeatmapInfo>())
|
foreach (var beatmap in migration.NewRealm.All<BeatmapInfo>())
|
||||||
@ -1012,6 +1017,19 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 33:
|
||||||
|
{
|
||||||
|
// Clear default bindings for the chat focus toggle,
|
||||||
|
// as they would conflict with the newly-added leaderboard toggle.
|
||||||
|
var keyBindings = migration.NewRealm.All<RealmKeyBinding>();
|
||||||
|
|
||||||
|
var toggleChatBind = keyBindings.FirstOrDefault(bind => bind.ActionInt == (int)GlobalAction.ToggleChatFocus);
|
||||||
|
if (toggleChatBind != null && toggleChatBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Tab }))
|
||||||
|
migration.NewRealm.Remove(toggleChatBind);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
||||||
|
@ -8,6 +8,34 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
public static class RealmExtensions
|
public static class RealmExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a <see cref="Realm.Find{T}(System.Nullable{long})"/>.
|
||||||
|
/// If a match was not found, a <see cref="Realm.Refresh"/> is performed before trying a second time.
|
||||||
|
/// This ensures that an instance is found even if the realm requested against was not in a consistent state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="realm">The realm to operate on.</param>
|
||||||
|
/// <param name="id">The ID of the entity to find in the realm.</param>
|
||||||
|
/// <typeparam name="T">The type of the entity to find in the realm.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// The retrieved entity of type <typeparamref name="T"/>.
|
||||||
|
/// Can be <see langword="null"/> if the entity is still not found by <paramref name="id"/> even after a refresh.
|
||||||
|
/// </returns>
|
||||||
|
public static T? FindWithRefresh<T>(this Realm realm, Guid id) where T : IRealmObject
|
||||||
|
{
|
||||||
|
var found = realm.Find<T>(id);
|
||||||
|
|
||||||
|
if (found == null)
|
||||||
|
{
|
||||||
|
// It may be that we access this from the update thread before a refresh has taken place.
|
||||||
|
// To ensure that behaviour matches what we'd expect (the object generally *should be* available), force
|
||||||
|
// a refresh to bring in any off-thread changes immediately.
|
||||||
|
realm.Refresh();
|
||||||
|
found = realm.Find<T>(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform a write operation against the provided realm instance.
|
/// Perform a write operation against the provided realm instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a new instance of live realm data.
|
/// Construct a new instance of live realm data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">The realm data.</param>
|
/// <param name="data">The realm data. Must be managed (see <see cref="IRealmObjectBase.IsManaged"/>).</param>
|
||||||
/// <param name="realm">The realm factory the data was sourced from. May be null for an unmanaged object.</param>
|
/// <param name="realm">The realm factory the data was sourced from. May be null for an unmanaged object.</param>
|
||||||
public RealmLive(T data, RealmAccess realm)
|
public RealmLive(T data, RealmAccess realm)
|
||||||
: base(data.ID)
|
: base(data.ID)
|
||||||
@ -62,7 +62,7 @@ namespace osu.Game.Database
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
perform(retrieveFromID(r));
|
perform(r.FindWithRefresh<T>(ID)!);
|
||||||
RealmLiveStatistics.USAGE_ASYNC.Value++;
|
RealmLiveStatistics.USAGE_ASYNC.Value++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
return realm.Run(r =>
|
return realm.Run(r =>
|
||||||
{
|
{
|
||||||
var returnData = perform(retrieveFromID(r));
|
var returnData = perform(r.FindWithRefresh<T>(ID)!);
|
||||||
RealmLiveStatistics.USAGE_ASYNC.Value++;
|
RealmLiveStatistics.USAGE_ASYNC.Value++;
|
||||||
|
|
||||||
if (returnData is RealmObjectBase realmObject && realmObject.IsManaged)
|
if (returnData is RealmObjectBase realmObject && realmObject.IsManaged)
|
||||||
@ -141,25 +141,10 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
dataIsFromUpdateThread = true;
|
dataIsFromUpdateThread = true;
|
||||||
data = retrieveFromID(realm.Realm);
|
data = realm.Realm.FindWithRefresh<T>(ID)!;
|
||||||
|
|
||||||
RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++;
|
RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private T retrieveFromID(Realm realm)
|
|
||||||
{
|
|
||||||
var found = realm.Find<T>(ID);
|
|
||||||
|
|
||||||
if (found == null)
|
|
||||||
{
|
|
||||||
// It may be that we access this from the update thread before a refresh has taken place.
|
|
||||||
// To ensure that behaviour matches what we'd expect (the object *is* available), force
|
|
||||||
// a refresh to bring in any off-thread changes immediately.
|
|
||||||
realm.Refresh();
|
|
||||||
found = realm.Find<T>(ID)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class RealmLiveStatistics
|
internal static class RealmLiveStatistics
|
||||||
|
@ -49,6 +49,18 @@ namespace osu.Game.Graphics
|
|||||||
this.maxDuration = maxDuration;
|
this.maxDuration = maxDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Active.BindValueChanged(active =>
|
||||||
|
{
|
||||||
|
// ensure that particles can be spawned immediately after the spewer becomes active.
|
||||||
|
if (active.NewValue)
|
||||||
|
lastParticleAdded = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -56,12 +68,8 @@ namespace osu.Game.Graphics
|
|||||||
Invalidate(Invalidation.DrawNode);
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
|
||||||
if (!Active.Value || !CanSpawnParticles)
|
if (!Active.Value || !CanSpawnParticles)
|
||||||
{
|
|
||||||
lastParticleAdded = null;
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Always want to spawn the first particle in an activation immediately.
|
|
||||||
if (lastParticleAdded == null)
|
if (lastParticleAdded == null)
|
||||||
{
|
{
|
||||||
lastParticleAdded = Time.Current;
|
lastParticleAdded = Time.Current;
|
||||||
|
@ -213,7 +213,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
requestDisplay();
|
requestDisplay();
|
||||||
else if (isDisplayed && Time.Current - lastDisplayRequiredTime > 2000 && !IsHovered)
|
else if (isDisplayed && Time.Current - lastDisplayRequiredTime > 2000 && !IsHovered)
|
||||||
{
|
{
|
||||||
mainContent.FadeTo(0, 300, Easing.OutQuint);
|
mainContent.FadeTo(0.7f, 300, Easing.OutQuint);
|
||||||
isDisplayed = false;
|
isDisplayed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
143
osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs
Normal file
143
osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// 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.Globalization;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public partial class SliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T>
|
||||||
|
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A custom step value for each key press which actuates a change on this control.
|
||||||
|
/// </summary>
|
||||||
|
public float KeyboardStep
|
||||||
|
{
|
||||||
|
get => slider.KeyboardStep;
|
||||||
|
set => slider.KeyboardStep = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bindable<T> Current
|
||||||
|
{
|
||||||
|
get => slider.Current;
|
||||||
|
set => slider.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool instantaneous;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether changes to the slider should instantaneously transfer to the text box (and vice versa).
|
||||||
|
/// If <see langword="false"/>, the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider drag end.
|
||||||
|
/// </summary>
|
||||||
|
public bool Instantaneous
|
||||||
|
{
|
||||||
|
get => instantaneous;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
instantaneous = value;
|
||||||
|
slider.TransferValueOnCommit = !instantaneous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SettingsSlider<T> slider;
|
||||||
|
private readonly LabelledTextBox textBox;
|
||||||
|
|
||||||
|
public SliderWithTextBoxInput(LocalisableString labelText)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(20),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
textBox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = labelText,
|
||||||
|
},
|
||||||
|
slider = new SettingsSlider<T>
|
||||||
|
{
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
textBox.OnCommit += textCommitted;
|
||||||
|
textBox.Current.BindValueChanged(textChanged);
|
||||||
|
|
||||||
|
Current.BindValueChanged(updateTextBoxFromSlider, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool updatingFromTextBox;
|
||||||
|
|
||||||
|
private void textChanged(ValueChangedEvent<string> change)
|
||||||
|
{
|
||||||
|
if (!instantaneous) return;
|
||||||
|
|
||||||
|
tryUpdateSliderFromTextBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void textCommitted(TextBox t, bool isNew)
|
||||||
|
{
|
||||||
|
tryUpdateSliderFromTextBox();
|
||||||
|
|
||||||
|
// If the attempted update above failed, restore text box to match the slider.
|
||||||
|
Current.TriggerChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryUpdateSliderFromTextBox()
|
||||||
|
{
|
||||||
|
updatingFromTextBox = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (slider.Current)
|
||||||
|
{
|
||||||
|
case Bindable<int> bindableInt:
|
||||||
|
bindableInt.Value = int.Parse(textBox.Current.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Bindable<double> bindableDouble:
|
||||||
|
bindableDouble.Value = double.Parse(textBox.Current.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
slider.Current.Parse(textBox.Current.Value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore parsing failures.
|
||||||
|
// sane state will eventually be restored by a commit (either explicit, or implicit via focus loss).
|
||||||
|
}
|
||||||
|
|
||||||
|
updatingFromTextBox = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTextBoxFromSlider(ValueChangedEvent<T> _)
|
||||||
|
{
|
||||||
|
if (updatingFromTextBox) return;
|
||||||
|
|
||||||
|
decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo);
|
||||||
|
textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -116,9 +116,10 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.DecreaseScrollSpeed),
|
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.DecreaseScrollSpeed),
|
||||||
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed),
|
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed),
|
||||||
new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface),
|
new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface),
|
||||||
|
new KeyBinding(InputKey.Tab, GlobalAction.ToggleInGameLeaderboard),
|
||||||
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
|
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
|
||||||
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
|
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
|
||||||
new KeyBinding(InputKey.Tab, GlobalAction.ToggleChatFocus),
|
new KeyBinding(InputKey.Enter, GlobalAction.ToggleChatFocus),
|
||||||
new KeyBinding(InputKey.F1, GlobalAction.SaveReplay),
|
new KeyBinding(InputKey.F1, GlobalAction.SaveReplay),
|
||||||
new KeyBinding(InputKey.F2, GlobalAction.ExportReplay),
|
new KeyBinding(InputKey.F2, GlobalAction.ExportReplay),
|
||||||
};
|
};
|
||||||
@ -204,7 +205,6 @@ namespace osu.Game.Input.Bindings
|
|||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleMute))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleMute))]
|
||||||
ToggleMute,
|
ToggleMute,
|
||||||
|
|
||||||
// In-Game Keybindings
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SkipCutscene))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SkipCutscene))]
|
||||||
SkipCutscene,
|
SkipCutscene,
|
||||||
|
|
||||||
@ -232,7 +232,6 @@ namespace osu.Game.Input.Bindings
|
|||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.QuickExit))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.QuickExit))]
|
||||||
QuickExit,
|
QuickExit,
|
||||||
|
|
||||||
// Game-wide beatmap music controller keybindings
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicNext))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicNext))]
|
||||||
MusicNext,
|
MusicNext,
|
||||||
|
|
||||||
@ -260,7 +259,6 @@ namespace osu.Game.Input.Bindings
|
|||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.PauseGameplay))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.PauseGameplay))]
|
||||||
PauseGameplay,
|
PauseGameplay,
|
||||||
|
|
||||||
// Editor
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSetupMode))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSetupMode))]
|
||||||
EditorSetupMode,
|
EditorSetupMode,
|
||||||
|
|
||||||
@ -285,7 +283,6 @@ namespace osu.Game.Input.Bindings
|
|||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))]
|
||||||
ToggleInGameInterface,
|
ToggleInGameInterface,
|
||||||
|
|
||||||
// Song select keybindings
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))]
|
||||||
ToggleModSelection,
|
ToggleModSelection,
|
||||||
|
|
||||||
@ -378,5 +375,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleReplaySettings))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleReplaySettings))]
|
||||||
ToggleReplaySettings,
|
ToggleReplaySettings,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameLeaderboard))]
|
||||||
|
ToggleInGameLeaderboard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,6 +219,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface");
|
public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Toggle in-game leaderboard"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ToggleInGameLeaderboard => new TranslatableString(getKey(@"toggle_in_game_leaderboard"), @"Toggle in-game leaderboard");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Toggle mod select"
|
/// "Toggle mod select"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public Bindable<APIUser> LocalUser { get; } = new Bindable<APIUser>(new APIUser
|
public Bindable<APIUser> LocalUser { get; } = new Bindable<APIUser>(new APIUser
|
||||||
{
|
{
|
||||||
Username = @"Dummy",
|
Username = @"Local user",
|
||||||
Id = DUMMY_USER_ID,
|
Id = DUMMY_USER_ID,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -104,9 +104,11 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
public virtual void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
public virtual void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
{
|
{
|
||||||
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
||||||
LegacyAccuracyScore = (int)values[ATTRIB_ID_LEGACY_ACCURACY_SCORE];
|
|
||||||
LegacyComboScore = (int)values[ATTRIB_ID_LEGACY_COMBO_SCORE];
|
// Temporarily allow these attributes to not exist so as to not block releases of server-side components while these attributes aren't populated/used yet.
|
||||||
LegacyBonusScoreRatio = (int)values[ATTRIB_ID_LEGACY_BONUS_SCORE_RATIO];
|
LegacyAccuracyScore = (int)values.GetValueOrDefault(ATTRIB_ID_LEGACY_ACCURACY_SCORE);
|
||||||
|
LegacyComboScore = (int)values.GetValueOrDefault(ATTRIB_ID_LEGACY_COMBO_SCORE);
|
||||||
|
LegacyBonusScoreRatio = values.GetValueOrDefault(ATTRIB_ID_LEGACY_BONUS_SCORE_RATIO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,11 +40,13 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
|
|
||||||
private readonly List<Vector2> calculatedPath = new List<Vector2>();
|
private readonly List<Vector2> calculatedPath = new List<Vector2>();
|
||||||
private readonly List<double> cumulativeLength = new List<double>();
|
private readonly List<double> cumulativeLength = new List<double>();
|
||||||
private readonly List<int> segmentEnds = new List<int>();
|
|
||||||
private readonly Cached pathCache = new Cached();
|
private readonly Cached pathCache = new Cached();
|
||||||
|
|
||||||
private double calculatedLength;
|
private double calculatedLength;
|
||||||
|
|
||||||
|
private readonly List<int> segmentEnds = new List<int>();
|
||||||
|
private double[] segmentEndDistances = Array.Empty<double>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="SliderPath"/>.
|
/// Creates a new <see cref="SliderPath"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -202,7 +204,7 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
ensureValid();
|
ensureValid();
|
||||||
|
|
||||||
return segmentEnds.Select(i => cumulativeLength[i] / calculatedLength);
|
return segmentEndDistances.Select(d => d / Distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invalidate()
|
private void invalidate()
|
||||||
@ -251,8 +253,9 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
calculatedPath.Add(t);
|
calculatedPath.Add(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember the index of the segment end
|
if (i > 0)
|
||||||
segmentEnds.Add(calculatedPath.Count - 1);
|
// Remember the index of the segment end
|
||||||
|
segmentEnds.Add(calculatedPath.Count - 1);
|
||||||
|
|
||||||
// Start the new segment at the current vertex
|
// Start the new segment at the current vertex
|
||||||
start = i;
|
start = i;
|
||||||
@ -298,6 +301,14 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
cumulativeLength.Add(calculatedLength);
|
cumulativeLength.Add(calculatedLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the distances of the segment ends now, because after shortening the indices may be out of range
|
||||||
|
segmentEndDistances = new double[segmentEnds.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < segmentEnds.Count; i++)
|
||||||
|
{
|
||||||
|
segmentEndDistances[i] = cumulativeLength[segmentEnds[i]];
|
||||||
|
}
|
||||||
|
|
||||||
if (ExpectedDistance.Value is double expectedDistance && calculatedLength != expectedDistance)
|
if (ExpectedDistance.Value is double expectedDistance && calculatedLength != expectedDistance)
|
||||||
{
|
{
|
||||||
// In osu-stable, if the last two control points of a slider are equal, extension is not performed.
|
// In osu-stable, if the last two control points of a slider are equal, extension is not performed.
|
||||||
@ -319,10 +330,6 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
cumulativeLength.RemoveAt(cumulativeLength.Count - 1);
|
cumulativeLength.RemoveAt(cumulativeLength.Count - 1);
|
||||||
calculatedPath.RemoveAt(pathEndIndex--);
|
calculatedPath.RemoveAt(pathEndIndex--);
|
||||||
|
|
||||||
// Shorten the last segment to the expected distance
|
|
||||||
if (segmentEnds.Count > 0)
|
|
||||||
segmentEnds[^1]--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +199,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
if (loadableBeatmap is DummyWorkingBeatmap)
|
if (loadableBeatmap is DummyWorkingBeatmap)
|
||||||
{
|
{
|
||||||
|
Logger.Log("Editor was loaded without a valid beatmap; creating a new beatmap.");
|
||||||
|
|
||||||
isNewBeatmap = true;
|
isNewBeatmap = true;
|
||||||
|
|
||||||
loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
|
loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
|
||||||
|
@ -147,13 +147,25 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
trackedType = null;
|
trackedType = null;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If the selected group only has one control point, update the tracking type.
|
switch (selectedGroup.Value.ControlPoints.Count)
|
||||||
if (selectedGroup.Value.ControlPoints.Count == 1)
|
{
|
||||||
trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
|
// If the selected group has no control points, clear the tracked type.
|
||||||
// If the selected group has more than one control point, choose the first as the tracking type
|
// Otherwise the user will be unable to select a group with no control points.
|
||||||
// if we don't already have a singular tracked type.
|
case 0:
|
||||||
else if (trackedType == null)
|
trackedType = null;
|
||||||
trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
break;
|
||||||
|
|
||||||
|
// If the selected group only has one control point, update the tracking type.
|
||||||
|
case 1:
|
||||||
|
trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// If the selected group has more than one control point, choose the first as the tracking type
|
||||||
|
// if we don't already have a singular tracked type.
|
||||||
|
default:
|
||||||
|
trackedType ??= selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackedType != null)
|
if (trackedType != null)
|
||||||
|
@ -1,106 +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.Globalization;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using osu.Game.Utils;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
|
||||||
{
|
|
||||||
public partial class SliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T>
|
|
||||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
|
||||||
{
|
|
||||||
private readonly SettingsSlider<T> slider;
|
|
||||||
|
|
||||||
public SliderWithTextBoxInput(LocalisableString labelText)
|
|
||||||
{
|
|
||||||
LabelledTextBox textBox;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(20),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
textBox = new LabelledTextBox
|
|
||||||
{
|
|
||||||
Label = labelText,
|
|
||||||
},
|
|
||||||
slider = new SettingsSlider<T>
|
|
||||||
{
|
|
||||||
TransferValueOnCommit = true,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
textBox.OnCommit += (t, isNew) =>
|
|
||||||
{
|
|
||||||
if (!isNew) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
switch (slider.Current)
|
|
||||||
{
|
|
||||||
case Bindable<int> bindableInt:
|
|
||||||
bindableInt.Value = int.Parse(t.Text);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Bindable<double> bindableDouble:
|
|
||||||
bindableDouble.Value = double.Parse(t.Text);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
slider.Current.Parse(t.Text);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// TriggerChange below will restore the previous text value on failure.
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is run regardless of parsing success as the parsed number may not actually trigger a change
|
|
||||||
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
|
|
||||||
Current.TriggerChange();
|
|
||||||
};
|
|
||||||
|
|
||||||
Current.BindValueChanged(_ =>
|
|
||||||
{
|
|
||||||
decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo);
|
|
||||||
textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}");
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A custom step value for each key press which actuates a change on this control.
|
|
||||||
/// </summary>
|
|
||||||
public float KeyboardStep
|
|
||||||
{
|
|
||||||
get => slider.KeyboardStep;
|
|
||||||
set => slider.KeyboardStep = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bindable<T> Current
|
|
||||||
{
|
|
||||||
get => slider.Current;
|
|
||||||
set => slider.Current = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
|
|
||||||
public bool Seek(double position)
|
public bool Seek(double position)
|
||||||
{
|
{
|
||||||
|
Logger.Log($"{nameof(SpectatorPlayerClock)} seeked to {position}");
|
||||||
CurrentTime = position;
|
CurrentTime = position;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,21 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
Seek(StartTime);
|
Seek(StartTime);
|
||||||
|
|
||||||
|
// This is a workaround for the fact that DecoupleableInterpolatingFramedClock doesn't seek the source
|
||||||
|
// if the source is not IsRunning. (see https://github.com/ppy/osu-framework/blob/2102638056dfcf85d21b4d85266d53b5dd018767/osu.Framework/Timing/DecoupleableInterpolatingFramedClock.cs#L209-L210)
|
||||||
|
// I hope to remove this once we knock some sense into clocks in general.
|
||||||
|
//
|
||||||
|
// Without this seek, the multiplayer spectator start sequence breaks:
|
||||||
|
// - Individual clients' clocks are never updated to their expected time
|
||||||
|
// - The sync manager thinks they are running behind
|
||||||
|
// - Gameplay doesn't start when it should (until a timeout occurs because nothing is happening for 10+ seconds)
|
||||||
|
//
|
||||||
|
// In addition, we use `CurrentTime` for this seek instead of `StartTime` as the above seek may have applied inherent
|
||||||
|
// offsets which need to be accounted for (ie. FramedBeatmapClock.TotalAppliedOffset).
|
||||||
|
//
|
||||||
|
// See https://github.com/ppy/osu/pull/24451/files/87fee001c786b29db34063ef3350e9a9f024d3ab#diff-28ca02979641e2d98a15fe5d5e806f56acf60ac100258a059fa72503b6cc54e8.
|
||||||
|
(SourceClock as IAdjustableClock)?.Seek(CurrentTime);
|
||||||
|
|
||||||
if (!wasPaused || startClock)
|
if (!wasPaused || startClock)
|
||||||
Start();
|
Start();
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -24,11 +22,13 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
public BindableLong Team1Score = new BindableLong();
|
public BindableLong Team1Score = new BindableLong();
|
||||||
public BindableLong Team2Score = new BindableLong();
|
public BindableLong Team2Score = new BindableLong();
|
||||||
|
|
||||||
protected MatchScoreCounter Score1Text;
|
protected MatchScoreCounter Score1Text = null!;
|
||||||
protected MatchScoreCounter Score2Text;
|
protected MatchScoreCounter Score2Text = null!;
|
||||||
|
|
||||||
private Drawable score1Bar;
|
private Drawable score1Bar = null!;
|
||||||
private Drawable score2Bar;
|
private Drawable score2Bar = null!;
|
||||||
|
|
||||||
|
private MatchScoreDiffCounter scoreDiffText = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
@ -98,6 +98,16 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
scoreDiffText = new MatchScoreDiffCounter
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = bar_height / 4,
|
||||||
|
Horizontal = 8
|
||||||
|
},
|
||||||
|
Alpha = 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +149,10 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
|
losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
|
||||||
winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint);
|
winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint);
|
||||||
|
|
||||||
|
scoreDiffText.Alpha = diff != 0 ? 1 : 0;
|
||||||
|
scoreDiffText.Current.Value = -diff;
|
||||||
|
scoreDiffText.Origin = Team1Score.Value > Team2Score.Value ? Anchor.TopLeft : Anchor.TopRight;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
@ -150,7 +164,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected partial class MatchScoreCounter : CommaSeparatedScoreCounter
|
protected partial class MatchScoreCounter : CommaSeparatedScoreCounter
|
||||||
{
|
{
|
||||||
private OsuSpriteText displayedSpriteText;
|
private OsuSpriteText displayedSpriteText = null!;
|
||||||
|
|
||||||
public MatchScoreCounter()
|
public MatchScoreCounter()
|
||||||
{
|
{
|
||||||
@ -174,5 +188,14 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
? OsuFont.Torus.With(weight: FontWeight.Bold, size: font_size, fixedWidth: true)
|
? OsuFont.Torus.With(weight: FontWeight.Bold, size: font_size, fixedWidth: true)
|
||||||
: OsuFont.Torus.With(weight: FontWeight.Regular, size: font_size * 0.8f, fixedWidth: true);
|
: OsuFont.Torus.With(weight: FontWeight.Regular, size: font_size * 0.8f, fixedWidth: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class MatchScoreDiffCounter : CommaSeparatedScoreCounter
|
||||||
|
{
|
||||||
|
protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
|
||||||
|
{
|
||||||
|
s.Spacing = new Vector2(-2);
|
||||||
|
s.Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: bar_height, fixedWidth: true);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ namespace osu.Game.Screens.Play
|
|||||||
public Bindable<bool> ShowHud { get; } = new BindableBool();
|
public Bindable<bool> ShowHud { get; } = new BindableBool();
|
||||||
|
|
||||||
private Bindable<HUDVisibilityMode> configVisibilityMode;
|
private Bindable<HUDVisibilityMode> configVisibilityMode;
|
||||||
|
private Bindable<bool> configLeaderboardVisibility;
|
||||||
private Bindable<bool> configSettingsOverlay;
|
private Bindable<bool> configSettingsOverlay;
|
||||||
|
|
||||||
private readonly BindableBool replayLoaded = new BindableBool();
|
private readonly BindableBool replayLoaded = new BindableBool();
|
||||||
@ -186,6 +187,7 @@ namespace osu.Game.Screens.Play
|
|||||||
ModDisplay.Current.Value = mods;
|
ModDisplay.Current.Value = mods;
|
||||||
|
|
||||||
configVisibilityMode = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode);
|
configVisibilityMode = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode);
|
||||||
|
configLeaderboardVisibility = config.GetBindable<bool>(OsuSetting.GameplayLeaderboard);
|
||||||
configSettingsOverlay = config.GetBindable<bool>(OsuSetting.ReplaySettingsOverlay);
|
configSettingsOverlay = config.GetBindable<bool>(OsuSetting.ReplaySettingsOverlay);
|
||||||
|
|
||||||
if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce)
|
if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce)
|
||||||
@ -398,6 +400,10 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.ToggleInGameLeaderboard:
|
||||||
|
configLeaderboardVisibility.Value = !configLeaderboardVisibility.Value;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -810,10 +810,13 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!canShowResults && !forceImport)
|
if (!canShowResults && !forceImport)
|
||||||
return Task.FromResult<ScoreInfo>(null);
|
return Task.FromResult<ScoreInfo>(null);
|
||||||
|
|
||||||
|
// Clone score before beginning any async processing.
|
||||||
|
// - Must be run synchronously as the score may potentially be mutated in the background.
|
||||||
|
// - Must be cloned for the same reason.
|
||||||
|
Score scoreCopy = Score.DeepClone();
|
||||||
|
|
||||||
return prepareScoreForDisplayTask = Task.Run(async () =>
|
return prepareScoreForDisplayTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var scoreCopy = Score.DeepClone();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PrepareScoreForResultsAsync(scoreCopy).ConfigureAwait(false);
|
await PrepareScoreForResultsAsync(scoreCopy).ConfigureAwait(false);
|
||||||
|
@ -124,7 +124,12 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
if (frames.Count == 0)
|
if (frames.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var bundle = new FrameDataBundle(new ScoreInfo { Combo = currentFrameIndex }, new ScoreProcessor(rulesetStore.GetRuleset(0)!.CreateInstance()), frames.ToArray());
|
var bundle = new FrameDataBundle(new ScoreInfo
|
||||||
|
{
|
||||||
|
Combo = currentFrameIndex,
|
||||||
|
TotalScore = (long)(currentFrameIndex * 123478 * RNG.NextDouble(0.99, 1.01)),
|
||||||
|
Accuracy = RNG.NextDouble(0.98, 1),
|
||||||
|
}, new ScoreProcessor(rulesetStore.GetRuleset(0)!.CreateInstance()), frames.ToArray());
|
||||||
((ISpectatorClient)this).UserSentFrames(userId, bundle);
|
((ISpectatorClient)this).UserSentFrames(userId, bundle);
|
||||||
|
|
||||||
frames.Clear();
|
frames.Clear();
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="11.1.2" />
|
<PackageReference Include="Realm" Version="11.1.2" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2023.801.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2023.815.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.719.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.719.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.28.1" />
|
<PackageReference Include="Sentry" Version="3.28.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
|
@ -23,6 +23,6 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.801.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.815.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user