mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 10:07:52 +08:00
Merge branch 'master' into taiko-hitsounding-drum-sample-player
This commit is contained in:
commit
4bb65a54b8
@ -18,6 +18,7 @@ using osu.Game.Extensions;
|
|||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using Realms;
|
using Realms;
|
||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
@ -416,6 +417,53 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestImport_ThenModifyMapWithScore_ThenImport()
|
||||||
|
{
|
||||||
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
|
{
|
||||||
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
|
await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
|
||||||
|
|
||||||
|
// imitate making local changes via editor
|
||||||
|
// ReSharper disable once MethodHasAsyncOverload
|
||||||
|
realm.Write(_ =>
|
||||||
|
{
|
||||||
|
BeatmapInfo beatmap = imported.Beatmaps.First();
|
||||||
|
beatmap.Hash = "new_hash";
|
||||||
|
beatmap.ResetOnlineInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
// for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap.
|
||||||
|
// the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (see: https://github.com/ppy/osu/pull/22539).
|
||||||
|
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
|
||||||
|
Assert.That(imported.Beatmaps.First().Scores.Any());
|
||||||
|
|
||||||
|
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
||||||
|
|
||||||
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
|
// check the newly "imported" beatmap is not the original.
|
||||||
|
Assert.NotNull(importedSecondTime);
|
||||||
|
Debug.Assert(importedSecondTime != null);
|
||||||
|
Assert.That(imported.ID != importedSecondTime.ID);
|
||||||
|
|
||||||
|
var importedFirstTimeBeatmap = imported.Beatmaps.First();
|
||||||
|
var importedSecondTimeBeatmap = importedSecondTime.PerformRead(s => s.Beatmaps.First());
|
||||||
|
|
||||||
|
Assert.That(importedFirstTimeBeatmap.ID != importedSecondTimeBeatmap.ID);
|
||||||
|
Assert.That(importedFirstTimeBeatmap.Hash != importedSecondTimeBeatmap.Hash);
|
||||||
|
Assert.That(!importedFirstTimeBeatmap.Scores.Any());
|
||||||
|
Assert.That(importedSecondTimeBeatmap.Scores.Count() == 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenImportWithChangedFile()
|
public void TestImportThenImportWithChangedFile()
|
||||||
{
|
{
|
||||||
@ -1074,18 +1122,16 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.IsTrue(realm.All<BeatmapSetInfo>().First(_ => true).DeletePending);
|
Assert.IsTrue(realm.All<BeatmapSetInfo>().First(_ => true).DeletePending);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap)
|
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap) =>
|
||||||
|
realm.WriteAsync(() =>
|
||||||
{
|
{
|
||||||
// TODO: reimplement when we have score support in realm.
|
realm.Add(new ScoreInfo
|
||||||
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
|
{
|
||||||
// {
|
OnlineID = 2,
|
||||||
// OnlineID = 2,
|
BeatmapInfo = beatmap,
|
||||||
// Beatmap = beatmap,
|
BeatmapHash = beatmap.Hash
|
||||||
// BeatmapInfoID = beatmap.ID
|
});
|
||||||
// }, new ImportScoreTest.TestArchiveReader());
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
|
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
|
||||||
{
|
{
|
||||||
|
@ -347,6 +347,73 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDanglingScoreTransferred()
|
||||||
|
{
|
||||||
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
|
{
|
||||||
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
|
using var __ = getBeatmapArchive(out string pathOriginal);
|
||||||
|
using var _ = getBeatmapArchive(out string pathOnlineCopy);
|
||||||
|
|
||||||
|
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
|
||||||
|
|
||||||
|
Assert.That(importBeforeUpdate, Is.Not.Null);
|
||||||
|
Debug.Assert(importBeforeUpdate != null);
|
||||||
|
|
||||||
|
string scoreTargetBeatmapHash = string.Empty;
|
||||||
|
|
||||||
|
// set a score on the beatmap
|
||||||
|
importBeforeUpdate.PerformWrite(s =>
|
||||||
|
{
|
||||||
|
var beatmapInfo = s.Beatmaps.First();
|
||||||
|
|
||||||
|
scoreTargetBeatmapHash = beatmapInfo.Hash;
|
||||||
|
|
||||||
|
s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All<RulesetInfo>().First(), new RealmUser()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// locally modify beatmap
|
||||||
|
const string new_beatmap_hash = "new_hash";
|
||||||
|
importBeforeUpdate.PerformWrite(s =>
|
||||||
|
{
|
||||||
|
var beatmapInfo = s.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash);
|
||||||
|
|
||||||
|
beatmapInfo.Hash = new_beatmap_hash;
|
||||||
|
beatmapInfo.ResetOnlineInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
// for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap.
|
||||||
|
// the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (https://github.com/ppy/osu/pull/22539).
|
||||||
|
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
|
||||||
|
checkCount<ScoreInfo>(realm, 1);
|
||||||
|
|
||||||
|
// reimport the original beatmap before local modifications
|
||||||
|
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOnlineCopy), importBeforeUpdate.Value);
|
||||||
|
|
||||||
|
Assert.That(importAfterUpdate, Is.Not.Null);
|
||||||
|
Debug.Assert(importAfterUpdate != null);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
// both original and locally modified versions present
|
||||||
|
checkCount<BeatmapInfo>(realm, count_beatmaps + 1);
|
||||||
|
checkCount<BeatmapMetadata>(realm, count_beatmaps + 1);
|
||||||
|
checkCount<BeatmapSetInfo>(realm, 2);
|
||||||
|
|
||||||
|
// score is preserved
|
||||||
|
checkCount<ScoreInfo>(realm, 1);
|
||||||
|
|
||||||
|
// score is transferred to new beatmap
|
||||||
|
Assert.That(importBeforeUpdate.Value.Beatmaps.First(b => b.Hash == new_beatmap_hash).Scores, Has.Count.EqualTo(0));
|
||||||
|
Assert.That(importAfterUpdate.Value.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash).Scores, Has.Count.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScoreLostOnModification()
|
public void TestScoreLostOnModification()
|
||||||
{
|
{
|
||||||
|
@ -170,7 +170,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
ManualClock clock = null;
|
ManualClock clock = null;
|
||||||
|
|
||||||
var beatmap = new Beatmap();
|
var beatmap = new Beatmap();
|
||||||
beatmap.HitObjects.Add(new TestHitObjectWithNested { Duration = 40 });
|
beatmap.HitObjects.Add(new TestHitObjectWithNested
|
||||||
|
{
|
||||||
|
Duration = 40,
|
||||||
|
NestedObjects = new HitObject[]
|
||||||
|
{
|
||||||
|
new PooledNestedHitObject { StartTime = 10 },
|
||||||
|
new PooledNestedHitObject { StartTime = 20 },
|
||||||
|
new PooledNestedHitObject { StartTime = 30 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
|
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
|
||||||
|
|
||||||
@ -209,6 +218,49 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("object judged", () => playfield.JudgedObjects.Count == 1);
|
AddAssert("object judged", () => playfield.JudgedObjects.Count == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPooledObjectWithNonPooledNesteds()
|
||||||
|
{
|
||||||
|
ManualClock clock = null;
|
||||||
|
TestHitObjectWithNested hitObjectWithNested;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap();
|
||||||
|
beatmap.HitObjects.Add(hitObjectWithNested = new TestHitObjectWithNested
|
||||||
|
{
|
||||||
|
Duration = 40,
|
||||||
|
NestedObjects = new HitObject[]
|
||||||
|
{
|
||||||
|
new PooledNestedHitObject { StartTime = 10 },
|
||||||
|
new NonPooledNestedHitObject { StartTime = 20 },
|
||||||
|
new NonPooledNestedHitObject { StartTime = 30 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
|
||||||
|
|
||||||
|
AddAssert("hitobject entry has all nesteds", () => playfield.HitObjectContainer.Entries.Single().NestedEntries, () => Has.Count.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("skip to middle of object", () => clock.CurrentTime = (hitObjectWithNested.StartTime + hitObjectWithNested.GetEndTime()) / 2);
|
||||||
|
AddAssert("2 objects judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(2));
|
||||||
|
AddAssert("entry not all judged", () => playfield.HitObjectContainer.Entries.Single().AllJudged, () => Is.False);
|
||||||
|
|
||||||
|
AddStep("skip to before end of object", () => clock.CurrentTime = hitObjectWithNested.GetEndTime() - 1);
|
||||||
|
AddAssert("3 objects judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
|
||||||
|
AddAssert("entry not all judged", () => playfield.HitObjectContainer.Entries.Single().AllJudged, () => Is.False);
|
||||||
|
|
||||||
|
AddStep("removing object doesn't crash", () => playfield.Remove(hitObjectWithNested));
|
||||||
|
AddStep("clear judged", () => playfield.JudgedObjects.Clear());
|
||||||
|
|
||||||
|
AddStep("add object back", () => playfield.Add(hitObjectWithNested));
|
||||||
|
AddAssert("entry not all judged", () => playfield.HitObjectContainer.Entries.Single().AllJudged, () => Is.False);
|
||||||
|
|
||||||
|
AddStep("skip to long past object", () => clock.CurrentTime = 100_000);
|
||||||
|
// the parent entry should still be linked to nested entries of pooled objects that are managed externally
|
||||||
|
// but not contain synthetic entries that were created for the non-pooled objects.
|
||||||
|
AddAssert("entry still has non-synthetic nested entries", () => playfield.HitObjectContainer.Entries.Single().NestedEntries, () => Has.Count.EqualTo(1));
|
||||||
|
AddAssert("entry all judged", () => playfield.HitObjectContainer.Entries.Single().AllJudged, () => Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null)
|
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null)
|
||||||
{
|
{
|
||||||
AddStep("create test", () =>
|
AddStep("create test", () =>
|
||||||
@ -289,7 +341,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
|
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
|
||||||
RegisterPool<TestKilledHitObject, DrawableTestKilledHitObject>(poolSize);
|
RegisterPool<TestKilledHitObject, DrawableTestKilledHitObject>(poolSize);
|
||||||
RegisterPool<TestHitObjectWithNested, DrawableTestHitObjectWithNested>(poolSize);
|
RegisterPool<TestHitObjectWithNested, DrawableTestHitObjectWithNested>(poolSize);
|
||||||
RegisterPool<NestedHitObject, DrawableNestedHitObject>(poolSize);
|
RegisterPool<PooledNestedHitObject, DrawableNestedHitObject>(poolSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
||||||
@ -422,16 +474,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private class TestHitObjectWithNested : TestHitObject
|
private class TestHitObjectWithNested : TestHitObject
|
||||||
{
|
{
|
||||||
|
public IEnumerable<HitObject> NestedObjects { get; init; } = Array.Empty<HitObject>();
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
for (int i = 0; i < 3; ++i)
|
foreach (var ho in NestedObjects)
|
||||||
AddNested(new NestedHitObject { StartTime = (float)Duration * (i + 1) / 4 });
|
AddNested(ho);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NestedHitObject : ConvertHitObject
|
private class PooledNestedHitObject : ConvertHitObject
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonPooledNestedHitObject : ConvertHitObject
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,6 +540,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
nestedContainer.Clear(false);
|
nestedContainer.Clear(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
|
=> hitObject is NonPooledNestedHitObject nonPooled ? new DrawableNestedHitObject(nonPooled) : null;
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
base.CheckForResult(userTriggered, timeOffset);
|
base.CheckForResult(userTriggered, timeOffset);
|
||||||
@ -490,25 +551,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class DrawableNestedHitObject : DrawableHitObject<NestedHitObject>
|
private partial class DrawableNestedHitObject : DrawableHitObject
|
||||||
{
|
{
|
||||||
public DrawableNestedHitObject()
|
public DrawableNestedHitObject()
|
||||||
: this(null)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableNestedHitObject(NestedHitObject hitObject)
|
public DrawableNestedHitObject(PooledNestedHitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawableNestedHitObject(NonPooledNestedHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
Size = new Vector2(15);
|
|
||||||
Colour = Colour4.White;
|
|
||||||
RelativePositionAxes = Axes.Both;
|
|
||||||
Origin = Anchor.Centre;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
Size = new Vector2(15);
|
||||||
|
Colour = Colour4.White;
|
||||||
|
RelativePositionAxes = Axes.Both;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
@ -107,6 +107,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBeatmapDownloadingStates()
|
public void TestBeatmapDownloadingStates()
|
||||||
{
|
{
|
||||||
|
AddStep("set to unknown", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Unknown()));
|
||||||
AddStep("set to no map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()));
|
AddStep("set to no map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()));
|
||||||
AddStep("set to downloading map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
|
AddStep("set to downloading map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
|
||||||
|
|
||||||
@ -382,6 +383,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);
|
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);
|
||||||
|
|
||||||
|
AddStep("set beatmap available", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkProgressBarVisibility(bool visible) =>
|
private void checkProgressBarVisibility(bool visible) =>
|
||||||
|
@ -20,6 +20,7 @@ using osu.Game.IO;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -204,6 +205,15 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
|
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
|
||||||
{
|
{
|
||||||
base.PostImport(model, realm, parameters);
|
base.PostImport(model, realm, parameters);
|
||||||
|
|
||||||
|
// Scores are stored separately from beatmaps, and persist even when a beatmap is modified or deleted.
|
||||||
|
// Let's reattach any matching scores that exist in the database, based on hash.
|
||||||
|
foreach (BeatmapInfo beatmap in model.Beatmaps)
|
||||||
|
{
|
||||||
|
foreach (var score in realm.All<ScoreInfo>().Where(score => score.BeatmapHash == beatmap.Hash))
|
||||||
|
score.BeatmapInfo = beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst);
|
ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Collections
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
X = -OsuScrollContainer.SCROLL_BAR_HEIGHT,
|
X = -OsuScrollContainer.SCROLL_BAR_WIDTH,
|
||||||
Scale = new Vector2(0.65f),
|
Scale = new Vector2(0.65f),
|
||||||
Action = addOrRemove,
|
Action = addOrRemove,
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
public partial class OsuScrollContainer<T> : ScrollContainer<T> where T : Drawable
|
public partial class OsuScrollContainer<T> : ScrollContainer<T> where T : Drawable
|
||||||
{
|
{
|
||||||
public const float SCROLL_BAR_HEIGHT = 10;
|
public const float SCROLL_BAR_WIDTH = 10;
|
||||||
public const float SCROLL_BAR_PADDING = 3;
|
public const float SCROLL_BAR_PADDING = 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -139,6 +139,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
private readonly Box box;
|
private readonly Box box;
|
||||||
|
|
||||||
|
protected override float MinimumDimSize => SCROLL_BAR_WIDTH * 3;
|
||||||
|
|
||||||
public OsuScrollbar(Direction scrollDir)
|
public OsuScrollbar(Direction scrollDir)
|
||||||
: base(scrollDir)
|
: base(scrollDir)
|
||||||
{
|
{
|
||||||
@ -147,7 +149,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
CornerRadius = 5;
|
CornerRadius = 5;
|
||||||
|
|
||||||
// needs to be set initially for the ResizeTo to respect minimum size
|
// needs to be set initially for the ResizeTo to respect minimum size
|
||||||
Size = new Vector2(SCROLL_BAR_HEIGHT);
|
Size = new Vector2(SCROLL_BAR_WIDTH);
|
||||||
|
|
||||||
const float margin = 3;
|
const float margin = 3;
|
||||||
|
|
||||||
@ -173,11 +175,10 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None)
|
public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None)
|
||||||
{
|
{
|
||||||
Vector2 size = new Vector2(SCROLL_BAR_HEIGHT)
|
this.ResizeTo(new Vector2(SCROLL_BAR_WIDTH)
|
||||||
{
|
{
|
||||||
[(int)ScrollDirection] = val
|
[(int)ScrollDirection] = val
|
||||||
};
|
}, duration, easing);
|
||||||
this.ResizeTo(size, duration, easing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SignedIn => new TranslatableString(getKey(@"signed_in"), @"Signed in");
|
public static LocalisableString SignedIn => new TranslatableString(getKey(@"signed_in"), @"Signed in");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Sign out"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SignOut => new TranslatableString(getKey(@"sign_out"), @"Sign out");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Account"
|
/// "Account"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// The availability state of the current beatmap.
|
/// The availability state of the current beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Key(2)]
|
[Key(2)]
|
||||||
public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable();
|
public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.Unknown();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Any mods applicable only to the local user.
|
/// Any mods applicable only to the local user.
|
||||||
|
@ -34,6 +34,7 @@ namespace osu.Game.Online.Rooms
|
|||||||
DownloadProgress = downloadProgress;
|
DownloadProgress = downloadProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static BeatmapAvailability Unknown() => new BeatmapAvailability(DownloadState.Unknown);
|
||||||
public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded);
|
public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded);
|
||||||
public static BeatmapAvailability Downloading(float progress) => new BeatmapAvailability(DownloadState.Downloading, progress);
|
public static BeatmapAvailability Downloading(float progress) => new BeatmapAvailability(DownloadState.Downloading, progress);
|
||||||
public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing);
|
public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing);
|
||||||
|
@ -60,6 +60,15 @@ namespace osu.Game.Online.Rooms
|
|||||||
if (item.NewValue == null)
|
if (item.NewValue == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Initially set to unknown until we have attained a good state.
|
||||||
|
// This has the wanted side effect of forcing a state change when the current playlist
|
||||||
|
// item changes at the server but our local availability doesn't necessarily change
|
||||||
|
// (ie. we have both the previous and next item LocallyAvailable).
|
||||||
|
//
|
||||||
|
// Note that even without this, the server will trigger a state change and things will work.
|
||||||
|
// This is just for safety.
|
||||||
|
availability.Value = BeatmapAvailability.Unknown();
|
||||||
|
|
||||||
downloadTracker?.RemoveAndDisposeImmediately();
|
downloadTracker?.RemoveAndDisposeImmediately();
|
||||||
selectedBeatmap = null;
|
selectedBeatmap = null;
|
||||||
|
|
||||||
@ -115,6 +124,9 @@ namespace osu.Game.Online.Rooms
|
|||||||
switch (downloadTracker.State.Value)
|
switch (downloadTracker.State.Value)
|
||||||
{
|
{
|
||||||
case DownloadState.Unknown:
|
case DownloadState.Unknown:
|
||||||
|
availability.Value = BeatmapAvailability.Unknown();
|
||||||
|
break;
|
||||||
|
|
||||||
case DownloadState.NotDownloaded:
|
case DownloadState.NotDownloaded:
|
||||||
availability.Value = BeatmapAvailability.NotDownloaded();
|
availability.Value = BeatmapAvailability.NotDownloaded();
|
||||||
break;
|
break;
|
||||||
|
@ -1164,7 +1164,9 @@ namespace osu.Game
|
|||||||
private void forwardTabletLogsToNotifications()
|
private void forwardTabletLogsToNotifications()
|
||||||
{
|
{
|
||||||
const string tablet_prefix = @"[Tablet] ";
|
const string tablet_prefix = @"[Tablet] ";
|
||||||
|
|
||||||
bool notifyOnWarning = true;
|
bool notifyOnWarning = true;
|
||||||
|
bool notifyOnError = true;
|
||||||
|
|
||||||
Logger.NewEntry += entry =>
|
Logger.NewEntry += entry =>
|
||||||
{
|
{
|
||||||
@ -1175,6 +1177,11 @@ namespace osu.Game
|
|||||||
|
|
||||||
if (entry.Level == LogLevel.Error)
|
if (entry.Level == LogLevel.Error)
|
||||||
{
|
{
|
||||||
|
if (!notifyOnError)
|
||||||
|
return;
|
||||||
|
|
||||||
|
notifyOnError = false;
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
Notifications.Post(new SimpleNotification
|
Notifications.Post(new SimpleNotification
|
||||||
@ -1213,7 +1220,11 @@ namespace osu.Game
|
|||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
ITabletHandler tablet = Host.AvailableInputHandlers.OfType<ITabletHandler>().SingleOrDefault();
|
ITabletHandler tablet = Host.AvailableInputHandlers.OfType<ITabletHandler>().SingleOrDefault();
|
||||||
tablet?.Tablet.BindValueChanged(_ => notifyOnWarning = true, true);
|
tablet?.Tablet.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
notifyOnWarning = true;
|
||||||
|
notifyOnError = true;
|
||||||
|
}, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,6 @@ namespace osu.Game.Overlays
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// All ongoing operations (ie. any <see cref="ProgressNotification"/> not in a completed state).
|
/// All ongoing operations (ie. any <see cref="ProgressNotification"/> not in a completed state).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<ProgressNotification> OngoingOperations => AllNotifications.OfType<ProgressNotification>().Where(p => p.State != ProgressNotificationState.Completed);
|
public IEnumerable<ProgressNotification> OngoingOperations => AllNotifications.OfType<ProgressNotification>().Where(p => p.State != ProgressNotificationState.Completed && p.State != ProgressNotificationState.Cancelled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
[LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.AppearOffline))]
|
[LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.AppearOffline))]
|
||||||
AppearOffline,
|
AppearOffline,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(UserVerificationStrings), nameof(UserVerificationStrings.BoxInfoLogoutLink))]
|
[LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.SignOut))]
|
||||||
SignOut,
|
SignOut,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Overlays
|
|||||||
scrollbarBackground = new Box
|
scrollbarBackground = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Width = OsuScrollContainer.SCROLL_BAR_HEIGHT,
|
Width = OsuScrollContainer.SCROLL_BAR_WIDTH,
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Alpha = 0.5f
|
Alpha = 0.5f
|
||||||
|
@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
public virtual bool DisplayResult => true;
|
public virtual bool DisplayResult => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
|
/// The scoring result of this <see cref="DrawableHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllJudged => Judged && NestedHitObjects.All(h => h.AllJudged);
|
public JudgementResult Result => Entry?.Result;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this <see cref="DrawableHitObject"/> has been hit. This occurs if <see cref="Result"/> is hit.
|
/// Whether this <see cref="DrawableHitObject"/> has been hit. This occurs if <see cref="Result"/> is hit.
|
||||||
@ -112,12 +112,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// Whether this <see cref="DrawableHitObject"/> has been judged.
|
/// Whether this <see cref="DrawableHitObject"/> has been judged.
|
||||||
/// Note: This does NOT include nested hitobjects.
|
/// Note: This does NOT include nested hitobjects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Judged => Result?.HasResult ?? true;
|
public bool Judged => Entry?.Judged ?? true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The scoring result of this <see cref="DrawableHitObject"/>.
|
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JudgementResult Result => Entry?.Result;
|
public bool AllJudged => Entry?.AllJudged ?? true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The relative X position of this hit object for sample playback balance adjustment.
|
/// The relative X position of this hit object for sample playback balance adjustment.
|
||||||
@ -218,6 +218,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
protected sealed override void OnApply(HitObjectLifetimeEntry entry)
|
protected sealed override void OnApply(HitObjectLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(Entry != null);
|
||||||
|
|
||||||
// LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset.
|
// LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset.
|
||||||
// We override this with DHO's InitialLifetimeOffset for a non-pooled DHO.
|
// We override this with DHO's InitialLifetimeOffset for a non-pooled DHO.
|
||||||
if (entry is SyntheticHitObjectEntry)
|
if (entry is SyntheticHitObjectEntry)
|
||||||
@ -247,6 +249,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
drawableNested.ParentHitObject = this;
|
drawableNested.ParentHitObject = this;
|
||||||
|
|
||||||
nestedHitObjects.Add(drawableNested);
|
nestedHitObjects.Add(drawableNested);
|
||||||
|
|
||||||
|
// assume that synthetic entries are not pooled and therefore need to be managed from within the DHO.
|
||||||
|
// this is important for the correctness of value of flags such as `AllJudged`.
|
||||||
|
if (drawableNested.Entry is SyntheticHitObjectEntry syntheticNestedEntry)
|
||||||
|
Entry.NestedEntries.Add(syntheticNestedEntry);
|
||||||
|
|
||||||
AddNestedHitObject(drawableNested);
|
AddNestedHitObject(drawableNested);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,6 +298,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
protected sealed override void OnFree(HitObjectLifetimeEntry entry)
|
protected sealed override void OnFree(HitObjectLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(Entry != null);
|
||||||
|
|
||||||
StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable);
|
StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable);
|
||||||
|
|
||||||
if (HitObject is IHasComboInformation combo)
|
if (HitObject is IHasComboInformation combo)
|
||||||
@ -318,6 +328,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
nestedHitObjects.Clear();
|
nestedHitObjects.Clear();
|
||||||
|
// clean up synthetic entries manually added in `Apply()`.
|
||||||
|
Entry.NestedEntries.RemoveAll(nestedEntry => nestedEntry is SyntheticHitObjectEntry);
|
||||||
ClearNestedHitObjects();
|
ClearNestedHitObjects();
|
||||||
|
|
||||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -19,12 +21,28 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly HitObject HitObject;
|
public readonly HitObject HitObject;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of <see cref="HitObjectLifetimeEntry"/> for the <see cref="HitObject"/>'s nested objects (if any).
|
||||||
|
/// </summary>
|
||||||
|
public List<HitObjectLifetimeEntry> NestedEntries { get; internal set; } = new List<HitObjectLifetimeEntry>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The result that <see cref="HitObject"/> was judged with.
|
/// The result that <see cref="HitObject"/> was judged with.
|
||||||
/// This is set by the accompanying <see cref="DrawableHitObject"/>, and reused when required for rewinding.
|
/// This is set by the accompanying <see cref="DrawableHitObject"/>, and reused when required for rewinding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal JudgementResult? Result;
|
internal JudgementResult? Result;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether <see cref="HitObject"/> has been judged.
|
||||||
|
/// Note: This does NOT include nested hitobjects.
|
||||||
|
/// </summary>
|
||||||
|
public bool Judged => Result?.HasResult ?? true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether <see cref="HitObject"/> and all of its nested objects have been judged.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllJudged => Judged && NestedEntries.All(h => h.AllJudged);
|
||||||
|
|
||||||
private readonly IBindable<double> startTimeBindable = new BindableDouble();
|
private readonly IBindable<double> startTimeBindable = new BindableDouble();
|
||||||
|
|
||||||
internal event Action? RevertResult;
|
internal event Action? RevertResult;
|
||||||
|
@ -43,11 +43,6 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private readonly Dictionary<HitObjectLifetimeEntry, HitObject> parentMap = new Dictionary<HitObjectLifetimeEntry, HitObject>();
|
private readonly Dictionary<HitObjectLifetimeEntry, HitObject> parentMap = new Dictionary<HitObjectLifetimeEntry, HitObject>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stores the list of child entries for each hit object managed by this <see cref="HitObjectEntryManager"/>.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Dictionary<HitObject, List<HitObjectLifetimeEntry>> childrenMap = new Dictionary<HitObject, List<HitObjectLifetimeEntry>>();
|
|
||||||
|
|
||||||
public void Add(HitObjectLifetimeEntry entry, HitObject? parent)
|
public void Add(HitObjectLifetimeEntry entry, HitObject? parent)
|
||||||
{
|
{
|
||||||
HitObject hitObject = entry.HitObject;
|
HitObject hitObject = entry.HitObject;
|
||||||
@ -57,22 +52,24 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
|
|
||||||
// Add the entry.
|
// Add the entry.
|
||||||
entryMap[hitObject] = entry;
|
entryMap[hitObject] = entry;
|
||||||
childrenMap[hitObject] = new List<HitObjectLifetimeEntry>();
|
|
||||||
|
|
||||||
// If the entry has a parent, set it and add the entry to the parent's children.
|
// If the entry has a parent, set it and add the entry to the parent's children.
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
parentMap[entry] = parent;
|
parentMap[entry] = parent;
|
||||||
if (childrenMap.TryGetValue(parent, out var parentChildEntries))
|
if (entryMap.TryGetValue(parent, out var parentEntry))
|
||||||
parentChildEntries.Add(entry);
|
parentEntry.NestedEntries.Add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
hitObject.DefaultsApplied += onDefaultsApplied;
|
hitObject.DefaultsApplied += onDefaultsApplied;
|
||||||
OnEntryAdded?.Invoke(entry, parent);
|
OnEntryAdded?.Invoke(entry, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(HitObjectLifetimeEntry entry)
|
public bool Remove(HitObjectLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
|
if (entry is SyntheticHitObjectEntry)
|
||||||
|
return false;
|
||||||
|
|
||||||
HitObject hitObject = entry.HitObject;
|
HitObject hitObject = entry.HitObject;
|
||||||
|
|
||||||
if (!entryMap.ContainsKey(hitObject))
|
if (!entryMap.ContainsKey(hitObject))
|
||||||
@ -81,18 +78,16 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
entryMap.Remove(hitObject);
|
entryMap.Remove(hitObject);
|
||||||
|
|
||||||
// If the entry has a parent, unset it and remove the entry from the parents' children.
|
// If the entry has a parent, unset it and remove the entry from the parents' children.
|
||||||
if (parentMap.Remove(entry, out var parent) && childrenMap.TryGetValue(parent, out var parentChildEntries))
|
if (parentMap.Remove(entry, out var parent) && entryMap.TryGetValue(parent, out var parentEntry))
|
||||||
parentChildEntries.Remove(entry);
|
parentEntry.NestedEntries.Remove(entry);
|
||||||
|
|
||||||
// Remove all the entries' children.
|
// Remove all the entries' children.
|
||||||
if (childrenMap.Remove(hitObject, out var childEntries))
|
foreach (var childEntry in entry.NestedEntries)
|
||||||
{
|
|
||||||
foreach (var childEntry in childEntries)
|
|
||||||
Remove(childEntry);
|
Remove(childEntry);
|
||||||
}
|
|
||||||
|
|
||||||
hitObject.DefaultsApplied -= onDefaultsApplied;
|
hitObject.DefaultsApplied -= onDefaultsApplied;
|
||||||
OnEntryRemoved?.Invoke(entry, parent);
|
OnEntryRemoved?.Invoke(entry, parent);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGet(HitObject hitObject, [MaybeNullWhen(false)] out HitObjectLifetimeEntry entry)
|
public bool TryGet(HitObject hitObject, [MaybeNullWhen(false)] out HitObjectLifetimeEntry entry)
|
||||||
@ -105,16 +100,16 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void onDefaultsApplied(HitObject hitObject)
|
private void onDefaultsApplied(HitObject hitObject)
|
||||||
{
|
{
|
||||||
if (!childrenMap.Remove(hitObject, out var childEntries))
|
if (!entryMap.TryGetValue(hitObject, out var entry))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Remove all the entries' children. At this point the parents' (this entries') children list has been removed from the map, so this does not cause upwards traversal.
|
// Replace the entire list rather than clearing to prevent circular traversal later.
|
||||||
foreach (var entry in childEntries)
|
var previousEntries = entry.NestedEntries;
|
||||||
Remove(entry);
|
entry.NestedEntries = new List<HitObjectLifetimeEntry>();
|
||||||
|
|
||||||
// The removed children list needs to be added back to the map for the entry to potentially receive children.
|
// Remove all the entries' children. At this point the parents' (this entries') children list has been reconstructed, so this does not cause upwards traversal.
|
||||||
childEntries.Clear();
|
foreach (var nested in previousEntries)
|
||||||
childrenMap[hitObject] = childEntries;
|
Remove(nested);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,7 @@ namespace osu.Game.Scoring
|
|||||||
{
|
{
|
||||||
Ruleset = ruleset ?? new RulesetInfo();
|
Ruleset = ruleset ?? new RulesetInfo();
|
||||||
BeatmapInfo = beatmap ?? new BeatmapInfo();
|
BeatmapInfo = beatmap ?? new BeatmapInfo();
|
||||||
|
BeatmapHash = BeatmapInfo.Hash;
|
||||||
RealmUser = realmUser ?? new RealmUser();
|
RealmUser = realmUser ?? new RealmUser();
|
||||||
ID = Guid.NewGuid();
|
ID = Guid.NewGuid();
|
||||||
}
|
}
|
||||||
|
@ -377,10 +377,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
float leftExcess = thisQuad.TopLeft.X - parentQuad.TopLeft.X;
|
float leftExcess = thisQuad.TopLeft.X - parentQuad.TopLeft.X;
|
||||||
float rightExcess = parentQuad.TopRight.X - thisQuad.TopRight.X;
|
float rightExcess = parentQuad.TopRight.X - thisQuad.TopRight.X;
|
||||||
|
|
||||||
if (topExcess + bottomExcess < buttons.Height + button_padding)
|
float minHeight = buttons.ScreenSpaceDrawQuad.Height;
|
||||||
|
|
||||||
|
if (topExcess < minHeight && bottomExcess < minHeight)
|
||||||
{
|
{
|
||||||
buttons.Anchor = Anchor.BottomCentre;
|
buttons.Anchor = Anchor.BottomCentre;
|
||||||
buttons.Origin = Anchor.BottomCentre;
|
buttons.Origin = Anchor.BottomCentre;
|
||||||
|
buttons.Y = Math.Min(0, ToLocalSpace(Parent.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight);
|
||||||
}
|
}
|
||||||
else if (topExcess > bottomExcess)
|
else if (topExcess > bottomExcess)
|
||||||
{
|
{
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
scroll.RelativeSizeAxes = Axes.X;
|
scroll.RelativeSizeAxes = Axes.X;
|
||||||
scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_HEIGHT + OsuScrollContainer.SCROLL_BAR_PADDING * 2;
|
scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_WIDTH + OsuScrollContainer.SCROLL_BAR_PADDING * 2;
|
||||||
|
|
||||||
list.RelativeSizeAxes = Axes.Y;
|
list.RelativeSizeAxes = Axes.Y;
|
||||||
list.AutoSizeAxes = Axes.X;
|
list.AutoSizeAxes = Axes.X;
|
||||||
|
@ -313,17 +313,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
client.ChangeBeatmapAvailability(availability.NewValue).FireAndForget();
|
client.ChangeBeatmapAvailability(availability.NewValue).FireAndForget();
|
||||||
|
|
||||||
if (availability.NewValue.State != DownloadState.LocallyAvailable)
|
switch (availability.NewValue.State)
|
||||||
{
|
{
|
||||||
// while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap.
|
case DownloadState.LocallyAvailable:
|
||||||
if (client.LocalUser?.State == MultiplayerUserState.Ready)
|
if (client.LocalUser?.State == MultiplayerUserState.Spectating
|
||||||
client.ChangeState(MultiplayerUserState.Idle);
|
|
||||||
}
|
|
||||||
else if (client.LocalUser?.State == MultiplayerUserState.Spectating
|
|
||||||
&& (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing))
|
&& (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing))
|
||||||
{
|
{
|
||||||
onLoadRequested();
|
onLoadRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DownloadState.Unknown:
|
||||||
|
// Don't do anything rash in an unknown state.
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap.
|
||||||
|
if (client.LocalUser?.State == MultiplayerUserState.Ready)
|
||||||
|
client.ChangeState(MultiplayerUserState.Idle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRoomUpdated()
|
private void onRoomUpdated()
|
||||||
|
@ -154,6 +154,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
this.FadeOut(fade_time);
|
this.FadeOut(fade_time);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DownloadState.Unknown:
|
||||||
|
text.Text = "checking availability";
|
||||||
|
icon.Icon = FontAwesome.Solid.Question;
|
||||||
|
icon.Colour = colours.Orange0;
|
||||||
|
break;
|
||||||
|
|
||||||
case DownloadState.NotDownloaded:
|
case DownloadState.NotDownloaded:
|
||||||
text.Text = "no map";
|
text.Text = "no map";
|
||||||
icon.Icon = FontAwesome.Solid.MinusCircle;
|
icon.Icon = FontAwesome.Solid.MinusCircle;
|
||||||
|
Loading…
Reference in New Issue
Block a user