mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 02:13:21 +08:00
Merge branch 'master' into activity-on-multiplayer-screens
This commit is contained in:
commit
e503182a8d
@ -9,6 +9,8 @@ namespace osu.Game.Tests.Resources
|
|||||||
{
|
{
|
||||||
public static class TestResources
|
public static class TestResources
|
||||||
{
|
{
|
||||||
|
public const double QUICK_BEATMAP_LENGTH = 10000;
|
||||||
|
|
||||||
public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
|
public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
|
||||||
|
|
||||||
public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
|
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();
|
var zipStream = new MemoryStream();
|
||||||
using var zip = ZipArchive.Create();
|
using var zip = ZipArchive.Create();
|
||||||
zip.AddEntry("skin.ini", generateSkinIni(name, author));
|
zip.AddEntry("skin.ini", generateSkinIni(name, author, makeUnique));
|
||||||
zip.SaveTo(zipStream);
|
zip.SaveTo(zipStream);
|
||||||
return 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 stream = new MemoryStream();
|
||||||
var writer = new StreamWriter(stream);
|
var writer = new StreamWriter(stream);
|
||||||
@ -155,8 +181,12 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
writer.WriteLine("[General]");
|
writer.WriteLine("[General]");
|
||||||
writer.WriteLine($"Name: {name}");
|
writer.WriteLine($"Name: {name}");
|
||||||
writer.WriteLine($"Author: {author}");
|
writer.WriteLine($"Author: {author}");
|
||||||
writer.WriteLine();
|
|
||||||
writer.WriteLine($"# unique {Guid.NewGuid()}");
|
if (makeUnique)
|
||||||
|
{
|
||||||
|
writer.WriteLine();
|
||||||
|
writer.WriteLine($"# unique {Guid.NewGuid()}");
|
||||||
|
}
|
||||||
|
|
||||||
writer.Flush();
|
writer.Flush();
|
||||||
|
|
||||||
|
@ -142,7 +142,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
|
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
|
||||||
|
|
||||||
createNew();
|
createNew();
|
||||||
|
|
||||||
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
||||||
|
AddUntilStep("wait for components to be hidden", () => !hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().IsPresent);
|
||||||
|
|
||||||
|
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().Reload());
|
||||||
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().ComponentsLoaded);
|
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().ComponentsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
|
|
||||||
AddStep("load chat display", () => Child = chatDisplay = new GameplayChatDisplay
|
AddStep("load chat display", () => Child = chatDisplay = new GameplayChatDisplay(SelectedRoom.Value)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = 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.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK.Input;
|
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)
|
private void createRoom(Func<Room> room)
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddStep("initialise gameplay", () =>
|
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);
|
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().IsLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(false)]
|
||||||
public void TestSingleCommentsPage()
|
[TestCase(true)]
|
||||||
|
public void TestSingleCommentsPage(bool withPinned)
|
||||||
{
|
{
|
||||||
setUpCommentsResponse(exampleComments);
|
setUpCommentsResponse(getExampleComments(withPinned));
|
||||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||||
AddUntilStep("show more button hidden",
|
AddUntilStep("show more button hidden",
|
||||||
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0);
|
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(false)]
|
||||||
public void TestMultipleCommentPages()
|
[TestCase(true)]
|
||||||
|
public void TestMultipleCommentPages(bool withPinned)
|
||||||
{
|
{
|
||||||
var comments = exampleComments;
|
var comments = getExampleComments(withPinned);
|
||||||
comments.HasMore = true;
|
comments.HasMore = true;
|
||||||
comments.TopLevelCount = 10;
|
comments.TopLevelCount = 10;
|
||||||
|
|
||||||
@ -64,11 +66,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 1);
|
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(false)]
|
||||||
public void TestMultipleLoads()
|
[TestCase(true)]
|
||||||
|
public void TestMultipleLoads(bool withPinned)
|
||||||
{
|
{
|
||||||
var comments = exampleComments;
|
var comments = getExampleComments(withPinned);
|
||||||
int topLevelCommentCount = exampleComments.Comments.Count;
|
int topLevelCommentCount = comments.Comments.Count;
|
||||||
|
|
||||||
AddStep("hide container", () => commentsContainer.Hide());
|
AddStep("hide container", () => commentsContainer.Hide());
|
||||||
setUpCommentsResponse(comments);
|
setUpCommentsResponse(comments);
|
||||||
@ -79,6 +82,48 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
() => commentsContainer.ChildrenOfType<DrawableComment>().Count() == topLevelCommentCount);
|
() => 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)
|
private void setUpCommentsResponse(CommentBundle commentBundle)
|
||||||
=> AddStep("set up response", () =>
|
=> 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,
|
new Comment
|
||||||
Message = "This is a comment",
|
{
|
||||||
LegacyName = "FirstUser",
|
Id = 1,
|
||||||
CreatedAt = DateTimeOffset.Now,
|
Message = "This is a comment",
|
||||||
VotesCount = 19,
|
LegacyName = "FirstUser",
|
||||||
RepliesCount = 1
|
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,
|
Id = 15,
|
||||||
ParentId = 1,
|
Message = "This is pinned comment",
|
||||||
Message = "This is a child comment",
|
LegacyName = "PinnedUser",
|
||||||
LegacyName = "SecondUser",
|
|
||||||
CreatedAt = DateTimeOffset.Now,
|
CreatedAt = DateTimeOffset.Now,
|
||||||
VotesCount = 4,
|
VotesCount = 999,
|
||||||
},
|
Pinned = true,
|
||||||
new Comment
|
RepliesCount = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
bundle.Comments.Add(pinnedComment);
|
||||||
|
bundle.PinnedComments.Add(pinnedComment);
|
||||||
|
|
||||||
|
bundle.Comments.Add(new Comment
|
||||||
{
|
{
|
||||||
Id = 10,
|
Id = 20,
|
||||||
Message = "This is another comment",
|
Message = "Reply to pinned comment",
|
||||||
LegacyName = "ThirdUser",
|
LegacyName = "AbandonedUser",
|
||||||
CreatedAt = DateTimeOffset.Now,
|
CreatedAt = DateTimeOffset.Now,
|
||||||
VotesCount = 0
|
VotesCount = 0,
|
||||||
},
|
ParentId = 15,
|
||||||
},
|
});
|
||||||
IncludedComments = new List<Comment>(),
|
}
|
||||||
};
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
AddStep(description, () =>
|
AddStep(description, () =>
|
||||||
{
|
{
|
||||||
|
comment.Pinned = description == "Pinned";
|
||||||
comment.Message = text;
|
comment.Message = text;
|
||||||
container.Add(new DrawableComment(comment));
|
container.Add(new DrawableComment(comment));
|
||||||
});
|
});
|
||||||
@ -59,6 +60,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
private static object[] comments =
|
private static object[] comments =
|
||||||
{
|
{
|
||||||
new[] { "Plain", "This is plain comment" },
|
new[] { "Plain", "This is plain comment" },
|
||||||
|
new[] { "Pinned", "This is pinned comment" },
|
||||||
new[] { "Link", "Please visit https://osu.ppy.sh" },
|
new[] { "Link", "Please visit https://osu.ppy.sh" },
|
||||||
|
|
||||||
new[]
|
new[]
|
||||||
|
@ -149,6 +149,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
IncludedComments = new List<Comment>(),
|
IncludedComments = new List<Comment>(),
|
||||||
|
PinnedComments = new List<Comment>(),
|
||||||
UserVotes = new List<long>
|
UserVotes = new List<long>
|
||||||
{
|
{
|
||||||
5
|
5
|
||||||
@ -178,6 +179,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
IncludedComments = new List<Comment>(),
|
IncludedComments = new List<Comment>(),
|
||||||
|
PinnedComments = new List<Comment>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
private class TestCommentsContainer : CommentsContainer
|
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);
|
protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// Only valid when <see cref="ComputeHash"/> is not overridden.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="existing">The existing model.</param>
|
/// <param name="existing">The existing model.</param>
|
||||||
|
@ -58,6 +58,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty(@"edited_by_id")]
|
[JsonProperty(@"edited_by_id")]
|
||||||
public long? EditedById { get; set; }
|
public long? EditedById { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(@"pinned")]
|
||||||
|
public bool Pinned { get; set; }
|
||||||
|
|
||||||
public User EditedUser { get; set; }
|
public User EditedUser { get; set; }
|
||||||
|
|
||||||
public bool IsTopLevel => !ParentId.HasValue;
|
public bool IsTopLevel => !ParentId.HasValue;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace osu.Game.Online.API.Requests.Responses
|
namespace osu.Game.Online.API.Requests.Responses
|
||||||
{
|
{
|
||||||
@ -24,6 +25,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty(@"included_comments")]
|
[JsonProperty(@"included_comments")]
|
||||||
public List<Comment> IncludedComments { get; set; }
|
public List<Comment> IncludedComments { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(@"pinned_comments")]
|
||||||
|
public List<Comment> PinnedComments { get; set; }
|
||||||
|
|
||||||
private List<long> userVotes;
|
private List<long> userVotes;
|
||||||
|
|
||||||
[JsonProperty(@"user_votes")]
|
[JsonProperty(@"user_votes")]
|
||||||
@ -49,26 +53,17 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
{
|
{
|
||||||
users = value;
|
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)
|
if (comment.UserId == user.Id)
|
||||||
c.User = u;
|
comment.User = user;
|
||||||
|
|
||||||
if (c.EditedById == u.Id)
|
if (comment.EditedById == user.Id)
|
||||||
c.EditedUser = u;
|
comment.EditedUser = user;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
IncludedComments.ForEach(c =>
|
|
||||||
{
|
|
||||||
if (c.UserId == u.Id)
|
|
||||||
c.User = u;
|
|
||||||
|
|
||||||
if (c.EditedById == u.Id)
|
|
||||||
c.EditedUser = u;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
private CancellationTokenSource loadCancellation;
|
private CancellationTokenSource loadCancellation;
|
||||||
private int currentPage;
|
private int currentPage;
|
||||||
|
|
||||||
|
private FillFlowContainer pinnedContent;
|
||||||
private FillFlowContainer content;
|
private FillFlowContainer content;
|
||||||
private DeletedCommentsCounter deletedCommentsCounter;
|
private DeletedCommentsCounter deletedCommentsCounter;
|
||||||
private CommentsShowMoreButton moreButton;
|
private CommentsShowMoreButton moreButton;
|
||||||
@ -63,6 +64,25 @@ namespace osu.Game.Overlays.Comments
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
commentCounter = new TotalCommentsCounter(),
|
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
|
new CommentsHeader
|
||||||
{
|
{
|
||||||
Sort = { BindTarget = Sort },
|
Sort = { BindTarget = Sort },
|
||||||
@ -173,6 +193,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
deletedCommentsCounter.Count.Value = 0;
|
deletedCommentsCounter.Count.Value = 0;
|
||||||
moreButton.Show();
|
moreButton.Show();
|
||||||
moreButton.IsLoading = true;
|
moreButton.IsLoading = true;
|
||||||
|
pinnedContent.Clear();
|
||||||
content.Clear();
|
content.Clear();
|
||||||
CommentDictionary.Clear();
|
CommentDictionary.Clear();
|
||||||
}
|
}
|
||||||
@ -202,7 +223,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
var topLevelComments = new List<DrawableComment>();
|
var topLevelComments = new List<DrawableComment>();
|
||||||
var orphaned = new List<Comment>();
|
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.
|
// Exclude possible duplicated comments.
|
||||||
if (CommentDictionary.ContainsKey(comment.Id))
|
if (CommentDictionary.ContainsKey(comment.Id))
|
||||||
@ -219,13 +240,15 @@ namespace osu.Game.Overlays.Comments
|
|||||||
{
|
{
|
||||||
LoadComponentsAsync(topLevelComments, loaded =>
|
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);
|
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
|
||||||
|
|
||||||
if (bundle.HasMore)
|
if (bundle.HasMore)
|
||||||
{
|
{
|
||||||
int loadedTopLevelComments = 0;
|
int loadedTopLevelComments = 0;
|
||||||
|
pinnedContent.Children.OfType<DrawableComment>().ForEach(p => loadedTopLevelComments++);
|
||||||
content.Children.OfType<DrawableComment>().ForEach(p => loadedTopLevelComments++);
|
content.Children.OfType<DrawableComment>().ForEach(p => loadedTopLevelComments++);
|
||||||
|
|
||||||
moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments;
|
moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments;
|
||||||
@ -300,11 +323,6 @@ namespace osu.Game.Overlays.Comments
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = colourProvider.Background4
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
|
@ -21,6 +21,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Overlays.Comments.Buttons;
|
using osu.Game.Overlays.Comments.Buttons;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Comments
|
namespace osu.Game.Overlays.Comments
|
||||||
{
|
{
|
||||||
@ -137,12 +138,13 @@ namespace osu.Game.Overlays.Comments
|
|||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Spacing = new Vector2(10, 0),
|
Spacing = new Vector2(10, 0),
|
||||||
Children = new Drawable[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold))
|
username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold))
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both
|
AutoSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
|
Comment.Pinned ? new PinnedCommentNotice() : Empty(),
|
||||||
new ParentUsername(Comment),
|
new ParentUsername(Comment),
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
@ -321,9 +323,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
this.FadeTo(show.NewValue ? 1 : 0);
|
this.FadeTo(show.NewValue ? 1 : 0);
|
||||||
}, true);
|
}, true);
|
||||||
childrenExpanded.BindValueChanged(expanded => childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0), true);
|
childrenExpanded.BindValueChanged(expanded => childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0), true);
|
||||||
|
|
||||||
updateButtonsState();
|
updateButtonsState();
|
||||||
|
|
||||||
base.LoadComplete();
|
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
|
private class ParentUsername : FillFlowContainer, IHasTooltip
|
||||||
{
|
{
|
||||||
public LocalisableString TooltipText => getParentMessage();
|
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.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a clickable button which can cycle through one of more mods.
|
/// Represents a clickable button which can cycle through one of more mods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ModButton : ModButtonEmpty, IHasTooltip
|
public class ModButton : ModButtonEmpty, IHasCustomTooltip
|
||||||
{
|
{
|
||||||
private ModIcon foregroundIcon;
|
private ModIcon foregroundIcon;
|
||||||
private ModIcon backgroundIcon;
|
private ModIcon backgroundIcon;
|
||||||
private readonly SpriteText text;
|
private readonly SpriteText text;
|
||||||
private readonly Container<ModIcon> iconsContainer;
|
private readonly Container<ModIcon> iconsContainer;
|
||||||
|
private readonly CompositeDrawable incompatibleIcon;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the selection changes.
|
/// Fired when the selection changes.
|
||||||
@ -43,6 +48,9 @@ namespace osu.Game.Overlays.Mods
|
|||||||
// A selected index of -1 means not selected.
|
// A selected index of -1 means not selected.
|
||||||
private int selectedIndex = -1;
|
private int selectedIndex = -1;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change the selected mod index of this button.
|
/// Change the selected mod index of this button.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -237,6 +245,23 @@ namespace osu.Game.Overlays.Mods
|
|||||||
foregroundIcon.Mod = mod;
|
foregroundIcon.Mod = mod;
|
||||||
text.Text = mod.Name;
|
text.Text = mod.Name;
|
||||||
Colour = mod.HasImplementation ? Color4.White : Color4.Gray;
|
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()
|
private void createIcons()
|
||||||
@ -284,11 +309,20 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
scaleContainer = new Container
|
scaleContainer = new Container
|
||||||
{
|
{
|
||||||
Child = iconsContainer = new Container<ModIcon>
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
iconsContainer = new Container<ModIcon>
|
||||||
Origin = Anchor.Centre,
|
{
|
||||||
Anchor = Anchor.Centre,
|
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,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -305,8 +339,18 @@ namespace osu.Game.Overlays.Mods
|
|||||||
},
|
},
|
||||||
new HoverSounds()
|
new HoverSounds()
|
||||||
};
|
};
|
||||||
|
|
||||||
Mod = mod;
|
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;
|
protected readonly ModSettingsContainer ModSettingsContainer;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
private Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods;
|
private Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods;
|
||||||
|
@ -10,20 +10,18 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
|||||||
{
|
{
|
||||||
public class MatchChatDisplay : StandAloneChatDisplay
|
public class MatchChatDisplay : StandAloneChatDisplay
|
||||||
{
|
{
|
||||||
[Resolved(typeof(Room), nameof(Room.RoomID))]
|
private readonly IBindable<int> channelId = new Bindable<int>();
|
||||||
private Bindable<long?> roomId { get; set; }
|
|
||||||
|
|
||||||
[Resolved(typeof(Room), nameof(Room.ChannelId))]
|
|
||||||
private Bindable<int> channelId { get; set; }
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private ChannelManager channelManager { get; set; }
|
private ChannelManager channelManager { get; set; }
|
||||||
|
|
||||||
|
private readonly Room room;
|
||||||
private readonly bool leaveChannelOnDispose;
|
private readonly bool leaveChannelOnDispose;
|
||||||
|
|
||||||
public MatchChatDisplay(bool leaveChannelOnDispose = true)
|
public MatchChatDisplay(Room room, bool leaveChannelOnDispose = true)
|
||||||
: base(true)
|
: base(true)
|
||||||
{
|
{
|
||||||
|
this.room = room;
|
||||||
this.leaveChannelOnDispose = leaveChannelOnDispose;
|
this.leaveChannelOnDispose = leaveChannelOnDispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,15 +29,17 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
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);
|
channelId.BindValueChanged(_ => updateChannel(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateChannel()
|
private void updateChannel()
|
||||||
{
|
{
|
||||||
if (roomId.Value == null || channelId.Value == 0)
|
if (room.RoomID.Value == null || channelId.Value == 0)
|
||||||
return;
|
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)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
@ -29,8 +30,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
public override bool PropagateNonPositionalInputSubTree => true;
|
public override bool PropagateNonPositionalInputSubTree => true;
|
||||||
|
|
||||||
public GameplayChatDisplay()
|
public GameplayChatDisplay(Room room)
|
||||||
: base(leaveChannelOnDispose: false)
|
: base(room, leaveChannelOnDispose: false)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
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 OverlinedHeader("Chat") { Margin = new MarginPadding { Vertical = 5 }, }, },
|
||||||
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
|
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
@ -395,7 +395,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
|
return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, users));
|
return new PlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +48,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a multiplayer player.
|
/// Construct a multiplayer player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="room">The room.</param>
|
||||||
/// <param name="playlistItem">The playlist item to be played.</param>
|
/// <param name="playlistItem">The playlist item to be played.</param>
|
||||||
/// <param name="users">The users which are participating in this game.</param>
|
/// <param name="users">The users which are participating in this game.</param>
|
||||||
public MultiplayerPlayer(PlaylistItem playlistItem, MultiplayerRoomUser[] users)
|
public MultiplayerPlayer(Room room, PlaylistItem playlistItem, MultiplayerRoomUser[] users)
|
||||||
: base(playlistItem, new PlayerConfiguration
|
: base(room, playlistItem, new PlayerConfiguration
|
||||||
{
|
{
|
||||||
AllowPause = false,
|
AllowPause = false,
|
||||||
AllowRestart = false,
|
AllowRestart = false,
|
||||||
@ -95,7 +96,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
LoadComponentAsync(new GameplayChatDisplay
|
LoadComponentAsync(new GameplayChatDisplay(Room)
|
||||||
{
|
{
|
||||||
Expanded = { BindTarget = HUDOverlay.ShowHud },
|
Expanded = { BindTarget = HUDOverlay.ShowHud },
|
||||||
}, chat => leaderboardFlow.Insert(2, chat));
|
}, chat => leaderboardFlow.Insert(2, chat));
|
||||||
@ -189,10 +190,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||||
{
|
{
|
||||||
Debug.Assert(RoomId.Value != null);
|
Debug.Assert(Room.RoomID.Value != null);
|
||||||
|
|
||||||
return leaderboard.TeamScores.Count == 2
|
return leaderboard.TeamScores.Count == 2
|
||||||
? new MultiplayerTeamResultsScreen(score, RoomId.Value.Value, PlaylistItem, leaderboard.TeamScores)
|
? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, leaderboard.TeamScores)
|
||||||
: new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
|
: new MultiplayerResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -23,8 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
|||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.InPlaylistGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
protected override UserActivity InitialActivity => new UserActivity.InPlaylistGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||||
|
|
||||||
public PlaylistsPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null)
|
public PlaylistsPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration configuration = null)
|
||||||
: base(playlistItem, configuration)
|
: base(room, playlistItem, configuration)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,8 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
|||||||
|
|
||||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||||
{
|
{
|
||||||
Debug.Assert(RoomId.Value != null);
|
Debug.Assert(Room.RoomID.Value != null);
|
||||||
return new PlaylistsResultsScreen(score, RoomId.Value.Value, PlaylistItem, true);
|
return new PlaylistsResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task PrepareScoreForResultsAsync(Score score)
|
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[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
|
||||||
new Drawable[] { new OverlinedHeader("Chat"), },
|
new Drawable[] { new OverlinedHeader("Chat"), },
|
||||||
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
|
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
@ -199,7 +199,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
|||||||
Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})");
|
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()
|
Exited = () => leaderboard.RefreshScores()
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -14,25 +13,28 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RoomSubmittingPlayer : SubmittingPlayer
|
public abstract class RoomSubmittingPlayer : SubmittingPlayer
|
||||||
{
|
{
|
||||||
[Resolved(typeof(Room), nameof(Room.RoomID))]
|
|
||||||
protected Bindable<long?> RoomId { get; private set; }
|
|
||||||
|
|
||||||
protected readonly PlaylistItem PlaylistItem;
|
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)
|
: base(configuration)
|
||||||
{
|
{
|
||||||
|
Room = room;
|
||||||
PlaylistItem = playlistItem;
|
PlaylistItem = playlistItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override APIRequest<APIScoreToken> CreateTokenRequest()
|
protected override APIRequest<APIScoreToken> CreateTokenRequest()
|
||||||
{
|
{
|
||||||
if (!(RoomId.Value is long roomId))
|
if (!(Room.RoomID.Value is long roomId))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new CreateRoomScoreRequest(roomId, PlaylistItem.ID, Game.VersionHash);
|
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]
|
[Resolved]
|
||||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
|
||||||
|
|
||||||
protected Container DisplayedContent { get; private set; }
|
protected Container DisplayedContent { get; private set; }
|
||||||
|
|
||||||
protected WedgeInfoText Info { get; private set; }
|
protected WedgeInfoText Info { get; private set; }
|
||||||
@ -71,7 +68,6 @@ namespace osu.Game.Screens.Select
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ruleset.BindValueChanged(_ => updateDisplay());
|
ruleset.BindValueChanged(_ => updateDisplay());
|
||||||
mods.BindValueChanged(_ => updateDisplay());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const double animation_duration = 800;
|
private const double animation_duration = 800;
|
||||||
@ -138,7 +134,7 @@ namespace osu.Game.Screens.Select
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new BeatmapInfoWedgeBackground(beatmap),
|
new BeatmapInfoWedgeBackground(beatmap),
|
||||||
Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value),
|
Info = new WedgeInfoText(beatmap, ruleset.Value),
|
||||||
}
|
}
|
||||||
}, loaded =>
|
}, loaded =>
|
||||||
{
|
{
|
||||||
@ -169,15 +165,16 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private readonly WorkingBeatmap beatmap;
|
private readonly WorkingBeatmap beatmap;
|
||||||
private readonly RulesetInfo ruleset;
|
private readonly RulesetInfo ruleset;
|
||||||
private readonly IReadOnlyList<Mod> mods;
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
||||||
|
|
||||||
private ModSettingChangeTracker settingChangeTracker;
|
private ModSettingChangeTracker settingChangeTracker;
|
||||||
|
|
||||||
public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList<Mod> mods)
|
public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset)
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
|
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
|
||||||
this.mods = mods;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CancellationTokenSource cancellationSource;
|
private CancellationTokenSource cancellationSource;
|
||||||
@ -363,10 +360,15 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
settingChangeTracker = new ModSettingChangeTracker(mods);
|
mods.BindValueChanged(m =>
|
||||||
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();
|
{
|
||||||
|
settingChangeTracker?.Dispose();
|
||||||
|
|
||||||
refreshBPMLabel();
|
refreshBPMLabel();
|
||||||
|
|
||||||
|
settingChangeTracker = new ModSettingChangeTracker(m.NewValue);
|
||||||
|
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InfoLabel[] getRulesetInfoLabels()
|
private InfoLabel[] getRulesetInfoLabels()
|
||||||
@ -404,7 +406,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
// this doesn't consider mods which apply variable rates, yet.
|
// this doesn't consider mods which apply variable rates, yet.
|
||||||
double rate = 1;
|
double rate = 1;
|
||||||
foreach (var mod in mods.OfType<IApplicableToRate>())
|
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||||
rate = mod.ApplyToRate(0, rate);
|
rate = mod.ApplyToRate(0, rate);
|
||||||
|
|
||||||
double bpmMax = b.ControlPointInfo.BPMMaximum * rate;
|
double bpmMax = b.ControlPointInfo.BPMMaximum * rate;
|
||||||
|
@ -136,18 +136,19 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null)
|
protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null)
|
||||||
{
|
{
|
||||||
// we need to populate early to create a hash based off skin.ini contents
|
var instance = GetSkin(item);
|
||||||
if (item.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true)
|
|
||||||
populateMetadata(item, 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.
|
// we need to populate early to create a hash based off skin.ini contents
|
||||||
// likely, the skin should expose a real version (ie. the version of the skin, not the skin.ini version it's targeting).
|
populateMetadata(item, instance, reader?.Name);
|
||||||
|
|
||||||
return item.ToString().ComputeSHA2Hash();
|
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);
|
return base.ComputeHash(item, reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,13 +158,12 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo();
|
model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo();
|
||||||
|
|
||||||
if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true)
|
populateMetadata(model, instance, archive?.Name);
|
||||||
populateMetadata(model, instance);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
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))
|
if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name))
|
||||||
{
|
{
|
||||||
@ -175,6 +175,13 @@ namespace osu.Game.Skinning
|
|||||||
item.Name = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase);
|
item.Name = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase);
|
||||||
item.Creator ??= unknown_creator_string;
|
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>
|
/// <summary>
|
||||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private readonly BindableList<ISkinnableDrawable> components = new BindableList<ISkinnableDrawable>();
|
private readonly BindableList<ISkinnableDrawable> components = new BindableList<ISkinnableDrawable>();
|
||||||
|
|
||||||
|
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; // ensure that components are loaded even if the target container is hidden (ie. due to user toggle).
|
||||||
|
|
||||||
public bool ComponentsLoaded { get; private set; }
|
public bool ComponentsLoaded { get; private set; }
|
||||||
|
|
||||||
public SkinnableTargetContainer(SkinnableTarget target)
|
public SkinnableTargetContainer(SkinnableTarget target)
|
||||||
|
Loading…
Reference in New Issue
Block a user