mirror of
https://github.com/ppy/osu.git
synced 2026-05-21 05:09:57 +08:00
Merge branch 'master' into carousel-multiple-copies-of-beatmap-clean
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.826.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.829.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -176,6 +176,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private void applyPosition()
|
||||
{
|
||||
// can happen if popover is dismissed by a keyboard key press while dragging UI controls
|
||||
// it doesn't cause a crash, but it looks wrong
|
||||
if (!editorBeatmap.TransactionActive)
|
||||
return;
|
||||
|
||||
editorBeatmap.PerformOnSelection(ho =>
|
||||
{
|
||||
if (!initialPositions.TryGetValue(ho, out var initialPosition))
|
||||
|
||||
@@ -157,6 +157,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
rotationInfo.BindValueChanged(rotation =>
|
||||
{
|
||||
// can happen if the popover is dismissed by a keyboard key press while dragging UI controls
|
||||
if (!rotationHandler.OperationInProgress.Value)
|
||||
return;
|
||||
|
||||
rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,6 +220,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
scaleInfo.BindValueChanged(scale =>
|
||||
{
|
||||
// can happen if the popover is dismissed by a keyboard key press while dragging UI controls
|
||||
if (!scaleHandler.OperationInProgress.Value)
|
||||
return;
|
||||
|
||||
var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale);
|
||||
scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), getRotation(scale.NewValue));
|
||||
});
|
||||
|
||||
@@ -5,10 +5,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
{
|
||||
@@ -69,5 +72,106 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIncreasedVisibilityOnFirstObject()
|
||||
{
|
||||
bool firstHitNeverFadedOut = true;
|
||||
AddStep("enable increased visibility", () => LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, true));
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModHidden(),
|
||||
Autoplay = true,
|
||||
PassCondition = () =>
|
||||
{
|
||||
var firstHit = this.ChildrenOfType<DrawableHit>().FirstOrDefault(h => h.HitObject.StartTime == 100);
|
||||
|
||||
if (firstHit?.Alpha < 1 && !firstHit.IsHit)
|
||||
firstHitNeverFadedOut = false;
|
||||
|
||||
return firstHitNeverFadedOut && checkAllMaxResultJudgements(2).Invoke();
|
||||
},
|
||||
CreateBeatmap = () =>
|
||||
{
|
||||
var beatmap = new Beatmap<TaikoHitObject>
|
||||
{
|
||||
HitObjects = new List<TaikoHitObject>
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
Type = HitType.Rim,
|
||||
StartTime = 100,
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
Type = HitType.Centre,
|
||||
StartTime = 200,
|
||||
},
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
SliderTickRate = 4,
|
||||
OverallDifficulty = 0,
|
||||
},
|
||||
Ruleset = new TaikoRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||
return beatmap;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoIncreasedVisibilityOnFirstObject()
|
||||
{
|
||||
bool firstHitFadedOut = true;
|
||||
AddStep("enable increased visibility", () => LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false));
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModHidden(),
|
||||
Autoplay = true,
|
||||
PassCondition = () =>
|
||||
{
|
||||
var firstHit = this.ChildrenOfType<DrawableHit>().FirstOrDefault(h => h.HitObject.StartTime == 100);
|
||||
firstHitFadedOut |= firstHit?.IsHit == false && firstHit.Alpha < 1;
|
||||
return firstHitFadedOut && checkAllMaxResultJudgements(2).Invoke();
|
||||
},
|
||||
CreateBeatmap = () =>
|
||||
{
|
||||
var beatmap = new Beatmap<TaikoHitObject>
|
||||
{
|
||||
HitObjects = new List<TaikoHitObject>
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
Type = HitType.Rim,
|
||||
StartTime = 100,
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
Type = HitType.Centre,
|
||||
StartTime = 200,
|
||||
},
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
SliderTickRate = 4,
|
||||
OverallDifficulty = 0,
|
||||
},
|
||||
Ruleset = new TaikoRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||
return beatmap;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
ApplyNormalVisibilityState(hitObject, state);
|
||||
// intentional no-op
|
||||
}
|
||||
|
||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
|
||||
@@ -1018,6 +1018,49 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapFilesInNestedDirectoriesAreIgnored()
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
string extractedFolder = $"{temp}_extracted";
|
||||
Directory.CreateDirectory(extractedFolder);
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
var subdirectory = Directory.CreateDirectory(Path.Combine(extractedFolder, "subdir"));
|
||||
string modifiedCopyPath = Path.Combine(subdirectory.FullName, "duplicate.osu");
|
||||
File.Copy(Directory.GetFiles(extractedFolder, "*.osu").First(), modifiedCopyPath);
|
||||
|
||||
using (var stream = File.OpenWrite(modifiedCopyPath))
|
||||
using (var textWriter = new StreamWriter(stream))
|
||||
await textWriter.WriteLineAsync("# adding a comment so that the hashes are different");
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||
}
|
||||
|
||||
await importer.Import(temp);
|
||||
|
||||
EnsureLoaded(realm.Realm);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(extractedFolder, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportNestedStructure()
|
||||
{
|
||||
|
||||
@@ -52,25 +52,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestBasicListChanges()
|
||||
{
|
||||
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(5, withSpotlightRooms: true)));
|
||||
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(5, withPinnedRooms: true)));
|
||||
|
||||
AddAssert("has 5 rooms", () => container.DrawableRooms.Count == 5);
|
||||
|
||||
AddAssert("all spotlights at top", () => container.DrawableRooms
|
||||
.SkipWhile(r => r.Room.Category == RoomCategory.Spotlight)
|
||||
.All(r => r.Room.Category == RoomCategory.Normal));
|
||||
AddAssert("all pinned at top", () => container.DrawableRooms
|
||||
.SkipWhile(r => r.Room.Pinned)
|
||||
.All(r => !r.Room.Pinned));
|
||||
|
||||
AddStep("remove first room", () => rooms.RemoveAt(0));
|
||||
AddAssert("has 4 rooms", () => container.DrawableRooms.Count == 4);
|
||||
AddAssert("first room removed", () => container.DrawableRooms.All(r => r.Room.RoomID != 0));
|
||||
|
||||
AddStep("select first room", () => container.DrawableRooms.First().TriggerClick());
|
||||
AddAssert("first spotlight selected", () => checkRoomSelected(rooms.First(r => r.Category == RoomCategory.Spotlight)));
|
||||
AddAssert("first pinned room selected", () => checkRoomSelected(rooms.First(r => r.Pinned)));
|
||||
|
||||
AddStep("remove last room", () => rooms.RemoveAt(rooms.Count - 1));
|
||||
AddAssert("first spotlight still selected", () => checkRoomSelected(rooms.First(r => r.Category == RoomCategory.Spotlight)));
|
||||
AddAssert("first pinned room selected", () => checkRoomSelected(rooms.First(r => r.Pinned)));
|
||||
|
||||
AddStep("remove spotlight room", () => rooms.RemoveAll(r => r.Category == RoomCategory.Spotlight));
|
||||
AddStep("remove pinned rooms", () => rooms.RemoveAll(r => r.Pinned));
|
||||
AddAssert("selection vacated", () => checkRoomSelected(null));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
@@ -38,10 +39,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("create rooms", () =>
|
||||
{
|
||||
PlaylistItem item1 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
PlaylistItem item1 = new PlaylistItem(new APIBeatmap
|
||||
{
|
||||
BeatmapInfo = { StarRating = 2.5 }
|
||||
}.BeatmapInfo);
|
||||
OnlineBeatmapSetID = 173612,
|
||||
OnlineID = 502132,
|
||||
});
|
||||
|
||||
PlaylistItem item2 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
@@ -72,6 +74,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
createMultiplayerPanel(new Room
|
||||
{
|
||||
Name = "Multiplayer room",
|
||||
EndDate = DateTimeOffset.Now.AddDays(1),
|
||||
Type = MatchType.HeadToHead,
|
||||
Playlist = [item1],
|
||||
CurrentPlaylistItem = item1
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Multiplayer room",
|
||||
@@ -81,6 +91,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
CurrentPlaylistItem = item1
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Pinned room",
|
||||
Pinned = true,
|
||||
EndDate = DateTimeOffset.Now.AddDays(1),
|
||||
Type = MatchType.HeadToHead,
|
||||
Playlist = [item1],
|
||||
CurrentPlaylistItem = item1
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Private room",
|
||||
Password = "*",
|
||||
@@ -89,6 +108,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Playlist = [item3],
|
||||
CurrentPlaylistItem = item3
|
||||
}),
|
||||
createPlaylistRoomPanel(new Room
|
||||
{
|
||||
Name = "Playlist room with multiple beatmaps",
|
||||
Status = RoomStatus.Playing,
|
||||
EndDate = DateTimeOffset.Now.AddDays(1),
|
||||
Playlist = [item1, item2],
|
||||
CurrentPlaylistItem = item1
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Playlist room with multiple beatmaps",
|
||||
@@ -121,9 +148,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
};
|
||||
});
|
||||
|
||||
AddUntilStep("wait for panel load", () => rooms.Count == 7);
|
||||
AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Currently playing", StringComparison.Ordinal)) == 2);
|
||||
AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 5);
|
||||
AddUntilStep("wait for panel load", () => rooms.Count, () => Is.EqualTo(10));
|
||||
AddUntilStep("\"currently playing\" room count correct",
|
||||
() => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Currently playing", StringComparison.Ordinal)), () => Is.EqualTo(4));
|
||||
AddUntilStep("\"ready to play\" room count correct", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)),
|
||||
() => Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -140,13 +169,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for panel load", () => panel.ChildrenOfType<DrawableRoomParticipantsList>().Any());
|
||||
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, panel.ChildrenOfType<RoomPanel.CornerIcon>().First().Alpha));
|
||||
|
||||
AddStep("set password", () => room.Password = "password");
|
||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, panel.ChildrenOfType<RoomPanel.CornerIcon>().First().Alpha));
|
||||
|
||||
AddStep("unset password", () => room.Password = string.Empty);
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, panel.ChildrenOfType<RoomPanel.CornerIcon>().First().Alpha));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -198,7 +227,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "This room has a very very long title enough to make the external link button reach the participants list on the right side unless the test window is very wide, at which point I don't know, hi.",
|
||||
Name =
|
||||
"This room has a very very long title enough to make the external link button reach the participants list on the right side unless the test window is very wide, at which point I don't know, hi.",
|
||||
QueueMode = QueueMode.HostOnly,
|
||||
Type = MatchType.HeadToHead,
|
||||
RoomID = 1337,
|
||||
@@ -222,7 +252,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
new MultiplayerRoomPanel(room = new Room
|
||||
{
|
||||
Name = "This room has a very very long title enough to make the external link button reach the participants list on the right side unless the test window is very wide, at which point I don't know, hi.",
|
||||
Name =
|
||||
"This room has a very very long title enough to make the external link button reach the participants list on the right side unless the test window is very wide, at which point I don't know, hi.",
|
||||
QueueMode = QueueMode.HostOnly,
|
||||
Type = MatchType.HeadToHead,
|
||||
}),
|
||||
@@ -234,6 +265,41 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("clear room ID", () => room.RoomID = null);
|
||||
}
|
||||
|
||||
private RoomPanel createPlaylistRoomPanel(Room room)
|
||||
{
|
||||
room.Host ??= new APIUser { Username = "peppy", Id = 2 };
|
||||
|
||||
if (room.RecentParticipants.Count == 0)
|
||||
{
|
||||
room.RecentParticipants = Enumerable.Range(0, 20).Select(i => new APIUser
|
||||
{
|
||||
Id = i,
|
||||
Username = $"User {i}"
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
return new PlaylistsRoomPanel(room)
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>(room.CurrentPlaylistItem),
|
||||
};
|
||||
}
|
||||
|
||||
private RoomPanel createMultiplayerPanel(Room room)
|
||||
{
|
||||
room.Host ??= new APIUser { Username = "peppy", Id = 2 };
|
||||
|
||||
if (room.RecentParticipants.Count == 0)
|
||||
{
|
||||
room.RecentParticipants = Enumerable.Range(0, 20).Select(i => new APIUser
|
||||
{
|
||||
Id = i,
|
||||
Username = $"User {i}"
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
return new MultiplayerRoomPanel(room);
|
||||
}
|
||||
|
||||
private RoomPanel createLoungeRoom(Room room)
|
||||
{
|
||||
room.Host ??= new APIUser { Username = "peppy", Id = 2 };
|
||||
|
||||
@@ -22,8 +22,6 @@ using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
@@ -43,9 +41,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
protected Screens.SelectV2.SongSelect SongSelect { get; private set; } = null!;
|
||||
protected BeatmapCarousel Carousel => SongSelect.ChildrenOfType<BeatmapCarousel>().Single();
|
||||
|
||||
[Cached]
|
||||
protected readonly ScreenFooter Footer;
|
||||
|
||||
[Cached]
|
||||
private readonly OsuLogo logo;
|
||||
|
||||
@@ -72,10 +67,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
State = { Value = Visibility.Visible },
|
||||
},
|
||||
Footer = new ScreenFooter
|
||||
{
|
||||
BackButtonPressed = () => Stack.CurrentScreen.Exit(),
|
||||
},
|
||||
logo = new OsuLogo
|
||||
{
|
||||
Alpha = 0f,
|
||||
@@ -111,14 +102,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Add(beatmapStore);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Stack.ScreenPushed += updateFooter;
|
||||
Stack.ScreenExited += updateFooter;
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
@@ -207,38 +190,5 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
}
|
||||
|
||||
protected void WaitForSuspension() => AddUntilStep("wait for not current", () => !SongSelect.AsNonNull().IsCurrentScreen());
|
||||
|
||||
private void updateFooter(IScreen? _, IScreen? newScreen)
|
||||
{
|
||||
if (newScreen is OsuScreen osuScreen && osuScreen.ShowFooter)
|
||||
{
|
||||
Footer.Show();
|
||||
|
||||
if (osuScreen.IsLoaded)
|
||||
updateFooterButtons();
|
||||
else
|
||||
{
|
||||
// ensure the current buttons are immediately disabled on screen change (so they can't be pressed).
|
||||
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||
|
||||
osuScreen.OnLoadComplete += _ => updateFooterButtons();
|
||||
}
|
||||
|
||||
void updateFooterButtons()
|
||||
{
|
||||
var buttons = osuScreen.CreateFooterButtons();
|
||||
|
||||
osuScreen.LoadComponentsAgainstScreenDependencies(buttons);
|
||||
|
||||
Footer.SetButtons(buttons);
|
||||
Footer.Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Footer.Hide();
|
||||
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,7 +367,11 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
var beatmaps = new List<BeatmapInfo>();
|
||||
|
||||
foreach (var file in beatmapSet.Files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)))
|
||||
// stable appears to ignore `.osu` files which are not placed at the top level of the beatmap archive.
|
||||
// the logic that achieves this is very difficult to make sense of, but appears to be located somewhere around
|
||||
// https://github.com/peppy/osu-stable-reference/blob/67795dba3c308e7d0493b296149dcb073ca47ecb/osu!/GameplayElements/Beatmaps/BeatmapManager.cs#L207-L208
|
||||
// only testing the `/` path separator character is sufficient as `RealmNamedFileUsage`s are normalised to use the front slash unix path separator convention
|
||||
foreach (var file in beatmapSet.Files.Where(f => !f.Filename.Contains('/') && f.Filename.EndsWith(@".osu", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
using (var memoryStream = new MemoryStream(Files.Store.Get(file.File.GetStoragePath()))) // we need a memory stream so we can seek
|
||||
{
|
||||
|
||||
@@ -59,7 +59,10 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
// An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting
|
||||
// high precision times (on windows there's generally only 5-10ms reporting intervals, as an example).
|
||||
interpolatedTrack = new InterpolatingFramedClock(decoupledTrack);
|
||||
interpolatedTrack = new InterpolatingFramedClock(decoupledTrack)
|
||||
{
|
||||
DriftRecoveryHalfLife = 80,
|
||||
};
|
||||
|
||||
if (applyOffsets)
|
||||
{
|
||||
|
||||
@@ -172,6 +172,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
protected Room? APIRoom { get; private set; }
|
||||
|
||||
private readonly Queue<Action> pendingRequests = new Queue<Action>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -200,6 +202,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
await joinOrLeaveTaskChain.Add(async () =>
|
||||
{
|
||||
await runOnUpdateThreadAsync(() => pendingRequests.Clear(), cancellationSource.Token).ConfigureAwait(false);
|
||||
var multiplayerRoom = await CreateRoomInternal(new MultiplayerRoom(room)).ConfigureAwait(false);
|
||||
await setupJoinedRoom(room, multiplayerRoom, cancellationSource.Token).ConfigureAwait(false);
|
||||
}, cancellationSource.Token).ConfigureAwait(false);
|
||||
@@ -223,6 +226,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
await joinOrLeaveTaskChain.Add(async () =>
|
||||
{
|
||||
await runOnUpdateThreadAsync(() => pendingRequests.Clear(), cancellationSource.Token).ConfigureAwait(false);
|
||||
var multiplayerRoom = await JoinRoomInternal(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false);
|
||||
await setupJoinedRoom(room, multiplayerRoom, cancellationSource.Token).ConfigureAwait(false);
|
||||
}, cancellationSource.Token).ConfigureAwait(false);
|
||||
@@ -266,6 +270,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
updateLocalRoomSettings(joinedRoom.Settings);
|
||||
|
||||
while (pendingRequests.TryDequeue(out Action? action))
|
||||
action();
|
||||
|
||||
postServerShuttingDownNotification();
|
||||
|
||||
OnRoomJoined();
|
||||
@@ -300,10 +307,23 @@ namespace osu.Game.Online.Multiplayer
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
|
||||
return joinOrLeaveTaskChain.Add(async () =>
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
await scheduledReset.ConfigureAwait(false);
|
||||
await LeaveRoomInternal().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await joinOrLeaveTaskChain.Add(async () =>
|
||||
{
|
||||
await scheduledReset.ConfigureAwait(false);
|
||||
await LeaveRoomInternal().ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await runOnUpdateThreadAsync(() =>
|
||||
{
|
||||
pendingRequests.Clear();
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -449,11 +469,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.State = state;
|
||||
@@ -476,7 +494,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
}
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -485,10 +503,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
await PopulateUsers([user]).ConfigureAwait(false);
|
||||
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
// for sanity, ensure that there can be no duplicate users in the room user list.
|
||||
if (Room.Users.Any(existing => existing.UserID == user.UserID))
|
||||
@@ -500,18 +517,18 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
UserJoined?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user)
|
||||
{
|
||||
Scheduler.Add(() => handleUserLeft(user, UserLeft), false);
|
||||
handleRoomRequest(() => handleUserLeft(user, UserLeft));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserKicked(MultiplayerRoomUser user)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (LocalUser == null)
|
||||
return;
|
||||
@@ -520,7 +537,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
LeaveRoom();
|
||||
|
||||
handleUserLeft(user, UserKicked);
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -528,9 +545,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
private void handleUserLeft(MultiplayerRoomUser user, Action<MultiplayerRoomUser>? callback)
|
||||
{
|
||||
Debug.Assert(ThreadSafety.IsUpdateThread);
|
||||
|
||||
if (Room == null)
|
||||
return;
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
Room.Users.Remove(user);
|
||||
PlayingUserIds.Remove(user.UserID);
|
||||
@@ -587,11 +602,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Task IMultiplayerClient.HostChanged(int userId)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
var user = Room.Users.FirstOrDefault(u => u.UserID == userId);
|
||||
@@ -601,22 +614,24 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
HostChanged?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings)
|
||||
{
|
||||
Scheduler.Add(() => updateLocalRoomSettings(newSettings));
|
||||
handleRoomRequest(() => updateLocalRoomSettings(newSettings));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
var user = Room?.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
var user = Room.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
|
||||
// TODO: user should NEVER be null here, see https://github.com/ppy/osu/issues/17713.
|
||||
if (user == null)
|
||||
@@ -626,16 +641,18 @@ namespace osu.Game.Online.Multiplayer
|
||||
updateUserPlayingState(userId, state);
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.MatchUserStateChanged(int userId, MatchUserState state)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
var user = Room?.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
var user = Room.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
|
||||
// TODO: user should NEVER be null here, see https://github.com/ppy/osu/issues/17713.
|
||||
if (user == null)
|
||||
@@ -643,31 +660,29 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
user.MatchState = state;
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.MatchRoomStateChanged(MatchRoomState state)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
Room.MatchState = state;
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task MatchEvent(MatchServerEvent e)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
switch (e)
|
||||
{
|
||||
@@ -691,7 +706,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
}
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -708,9 +723,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
var user = Room?.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
var user = Room.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
|
||||
// errors here are not critical - beatmap availability state is mostly for display.
|
||||
if (user == null)
|
||||
@@ -719,16 +736,18 @@ namespace osu.Game.Online.Multiplayer
|
||||
user.BeatmapAvailability = beatmapAvailability;
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserStyleChanged(int userId, int? beatmapId, int? rulesetId)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
var user = Room?.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
var user = Room.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
|
||||
// errors here are not critical - user style is mostly for display.
|
||||
if (user == null)
|
||||
@@ -739,16 +758,18 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
UserStyleChanged?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserModsChanged(int userId, IEnumerable<APIMod> mods)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
var user = Room?.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
var user = Room.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
|
||||
// errors here are not critical - user mods are mostly for display.
|
||||
if (user == null)
|
||||
@@ -758,70 +779,60 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
UserModsChanged?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.LoadRequested()
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
LoadRequested?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.GameplayAborted(GameplayAbortReason reason)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
GameplayAborted?.Invoke(reason);
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.GameplayStarted()
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
GameplayStarted?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.ResultsReady()
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
ResultsReady?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task PlaylistItemAdded(MultiplayerPlaylistItem item)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.Playlist.Add(item);
|
||||
@@ -836,11 +847,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public Task PlaylistItemRemoved(long playlistItemId)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId));
|
||||
@@ -857,11 +866,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public Task PlaylistItemChanged(MultiplayerPlaylistItem item)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
|
||||
@@ -908,9 +915,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <param name="settings">The new <see cref="MultiplayerRoomSettings"/> to update from.</param>
|
||||
private void updateLocalRoomSettings(MultiplayerRoomSettings settings)
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
// Update a few properties of the room instantaneously.
|
||||
@@ -972,6 +977,20 @@ namespace osu.Game.Online.Multiplayer
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private void handleRoomRequest(Action request)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
{
|
||||
pendingRequests.Enqueue(request);
|
||||
return;
|
||||
}
|
||||
|
||||
request();
|
||||
});
|
||||
}
|
||||
|
||||
Task IStatefulUserHubClient.DisconnectRequested()
|
||||
{
|
||||
Schedule(() =>
|
||||
|
||||
@@ -263,6 +263,12 @@ namespace osu.Game.Online.Rooms
|
||||
set => SetField(ref availability, value);
|
||||
}
|
||||
|
||||
public bool Pinned
|
||||
{
|
||||
get => pinned;
|
||||
set => SetField(ref pinned, value);
|
||||
}
|
||||
|
||||
[JsonProperty("id")]
|
||||
private long? roomId;
|
||||
|
||||
@@ -339,6 +345,9 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
|
||||
private RoomStatus status;
|
||||
|
||||
[JsonProperty("pinned")]
|
||||
private bool pinned;
|
||||
|
||||
// Not yet serialised (not implemented).
|
||||
private RoomAvailability availability;
|
||||
|
||||
|
||||
@@ -63,6 +63,9 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
private readonly FramedClock framedClock;
|
||||
|
||||
[Resolved]
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
private readonly Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
/// <summary>
|
||||
@@ -161,7 +164,9 @@ namespace osu.Game.Rulesets.UI
|
||||
//
|
||||
// In testing this triggers *very* rarely even when set to super low values (10 ms). The cases we're worried about involve multi-second jumps.
|
||||
// A difference of more than 500 ms seems like a sane number we should never exceed.
|
||||
if (!allowReferenceClockSeeks && Math.Abs(proposedTime - referenceClock.CurrentTime) > 1500)
|
||||
//
|
||||
// Double-checking against the parent clock ensures we don't accidentally freeze time when the game stutters due to a long running frame.
|
||||
if (!allowReferenceClockSeeks && Math.Abs(proposedTime - referenceClock.CurrentTime) > 500 && game?.Clock.ElapsedFrameTime <= 500)
|
||||
{
|
||||
if (invalidBassTimeLogCount < 10)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,6 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
@@ -57,9 +56,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
protected virtual double GetTime() => HitObject is IHasRepeats r ? HitObject.StartTime + r.Duration / r.SpanCount() / 2 : HitObject.StartTime;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
private void load()
|
||||
{
|
||||
HitObject.DefaultsApplied += _ => updateText();
|
||||
Label.AllowMultiline = false;
|
||||
LabelContainer.AutoSizeAxes = Axes.None;
|
||||
updateText();
|
||||
@@ -74,6 +72,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
HitObject.DefaultsApplied += onDefaultsApplied;
|
||||
|
||||
if (timelineBlueprintContainer != null)
|
||||
contracted.BindTo(timelineBlueprintContainer.SamplePointContracted);
|
||||
|
||||
@@ -96,12 +96,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
FinishTransforms();
|
||||
}
|
||||
|
||||
private void onDefaultsApplied(HitObject hitObject)
|
||||
{
|
||||
updateText();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (editor != null)
|
||||
editor.ShowSampleEditPopoverRequested -= onShowSampleEditPopoverRequested;
|
||||
|
||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||
}
|
||||
|
||||
private void onShowSampleEditPopoverRequested(double time)
|
||||
|
||||
@@ -55,12 +55,25 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
return masterGameplayClockContainer;
|
||||
}
|
||||
|
||||
protected override void LoadAsyncComplete()
|
||||
{
|
||||
base.LoadAsyncComplete();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return;
|
||||
|
||||
// This hack needs to be called to install its hooks before drawable hit objects get the chance to run update logic,
|
||||
// because it will not work otherwise due to being too late (various effects of the objects getting missed will have already taken place).
|
||||
preventMissOnPreviousHitObjects();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// this will notify components such as the skin's combo counter, which needs to happen on the update thread
|
||||
// and therefore can't happen alongside `preventMissOnPreviousHitObjects()` in `LoadAsyncComplete()`
|
||||
markPreviousObjectsHit();
|
||||
markVisibleDrawableObjectsHit();
|
||||
|
||||
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
|
||||
{
|
||||
@@ -111,38 +124,45 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
}
|
||||
}
|
||||
|
||||
private void markVisibleDrawableObjectsHit()
|
||||
private void preventMissOnPreviousHitObjects()
|
||||
{
|
||||
if (!DrawableRuleset.Playfield.IsLoaded)
|
||||
void preventMiss(HitObject hitObject)
|
||||
{
|
||||
Schedule(markVisibleDrawableObjectsHit);
|
||||
return;
|
||||
var drawableObject = DrawableRuleset.Playfield.HitObjectContainer
|
||||
.AliveObjects
|
||||
.SingleOrDefault(it => it.HitObject == hitObject);
|
||||
|
||||
if (drawableObject != null)
|
||||
preventMissOnDrawable(drawableObject);
|
||||
}
|
||||
|
||||
foreach (var drawableObject in enumerateDrawableObjects(DrawableRuleset.Playfield.AllHitObjects, editorState.Time))
|
||||
void preventMissOnDrawable(DrawableHitObject drawableObject)
|
||||
{
|
||||
if (drawableObject.Entry == null)
|
||||
continue;
|
||||
foreach (var nested in drawableObject.NestedHitObjects)
|
||||
preventMissOnDrawable(nested);
|
||||
|
||||
var result = drawableObject.CreateResult(drawableObject.HitObject.Judgement);
|
||||
result.Type = result.Judgement.MaxResult;
|
||||
drawableObject.Entry.Result = result;
|
||||
}
|
||||
|
||||
static IEnumerable<DrawableHitObject> enumerateDrawableObjects(IEnumerable<DrawableHitObject> drawableObjects, double cutoffTime)
|
||||
{
|
||||
foreach (var drawableObject in drawableObjects)
|
||||
if (drawableObject.Entry != null && drawableObject.HitObject.GetEndTime() < editorState.Time)
|
||||
{
|
||||
foreach (var nested in enumerateDrawableObjects(drawableObject.NestedHitObjects, cutoffTime))
|
||||
{
|
||||
if (nested.HitObject.GetEndTime() < cutoffTime)
|
||||
yield return nested;
|
||||
}
|
||||
|
||||
if (drawableObject.HitObject.GetEndTime() < cutoffTime)
|
||||
yield return drawableObject;
|
||||
var result = drawableObject.CreateResult(drawableObject.HitObject.Judgement);
|
||||
result.Type = result.Judgement.MaxResult;
|
||||
drawableObject.Entry.Result = result;
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener()
|
||||
{
|
||||
if (!DrawableRuleset.Playfield.IsLoaded)
|
||||
{
|
||||
Schedule(removeListener);
|
||||
return;
|
||||
}
|
||||
|
||||
DrawableRuleset.Playfield.HitObjectUsageBegan -= preventMiss;
|
||||
}
|
||||
|
||||
DrawableRuleset.Playfield.HitObjectUsageBegan += preventMiss;
|
||||
|
||||
Schedule(removeListener);
|
||||
}
|
||||
|
||||
protected override void PrepareReplay()
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public partial class Header : Container
|
||||
{
|
||||
public const float HEIGHT = 80;
|
||||
public const float HEIGHT = 50;
|
||||
|
||||
private readonly ScreenStack? stack;
|
||||
private readonly MultiHeaderTitle title;
|
||||
|
||||
@@ -23,10 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public partial class DrawableRoomParticipantsList : CompositeDrawable
|
||||
{
|
||||
public const float SHEAR_WIDTH = 12f;
|
||||
private const float avatar_size = 36;
|
||||
private const float height = 60f;
|
||||
private static readonly Vector2 shear = new Vector2(SHEAR_WIDTH / height, 0);
|
||||
private const float avatar_size = 30;
|
||||
private const float height = 40f;
|
||||
|
||||
private readonly Room room;
|
||||
|
||||
@@ -54,7 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
Shear = shear,
|
||||
Shear = OsuGame.SHEAR,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@@ -71,10 +69,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Spacing = new Vector2(8),
|
||||
Spacing = new Vector2(4),
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Left = 8,
|
||||
Left = 4,
|
||||
Right = 16
|
||||
},
|
||||
Children = new Drawable[]
|
||||
@@ -84,7 +82,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
hostText = new LinkFlowContainer
|
||||
hostText = new LinkFlowContainer(s => s.Font = OsuFont.Style.Caption2)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
@@ -103,7 +101,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
Shear = shear,
|
||||
Shear = OsuGame.SHEAR,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@@ -128,12 +126,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(16),
|
||||
Size = new Vector2(12),
|
||||
Icon = FontAwesome.Solid.User,
|
||||
},
|
||||
totalCount = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Default.With(weight: FontWeight.Bold),
|
||||
Font = OsuFont.Style.Caption2.With(weight: FontWeight.Bold),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
|
||||
@@ -45,8 +45,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
private readonly ScrollContainer<Drawable> scroll;
|
||||
private readonly FillFlowContainer<LoungeRoomPanel> roomFlow;
|
||||
|
||||
private const float display_scale = 0.8f;
|
||||
|
||||
// handle deselection
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
@@ -58,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = display_scale,
|
||||
Width = 0.8f,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Child = new OsuContextMenuContainer
|
||||
@@ -188,14 +186,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
SelectedRoom = selectedRoom,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(display_scale),
|
||||
Width = 1 / display_scale,
|
||||
};
|
||||
|
||||
roomFlow.Add(drawableRoom);
|
||||
|
||||
// Always show spotlight playlists at the top of the listing.
|
||||
roomFlow.SetLayoutPosition(drawableRoom, room.Category > RoomCategory.Normal ? float.MinValue : -(room.RoomID ?? 0));
|
||||
roomFlow.SetLayoutPosition(drawableRoom, room.Pinned ? float.MinValue : -(room.RoomID ?? 0));
|
||||
}
|
||||
|
||||
applyFilterCriteria(Filter.Value);
|
||||
|
||||
@@ -31,7 +31,6 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using Container = osu.Framework.Graphics.Containers.Container;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
@@ -39,7 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
public abstract partial class RoomPanel : CompositeDrawable, IHasContextMenu
|
||||
{
|
||||
protected const float CORNER_RADIUS = 10;
|
||||
private const float height = 100;
|
||||
private const float height = 80;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
@@ -59,7 +58,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
private DrawableRoomParticipantsList? drawableRoomParticipantsList;
|
||||
private RoomSpecialCategoryPill? specialCategoryPill;
|
||||
private PasswordProtectedIcon? passwordIcon;
|
||||
private CornerIcon? passwordIcon;
|
||||
private CornerIcon? pinnedIcon;
|
||||
private EndDateInfo? endDateInfo;
|
||||
private RoomNameLine? roomName;
|
||||
private DelayedLoadWrapper wrapper = null!;
|
||||
@@ -79,16 +79,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = CORNER_RADIUS;
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(40),
|
||||
Radius = 5,
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
ButtonsContainer = new Container
|
||||
{
|
||||
@@ -98,13 +92,20 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
AutoSizeAxes = Axes.X
|
||||
};
|
||||
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = colourProvider.Background6.Opacity(0.4f),
|
||||
Radius = 4,
|
||||
};
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Background5,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
CreateBackground().With(d =>
|
||||
{
|
||||
@@ -117,7 +118,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
Name = @"Room content",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// This negative padding resolves 1px gaps between this background and the background above.
|
||||
Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
|
||||
Padding = new MarginPadding { Left = 10, Vertical = -0.5f },
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@@ -128,7 +129,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Background5,
|
||||
Colour = colourProvider.Background5,
|
||||
Width = 0.2f,
|
||||
},
|
||||
new Box
|
||||
@@ -136,7 +137,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f)),
|
||||
Colour = ColourInfo.GradientHorizontal(colourProvider.Background5, colourProvider.Background5.Opacity(0.3f)),
|
||||
Width = 0.8f,
|
||||
},
|
||||
new GridContainer
|
||||
@@ -157,8 +158,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Left = 20,
|
||||
Right = DrawableRoomParticipantsList.SHEAR_WIDTH,
|
||||
Left = 10,
|
||||
Right = 10,
|
||||
Vertical = 5
|
||||
},
|
||||
Children = new Drawable[]
|
||||
@@ -254,7 +255,28 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
}
|
||||
}
|
||||
},
|
||||
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
|
||||
passwordIcon = new CornerIcon
|
||||
{
|
||||
Alpha = 0,
|
||||
Background = { Colour = colours.Gray8, },
|
||||
Icon =
|
||||
{
|
||||
Icon = FontAwesome.Solid.Lock,
|
||||
Colour = colours.Gray3,
|
||||
Rotation = 45,
|
||||
},
|
||||
},
|
||||
pinnedIcon = new CornerIcon
|
||||
{
|
||||
Alpha = 0,
|
||||
Background = { Colour = colours.Orange2 },
|
||||
Icon =
|
||||
{
|
||||
Icon = FontAwesome.Solid.Thumbtack,
|
||||
Colour = colours.Gray3,
|
||||
Rotation = 45,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}, 0)
|
||||
@@ -283,6 +305,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
updateRoomCategory();
|
||||
updateRoomType();
|
||||
updateRoomHasPassword();
|
||||
updateRoomPinned();
|
||||
};
|
||||
|
||||
SelectedItem.BindValueChanged(onSelectedItemChanged, true);
|
||||
@@ -311,6 +334,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
case nameof(Room.HasPassword):
|
||||
updateRoomHasPassword();
|
||||
break;
|
||||
|
||||
case nameof(Room.Pinned):
|
||||
updateRoomPinned();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,6 +398,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
passwordIcon.Alpha = Room.HasPassword ? 1 : 0;
|
||||
}
|
||||
|
||||
private void updateRoomPinned()
|
||||
{
|
||||
if (pinnedIcon != null)
|
||||
pinnedIcon.Alpha = Room.Pinned ? 1 : 0;
|
||||
}
|
||||
|
||||
private int numberOfAvatars = 7;
|
||||
|
||||
public int NumberOfAvatars
|
||||
@@ -483,12 +516,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
statusText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Default.With(size: 16),
|
||||
Font = OsuFont.Style.Caption2,
|
||||
Colour = colours.Lime1
|
||||
},
|
||||
beatmapText = new LinkFlowContainer(s =>
|
||||
{
|
||||
s.Font = OsuFont.Default.With(size: 16);
|
||||
s.Font = OsuFont.Style.Caption2;
|
||||
s.Colour = colours.Lime1;
|
||||
})
|
||||
{
|
||||
@@ -534,10 +567,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
}
|
||||
}
|
||||
|
||||
public partial class PasswordProtectedIcon : CompositeDrawable
|
||||
public partial class CornerIcon : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
public SpriteIcon Icon { get; }
|
||||
public Box Background { get; }
|
||||
|
||||
public CornerIcon()
|
||||
{
|
||||
Anchor = Anchor.TopRight;
|
||||
Origin = Anchor.TopRight;
|
||||
@@ -546,20 +581,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
Background = new Box
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopCentre,
|
||||
Colour = colours.Gray5,
|
||||
Rotation = 45,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 2,
|
||||
},
|
||||
new SpriteIcon
|
||||
Icon = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.Lock,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Origin = Anchor.Centre,
|
||||
Position = new Vector2(-13, 13),
|
||||
Margin = new MarginPadding(6),
|
||||
Size = new Vector2(14),
|
||||
}
|
||||
@@ -602,7 +636,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(size: 28),
|
||||
Font = OsuFont.Style.Heading2,
|
||||
},
|
||||
linkButton = new ExternalLinkButton
|
||||
{
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
{
|
||||
d.Anchor = Anchor.BottomLeft;
|
||||
d.Origin = Anchor.BottomLeft;
|
||||
d.Size = new Vector2(150, 37.5f);
|
||||
d.Size = new Vector2(150, 30f);
|
||||
d.Action = () => Open();
|
||||
})),
|
||||
new FillFlowContainer
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
new AddItemButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Height = 30,
|
||||
Text = "Add item",
|
||||
Action = () => ShowSongSelect()
|
||||
},
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
public ParticipantsList()
|
||||
: base(ParticipantPanel.HEIGHT, initialPoolSize: 20)
|
||||
: base(ParticipantPanel.HEIGHT + 1, initialPoolSize: 20)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
var request = new GetBeatmapSetRequest(id);
|
||||
var tcs = new TaskCompletionSource<APIBeatmapSet?>();
|
||||
|
||||
token.Register(() => request.Cancel());
|
||||
|
||||
// async request success callback is a bit of a dangerous game, but there's some reasoning for it.
|
||||
// - don't really want to use `IAPIAccess.PerformAsync()` because we still want to respect request queueing & online status checks
|
||||
// - we want the realm write here to be async because it is known to be slow for some users with large beatmap collections
|
||||
|
||||
@@ -656,6 +656,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
ensurePlayingSelected();
|
||||
updateBackgroundDim();
|
||||
fetchOnlineInfo();
|
||||
}
|
||||
|
||||
private void onLeavingScreen()
|
||||
@@ -1013,7 +1014,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
lastLookupResult.Value = BeatmapSetLookupResult.InProgress();
|
||||
onlineLookupCancellation = new CancellationTokenSource();
|
||||
currentOnlineLookup = onlineLookupSource.GetBeatmapSetAsync(beatmapSetInfo.OnlineID);
|
||||
currentOnlineLookup = onlineLookupSource.GetBeatmapSetAsync(beatmapSetInfo.OnlineID, onlineLookupCancellation.Token);
|
||||
currentOnlineLookup.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
/// </remarks>
|
||||
protected virtual OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestSceneDependencies();
|
||||
|
||||
protected Room[] GenerateRooms(int count, RulesetInfo? ruleset = null, bool withPassword = false, bool withSpotlightRooms = false)
|
||||
protected Room[] GenerateRooms(int count, RulesetInfo? ruleset = null, bool withPassword = false, bool withPinnedRooms = false)
|
||||
{
|
||||
Room[] rooms = new Room[count];
|
||||
|
||||
@@ -110,10 +110,10 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
Name = $@"Room {currentRoomId}",
|
||||
Host = new APIUser { Username = @"Host" },
|
||||
Duration = TimeSpan.FromSeconds(10),
|
||||
Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal,
|
||||
Password = withPassword ? @"password" : null,
|
||||
PlaylistItemStats = new Room.RoomPlaylistItemStats { RulesetIDs = [ruleset.OnlineID] },
|
||||
Playlist = [new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID }]
|
||||
Playlist = [new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID }],
|
||||
Pinned = withPinnedRooms && i % 2 == 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
@@ -32,7 +34,7 @@ namespace osu.Game.Tests.Visual
|
||||
protected DialogOverlay DialogOverlay { get; private set; }
|
||||
|
||||
[Cached]
|
||||
private ScreenFooter footer;
|
||||
protected ScreenFooter Footer { get; private set; }
|
||||
|
||||
protected ScreenTestScene()
|
||||
{
|
||||
@@ -43,17 +45,32 @@ namespace osu.Game.Tests.Visual
|
||||
Name = nameof(ScreenTestScene),
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
content = new Container { RelativeSizeAxes = Axes.Both },
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
content = new Container { RelativeSizeAxes = Axes.Both },
|
||||
Footer = new ScreenFooter(),
|
||||
}
|
||||
},
|
||||
overlayContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = DialogOverlay = new DialogOverlay()
|
||||
},
|
||||
footer = new ScreenFooter(),
|
||||
});
|
||||
|
||||
Stack.ScreenPushed += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}");
|
||||
Stack.ScreenExited += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}");
|
||||
Stack.ScreenPushed += (oldScreen, newScreen) =>
|
||||
{
|
||||
updateFooter(oldScreen, newScreen);
|
||||
Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}");
|
||||
};
|
||||
Stack.ScreenExited += (oldScreen, newScreen) =>
|
||||
{
|
||||
updateFooter(oldScreen, newScreen);
|
||||
Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}");
|
||||
};
|
||||
}
|
||||
|
||||
protected void LoadScreen(OsuScreen screen) => Stack.Push(screen);
|
||||
@@ -79,6 +96,39 @@ namespace osu.Game.Tests.Visual
|
||||
});
|
||||
}
|
||||
|
||||
private void updateFooter(IScreen? _, IScreen? newScreen)
|
||||
{
|
||||
if (newScreen is OsuScreen osuScreen && osuScreen.ShowFooter)
|
||||
{
|
||||
Footer.Show();
|
||||
|
||||
if (osuScreen.IsLoaded)
|
||||
updateFooterButtons();
|
||||
else
|
||||
{
|
||||
// ensure the current buttons are immediately disabled on screen change (so they can't be pressed).
|
||||
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||
|
||||
osuScreen.OnLoadComplete += _ => updateFooterButtons();
|
||||
}
|
||||
|
||||
void updateFooterButtons()
|
||||
{
|
||||
var buttons = osuScreen.CreateFooterButtons();
|
||||
|
||||
osuScreen.LoadComponentsAgainstScreenDependencies(buttons);
|
||||
|
||||
Footer.SetButtons(buttons);
|
||||
Footer.Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Footer.Hide();
|
||||
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||
}
|
||||
}
|
||||
|
||||
#region IOverlayManager
|
||||
|
||||
IBindable<OverlayActivation> IOverlayManager.OverlayActivationMode { get; } = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="20.1.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.826.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.829.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.821.0" />
|
||||
<PackageReference Include="Sentry" Version="5.1.1" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
||||
+1
-1
@@ -17,6 +17,6 @@
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.826.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.829.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user