1
0
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:
Dean Herbert
2025-09-03 15:35:03 +09:00
committed by GitHub
Unverified
30 changed files with 566 additions and 243 deletions
+1 -1
View File
@@ -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>());
}
}
}
}
+5 -1
View File
@@ -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
{
+4 -1
View File
@@ -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)
{
+101 -82
View File
@@ -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(() =>
+9
View File
@@ -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()
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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,
};
}
+55 -5
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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>