mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 08:22:56 +08:00
Merge branch 'master' into add-password-support
This commit is contained in:
commit
b5dd9403b1
@ -52,7 +52,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.713.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.714.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckTooShortSlidersTest
|
||||||
|
{
|
||||||
|
private CheckTooShortSliders check;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckTooShortSliders();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLongSlider()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 0,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(100, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { slider });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortSlider()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 0,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(25, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { slider });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSliderExpert()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 0,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(10, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { slider }, DifficultyRating.Expert);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSlider()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 0,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(10, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertTooShort(new List<HitObject> { slider });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSliderWithRepeats()
|
||||||
|
{
|
||||||
|
// Would be ok if we looked at the duration, but not if we look at the span duration.
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 2,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(10, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertTooShort(new List<HitObject> { slider });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOk(List<HitObject> hitObjects, DifficultyRating difficultyRating = DifficultyRating.Easy)
|
||||||
|
{
|
||||||
|
Assert.That(check.Run(getContext(hitObjects, difficultyRating)), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTooShort(List<HitObject> hitObjects, DifficultyRating difficultyRating = DifficultyRating.Easy)
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(hitObjects, difficultyRating)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.First().Template is CheckTooShortSliders.IssueTemplateTooShort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(List<HitObject> hitObjects, DifficultyRating difficultyRating)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject> { HitObjects = hitObjects };
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckTooShortSpinnersTest
|
||||||
|
{
|
||||||
|
private CheckTooShortSpinners check;
|
||||||
|
private BeatmapDifficulty difficulty;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckTooShortSpinners();
|
||||||
|
difficulty = new BeatmapDifficulty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLongSpinner()
|
||||||
|
{
|
||||||
|
Spinner spinner = new Spinner { StartTime = 0, Duration = 4000 };
|
||||||
|
spinner.ApplyDefaults(new ControlPointInfo(), difficulty);
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { spinner }, difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortSpinner()
|
||||||
|
{
|
||||||
|
Spinner spinner = new Spinner { StartTime = 0, Duration = 750 };
|
||||||
|
spinner.ApplyDefaults(new ControlPointInfo(), difficulty);
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { spinner }, difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVeryShortSpinner()
|
||||||
|
{
|
||||||
|
// Spinners at a certain duration only get 1000 points if approached by auto at a certain angle, making it difficult to determine.
|
||||||
|
Spinner spinner = new Spinner { StartTime = 0, Duration = 475 };
|
||||||
|
spinner.ApplyDefaults(new ControlPointInfo(), difficulty);
|
||||||
|
|
||||||
|
assertVeryShort(new List<HitObject> { spinner }, difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSpinner()
|
||||||
|
{
|
||||||
|
Spinner spinner = new Spinner { StartTime = 0, Duration = 400 };
|
||||||
|
spinner.ApplyDefaults(new ControlPointInfo(), difficulty);
|
||||||
|
|
||||||
|
assertTooShort(new List<HitObject> { spinner }, difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSpinnerVaryingOd()
|
||||||
|
{
|
||||||
|
const double duration = 450;
|
||||||
|
|
||||||
|
var difficultyLowOd = new BeatmapDifficulty { OverallDifficulty = 1 };
|
||||||
|
Spinner spinnerLowOd = new Spinner { StartTime = 0, Duration = duration };
|
||||||
|
spinnerLowOd.ApplyDefaults(new ControlPointInfo(), difficultyLowOd);
|
||||||
|
|
||||||
|
var difficultyHighOd = new BeatmapDifficulty { OverallDifficulty = 10 };
|
||||||
|
Spinner spinnerHighOd = new Spinner { StartTime = 0, Duration = duration };
|
||||||
|
spinnerHighOd.ApplyDefaults(new ControlPointInfo(), difficultyHighOd);
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { spinnerLowOd }, difficultyLowOd);
|
||||||
|
assertTooShort(new List<HitObject> { spinnerHighOd }, difficultyHighOd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOk(List<HitObject> hitObjects, BeatmapDifficulty beatmapDifficulty)
|
||||||
|
{
|
||||||
|
Assert.That(check.Run(getContext(hitObjects, beatmapDifficulty)), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertVeryShort(List<HitObject> hitObjects, BeatmapDifficulty beatmapDifficulty)
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateVeryShort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTooShort(List<HitObject> hitObjects, BeatmapDifficulty beatmapDifficulty)
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateTooShort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(List<HitObject> hitObjects, BeatmapDifficulty beatmapDifficulty)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = hitObjects,
|
||||||
|
BeatmapInfo = new BeatmapInfo { BaseDifficulty = beatmapDifficulty }
|
||||||
|
};
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs
Normal file
48
osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckTooShortSliders : ICheck
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The shortest acceptable duration between the head and tail of the slider (so ignoring repeats).
|
||||||
|
/// </summary>
|
||||||
|
private const double span_duration_threshold = 125; // 240 BPM 1/2
|
||||||
|
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Too short sliders");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateTooShort(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
{
|
||||||
|
if (context.InterpretedDifficulty > DifficultyRating.Easy)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
foreach (var hitObject in context.Beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
if (hitObject is Slider slider && slider.SpanDuration < span_duration_threshold)
|
||||||
|
yield return new IssueTemplateTooShort(this).Create(slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateTooShort : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateTooShort(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "This slider is too short ({0:0} ms), expected at least {1:0} ms.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(Slider slider) => new Issue(slider, this, slider.SpanDuration, span_duration_threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs
Normal file
61
osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckTooShortSpinners : ICheck
|
||||||
|
{
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Too short spinners");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateTooShort(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
{
|
||||||
|
double od = context.Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty;
|
||||||
|
|
||||||
|
// These are meant to reflect the duration necessary for auto to score at least 1000 points on the spinner.
|
||||||
|
// It's difficult to eliminate warnings here, as auto achieving 1000 points depends on the approach angle on some spinners.
|
||||||
|
double warningThreshold = 500 + (od < 5 ? (5 - od) * -21.8 : (od - 5) * 20); // Anything above this is always ok.
|
||||||
|
double problemThreshold = 450 + (od < 5 ? (5 - od) * -17 : (od - 5) * 17); // Anything below this is never ok.
|
||||||
|
|
||||||
|
foreach (var hitObject in context.Beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
if (!(hitObject is Spinner spinner))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (spinner.Duration < problemThreshold)
|
||||||
|
yield return new IssueTemplateTooShort(this).Create(spinner);
|
||||||
|
else if (spinner.Duration < warningThreshold)
|
||||||
|
yield return new IssueTemplateVeryShort(this).Create(spinner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateTooShort : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateTooShort(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "This spinner is too short. Auto cannot achieve 1000 points on this.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(Spinner spinner) => new Issue(spinner, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateVeryShort : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateVeryShort(ICheck check)
|
||||||
|
: base(check, IssueType.Warning, "This spinner may be too short. Ensure auto can achieve 1000 points on this.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(Spinner spinner) => new Issue(spinner, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
// Compose
|
// Compose
|
||||||
new CheckOffscreenObjects(),
|
new CheckOffscreenObjects(),
|
||||||
|
new CheckTooShortSpinners(),
|
||||||
|
|
||||||
// Spread
|
// Spread
|
||||||
new CheckTimeDistanceEquality(),
|
new CheckTimeDistanceEquality(),
|
||||||
new CheckLowDiffOverlaps()
|
new CheckLowDiffOverlaps(),
|
||||||
|
new CheckTooShortSliders(),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of previous hitobjects to be shifted together when another object is being moved.
|
||||||
|
/// </summary>
|
||||||
|
private const int preceding_hitobjects_to_shift = 10;
|
||||||
|
|
||||||
private Random rng;
|
private Random rng;
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
@ -49,8 +54,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
var current = new RandomObjectInfo(hitObject);
|
var current = new RandomObjectInfo(hitObject);
|
||||||
|
|
||||||
// rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams
|
// rateOfChangeMultiplier only changes every 5 iterations in a combo
|
||||||
if (i % 3 == 0)
|
// to prevent shaky-line-shaped streams
|
||||||
|
if (hitObject.IndexInCurrentCombo % 5 == 0)
|
||||||
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
|
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
|
||||||
|
|
||||||
if (hitObject is Spinner)
|
if (hitObject is Spinner)
|
||||||
@ -61,13 +67,35 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
applyRandomisation(rateOfChangeMultiplier, previous, current);
|
applyRandomisation(rateOfChangeMultiplier, previous, current);
|
||||||
|
|
||||||
hitObject.Position = current.PositionRandomised;
|
// Move hit objects back into the playfield if they are outside of it
|
||||||
|
Vector2 shift = Vector2.Zero;
|
||||||
|
|
||||||
// update end position as it may have changed as a result of the position update.
|
switch (hitObject)
|
||||||
current.EndPositionRandomised = current.PositionRandomised;
|
{
|
||||||
|
case HitCircle circle:
|
||||||
|
shift = clampHitCircleToPlayfield(circle, current);
|
||||||
|
break;
|
||||||
|
|
||||||
if (hitObject is Slider slider)
|
case Slider slider:
|
||||||
moveSliderIntoPlayfield(slider, current);
|
shift = clampSliderToPlayfield(slider, current);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shift != Vector2.Zero)
|
||||||
|
{
|
||||||
|
var toBeShifted = new List<OsuHitObject>();
|
||||||
|
|
||||||
|
for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--)
|
||||||
|
{
|
||||||
|
// only shift hit circles
|
||||||
|
if (!(hitObjects[j] is HitCircle)) break;
|
||||||
|
|
||||||
|
toBeShifted.Add(hitObjects[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toBeShifted.Count > 0)
|
||||||
|
applyDecreasingShift(toBeShifted, shift);
|
||||||
|
}
|
||||||
|
|
||||||
previous = current;
|
previous = current;
|
||||||
}
|
}
|
||||||
@ -94,7 +122,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
// The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
|
// The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
|
||||||
// is proportional to the distance between the last and the current hit object
|
// is proportional to the distance between the last and the current hit object
|
||||||
// to allow jumps and prevent too sharp turns during streams.
|
// to allow jumps and prevent too sharp turns during streams.
|
||||||
var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal;
|
|
||||||
|
// Allow maximum jump angle when jump distance is more than half of playfield diagonal length
|
||||||
|
var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, distanceToPrev / (playfield_diagonal * 0.5f));
|
||||||
|
|
||||||
current.AngleRad = (float)randomAngleRad + previous.AngleRad;
|
current.AngleRad = (float)randomAngleRad + previous.AngleRad;
|
||||||
if (current.AngleRad < 0)
|
if (current.AngleRad < 0)
|
||||||
@ -109,56 +139,120 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
|
current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
|
||||||
|
|
||||||
var position = previous.EndPositionRandomised + posRelativeToPrev;
|
current.PositionRandomised = previous.EndPositionRandomised + posRelativeToPrev;
|
||||||
|
}
|
||||||
|
|
||||||
// Move hit objects back into the playfield if they are outside of it,
|
/// <summary>
|
||||||
// which would sometimes happen during big jumps otherwise.
|
/// Move the randomised position of a hit circle so that it fits inside the playfield.
|
||||||
position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X);
|
/// </summary>
|
||||||
position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y);
|
/// <returns>The deviation from the original randomised position in order to fit within the playfield.</returns>
|
||||||
|
private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo)
|
||||||
|
{
|
||||||
|
var previousPosition = objectInfo.PositionRandomised;
|
||||||
|
objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding(
|
||||||
|
objectInfo.PositionRandomised,
|
||||||
|
(float)circle.Radius
|
||||||
|
);
|
||||||
|
|
||||||
current.PositionRandomised = position;
|
circle.Position = objectInfo.PositionRandomised;
|
||||||
|
|
||||||
|
return objectInfo.PositionRandomised - previousPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
|
/// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo)
|
/// <returns>The deviation from the original randomised position in order to fit within the playfield.</returns>
|
||||||
|
private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo)
|
||||||
{
|
{
|
||||||
var minMargin = getMinSliderMargin(slider);
|
var possibleMovementBounds = calculatePossibleMovementBounds(slider);
|
||||||
|
|
||||||
slider.Position = new Vector2(
|
var previousPosition = objectInfo.PositionRandomised;
|
||||||
Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right),
|
|
||||||
Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom)
|
|
||||||
);
|
|
||||||
|
|
||||||
currentObjectInfo.PositionRandomised = slider.Position;
|
// Clamp slider position to the placement area
|
||||||
currentObjectInfo.EndPositionRandomised = slider.EndPosition;
|
// If the slider is larger than the playfield, force it to stay at the original position
|
||||||
|
var newX = possibleMovementBounds.Width < 0
|
||||||
|
? objectInfo.PositionOriginal.X
|
||||||
|
: Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right);
|
||||||
|
|
||||||
shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal);
|
var newY = possibleMovementBounds.Height < 0
|
||||||
|
? objectInfo.PositionOriginal.Y
|
||||||
|
: Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom);
|
||||||
|
|
||||||
|
slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY);
|
||||||
|
objectInfo.EndPositionRandomised = slider.EndPosition;
|
||||||
|
|
||||||
|
shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal);
|
||||||
|
|
||||||
|
return objectInfo.PositionRandomised - previousPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the min. distances from the <see cref="Slider"/>'s position to the playfield border for the slider to be fully inside of the playfield.
|
/// Decreasingly shift a list of <see cref="OsuHitObject"/>s by a specified amount.
|
||||||
|
/// The first item in the list is shifted by the largest amount, while the last item is shifted by the smallest amount.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private MarginPadding getMinSliderMargin(Slider slider)
|
/// <param name="hitObjects">The list of hit objects to be shifted.</param>
|
||||||
|
/// <param name="shift">The amount to be shifted.</param>
|
||||||
|
private void applyDecreasingShift(IList<OsuHitObject> hitObjects, Vector2 shift)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < hitObjects.Count; i++)
|
||||||
|
{
|
||||||
|
var hitObject = hitObjects[i];
|
||||||
|
// The first object is shifted by a vector slightly smaller than shift
|
||||||
|
// The last object is shifted by a vector slightly larger than zero
|
||||||
|
Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1));
|
||||||
|
|
||||||
|
hitObject.Position = clampToPlayfieldWithPadding(position, (float)hitObject.Radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates a <see cref="RectangleF"/> which contains all of the possible movements of the slider (in relative X/Y coordinates)
|
||||||
|
/// such that the entire slider is inside the playfield.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the slider is larger than the playfield, the returned <see cref="RectangleF"/> may have negative width/height.
|
||||||
|
/// </remarks>
|
||||||
|
private RectangleF calculatePossibleMovementBounds(Slider slider)
|
||||||
{
|
{
|
||||||
var pathPositions = new List<Vector2>();
|
var pathPositions = new List<Vector2>();
|
||||||
slider.Path.GetPathToProgress(pathPositions, 0, 1);
|
slider.Path.GetPathToProgress(pathPositions, 0, 1);
|
||||||
|
|
||||||
var minMargin = new MarginPadding();
|
float minX = float.PositiveInfinity;
|
||||||
|
float maxX = float.NegativeInfinity;
|
||||||
|
|
||||||
|
float minY = float.PositiveInfinity;
|
||||||
|
float maxY = float.NegativeInfinity;
|
||||||
|
|
||||||
|
// Compute the bounding box of the slider.
|
||||||
foreach (var pos in pathPositions)
|
foreach (var pos in pathPositions)
|
||||||
{
|
{
|
||||||
minMargin.Left = Math.Max(minMargin.Left, -pos.X);
|
minX = MathF.Min(minX, pos.X);
|
||||||
minMargin.Right = Math.Max(minMargin.Right, pos.X);
|
maxX = MathF.Max(maxX, pos.X);
|
||||||
minMargin.Top = Math.Max(minMargin.Top, -pos.Y);
|
|
||||||
minMargin.Bottom = Math.Max(minMargin.Bottom, pos.Y);
|
minY = MathF.Min(minY, pos.Y);
|
||||||
|
maxY = MathF.Max(maxY, pos.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right);
|
// Take the circle radius into account.
|
||||||
minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom);
|
var radius = (float)slider.Radius;
|
||||||
|
|
||||||
return minMargin;
|
minX -= radius;
|
||||||
|
minY -= radius;
|
||||||
|
|
||||||
|
maxX += radius;
|
||||||
|
maxY += radius;
|
||||||
|
|
||||||
|
// Given the bounding box of the slider (via min/max X/Y),
|
||||||
|
// the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right),
|
||||||
|
// and the amount that it can move to the right is WIDTH - maxX.
|
||||||
|
// Same calculation applies for the Y axis.
|
||||||
|
float left = -minX;
|
||||||
|
float right = OsuPlayfield.BASE_SIZE.X - maxX;
|
||||||
|
float top = -minY;
|
||||||
|
float bottom = OsuPlayfield.BASE_SIZE.Y - maxY;
|
||||||
|
|
||||||
|
return new RectangleF(left, top, right - left, bottom - top);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -177,6 +271,20 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clamp a position to playfield, keeping a specified distance from the edges.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The position to be clamped.</param>
|
||||||
|
/// <param name="padding">The minimum distance allowed from playfield edges.</param>
|
||||||
|
/// <returns>The clamped position.</returns>
|
||||||
|
private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding)
|
||||||
|
{
|
||||||
|
return new Vector2(
|
||||||
|
Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding),
|
||||||
|
Math.Clamp(position.Y, padding, OsuPlayfield.BASE_SIZE.Y - padding)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private class RandomObjectInfo
|
private class RandomObjectInfo
|
||||||
{
|
{
|
||||||
public float AngleRad { get; set; }
|
public float AngleRad { get; set; }
|
||||||
|
94
osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs
Normal file
94
osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckZeroLengthObjectsTest
|
||||||
|
{
|
||||||
|
private CheckZeroLengthObjects check;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckZeroLengthObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircle()
|
||||||
|
{
|
||||||
|
assertOk(new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(0, 0) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRegularSlider()
|
||||||
|
{
|
||||||
|
assertOk(new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(1000).Object
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestZeroLengthSlider()
|
||||||
|
{
|
||||||
|
assertZeroLength(new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(0).Object
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNegativeLengthSlider()
|
||||||
|
{
|
||||||
|
assertZeroLength(new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(-1000).Object
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mock<Slider> getSliderMock(double duration)
|
||||||
|
{
|
||||||
|
var mockSlider = new Mock<Slider>();
|
||||||
|
mockSlider.As<IHasDuration>().Setup(d => d.Duration).Returns(duration);
|
||||||
|
|
||||||
|
return mockSlider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOk(List<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertZeroLength(List<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(hitObjects)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.First().Template is CheckZeroLengthObjects.IssueTemplateZeroLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(List<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject> { HitObjects = hitObjects };
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
@ -184,6 +185,26 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen());
|
AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSubScreenExitedWhenDisconnectedFromMultiplayerServer()
|
||||||
|
{
|
||||||
|
createRoom(() => new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Test Room" },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||||
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("disconnect", () => client.Disconnect());
|
||||||
|
AddUntilStep("back in lounge", () => this.ChildrenOfType<LoungeSubScreen>().FirstOrDefault()?.IsCurrentScreen() == true);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLeaveNavigation()
|
public void TestLeaveNavigation()
|
||||||
{
|
{
|
||||||
|
@ -95,6 +95,15 @@ _**italic with underscore, bold with asterisk**_";
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAutoLink()
|
||||||
|
{
|
||||||
|
AddStep("Add autolink", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = "<https://discord.gg/ppy>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestInlineCode()
|
public void TestInlineCode()
|
||||||
{
|
{
|
||||||
|
103
osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs
Normal file
103
osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneOsuPopover : OsuGridTestScene
|
||||||
|
{
|
||||||
|
public TestSceneOsuPopover()
|
||||||
|
: base(1, 2)
|
||||||
|
{
|
||||||
|
Cell(0, 0).Child = new PopoverContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = @"No OverlayColourProvider",
|
||||||
|
Font = OsuFont.Default.With(size: 40)
|
||||||
|
},
|
||||||
|
new TriangleButtonWithPopover()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Cell(0, 1).Child = new ColourProvidingContainer(OverlayColourScheme.Orange)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new PopoverContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = @"With OverlayColourProvider (orange)",
|
||||||
|
Font = OsuFont.Default.With(size: 40)
|
||||||
|
},
|
||||||
|
new TriangleButtonWithPopover()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TriangleButtonWithPopover : TriangleButton, IHasPopover
|
||||||
|
{
|
||||||
|
public TriangleButtonWithPopover()
|
||||||
|
{
|
||||||
|
Width = 100;
|
||||||
|
Height = 30;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Text = @"open";
|
||||||
|
Action = this.ShowPopover;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Popover GetPopover() => new OsuPopover
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = @"sample text"
|
||||||
|
},
|
||||||
|
new OsuTextBox
|
||||||
|
{
|
||||||
|
Width = 150,
|
||||||
|
Height = 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ColourProvidingContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider provider { get; }
|
||||||
|
|
||||||
|
public ColourProvidingContainer(OverlayColourScheme colourScheme)
|
||||||
|
{
|
||||||
|
provider = new OverlayColourProvider(colourScheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,12 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
title = linkInline.Title;
|
title = linkInline.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OsuMarkdownLinkText(AutolinkInline autolinkInline)
|
||||||
|
: base(autolinkInline)
|
||||||
|
{
|
||||||
|
text = autolinkInline.Url;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,9 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
protected override void AddLinkText(string text, LinkInline linkInline)
|
protected override void AddLinkText(string text, LinkInline linkInline)
|
||||||
=> AddDrawable(new OsuMarkdownLinkText(text, linkInline));
|
=> AddDrawable(new OsuMarkdownLinkText(text, linkInline));
|
||||||
|
|
||||||
|
protected override void AddAutoLink(AutolinkInline autolinkInline)
|
||||||
|
=> AddDrawable(new OsuMarkdownLinkText(autolinkInline));
|
||||||
|
|
||||||
protected override void AddImage(LinkInline linkInline) => AddDrawable(new OsuMarkdownImage(linkInline));
|
protected override void AddImage(LinkInline linkInline) => AddDrawable(new OsuMarkdownImage(linkInline));
|
||||||
|
|
||||||
// TODO : Change font to monospace
|
// TODO : Change font to monospace
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -198,8 +199,14 @@ namespace osu.Game.Graphics
|
|||||||
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
|
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
|
||||||
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
|
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
|
||||||
|
|
||||||
// in latest editor design logic, need to figure out where these sit...
|
/// <summary>
|
||||||
|
/// Equivalent to <see cref="OverlayColourProvider.Lime"/>'s <see cref="OverlayColourProvider.Colour1"/>.
|
||||||
|
/// </summary>
|
||||||
public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66");
|
public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to <see cref="OverlayColourProvider.Orange"/>'s <see cref="OverlayColourProvider.Colour1"/>.
|
||||||
|
/// </summary>
|
||||||
public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966");
|
public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966");
|
||||||
|
|
||||||
// Content Background
|
// Content Background
|
||||||
|
55
osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
Normal file
55
osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public class OsuPopover : Popover
|
||||||
|
{
|
||||||
|
private const float fade_duration = 250;
|
||||||
|
private const double scale_duration = 500;
|
||||||
|
|
||||||
|
public OsuPopover(bool withPadding = true)
|
||||||
|
{
|
||||||
|
Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding();
|
||||||
|
|
||||||
|
Body.Masking = true;
|
||||||
|
Body.CornerRadius = 10;
|
||||||
|
Body.Margin = new MarginPadding(10);
|
||||||
|
Body.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Offset = new Vector2(0, 2),
|
||||||
|
Radius = 5,
|
||||||
|
Colour = Colour4.Black.Opacity(0.3f)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
|
{
|
||||||
|
Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeafoamDarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateArrow() => Empty();
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
this.ScaleTo(1, scale_duration, Easing.OutElasticHalf);
|
||||||
|
this.FadeIn(fade_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
this.ScaleTo(0.7f, scale_duration, Easing.OutQuint);
|
||||||
|
this.FadeOut(fade_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -525,16 +525,11 @@ namespace osu.Game
|
|||||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
|
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
|
||||||
{
|
{
|
||||||
beatmap.OldValue?.CancelAsyncLoad();
|
beatmap.OldValue?.CancelAsyncLoad();
|
||||||
|
|
||||||
updateModDefaults();
|
|
||||||
|
|
||||||
beatmap.NewValue?.BeginAsyncLoad();
|
beatmap.NewValue?.BeginAsyncLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||||
{
|
{
|
||||||
updateModDefaults();
|
|
||||||
|
|
||||||
// a lease may be taken on the mods bindable, at which point we can't really ensure valid mods.
|
// a lease may be taken on the mods bindable, at which point we can't really ensure valid mods.
|
||||||
if (SelectedMods.Disabled)
|
if (SelectedMods.Disabled)
|
||||||
return;
|
return;
|
||||||
@ -546,19 +541,6 @@ namespace osu.Game
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateModDefaults()
|
|
||||||
{
|
|
||||||
BeatmapDifficulty baseDifficulty = Beatmap.Value.BeatmapInfo.BaseDifficulty;
|
|
||||||
|
|
||||||
if (baseDifficulty != null && SelectedMods.Value.Any(m => m is IApplicableToDifficulty))
|
|
||||||
{
|
|
||||||
var adjustedDifficulty = baseDifficulty.Clone();
|
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDifficulty>())
|
|
||||||
mod.ReadFromDifficulty(adjustedDifficulty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private PerformFromMenuRunner performFromMainMenuTask;
|
private PerformFromMenuRunner performFromMainMenuTask;
|
||||||
|
@ -14,6 +14,7 @@ namespace osu.Game.Overlays
|
|||||||
public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red);
|
public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red);
|
||||||
public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink);
|
public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||||
public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange);
|
public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange);
|
||||||
|
public static OverlayColourProvider Lime { get; } = new OverlayColourProvider(OverlayColourScheme.Lime);
|
||||||
public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
|
public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple);
|
public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue);
|
public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
@ -51,7 +52,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1));
|
private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1));
|
||||||
|
|
||||||
// See https://github.com/ppy/osu-web/blob/4218c288292d7c810b619075471eaea8bbb8f9d8/app/helpers.php#L1463
|
// See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628
|
||||||
private static float getBaseHue(OverlayColourScheme colourScheme)
|
private static float getBaseHue(OverlayColourScheme colourScheme)
|
||||||
{
|
{
|
||||||
switch (colourScheme)
|
switch (colourScheme)
|
||||||
@ -66,10 +67,13 @@ namespace osu.Game.Overlays
|
|||||||
return 333 / 360f;
|
return 333 / 360f;
|
||||||
|
|
||||||
case OverlayColourScheme.Orange:
|
case OverlayColourScheme.Orange:
|
||||||
return 46 / 360f;
|
return 45 / 360f;
|
||||||
|
|
||||||
|
case OverlayColourScheme.Lime:
|
||||||
|
return 90 / 360f;
|
||||||
|
|
||||||
case OverlayColourScheme.Green:
|
case OverlayColourScheme.Green:
|
||||||
return 115 / 360f;
|
return 125 / 360f;
|
||||||
|
|
||||||
case OverlayColourScheme.Purple:
|
case OverlayColourScheme.Purple:
|
||||||
return 255 / 360f;
|
return 255 / 360f;
|
||||||
@ -85,6 +89,7 @@ namespace osu.Game.Overlays
|
|||||||
Red,
|
Red,
|
||||||
Pink,
|
Pink,
|
||||||
Orange,
|
Orange,
|
||||||
|
Lime,
|
||||||
Green,
|
Green,
|
||||||
Purple,
|
Purple,
|
||||||
Blue
|
Blue
|
||||||
|
@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
new CheckUnsnappedObjects(),
|
new CheckUnsnappedObjects(),
|
||||||
new CheckConcurrentObjects()
|
new CheckConcurrentObjects(),
|
||||||
|
new CheckZeroLengthObjects(),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
47
osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs
Normal file
47
osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckZeroLengthObjects : ICheck
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The duration can be this low before being treated as having no length, in case of precision errors. Unit is milliseconds.
|
||||||
|
/// </summary>
|
||||||
|
private const double leniency = 0.5d;
|
||||||
|
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Zero-length hitobjects");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateZeroLength(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
{
|
||||||
|
foreach (var hitObject in context.Beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
if (!(hitObject is IHasDuration hasDuration))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (hasDuration.Duration < leniency)
|
||||||
|
yield return new IssueTemplateZeroLength(this).Create(hitObject, hasDuration.Duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateZeroLength : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateZeroLength(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "{0} has a duration of {1:0}.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(HitObject hitobject, double duration) => new Issue(hitobject, this, hitobject.GetType(), duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,13 +10,6 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IApplicableToDifficulty : IApplicableMod
|
public interface IApplicableToDifficulty : IApplicableMod
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Called when a beatmap is changed. Can be used to read default values.
|
|
||||||
/// Any changes made will not be preserved.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="difficulty">The difficulty to read from.</param>
|
|
||||||
void ReadFromDifficulty(BeatmapDifficulty difficulty);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called post beatmap conversion. Can be used to apply changes to difficulty attributes.
|
/// Called post beatmap conversion. Can be used to apply changes to difficulty attributes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -72,25 +71,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
var localUser = Client.LocalUser;
|
var localUser = Client.LocalUser;
|
||||||
|
|
||||||
if (localUser == null)
|
int newCountReady = Room?.Users.Count(u => u.State == MultiplayerUserState.Ready) ?? 0;
|
||||||
return;
|
int newCountTotal = Room?.Users.Count(u => u.State != MultiplayerUserState.Spectating) ?? 0;
|
||||||
|
|
||||||
Debug.Assert(Room != null);
|
switch (localUser?.State)
|
||||||
|
|
||||||
int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
|
||||||
int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
|
||||||
|
|
||||||
string countText = $"({newCountReady} / {newCountTotal} ready)";
|
|
||||||
|
|
||||||
switch (localUser.State)
|
|
||||||
{
|
{
|
||||||
case MultiplayerUserState.Idle:
|
default:
|
||||||
button.Text = "Ready";
|
button.Text = "Ready";
|
||||||
updateButtonColour(true);
|
updateButtonColour(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
case MultiplayerUserState.Ready:
|
case MultiplayerUserState.Ready:
|
||||||
|
string countText = $"({newCountReady} / {newCountTotal} ready)";
|
||||||
|
|
||||||
if (Room?.Host?.Equals(localUser) == true)
|
if (Room?.Host?.Equals(localUser) == true)
|
||||||
{
|
{
|
||||||
button.Text = $"Start match {countText}";
|
button.Text = $"Start match {countText}";
|
||||||
@ -108,7 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
bool enableButton = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
bool enableButton = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
||||||
|
|
||||||
// When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready.
|
// When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready.
|
||||||
if (localUser.State == MultiplayerUserState.Spectating)
|
if (localUser?.State == MultiplayerUserState.Spectating)
|
||||||
enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0;
|
enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0;
|
||||||
|
|
||||||
button.Enabled.Value = enableButton;
|
button.Enabled.Value = enableButton;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -57,14 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
var localUser = Client.LocalUser;
|
switch (Client.LocalUser?.State)
|
||||||
|
|
||||||
if (localUser == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Debug.Assert(Room != null);
|
|
||||||
|
|
||||||
switch (localUser.State)
|
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
button.Text = "Spectate";
|
button.Text = "Spectate";
|
||||||
@ -81,7 +73,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.Enabled.Value = Client.Room?.State != MultiplayerRoomState.Closed && !operationInProgress.Value;
|
button.Enabled.Value = Client.Room != null
|
||||||
|
&& Client.Room.State != MultiplayerRoomState.Closed
|
||||||
|
&& !operationInProgress.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ButtonWithTrianglesExposed : TriangleButton
|
private class ButtonWithTrianglesExposed : TriangleButton
|
||||||
|
@ -48,6 +48,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<Room> currentRoom { get; set; }
|
||||||
|
|
||||||
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
||||||
|
|
||||||
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
||||||
@ -273,6 +276,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
if (!connected.NewValue)
|
if (!connected.NewValue)
|
||||||
Schedule(this.Exit);
|
Schedule(this.Exit);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
currentRoom.BindValueChanged(room =>
|
||||||
|
{
|
||||||
|
if (room.NewValue == null)
|
||||||
|
{
|
||||||
|
// the room has gone away.
|
||||||
|
// this could mean something happened during the join process, or an external connection issue occurred.
|
||||||
|
// one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97)
|
||||||
|
Schedule(this.Exit);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateMods()
|
protected override void UpdateMods()
|
||||||
@ -310,7 +324,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
if (client.Room == null)
|
// the room may not be left immediately after a disconnection due to async flow,
|
||||||
|
// so checking the IsConnected status is also required.
|
||||||
|
if (client.Room == null || !client.IsConnected.Value)
|
||||||
{
|
{
|
||||||
// room has not been created yet; exit immediately.
|
// room has not been created yet; exit immediately.
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.3.0" />
|
<PackageReference Include="Realm" Version="10.3.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.713.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.714.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.6.0" />
|
<PackageReference Include="Sentry" Version="3.6.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.713.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.714.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.713.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.714.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user