mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 10:12:54 +08:00
Merge branch 'master' into fix-hud-test-failure-2
This commit is contained in:
commit
568a8ee97e
@ -9,6 +9,8 @@ namespace osu.Game.Tests.Resources
|
||||
{
|
||||
public static class TestResources
|
||||
{
|
||||
public const double QUICK_BEATMAP_LENGTH = 10000;
|
||||
|
||||
public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
|
||||
|
||||
public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
|
||||
|
@ -138,16 +138,42 @@ namespace osu.Game.Tests.Skins.IO
|
||||
}
|
||||
}
|
||||
|
||||
private MemoryStream createOsk(string name, string author)
|
||||
[Test]
|
||||
public async Task TestSameMetadataNameDifferentFolderName()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
|
||||
var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1", false), "my custom skin 1"));
|
||||
Assert.That(imported.Name, Is.EqualTo("name 1 [my custom skin 1]"));
|
||||
Assert.That(imported.Creator, Is.EqualTo("author 1"));
|
||||
|
||||
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1", false), "my custom skin 2"));
|
||||
Assert.That(imported2.Name, Is.EqualTo("name 1 [my custom skin 2]"));
|
||||
Assert.That(imported2.Creator, Is.EqualTo("author 1"));
|
||||
|
||||
Assert.That(imported2.Hash, Is.Not.EqualTo(imported.Hash));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MemoryStream createOsk(string name, string author, bool makeUnique = true)
|
||||
{
|
||||
var zipStream = new MemoryStream();
|
||||
using var zip = ZipArchive.Create();
|
||||
zip.AddEntry("skin.ini", generateSkinIni(name, author));
|
||||
zip.AddEntry("skin.ini", generateSkinIni(name, author, makeUnique));
|
||||
zip.SaveTo(zipStream);
|
||||
return zipStream;
|
||||
}
|
||||
|
||||
private MemoryStream generateSkinIni(string name, string author)
|
||||
private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream);
|
||||
@ -155,8 +181,12 @@ namespace osu.Game.Tests.Skins.IO
|
||||
writer.WriteLine("[General]");
|
||||
writer.WriteLine($"Name: {name}");
|
||||
writer.WriteLine($"Author: {author}");
|
||||
writer.WriteLine();
|
||||
writer.WriteLine($"# unique {Guid.NewGuid()}");
|
||||
|
||||
if (makeUnique)
|
||||
{
|
||||
writer.WriteLine();
|
||||
writer.WriteLine($"# unique {Guid.NewGuid()}");
|
||||
}
|
||||
|
||||
writer.Flush();
|
||||
|
||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("load chat display", () => Child = chatDisplay = new GameplayChatDisplay
|
||||
AddStep("load chat display", () => Child = chatDisplay = new GameplayChatDisplay(SelectedRoom.Value)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -28,6 +28,8 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
@ -430,6 +432,40 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayFlow()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddRepeatStep("click spectate button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
}, 2);
|
||||
|
||||
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
||||
|
||||
// Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out.
|
||||
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
|
||||
{
|
||||
var time = i;
|
||||
AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType<GameplayClockContainer>().SingleOrDefault()?.GameplayClock.CurrentTime > time);
|
||||
}
|
||||
|
||||
AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen);
|
||||
}
|
||||
|
||||
private void createRoom(Func<Room> room)
|
||||
{
|
||||
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("initialise gameplay", () =>
|
||||
{
|
||||
Stack.Push(player = new MultiplayerPlayer(Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray()));
|
||||
Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray()));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -42,19 +42,21 @@ namespace osu.Game.Tests.Visual.Online
|
||||
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().IsLoading);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleCommentsPage()
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestSingleCommentsPage(bool withPinned)
|
||||
{
|
||||
setUpCommentsResponse(exampleComments);
|
||||
setUpCommentsResponse(getExampleComments(withPinned));
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
AddUntilStep("show more button hidden",
|
||||
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleCommentPages()
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestMultipleCommentPages(bool withPinned)
|
||||
{
|
||||
var comments = exampleComments;
|
||||
var comments = getExampleComments(withPinned);
|
||||
comments.HasMore = true;
|
||||
comments.TopLevelCount = 10;
|
||||
|
||||
@ -64,11 +66,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleLoads()
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestMultipleLoads(bool withPinned)
|
||||
{
|
||||
var comments = exampleComments;
|
||||
int topLevelCommentCount = exampleComments.Comments.Count;
|
||||
var comments = getExampleComments(withPinned);
|
||||
int topLevelCommentCount = comments.Comments.Count;
|
||||
|
||||
AddStep("hide container", () => commentsContainer.Hide());
|
||||
setUpCommentsResponse(comments);
|
||||
@ -79,6 +82,48 @@ namespace osu.Game.Tests.Visual.Online
|
||||
() => commentsContainer.ChildrenOfType<DrawableComment>().Count() == topLevelCommentCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoComment()
|
||||
{
|
||||
var comments = getExampleComments();
|
||||
comments.Comments.Clear();
|
||||
|
||||
setUpCommentsResponse(comments);
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
AddAssert("no comment shown", () => !commentsContainer.ChildrenOfType<DrawableComment>().Any());
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestSingleComment(bool withPinned)
|
||||
{
|
||||
var comment = new Comment
|
||||
{
|
||||
Id = 1,
|
||||
Message = "This is a single comment",
|
||||
LegacyName = "SingleUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 0,
|
||||
Pinned = withPinned,
|
||||
};
|
||||
|
||||
var bundle = new CommentBundle
|
||||
{
|
||||
Comments = new List<Comment> { comment },
|
||||
IncludedComments = new List<Comment>(),
|
||||
PinnedComments = new List<Comment>(),
|
||||
};
|
||||
|
||||
if (withPinned)
|
||||
bundle.PinnedComments.Add(comment);
|
||||
|
||||
setUpCommentsResponse(bundle);
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
AddUntilStep("wait comment load", () => commentsContainer.ChildrenOfType<DrawableComment>().Any());
|
||||
AddAssert("only one comment shown", () =>
|
||||
commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1);
|
||||
}
|
||||
|
||||
private void setUpCommentsResponse(CommentBundle commentBundle)
|
||||
=> AddStep("set up response", () =>
|
||||
{
|
||||
@ -92,38 +137,71 @@ namespace osu.Game.Tests.Visual.Online
|
||||
};
|
||||
});
|
||||
|
||||
private CommentBundle exampleComments => new CommentBundle
|
||||
private CommentBundle getExampleComments(bool withPinned = false)
|
||||
{
|
||||
Comments = new List<Comment>
|
||||
var bundle = new CommentBundle
|
||||
{
|
||||
new Comment
|
||||
Comments = new List<Comment>
|
||||
{
|
||||
Id = 1,
|
||||
Message = "This is a comment",
|
||||
LegacyName = "FirstUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 19,
|
||||
RepliesCount = 1
|
||||
new Comment
|
||||
{
|
||||
Id = 1,
|
||||
Message = "This is a comment",
|
||||
LegacyName = "FirstUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 19,
|
||||
RepliesCount = 1
|
||||
},
|
||||
new Comment
|
||||
{
|
||||
Id = 5,
|
||||
ParentId = 1,
|
||||
Message = "This is a child comment",
|
||||
LegacyName = "SecondUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 4,
|
||||
},
|
||||
new Comment
|
||||
{
|
||||
Id = 10,
|
||||
Message = "This is another comment",
|
||||
LegacyName = "ThirdUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 0
|
||||
},
|
||||
},
|
||||
new Comment
|
||||
IncludedComments = new List<Comment>(),
|
||||
PinnedComments = new List<Comment>(),
|
||||
};
|
||||
|
||||
if (withPinned)
|
||||
{
|
||||
var pinnedComment = new Comment
|
||||
{
|
||||
Id = 5,
|
||||
ParentId = 1,
|
||||
Message = "This is a child comment",
|
||||
LegacyName = "SecondUser",
|
||||
Id = 15,
|
||||
Message = "This is pinned comment",
|
||||
LegacyName = "PinnedUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 4,
|
||||
},
|
||||
new Comment
|
||||
VotesCount = 999,
|
||||
Pinned = true,
|
||||
RepliesCount = 1,
|
||||
};
|
||||
|
||||
bundle.Comments.Add(pinnedComment);
|
||||
bundle.PinnedComments.Add(pinnedComment);
|
||||
|
||||
bundle.Comments.Add(new Comment
|
||||
{
|
||||
Id = 10,
|
||||
Message = "This is another comment",
|
||||
LegacyName = "ThirdUser",
|
||||
Id = 20,
|
||||
Message = "Reply to pinned comment",
|
||||
LegacyName = "AbandonedUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 0
|
||||
},
|
||||
},
|
||||
IncludedComments = new List<Comment>(),
|
||||
};
|
||||
VotesCount = 0,
|
||||
ParentId = 15,
|
||||
});
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep(description, () =>
|
||||
{
|
||||
comment.Pinned = description == "Pinned";
|
||||
comment.Message = text;
|
||||
container.Add(new DrawableComment(comment));
|
||||
});
|
||||
@ -59,6 +60,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
private static object[] comments =
|
||||
{
|
||||
new[] { "Plain", "This is plain comment" },
|
||||
new[] { "Pinned", "This is pinned comment" },
|
||||
new[] { "Link", "Please visit https://osu.ppy.sh" },
|
||||
|
||||
new[]
|
||||
|
@ -149,6 +149,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
}
|
||||
},
|
||||
IncludedComments = new List<Comment>(),
|
||||
PinnedComments = new List<Comment>(),
|
||||
UserVotes = new List<long>
|
||||
{
|
||||
5
|
||||
@ -178,6 +179,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
},
|
||||
},
|
||||
IncludedComments = new List<Comment>(),
|
||||
PinnedComments = new List<Comment>(),
|
||||
};
|
||||
|
||||
private class TestCommentsContainer : CommentsContainer
|
||||
|
@ -806,7 +806,7 @@ namespace osu.Game.Database
|
||||
protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
|
||||
|
||||
/// <summary>
|
||||
/// Whether inport can be skipped after finding an existing import early in the process.
|
||||
/// Whether import can be skipped after finding an existing import early in the process.
|
||||
/// Only valid when <see cref="ComputeHash"/> is not overridden.
|
||||
/// </summary>
|
||||
/// <param name="existing">The existing model.</param>
|
||||
|
@ -58,6 +58,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"edited_by_id")]
|
||||
public long? EditedById { get; set; }
|
||||
|
||||
[JsonProperty(@"pinned")]
|
||||
public bool Pinned { get; set; }
|
||||
|
||||
public User EditedUser { get; set; }
|
||||
|
||||
public bool IsTopLevel => !ParentId.HasValue;
|
||||
|
@ -4,6 +4,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Users;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
@ -24,6 +25,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"included_comments")]
|
||||
public List<Comment> IncludedComments { get; set; }
|
||||
|
||||
[JsonProperty(@"pinned_comments")]
|
||||
public List<Comment> PinnedComments { get; set; }
|
||||
|
||||
private List<long> userVotes;
|
||||
|
||||
[JsonProperty(@"user_votes")]
|
||||
@ -49,26 +53,17 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
users = value;
|
||||
|
||||
value.ForEach(u =>
|
||||
foreach (var user in value)
|
||||
{
|
||||
Comments.ForEach(c =>
|
||||
foreach (var comment in Comments.Concat(IncludedComments).Concat(PinnedComments))
|
||||
{
|
||||
if (c.UserId == u.Id)
|
||||
c.User = u;
|
||||
if (comment.UserId == user.Id)
|
||||
comment.User = user;
|
||||
|
||||
if (c.EditedById == u.Id)
|
||||
c.EditedUser = u;
|
||||
});
|
||||
|
||||
IncludedComments.ForEach(c =>
|
||||
{
|
||||
if (c.UserId == u.Id)
|
||||
c.User = u;
|
||||
|
||||
if (c.EditedById == u.Id)
|
||||
c.EditedUser = u;
|
||||
});
|
||||
});
|
||||
if (comment.EditedById == user.Id)
|
||||
comment.EditedUser = user;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Overlays.Comments
|
||||
private CancellationTokenSource loadCancellation;
|
||||
private int currentPage;
|
||||
|
||||
private FillFlowContainer pinnedContent;
|
||||
private FillFlowContainer content;
|
||||
private DeletedCommentsCounter deletedCommentsCounter;
|
||||
private CommentsShowMoreButton moreButton;
|
||||
@ -63,6 +64,25 @@ namespace osu.Game.Overlays.Comments
|
||||
Children = new Drawable[]
|
||||
{
|
||||
commentCounter = new TotalCommentsCounter(),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4,
|
||||
},
|
||||
pinnedContent = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
},
|
||||
},
|
||||
},
|
||||
new CommentsHeader
|
||||
{
|
||||
Sort = { BindTarget = Sort },
|
||||
@ -173,6 +193,7 @@ namespace osu.Game.Overlays.Comments
|
||||
deletedCommentsCounter.Count.Value = 0;
|
||||
moreButton.Show();
|
||||
moreButton.IsLoading = true;
|
||||
pinnedContent.Clear();
|
||||
content.Clear();
|
||||
CommentDictionary.Clear();
|
||||
}
|
||||
@ -202,7 +223,7 @@ namespace osu.Game.Overlays.Comments
|
||||
var topLevelComments = new List<DrawableComment>();
|
||||
var orphaned = new List<Comment>();
|
||||
|
||||
foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
|
||||
foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments).Concat(bundle.PinnedComments))
|
||||
{
|
||||
// Exclude possible duplicated comments.
|
||||
if (CommentDictionary.ContainsKey(comment.Id))
|
||||
@ -219,13 +240,15 @@ namespace osu.Game.Overlays.Comments
|
||||
{
|
||||
LoadComponentsAsync(topLevelComments, loaded =>
|
||||
{
|
||||
content.AddRange(loaded);
|
||||
pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned));
|
||||
content.AddRange(loaded.Where(d => !d.Comment.Pinned));
|
||||
|
||||
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
|
||||
|
||||
if (bundle.HasMore)
|
||||
{
|
||||
int loadedTopLevelComments = 0;
|
||||
pinnedContent.Children.OfType<DrawableComment>().ForEach(p => loadedTopLevelComments++);
|
||||
content.Children.OfType<DrawableComment>().ForEach(p => loadedTopLevelComments++);
|
||||
|
||||
moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments;
|
||||
@ -300,11 +323,6 @@ namespace osu.Game.Overlays.Comments
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
|
@ -21,6 +21,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using System.Collections.Specialized;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Overlays.Comments.Buttons;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Overlays.Comments
|
||||
{
|
||||
@ -137,12 +138,13 @@ namespace osu.Game.Overlays.Comments
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
Children = new[]
|
||||
{
|
||||
username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold))
|
||||
{
|
||||
AutoSizeAxes = Axes.Both
|
||||
},
|
||||
Comment.Pinned ? new PinnedCommentNotice() : Empty(),
|
||||
new ParentUsername(Comment),
|
||||
new OsuSpriteText
|
||||
{
|
||||
@ -321,9 +323,7 @@ namespace osu.Game.Overlays.Comments
|
||||
this.FadeTo(show.NewValue ? 1 : 0);
|
||||
}, true);
|
||||
childrenExpanded.BindValueChanged(expanded => childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0), true);
|
||||
|
||||
updateButtonsState();
|
||||
|
||||
base.LoadComplete();
|
||||
}
|
||||
|
||||
@ -392,6 +392,33 @@ namespace osu.Game.Overlays.Comments
|
||||
};
|
||||
}
|
||||
|
||||
private class PinnedCommentNotice : FillFlowContainer
|
||||
{
|
||||
public PinnedCommentNotice()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Direction = FillDirection.Horizontal;
|
||||
Spacing = new Vector2(2, 0);
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.Thumbtack,
|
||||
Size = new Vector2(14),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold),
|
||||
Text = CommentsStrings.Pinned,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class ParentUsername : FillFlowContainer, IHasTooltip
|
||||
{
|
||||
public LocalisableString TooltipText => getParentMessage();
|
||||
|
64
osu.Game/Overlays/Mods/IncompatibleIcon.cs
Normal file
64
osu.Game/Overlays/Mods/IncompatibleIcon.cs
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class IncompatibleIcon : VisibilityContainer, IHasTooltip
|
||||
{
|
||||
private Circle circle;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Size = new Vector2(20);
|
||||
|
||||
State.Value = Visibility.Hidden;
|
||||
Alpha = 0;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray4,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Size = new Vector2(0.6f),
|
||||
Icon = FontAwesome.Solid.Slash,
|
||||
Colour = Color4.White,
|
||||
Shadow = true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.FadeIn(200, Easing.OutQuint);
|
||||
circle.FlashColour(Color4.Red, 500, Easing.OutQuint);
|
||||
this.ScaleTo(1.8f).Then().ScaleTo(1, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.FadeOut(200, Easing.OutQuint);
|
||||
this.ScaleTo(0.8f, 200, Easing.In);
|
||||
}
|
||||
|
||||
public LocalisableString TooltipText => "Incompatible with current selected mods";
|
||||
}
|
||||
}
|
@ -11,24 +11,29 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a clickable button which can cycle through one of more mods.
|
||||
/// </summary>
|
||||
public class ModButton : ModButtonEmpty, IHasTooltip
|
||||
public class ModButton : ModButtonEmpty, IHasCustomTooltip
|
||||
{
|
||||
private ModIcon foregroundIcon;
|
||||
private ModIcon backgroundIcon;
|
||||
private readonly SpriteText text;
|
||||
private readonly Container<ModIcon> iconsContainer;
|
||||
private readonly CompositeDrawable incompatibleIcon;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the selection changes.
|
||||
@ -43,6 +48,9 @@ namespace osu.Game.Overlays.Mods
|
||||
// A selected index of -1 means not selected.
|
||||
private int selectedIndex = -1;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Change the selected mod index of this button.
|
||||
/// </summary>
|
||||
@ -237,6 +245,23 @@ namespace osu.Game.Overlays.Mods
|
||||
foregroundIcon.Mod = mod;
|
||||
text.Text = mod.Name;
|
||||
Colour = mod.HasImplementation ? Color4.White : Color4.Gray;
|
||||
|
||||
Scheduler.AddOnce(updateCompatibility);
|
||||
}
|
||||
|
||||
private void updateCompatibility()
|
||||
{
|
||||
var m = SelectedMod ?? Mods.First();
|
||||
|
||||
bool isIncompatible = false;
|
||||
|
||||
if (selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(m))
|
||||
isIncompatible = !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(m));
|
||||
|
||||
if (isIncompatible)
|
||||
incompatibleIcon.Show();
|
||||
else
|
||||
incompatibleIcon.Hide();
|
||||
}
|
||||
|
||||
private void createIcons()
|
||||
@ -284,11 +309,20 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
scaleContainer = new Container
|
||||
{
|
||||
Child = iconsContainer = new Container<ModIcon>
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
iconsContainer = new Container<ModIcon>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
},
|
||||
incompatibleIcon = new IncompatibleIcon
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Position = new Vector2(-13),
|
||||
}
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
@ -305,8 +339,18 @@ namespace osu.Game.Overlays.Mods
|
||||
},
|
||||
new HoverSounds()
|
||||
};
|
||||
|
||||
Mod = mod;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateCompatibility), true);
|
||||
}
|
||||
|
||||
public ITooltip GetCustomTooltip() => new ModButtonTooltip();
|
||||
|
||||
public object TooltipContent => SelectedMod ?? Mods.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
115
osu.Game/Overlays/Mods/ModButtonTooltip.cs
Normal file
115
osu.Game/Overlays/Mods/ModButtonTooltip.cs
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModButtonTooltip : VisibilityContainer, ITooltip
|
||||
{
|
||||
private readonly OsuSpriteText descriptionText;
|
||||
private readonly Box background;
|
||||
private readonly OsuSpriteText incompatibleText;
|
||||
|
||||
private readonly Bindable<IReadOnlyList<Mod>> incompatibleMods = new Bindable<IReadOnlyList<Mod>>();
|
||||
|
||||
[Resolved]
|
||||
private Bindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
public ModButtonTooltip()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
descriptionText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Regular),
|
||||
Margin = new MarginPadding { Bottom = 5 }
|
||||
},
|
||||
incompatibleText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Regular),
|
||||
Text = "Incompatible with:"
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Current = incompatibleMods,
|
||||
ExpansionMode = ExpansionMode.AlwaysExpanded,
|
||||
Scale = new Vector2(0.7f)
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
background.Colour = colours.Gray3;
|
||||
descriptionText.Colour = colours.BlueLighter;
|
||||
incompatibleText.Colour = colours.BlueLight;
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
|
||||
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
|
||||
|
||||
private Mod lastMod;
|
||||
|
||||
public bool SetContent(object content)
|
||||
{
|
||||
if (!(content is Mod mod))
|
||||
return false;
|
||||
|
||||
if (mod.Equals(lastMod)) return true;
|
||||
|
||||
lastMod = mod;
|
||||
|
||||
descriptionText.Text = mod.Description;
|
||||
|
||||
var incompatibleTypes = mod.IncompatibleMods;
|
||||
|
||||
var allMods = ruleset.Value.CreateInstance().GetAllMods();
|
||||
|
||||
incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList();
|
||||
|
||||
if (!incompatibleMods.Value.Any())
|
||||
{
|
||||
incompatibleText.Text = "Compatible with all mods";
|
||||
return true;
|
||||
}
|
||||
|
||||
incompatibleText.Text = "Incompatible with:";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Move(Vector2 pos) => Position = pos;
|
||||
}
|
||||
}
|
@ -74,6 +74,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
protected readonly ModSettingsContainer ModSettingsContainer;
|
||||
|
||||
[Cached]
|
||||
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
private Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods;
|
||||
|
@ -10,20 +10,18 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
{
|
||||
public class MatchChatDisplay : StandAloneChatDisplay
|
||||
{
|
||||
[Resolved(typeof(Room), nameof(Room.RoomID))]
|
||||
private Bindable<long?> roomId { get; set; }
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.ChannelId))]
|
||||
private Bindable<int> channelId { get; set; }
|
||||
private readonly IBindable<int> channelId = new Bindable<int>();
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ChannelManager channelManager { get; set; }
|
||||
|
||||
private readonly Room room;
|
||||
private readonly bool leaveChannelOnDispose;
|
||||
|
||||
public MatchChatDisplay(bool leaveChannelOnDispose = true)
|
||||
public MatchChatDisplay(Room room, bool leaveChannelOnDispose = true)
|
||||
: base(true)
|
||||
{
|
||||
this.room = room;
|
||||
this.leaveChannelOnDispose = leaveChannelOnDispose;
|
||||
}
|
||||
|
||||
@ -31,15 +29,17 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// Required for the time being since this component is created prior to the room being joined.
|
||||
channelId.BindTo(room.ChannelId);
|
||||
channelId.BindValueChanged(_ => updateChannel(), true);
|
||||
}
|
||||
|
||||
private void updateChannel()
|
||||
{
|
||||
if (roomId.Value == null || channelId.Value == 0)
|
||||
if (room.RoomID.Value == null || channelId.Value == 0)
|
||||
return;
|
||||
|
||||
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{roomId.Value}" });
|
||||
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" });
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
@ -29,8 +30,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
public override bool PropagateNonPositionalInputSubTree => true;
|
||||
|
||||
public GameplayChatDisplay()
|
||||
: base(leaveChannelOnDispose: false)
|
||||
public GameplayChatDisplay(Room room)
|
||||
: base(room, leaveChannelOnDispose: false)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
|
@ -173,7 +173,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
},
|
||||
},
|
||||
new Drawable[] { new OverlinedHeader("Chat") { Margin = new MarginPadding { Vertical = 5 }, }, },
|
||||
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
|
||||
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
@ -395,7 +395,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
|
||||
|
||||
default:
|
||||
return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, users));
|
||||
return new PlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,10 +45,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
/// <summary>
|
||||
/// Construct a multiplayer player.
|
||||
/// </summary>
|
||||
/// <param name="room">The room.</param>
|
||||
/// <param name="playlistItem">The playlist item to be played.</param>
|
||||
/// <param name="users">The users which are participating in this game.</param>
|
||||
public MultiplayerPlayer(PlaylistItem playlistItem, MultiplayerRoomUser[] users)
|
||||
: base(playlistItem, new PlayerConfiguration
|
||||
public MultiplayerPlayer(Room room, PlaylistItem playlistItem, MultiplayerRoomUser[] users)
|
||||
: base(room, playlistItem, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
AllowRestart = false,
|
||||
@ -92,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}
|
||||
});
|
||||
|
||||
LoadComponentAsync(new GameplayChatDisplay
|
||||
LoadComponentAsync(new GameplayChatDisplay(Room)
|
||||
{
|
||||
Expanded = { BindTarget = HUDOverlay.ShowHud },
|
||||
}, chat => leaderboardFlow.Insert(2, chat));
|
||||
@ -186,10 +187,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||
{
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
Debug.Assert(Room.RoomID.Value != null);
|
||||
|
||||
return leaderboard.TeamScores.Count == 2
|
||||
? new MultiplayerTeamResultsScreen(score, RoomId.Value.Value, PlaylistItem, leaderboard.TeamScores)
|
||||
: new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
|
||||
? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, leaderboard.TeamScores)
|
||||
: new MultiplayerResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -20,8 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public Action Exited;
|
||||
|
||||
public PlaylistsPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null)
|
||||
: base(playlistItem, configuration)
|
||||
public PlaylistsPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration configuration = null)
|
||||
: base(room, playlistItem, configuration)
|
||||
{
|
||||
}
|
||||
|
||||
@ -51,8 +51,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||
{
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
return new PlaylistsResultsScreen(score, RoomId.Value.Value, PlaylistItem, true);
|
||||
Debug.Assert(Room.RoomID.Value != null);
|
||||
return new PlaylistsResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, true);
|
||||
}
|
||||
|
||||
protected override async Task PrepareScoreForResultsAsync(Score score)
|
||||
|
@ -158,7 +158,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
},
|
||||
new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
|
||||
new Drawable[] { new OverlinedHeader("Chat"), },
|
||||
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
|
||||
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
@ -199,7 +199,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})");
|
||||
}
|
||||
|
||||
protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(SelectedItem.Value)
|
||||
protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(Room, SelectedItem.Value)
|
||||
{
|
||||
Exited = () => leaderboard.RefreshScores()
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using System.Diagnostics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Scoring;
|
||||
@ -14,25 +13,28 @@ namespace osu.Game.Screens.Play
|
||||
/// </summary>
|
||||
public abstract class RoomSubmittingPlayer : SubmittingPlayer
|
||||
{
|
||||
[Resolved(typeof(Room), nameof(Room.RoomID))]
|
||||
protected Bindable<long?> RoomId { get; private set; }
|
||||
|
||||
protected readonly PlaylistItem PlaylistItem;
|
||||
protected readonly Room Room;
|
||||
|
||||
protected RoomSubmittingPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null)
|
||||
protected RoomSubmittingPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration configuration = null)
|
||||
: base(configuration)
|
||||
{
|
||||
Room = room;
|
||||
PlaylistItem = playlistItem;
|
||||
}
|
||||
|
||||
protected override APIRequest<APIScoreToken> CreateTokenRequest()
|
||||
{
|
||||
if (!(RoomId.Value is long roomId))
|
||||
if (!(Room.RoomID.Value is long roomId))
|
||||
return null;
|
||||
|
||||
return new CreateRoomScoreRequest(roomId, PlaylistItem.ID, Game.VersionHash);
|
||||
}
|
||||
|
||||
protected override APIRequest<MultiplayerScore> CreateSubmissionRequest(Score score, long token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo);
|
||||
protected override APIRequest<MultiplayerScore> CreateSubmissionRequest(Score score, long token)
|
||||
{
|
||||
Debug.Assert(Room.RoomID.Value != null);
|
||||
return new SubmitRoomScoreRequest(token, Room.RoomID.Value.Value, PlaylistItem.ID, score.ScoreInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,9 +44,6 @@ namespace osu.Game.Screens.Select
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
||||
|
||||
protected Container DisplayedContent { get; private set; }
|
||||
|
||||
protected WedgeInfoText Info { get; private set; }
|
||||
@ -71,7 +68,6 @@ namespace osu.Game.Screens.Select
|
||||
private void load()
|
||||
{
|
||||
ruleset.BindValueChanged(_ => updateDisplay());
|
||||
mods.BindValueChanged(_ => updateDisplay());
|
||||
}
|
||||
|
||||
private const double animation_duration = 800;
|
||||
@ -138,7 +134,7 @@ namespace osu.Game.Screens.Select
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapInfoWedgeBackground(beatmap),
|
||||
Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value),
|
||||
Info = new WedgeInfoText(beatmap, ruleset.Value),
|
||||
}
|
||||
}, loaded =>
|
||||
{
|
||||
@ -169,15 +165,16 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private readonly WorkingBeatmap beatmap;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly IReadOnlyList<Mod> mods;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
||||
|
||||
private ModSettingChangeTracker settingChangeTracker;
|
||||
|
||||
public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList<Mod> mods)
|
||||
public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
|
||||
this.mods = mods;
|
||||
}
|
||||
|
||||
private CancellationTokenSource cancellationSource;
|
||||
@ -363,10 +360,15 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
};
|
||||
|
||||
settingChangeTracker = new ModSettingChangeTracker(mods);
|
||||
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();
|
||||
mods.BindValueChanged(m =>
|
||||
{
|
||||
settingChangeTracker?.Dispose();
|
||||
|
||||
refreshBPMLabel();
|
||||
refreshBPMLabel();
|
||||
|
||||
settingChangeTracker = new ModSettingChangeTracker(m.NewValue);
|
||||
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();
|
||||
}, true);
|
||||
}
|
||||
|
||||
private InfoLabel[] getRulesetInfoLabels()
|
||||
@ -404,7 +406,7 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
// this doesn't consider mods which apply variable rates, yet.
|
||||
double rate = 1;
|
||||
foreach (var mod in mods.OfType<IApplicableToRate>())
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||
rate = mod.ApplyToRate(0, rate);
|
||||
|
||||
double bpmMax = b.ControlPointInfo.BPMMaximum * rate;
|
||||
|
@ -136,18 +136,19 @@ namespace osu.Game.Skinning
|
||||
|
||||
protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null)
|
||||
{
|
||||
// we need to populate early to create a hash based off skin.ini contents
|
||||
if (item.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true)
|
||||
populateMetadata(item, GetSkin(item));
|
||||
var instance = GetSkin(item);
|
||||
|
||||
if (item.Creator != null && item.Creator != unknown_creator_string)
|
||||
// in the case the skin has a skin.ini file, we are going to create a hash based on that.
|
||||
// we don't want to do this in the case we don't have a skin.ini, as it would match only on the filename portion,
|
||||
// causing potentially unique skin imports to be considered as a duplicate.
|
||||
if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name))
|
||||
{
|
||||
// this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation.
|
||||
// likely, the skin should expose a real version (ie. the version of the skin, not the skin.ini version it's targeting).
|
||||
// we need to populate early to create a hash based off skin.ini contents
|
||||
populateMetadata(item, instance, reader?.Name);
|
||||
|
||||
return item.ToString().ComputeSHA2Hash();
|
||||
}
|
||||
|
||||
// if there was no creator, the ToString above would give the filename, which alone isn't really enough to base any decisions on.
|
||||
return base.ComputeHash(item, reader);
|
||||
}
|
||||
|
||||
@ -157,13 +158,12 @@ namespace osu.Game.Skinning
|
||||
|
||||
model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo();
|
||||
|
||||
if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true)
|
||||
populateMetadata(model, instance);
|
||||
populateMetadata(model, instance, archive?.Name);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void populateMetadata(SkinInfo item, Skin instance)
|
||||
private void populateMetadata(SkinInfo item, Skin instance, string archiveName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name))
|
||||
{
|
||||
@ -175,6 +175,13 @@ namespace osu.Game.Skinning
|
||||
item.Name = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase);
|
||||
item.Creator ??= unknown_creator_string;
|
||||
}
|
||||
|
||||
// generally when importing from a folder, the ".osk" extension will not be present.
|
||||
// if we ever need a more reliable method of determining this, the type of `ArchiveReader` can be checked.
|
||||
bool isArchiveImport = archiveName?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
if (archiveName != null && !isArchiveImport && archiveName != item.Name)
|
||||
item.Name = $"{item.Name} [{archiveName}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
Loading…
Reference in New Issue
Block a user