mirror of
https://github.com/ppy/osu.git
synced 2025-01-27 14:03:01 +08:00
Merge branch 'master' into user-profile-daily-challenge-streak-display
This commit is contained in:
commit
310def64f4
@ -21,7 +21,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2024.517.0",
|
"version": "2024.802.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
@ -220,7 +220,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private float fadeExponent;
|
private float fadeExponent;
|
||||||
|
|
||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private Vector2 size;
|
|
||||||
private Vector2 originPosition;
|
private Vector2 originPosition;
|
||||||
|
|
||||||
private IVertexBatch<TexturedTrailVertex> vertexBatch;
|
private IVertexBatch<TexturedTrailVertex> vertexBatch;
|
||||||
@ -236,7 +235,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
shader = Source.shader;
|
shader = Source.shader;
|
||||||
texture = Source.texture;
|
texture = Source.texture;
|
||||||
size = Source.partSize;
|
|
||||||
time = Source.time;
|
time = Source.time;
|
||||||
fadeExponent = Source.FadeExponent;
|
fadeExponent = Source.FadeExponent;
|
||||||
|
|
||||||
@ -277,6 +275,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
RectangleF textureRect = texture.GetTextureRect();
|
RectangleF textureRect = texture.GetTextureRect();
|
||||||
|
|
||||||
|
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
||||||
|
|
||||||
foreach (var part in parts)
|
foreach (var part in parts)
|
||||||
{
|
{
|
||||||
if (part.InvalidationID == -1)
|
if (part.InvalidationID == -1)
|
||||||
@ -285,11 +285,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
if (time - part.Time >= 1)
|
if (time - part.Time >= 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Vector2 screenSpacePos = Source.ToScreenSpace(part.Position);
|
|
||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y + size.Y * (1 - originPosition.Y)),
|
Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)),
|
||||||
TexturePosition = textureRect.BottomLeft,
|
TexturePosition = textureRect.BottomLeft,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
||||||
@ -298,7 +296,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y + size.Y * (1 - originPosition.Y)),
|
Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)),
|
||||||
TexturePosition = textureRect.BottomRight,
|
TexturePosition = textureRect.BottomRight,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
||||||
@ -307,7 +305,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y - size.Y * originPosition.Y),
|
Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y - texture.DisplayHeight * originPosition.Y),
|
||||||
TexturePosition = textureRect.TopRight,
|
TexturePosition = textureRect.TopRight,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
||||||
@ -316,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y - size.Y * originPosition.Y),
|
Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y - texture.DisplayHeight * originPosition.Y),
|
||||||
TexturePosition = textureRect.TopLeft,
|
TexturePosition = textureRect.TopLeft,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
||||||
@ -324,6 +322,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderer.PopLocalMatrix();
|
||||||
|
|
||||||
vertexBatch.Draw();
|
vertexBatch.Draw();
|
||||||
shader.Unbind();
|
shader.Unbind();
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ namespace osu.Game.Tests.Editing
|
|||||||
new object?[] { "1:02:3000", false, null, null },
|
new object?[] { "1:02:3000", false, null, null },
|
||||||
new object?[] { "1:02:300 ()", false, null, null },
|
new object?[] { "1:02:300 ()", false, null, null },
|
||||||
new object?[] { "1:02:300 (1,2,3)", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
new object?[] { "1:02:300 (1,2,3)", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||||
|
new object?[] { "1:02:300 (1,2,3) - ", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||||
|
new object?[] { "1:02:300 (1,2,3) - following mod", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||||
|
new object?[] { "1:02:300 (1,2,3) - following mod\nwith newlines", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(nameof(test_cases))]
|
[TestCaseSource(nameof(test_cases))]
|
||||||
|
@ -8,6 +8,8 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Mods
|
namespace osu.Game.Tests.Mods
|
||||||
@ -105,9 +107,6 @@ namespace osu.Game.Tests.Mods
|
|||||||
testMod.ResetSettingsToDefaults();
|
testMod.ResetSettingsToDefaults();
|
||||||
|
|
||||||
Assert.That(testMod.DrainRate.Value, Is.Null);
|
Assert.That(testMod.DrainRate.Value, Is.Null);
|
||||||
|
|
||||||
// ReSharper disable once HeuristicUnreachableCode
|
|
||||||
// see https://youtrack.jetbrains.com/issue/RIDER-70159.
|
|
||||||
Assert.That(testMod.OverallDifficulty.Value, Is.Null);
|
Assert.That(testMod.OverallDifficulty.Value, Is.Null);
|
||||||
|
|
||||||
var applied = applyDifficulty(new BeatmapDifficulty
|
var applied = applyDifficulty(new BeatmapDifficulty
|
||||||
@ -119,6 +118,48 @@ namespace osu.Game.Tests.Mods
|
|||||||
Assert.That(applied.OverallDifficulty, Is.EqualTo(10));
|
Assert.That(applied.OverallDifficulty, Is.EqualTo(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeserializeIncorrectRange()
|
||||||
|
{
|
||||||
|
var apiMod = new APIMod
|
||||||
|
{
|
||||||
|
Acronym = @"DA",
|
||||||
|
Settings = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
[@"circle_size"] = -727,
|
||||||
|
[@"approach_rate"] = -727,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ruleset = new OsuRuleset();
|
||||||
|
|
||||||
|
var mod = (OsuModDifficultyAdjust)apiMod.ToMod(ruleset);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(mod.CircleSize.Value, Is.GreaterThanOrEqualTo(0).And.LessThanOrEqualTo(11));
|
||||||
|
Assert.That(mod.ApproachRate.Value, Is.GreaterThanOrEqualTo(-10).And.LessThanOrEqualTo(11));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeserializeNegativeApproachRate()
|
||||||
|
{
|
||||||
|
var apiMod = new APIMod
|
||||||
|
{
|
||||||
|
Acronym = @"DA",
|
||||||
|
Settings = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
[@"approach_rate"] = -9,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ruleset = new OsuRuleset();
|
||||||
|
|
||||||
|
var mod = (OsuModDifficultyAdjust)apiMod.ToMod(ruleset);
|
||||||
|
|
||||||
|
Assert.That(mod.ApproachRate.Value, Is.GreaterThanOrEqualTo(-10).And.LessThanOrEqualTo(11));
|
||||||
|
Assert.That(mod.ApproachRate.Value, Is.EqualTo(-9));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies a <see cref="BeatmapDifficulty"/> to the mod and returns a new <see cref="BeatmapDifficulty"/>
|
/// Applies a <see cref="BeatmapDifficulty"/> to the mod and returns a new <see cref="BeatmapDifficulty"/>
|
||||||
/// representing the result if the mod were applied to a fresh <see cref="BeatmapDifficulty"/> instance.
|
/// representing the result if the mod were applied to a fresh <see cref="BeatmapDifficulty"/> instance.
|
||||||
|
@ -4,16 +4,34 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Metadata;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
using osu.Game.Tests.Visual.Metadata;
|
||||||
using osu.Game.Tests.Visual.OnlinePlay;
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.DailyChallenge
|
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||||
{
|
{
|
||||||
public partial class TestSceneDailyChallenge : OnlinePlayTestScene
|
public partial class TestSceneDailyChallenge : OnlinePlayTestScene
|
||||||
{
|
{
|
||||||
|
[Cached(typeof(MetadataClient))]
|
||||||
|
private TestMetadataClient metadataClient = new TestMetadataClient();
|
||||||
|
|
||||||
|
[Cached(typeof(INotificationOverlay))]
|
||||||
|
private NotificationOverlay notificationOverlay = new NotificationOverlay();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
base.Content.Add(notificationOverlay);
|
||||||
|
base.Content.Add(metadataClient);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDailyChallenge()
|
public void TestDailyChallenge()
|
||||||
{
|
{
|
||||||
@ -36,5 +54,33 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
|||||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||||
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNotifications()
|
||||||
|
{
|
||||||
|
var room = new Room
|
||||||
|
{
|
||||||
|
RoomID = { Value = 1234 },
|
||||||
|
Name = { Value = "Daily Challenge: June 4, 2024" },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
|
||||||
|
{
|
||||||
|
RequiredMods = [new APIMod(new OsuModTraceable())],
|
||||||
|
AllowedMods = [new APIMod(new OsuModDoubleTime())]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
|
||||||
|
Category = { Value = RoomCategory.DailyChallenge }
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||||
|
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });
|
||||||
|
|
||||||
|
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
|
||||||
|
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||||
|
AddUntilStep("wait for screen", () => screen.IsCurrentScreen());
|
||||||
|
AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
@ -20,11 +21,11 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
|||||||
[Cached]
|
[Cached]
|
||||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
[Test]
|
private DailyChallengeScoreBreakdown breakdown = null!;
|
||||||
public void TestBasicAppearance()
|
|
||||||
{
|
|
||||||
DailyChallengeScoreBreakdown breakdown = null!;
|
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
AddStep("create content", () => Children = new Drawable[]
|
AddStep("create content", () => Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
@ -50,7 +51,14 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
|||||||
breakdown.Height = height;
|
breakdown.Height = height;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddToggleStep("toggle visible", v => breakdown.Alpha = v ? 1 : 0);
|
||||||
|
|
||||||
AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1]));
|
AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicAppearance()
|
||||||
|
{
|
||||||
AddStep("add new score", () =>
|
AddStep("add new score", () =>
|
||||||
{
|
{
|
||||||
var ev = new NewScoreEvent(1, new APIUser
|
var ev = new NewScoreEvent(1, new APIUser
|
||||||
@ -65,5 +73,24 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
|||||||
AddStep("set user score", () => breakdown.UserBestScore.Value = new MultiplayerScore { TotalScore = RNG.Next(1_000_000) });
|
AddStep("set user score", () => breakdown.UserBestScore.Value = new MultiplayerScore { TotalScore = RNG.Next(1_000_000) });
|
||||||
AddStep("unset user score", () => breakdown.UserBestScore.Value = null);
|
AddStep("unset user score", () => breakdown.UserBestScore.Value = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMassAdd()
|
||||||
|
{
|
||||||
|
AddStep("add 1000 scores at once", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 1000; i++)
|
||||||
|
{
|
||||||
|
var ev = new NewScoreEvent(1, new APIUser
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Username = "peppy",
|
||||||
|
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
}, RNG.Next(1_000_000), null);
|
||||||
|
|
||||||
|
breakdown.AddNewScore(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
|||||||
if (ring.IsNotNull())
|
if (ring.IsNotNull())
|
||||||
ring.Height = height;
|
ring.Height = height;
|
||||||
});
|
});
|
||||||
|
AddToggleStep("toggle visible", v => ring.Alpha = v ? 1 : 0);
|
||||||
|
|
||||||
AddStep("just started", () =>
|
AddStep("just started", () =>
|
||||||
{
|
{
|
||||||
room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1);
|
room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1);
|
||||||
|
@ -49,6 +49,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
|||||||
if (totals.IsNotNull())
|
if (totals.IsNotNull())
|
||||||
totals.Height = height;
|
totals.Height = height;
|
||||||
});
|
});
|
||||||
|
AddToggleStep("toggle visible", v => totals.Alpha = v ? 1 : 0);
|
||||||
|
|
||||||
AddStep("set counts", () => totals.SetInitialCounts(totalPassCount: 9650, cumulativeTotalScore: 10_000_000_000));
|
AddStep("set counts", () => totals.SetInitialCounts(totalPassCount: 9650, cumulativeTotalScore: 10_000_000_000));
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Overlays.Chat;
|
using osu.Game.Overlays.Chat;
|
||||||
@ -40,6 +41,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Id = 3,
|
Id = 3,
|
||||||
Username = "LocalUser"
|
Username = "LocalUser"
|
||||||
};
|
};
|
||||||
|
|
||||||
string uuid = Guid.NewGuid().ToString();
|
string uuid = Guid.NewGuid().ToString();
|
||||||
AddStep("add local echo message", () => channel.AddLocalEcho(new LocalEchoMessage
|
AddStep("add local echo message", () => channel.AddLocalEcho(new LocalEchoMessage
|
||||||
{
|
{
|
||||||
@ -83,5 +85,38 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddUntilStep("three day separators present", () => drawableChannel.ChildrenOfType<DaySeparator>().Count() == 3);
|
AddUntilStep("three day separators present", () => drawableChannel.ChildrenOfType<DaySeparator>().Count() == 3);
|
||||||
AddAssert("last day separator is from correct day", () => drawableChannel.ChildrenOfType<DaySeparator>().Last().Date.Date == new DateTime(2022, 11, 22));
|
AddAssert("last day separator is from correct day", () => drawableChannel.ChildrenOfType<DaySeparator>().Last().Date.Date == new DateTime(2022, 11, 22));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundAlternating()
|
||||||
|
{
|
||||||
|
int messageCount = 1;
|
||||||
|
|
||||||
|
AddRepeatStep("add messages", () =>
|
||||||
|
{
|
||||||
|
channel.AddNewMessages(new Message(messageCount)
|
||||||
|
{
|
||||||
|
Sender = new APIUser
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||||
|
},
|
||||||
|
Content = "Hi there all!",
|
||||||
|
Timestamp = new DateTimeOffset(2022, 11, 21, 20, messageCount, 13, TimeSpan.Zero),
|
||||||
|
Uuid = Guid.NewGuid().ToString(),
|
||||||
|
});
|
||||||
|
messageCount++;
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
AddUntilStep("10 message present", () => drawableChannel.ChildrenOfType<ChatLine>().Count() == 10);
|
||||||
|
|
||||||
|
int checkCount = 0;
|
||||||
|
|
||||||
|
AddRepeatStep("check background", () =>
|
||||||
|
{
|
||||||
|
// +1 because the day separator take one index
|
||||||
|
Assert.AreEqual((checkCount + 1) % 2 == 0, drawableChannel.ChildrenOfType<ChatLine>().ToList()[checkCount].AlternatingBackground);
|
||||||
|
checkCount++;
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -10,6 +11,7 @@ using osu.Game.Localisation;
|
|||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Metadata;
|
using osu.Game.Online.Metadata;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
using Color4 = osuTK.Graphics.Color4;
|
using Color4 = osuTK.Graphics.Color4;
|
||||||
@ -39,8 +41,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDailyChallengeButton()
|
public void TestDailyChallengeButton()
|
||||||
{
|
{
|
||||||
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
|
|
||||||
|
|
||||||
AddStep("set up API", () => dummyAPI.HandleRequest = req =>
|
AddStep("set up API", () => dummyAPI.HandleRequest = req =>
|
||||||
{
|
{
|
||||||
switch (req)
|
switch (req)
|
||||||
@ -67,17 +67,45 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
|
NotificationOverlay notificationOverlay = null!;
|
||||||
{
|
DependencyProvidingContainer buttonContainer = null!;
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
ButtonSystemState = ButtonSystemState.TopLevel,
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
|
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
|
||||||
{
|
{
|
||||||
RoomID = 1234,
|
RoomID = 1234,
|
||||||
}));
|
}));
|
||||||
|
AddStep("add content", () =>
|
||||||
|
{
|
||||||
|
notificationOverlay = new NotificationOverlay();
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
notificationOverlay,
|
||||||
|
buttonContainer = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)],
|
||||||
|
Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
ButtonSystemState = ButtonSystemState.TopLevel,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||||
|
|
||||||
|
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
|
||||||
|
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||||
|
|
||||||
|
AddStep("hide button's parent", () => buttonContainer.Hide());
|
||||||
|
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
|
||||||
|
{
|
||||||
|
RoomID = 1234,
|
||||||
|
}));
|
||||||
|
AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestOutOfRangeValueStillApplied()
|
public void TestValueAboveRangeStillApplied()
|
||||||
{
|
{
|
||||||
AddStep("set override cs to 11", () => modDifficultyAdjust.CircleSize.Value = 11);
|
AddStep("set override cs to 11", () => modDifficultyAdjust.CircleSize.Value = 11);
|
||||||
|
|
||||||
@ -91,6 +91,28 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
checkBindableAtValue("Circle Size", 11);
|
checkBindableAtValue("Circle Size", 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestValueBelowRangeStillApplied()
|
||||||
|
{
|
||||||
|
AddStep("set override cs to -5", () => modDifficultyAdjust.ApproachRate.Value = -5);
|
||||||
|
|
||||||
|
checkSliderAtValue("Approach Rate", -5);
|
||||||
|
checkBindableAtValue("Approach Rate", -5);
|
||||||
|
|
||||||
|
// this is a no-op, just showing that it won't reset the value during deserialisation.
|
||||||
|
setExtendedLimits(false);
|
||||||
|
|
||||||
|
checkSliderAtValue("Approach Rate", -5);
|
||||||
|
checkBindableAtValue("Approach Rate", -5);
|
||||||
|
|
||||||
|
// setting extended limits will reset the serialisation exception.
|
||||||
|
// this should be fine as the goal is to allow, at most, the value of extended limits.
|
||||||
|
setExtendedLimits(true);
|
||||||
|
|
||||||
|
checkSliderAtValue("Approach Rate", -5);
|
||||||
|
checkBindableAtValue("Approach Rate", -5);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestExtendedLimits()
|
public void TestExtendedLimits()
|
||||||
{
|
{
|
||||||
@ -109,6 +131,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
checkSliderAtValue("Circle Size", 11);
|
checkSliderAtValue("Circle Size", 11);
|
||||||
checkBindableAtValue("Circle Size", 11);
|
checkBindableAtValue("Circle Size", 11);
|
||||||
|
|
||||||
|
setSliderValue("Approach Rate", -5);
|
||||||
|
|
||||||
|
checkSliderAtValue("Approach Rate", -5);
|
||||||
|
checkBindableAtValue("Approach Rate", -5);
|
||||||
|
|
||||||
setExtendedLimits(false);
|
setExtendedLimits(false);
|
||||||
|
|
||||||
checkSliderAtValue("Circle Size", 10);
|
checkSliderAtValue("Circle Size", 10);
|
||||||
|
@ -43,6 +43,9 @@ namespace osu.Game.Collections
|
|||||||
//
|
//
|
||||||
// if we want to support user sorting (but changes will need to be made to realm to persist).
|
// if we want to support user sorting (but changes will need to be made to realm to persist).
|
||||||
ShowDragHandle.Value = false;
|
ShowDragHandle.Value = false;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = item_height / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateContent() => new ItemContent(Model);
|
protected override Drawable CreateContent() => new ItemContent(Model);
|
||||||
@ -50,7 +53,7 @@ namespace osu.Game.Collections
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main content of the <see cref="DrawableCollectionListItem"/>.
|
/// The main content of the <see cref="DrawableCollectionListItem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private partial class ItemContent : CircularContainer
|
private partial class ItemContent : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly Live<BeatmapCollection> collection;
|
private readonly Live<BeatmapCollection> collection;
|
||||||
|
|
||||||
@ -65,13 +68,12 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = item_height;
|
Height = item_height;
|
||||||
Masking = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Children = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
collection.IsManaged
|
collection.IsManaged
|
||||||
? new DeleteButton(collection)
|
? new DeleteButton(collection)
|
||||||
@ -132,7 +134,7 @@ namespace osu.Game.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class DeleteButton : CompositeDrawable
|
public partial class DeleteButton : OsuClickableContainer
|
||||||
{
|
{
|
||||||
public Func<Vector2, bool> IsTextBoxHovered = null!;
|
public Func<Vector2, bool> IsTextBoxHovered = null!;
|
||||||
|
|
||||||
@ -155,7 +157,7 @@ namespace osu.Game.Collections
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
InternalChild = fadeContainer = new Container
|
Child = fadeContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0.1f,
|
Alpha = 0.1f,
|
||||||
@ -176,6 +178,14 @@ namespace osu.Game.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0)
|
||||||
|
deleteCollection();
|
||||||
|
else
|
||||||
|
dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
|
||||||
@ -195,12 +205,7 @@ namespace osu.Game.Collections
|
|||||||
{
|
{
|
||||||
background.FlashColour(Color4.White, 150);
|
background.FlashColour(Color4.White, 150);
|
||||||
|
|
||||||
if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0)
|
return base.OnClick(e);
|
||||||
deleteCollection();
|
|
||||||
else
|
|
||||||
dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));
|
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));
|
||||||
|
@ -64,6 +64,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new HoverClickSounds(),
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -92,7 +93,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
||||||
},
|
},
|
||||||
new HoverClickSounds()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
osu.Game/Localisation/DailyChallengeStrings.cs
Normal file
29
osu.Game/Localisation/DailyChallengeStrings.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class DailyChallengeStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.DailyChallenge";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Today's daily challenge has concluded – thanks for playing!
|
||||||
|
///
|
||||||
|
/// Tomorrow's challenge is now being prepared and will appear soon."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ChallengeEndedNotification => new TranslatableString(getKey(@"todays_daily_challenge_has_concluded"),
|
||||||
|
@"Today's daily challenge has concluded – thanks for playing!
|
||||||
|
|
||||||
|
Tomorrow's challenge is now being prepared and will appear soon.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Today's daily challenge is now live! Click here to play."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ChallengeLiveNotification => new TranslatableString(getKey(@"todays_daily_challenge_is_now"), @"Today's daily challenge is now live! Click here to play.");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -118,12 +118,11 @@ namespace osu.Game.Online.API
|
|||||||
u.OldValue?.Activity.UnbindFrom(activity);
|
u.OldValue?.Activity.UnbindFrom(activity);
|
||||||
u.NewValue.Activity.BindTo(activity);
|
u.NewValue.Activity.BindTo(activity);
|
||||||
|
|
||||||
if (u.OldValue != null)
|
u.OldValue?.Status.UnbindFrom(localUserStatus);
|
||||||
localUserStatus.UnbindFrom(u.OldValue.Status);
|
u.NewValue.Status.BindTo(localUserStatus);
|
||||||
localUserStatus.BindTo(u.NewValue.Status);
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
localUserStatus.BindValueChanged(val => configStatus.Value = val.NewValue);
|
localUserStatus.BindTo(configStatus);
|
||||||
|
|
||||||
var thread = new Thread(run)
|
var thread = new Thread(run)
|
||||||
{
|
{
|
||||||
@ -600,6 +599,7 @@ namespace osu.Game.Online.API
|
|||||||
password = null;
|
password = null;
|
||||||
SecondFactorCode = null;
|
SecondFactorCode = null;
|
||||||
authentication.Clear();
|
authentication.Clear();
|
||||||
|
configStatus.Value = UserStatus.Online;
|
||||||
|
|
||||||
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
|
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
|
@ -178,7 +178,7 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
protected partial class StandAloneDaySeparator : DaySeparator
|
protected partial class StandAloneDaySeparator : DaySeparator
|
||||||
{
|
{
|
||||||
protected override float TextSize => 14;
|
protected override float TextSize => 13;
|
||||||
protected override float LineHeight => 1;
|
protected override float LineHeight => 1;
|
||||||
protected override float Spacing => 5;
|
protected override float Spacing => 5;
|
||||||
protected override float DateAlign => 125;
|
protected override float DateAlign => 125;
|
||||||
@ -198,9 +198,9 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
protected partial class StandAloneMessage : ChatLine
|
protected partial class StandAloneMessage : ChatLine
|
||||||
{
|
{
|
||||||
protected override float FontSize => 15;
|
protected override float FontSize => 13;
|
||||||
protected override float Spacing => 5;
|
protected override float Spacing => 5;
|
||||||
protected override float UsernameWidth => 75;
|
protected override float UsernameWidth => 90;
|
||||||
|
|
||||||
public StandAloneMessage(Message message)
|
public StandAloneMessage(Message message)
|
||||||
: base(message)
|
: base(message)
|
||||||
|
@ -215,6 +215,7 @@ namespace osu.Game.Online.Metadata
|
|||||||
Debug.Assert(connection != null);
|
Debug.Assert(connection != null);
|
||||||
await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingUserPresence)).ConfigureAwait(false);
|
await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingUserPresence)).ConfigureAwait(false);
|
||||||
Schedule(() => isWatchingUserPresence.Value = true);
|
Schedule(() => isWatchingUserPresence.Value = true);
|
||||||
|
Logger.Log($@"{nameof(OnlineMetadataClient)} began watching user presence", LoggingTarget.Network);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task EndWatchingUserPresence()
|
public override async Task EndWatchingUserPresence()
|
||||||
@ -228,6 +229,7 @@ namespace osu.Game.Online.Metadata
|
|||||||
Schedule(() => userStates.Clear());
|
Schedule(() => userStates.Clear());
|
||||||
Debug.Assert(connection != null);
|
Debug.Assert(connection != null);
|
||||||
await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false);
|
await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false);
|
||||||
|
Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching user presence", LoggingTarget.Network);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -247,7 +249,9 @@ namespace osu.Game.Online.Metadata
|
|||||||
throw new OperationCanceledException();
|
throw new OperationCanceledException();
|
||||||
|
|
||||||
Debug.Assert(connection != null);
|
Debug.Assert(connection != null);
|
||||||
return await connection.InvokeAsync<MultiplayerPlaylistItemStats[]>(nameof(IMetadataServer.BeginWatchingMultiplayerRoom), id).ConfigureAwait(false);
|
var result = await connection.InvokeAsync<MultiplayerPlaylistItemStats[]>(nameof(IMetadataServer.BeginWatchingMultiplayerRoom), id).ConfigureAwait(false);
|
||||||
|
Logger.Log($@"{nameof(OnlineMetadataClient)} began watching multiplayer room with ID {id}", LoggingTarget.Network);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task EndWatchingMultiplayerRoom(long id)
|
public override async Task EndWatchingMultiplayerRoom(long id)
|
||||||
@ -257,6 +261,7 @@ namespace osu.Game.Online.Metadata
|
|||||||
|
|
||||||
Debug.Assert(connection != null);
|
Debug.Assert(connection != null);
|
||||||
await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingMultiplayerRoom), id).ConfigureAwait(false);
|
await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingMultiplayerRoom), id).ConfigureAwait(false);
|
||||||
|
Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching multiplayer room with ID {id}", LoggingTarget.Network);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task DisconnectRequested()
|
public override async Task DisconnectRequested()
|
||||||
|
@ -63,7 +63,6 @@ using osu.Game.Screens;
|
|||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Footer;
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.OnlinePlay;
|
|
||||||
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -757,11 +756,13 @@ namespace osu.Game
|
|||||||
// As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select.
|
// As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select.
|
||||||
// This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the
|
// This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the
|
||||||
// song select leaderboard).
|
// song select leaderboard).
|
||||||
// Similar exemptions are made here for online flows where there are good chances that beatmap and ruleset match
|
// Similar exemptions are made here for daily challenge where it is guaranteed that beatmap and ruleset match.
|
||||||
// (playlists / multiplayer / daily challenge).
|
// `OnlinePlayScreen` is excluded because when resuming back to it,
|
||||||
|
// `RoomSubScreen` changes the global beatmap to the next playlist item on resume,
|
||||||
|
// which may not match the score, and thus crash.
|
||||||
IEnumerable<Type> validScreens =
|
IEnumerable<Type> validScreens =
|
||||||
Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset)
|
Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset)
|
||||||
? new[] { typeof(SongSelect), typeof(OnlinePlayScreen), typeof(DailyChallenge) }
|
? new[] { typeof(SongSelect), typeof(DailyChallenge) }
|
||||||
: Array.Empty<Type>();
|
: Array.Empty<Type>();
|
||||||
|
|
||||||
PerformFromScreen(screen =>
|
PerformFromScreen(screen =>
|
||||||
|
@ -20,8 +20,8 @@ using osu.Game.Graphics.Containers;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using Message = osu.Game.Online.Chat.Message;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Chat
|
namespace osu.Game.Overlays.Chat
|
||||||
{
|
{
|
||||||
@ -47,11 +47,11 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
public IReadOnlyCollection<Drawable> DrawableContentFlow => drawableContentFlow;
|
public IReadOnlyCollection<Drawable> DrawableContentFlow => drawableContentFlow;
|
||||||
|
|
||||||
protected virtual float FontSize => 14;
|
protected virtual float FontSize => 12;
|
||||||
|
|
||||||
protected virtual float Spacing => 15;
|
protected virtual float Spacing => 15;
|
||||||
|
|
||||||
protected virtual float UsernameWidth => 130;
|
protected virtual float UsernameWidth => 150;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ChannelManager? chatManager { get; set; }
|
private ChannelManager? chatManager { get; set; }
|
||||||
@ -69,6 +69,41 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
private Container? highlight;
|
private Container? highlight;
|
||||||
|
|
||||||
|
private Drawable? background;
|
||||||
|
|
||||||
|
private bool alternatingBackground;
|
||||||
|
private bool requiresTimestamp = true;
|
||||||
|
|
||||||
|
public bool RequiresTimestamp
|
||||||
|
{
|
||||||
|
get => requiresTimestamp;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (requiresTimestamp == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
requiresTimestamp = value;
|
||||||
|
|
||||||
|
if (!IsLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
updateMessageContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AlternatingBackground
|
||||||
|
{
|
||||||
|
get => alternatingBackground;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (alternatingBackground == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
alternatingBackground = value;
|
||||||
|
updateBackground();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The colour used to paint the author's username.
|
/// The colour used to paint the author's username.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -102,48 +137,74 @@ namespace osu.Game.Overlays.Chat
|
|||||||
configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime);
|
configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime);
|
||||||
prefer24HourTime.BindValueChanged(_ => updateTimestamp());
|
prefer24HourTime.BindValueChanged(_ => updateTimestamp());
|
||||||
|
|
||||||
InternalChild = new GridContainer
|
Padding = new MarginPadding { Right = 5 };
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
background = new Container
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
Masking = true,
|
||||||
new Dimension(GridSizeMode.Absolute, Spacing + UsernameWidth + Spacing),
|
CornerRadius = 4,
|
||||||
new Dimension(),
|
Alpha = 0,
|
||||||
},
|
RelativeSizeAxes = Axes.Both,
|
||||||
Content = new[]
|
Blending = BlendingParameters.Additive,
|
||||||
{
|
Child = new Box
|
||||||
new Drawable[]
|
|
||||||
{
|
{
|
||||||
drawableTimestamp = new OsuSpriteText
|
Colour = Colour4.FromHex("#3b3234"),
|
||||||
{
|
RelativeSizeAxes = Axes.Both,
|
||||||
Shadow = false,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true),
|
|
||||||
AlwaysPresent = true,
|
|
||||||
},
|
|
||||||
drawableUsername = new DrawableChatUsername(message.Sender)
|
|
||||||
{
|
|
||||||
Width = UsernameWidth,
|
|
||||||
FontSize = FontSize,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Margin = new MarginPadding { Horizontal = Spacing },
|
|
||||||
AccentColour = UsernameColour,
|
|
||||||
Inverted = !string.IsNullOrEmpty(message.Sender.Colour),
|
|
||||||
},
|
|
||||||
drawableContentFlow = new LinkFlowContainer(styleMessageContent)
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 2,
|
||||||
|
Vertical = 2,
|
||||||
|
},
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, 45),
|
||||||
|
new Dimension(GridSizeMode.Absolute, Spacing + UsernameWidth + Spacing),
|
||||||
|
new Dimension(),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
drawableTimestamp = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Shadow = false,
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Spacing = new Vector2(-1, 0),
|
||||||
|
Font = OsuFont.GetFont(size: FontSize, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||||
|
AlwaysPresent = true,
|
||||||
|
},
|
||||||
|
drawableUsername = new DrawableChatUsername(message.Sender)
|
||||||
|
{
|
||||||
|
Width = UsernameWidth,
|
||||||
|
FontSize = FontSize,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Margin = new MarginPadding { Horizontal = Spacing },
|
||||||
|
AccentColour = UsernameColour,
|
||||||
|
Inverted = !string.IsNullOrEmpty(message.Sender.Colour),
|
||||||
|
},
|
||||||
|
drawableContentFlow = new LinkFlowContainer(styleMessageContent)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -203,9 +264,17 @@ namespace osu.Game.Overlays.Chat
|
|||||||
private void updateMessageContent()
|
private void updateMessageContent()
|
||||||
{
|
{
|
||||||
this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint);
|
this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint);
|
||||||
drawableTimestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint);
|
|
||||||
|
|
||||||
updateTimestamp();
|
if (requiresTimestamp && !(message is LocalEchoMessage))
|
||||||
|
{
|
||||||
|
drawableTimestamp.Show();
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
drawableTimestamp.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
drawableUsername.Text = $@"{message.Sender.Username}";
|
drawableUsername.Text = $@"{message.Sender.Username}";
|
||||||
|
|
||||||
// remove non-existent channels from the link list
|
// remove non-existent channels from the link list
|
||||||
@ -217,7 +286,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
private void updateTimestamp()
|
private void updateTimestamp()
|
||||||
{
|
{
|
||||||
drawableTimestamp.Text = message.Timestamp.LocalDateTime.ToLocalisableString(prefer24HourTime.Value ? @"HH:mm:ss" : @"hh:mm:ss tt");
|
drawableTimestamp.Text = message.Timestamp.LocalDateTime.ToLocalisableString(prefer24HourTime.Value ? @"HH:mm" : @"hh:mm tt");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Color4[] default_username_colours =
|
private static readonly Color4[] default_username_colours =
|
||||||
@ -258,5 +327,11 @@ namespace osu.Game.Overlays.Chat
|
|||||||
Color4Extensions.FromHex("812a96"),
|
Color4Extensions.FromHex("812a96"),
|
||||||
Color4Extensions.FromHex("992861"),
|
Color4Extensions.FromHex("992861"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private void updateBackground()
|
||||||
|
{
|
||||||
|
if (background != null)
|
||||||
|
background.Alpha = alternatingBackground ? 0.2f : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
{
|
{
|
||||||
public partial class DaySeparator : Container
|
public partial class DaySeparator : Container
|
||||||
{
|
{
|
||||||
protected virtual float TextSize => 15;
|
protected virtual float TextSize => 13;
|
||||||
|
|
||||||
protected virtual float LineHeight => 2;
|
protected virtual float LineHeight => 2;
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
Padding = new MarginPadding { Bottom = 5 },
|
Padding = new MarginPadding { Bottom = 5 },
|
||||||
Child = ChatLineFlow = new FillFlowContainer
|
Child = ChatLineFlow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding { Horizontal = 10 },
|
Padding = new MarginPadding { Left = 3, Right = 10 },
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
@ -84,6 +84,25 @@ namespace osu.Game.Overlays.Chat
|
|||||||
highlightedMessage.BindValueChanged(_ => processMessageHighlighting(), true);
|
highlightedMessage.BindValueChanged(_ => processMessageHighlighting(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
long? lastMinutes = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < ChatLineFlow.Count; i++)
|
||||||
|
{
|
||||||
|
if (ChatLineFlow[i] is ChatLine chatline)
|
||||||
|
{
|
||||||
|
long minutes = chatline.Message.Timestamp.ToUnixTimeSeconds() / 60;
|
||||||
|
|
||||||
|
chatline.AlternatingBackground = i % 2 == 0;
|
||||||
|
chatline.RequiresTimestamp = minutes != lastMinutes;
|
||||||
|
lastMinutes = minutes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes any pending message in <see cref="highlightedMessage"/>.
|
/// Processes any pending message in <see cref="highlightedMessage"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -157,6 +157,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateDropdownCurrent(status.Value);
|
||||||
dropdown.Current.BindValueChanged(action =>
|
dropdown.Current.BindValueChanged(action =>
|
||||||
{
|
{
|
||||||
switch (action.NewValue)
|
switch (action.NewValue)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -11,14 +12,16 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK;
|
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public partial class ModCustomisationHeader : OsuHoverContainer
|
public partial class ModCustomisationHeader : OsuHoverContainer
|
||||||
{
|
{
|
||||||
private Box background = null!;
|
private Box background = null!;
|
||||||
|
private Box backgroundFlash = null!;
|
||||||
private SpriteIcon icon = null!;
|
private SpriteIcon icon = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -46,6 +49,12 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
backgroundFlash = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White.Opacity(0.4f),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
@ -84,6 +93,12 @@ namespace osu.Game.Overlays.Mods
|
|||||||
TooltipText = e.NewValue
|
TooltipText = e.NewValue
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: ModSelectOverlayStrings.CustomisationPanelDisabledReason;
|
: ModSelectOverlayStrings.CustomisationPanelDisabledReason;
|
||||||
|
|
||||||
|
if (e.NewValue)
|
||||||
|
{
|
||||||
|
backgroundFlash.FadeInFromZero(150, Easing.OutQuad).Then()
|
||||||
|
.FadeOutFromOne(350, Easing.OutQuad);
|
||||||
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Expanded.BindValueChanged(v =>
|
Expanded.BindValueChanged(v =>
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private const double transition_duration = 200;
|
private const double transition_duration = 200;
|
||||||
|
|
||||||
private readonly OsuSpriteText descriptionText;
|
private readonly TextFlowContainer descriptionText;
|
||||||
|
|
||||||
public ModPresetTooltip(OverlayColourProvider colourProvider)
|
public ModPresetTooltip(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
@ -44,11 +43,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Spacing = new Vector2(7),
|
Spacing = new Vector2(7),
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
descriptionText = new OsuSpriteText
|
descriptionText = new TextFlowContainer(f =>
|
||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Regular),
|
f.Font = OsuFont.GetFont(weight: FontWeight.Regular);
|
||||||
Colour = colourProvider.Content1,
|
f.Colour = colourProvider.Content1;
|
||||||
},
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used for parsing in contexts where we don't want e.g. normal times of day to be parsed as timestamps (e.g. chat)
|
/// Used for parsing in contexts where we don't want e.g. normal times of day to be parsed as timestamps (e.g. chat)
|
||||||
/// Original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78
|
/// Original osu-web regex:
|
||||||
|
/// https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <example>
|
/// <example>
|
||||||
/// 00:00:000 (...) - test
|
/// 00:00:000 (...) - test
|
||||||
@ -32,7 +33,10 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <item>1:02:300 (1,2,3) - parses to 01:02:300 with selection</item>
|
/// <item>1:02:300 (1,2,3) - parses to 01:02:300 with selection</item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </example>
|
/// </example>
|
||||||
private static readonly Regex time_regex_lenient = new Regex(@"^(((?<minutes>\d{1,3}):(?<seconds>([0-5]?\d))([:.](?<milliseconds>\d{0,3}))?)(?<selection>\s\([^)]+\))?)$", RegexOptions.Compiled);
|
private static readonly Regex time_regex_lenient = new Regex(
|
||||||
|
@"^(((?<minutes>\d{1,3}):(?<seconds>([0-5]?\d))([:.](?<milliseconds>\d{0,3}))?)(?<selection>\s\([^)]+\))?)(?<suffix>\s-.*)?$",
|
||||||
|
RegexOptions.Compiled | RegexOptions.Singleline
|
||||||
|
);
|
||||||
|
|
||||||
public static bool TryParse(string timestamp, [NotNullWhen(true)] out TimeSpan? parsedTime, out string? parsedSelection)
|
public static bool TryParse(string timestamp, [NotNullWhen(true)] out TimeSpan? parsedTime, out string? parsedSelection)
|
||||||
{
|
{
|
||||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public float MinValue
|
public float MinValue
|
||||||
{
|
{
|
||||||
|
get => minValue;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == minValue)
|
if (value == minValue)
|
||||||
@ -52,6 +53,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public float MaxValue
|
public float MaxValue
|
||||||
{
|
{
|
||||||
|
get => maxValue;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == maxValue)
|
if (value == maxValue)
|
||||||
@ -69,6 +71,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float? ExtendedMinValue
|
public float? ExtendedMinValue
|
||||||
{
|
{
|
||||||
|
get => extendedMinValue;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == extendedMinValue)
|
if (value == extendedMinValue)
|
||||||
@ -86,6 +89,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float? ExtendedMaxValue
|
public float? ExtendedMaxValue
|
||||||
{
|
{
|
||||||
|
get => extendedMaxValue;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == extendedMaxValue)
|
if (value == extendedMaxValue)
|
||||||
@ -114,9 +118,14 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
// Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated.
|
// Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated.
|
||||||
if (value != null)
|
if (value != null)
|
||||||
CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, value.Value);
|
{
|
||||||
|
CurrentNumber.MinValue = Math.Clamp(MathF.Min(CurrentNumber.MinValue, value.Value), ExtendedMinValue ?? MinValue, MinValue);
|
||||||
|
CurrentNumber.MaxValue = Math.Clamp(MathF.Max(CurrentNumber.MaxValue, value.Value), MaxValue, ExtendedMaxValue ?? MaxValue);
|
||||||
|
|
||||||
base.Value = value;
|
base.Value = Math.Clamp(value.Value, CurrentNumber.MinValue, CurrentNumber.MaxValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
base.Value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +147,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
// the following max value copies are only safe as long as these values are effectively constants.
|
// the following max value copies are only safe as long as these values are effectively constants.
|
||||||
otherDifficultyBindable.MaxValue = maxValue;
|
otherDifficultyBindable.MaxValue = maxValue;
|
||||||
otherDifficultyBindable.ExtendedMaxValue = extendedMaxValue;
|
otherDifficultyBindable.ExtendedMaxValue = extendedMaxValue;
|
||||||
|
otherDifficultyBindable.MinValue = minValue;
|
||||||
|
otherDifficultyBindable.ExtendedMinValue = extendedMinValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void BindTo(Bindable<float?> them)
|
public override void BindTo(Bindable<float?> them)
|
||||||
|
@ -58,7 +58,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
case IHasPosition pos:
|
case IHasPosition pos:
|
||||||
AddHeader("Position");
|
AddHeader("Position");
|
||||||
AddValue($"x:{pos.X:#,0.##} y:{pos.Y:#,0.##}");
|
AddValue($"x:{pos.X:#,0.##}");
|
||||||
|
AddValue($"y:{pos.Y:#,0.##}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IHasXPosition x:
|
case IHasXPosition x:
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
@ -30,11 +28,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private readonly Drawable userContent;
|
private readonly Drawable userContent;
|
||||||
|
|
||||||
[Resolved]
|
private bool alwaysShowControlPoints;
|
||||||
private EditorClock editorClock { get; set; }
|
|
||||||
|
public bool AlwaysShowControlPoints
|
||||||
|
{
|
||||||
|
get => alwaysShowControlPoints;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == alwaysShowControlPoints)
|
||||||
|
return;
|
||||||
|
|
||||||
|
alwaysShowControlPoints = value;
|
||||||
|
controlPointsVisible.TriggerChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap editorBeatmap { get; set; }
|
private EditorClock editorClock { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The timeline's scroll position in the last frame.
|
/// The timeline's scroll position in the last frame.
|
||||||
@ -61,6 +74,22 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private float defaultTimelineZoom;
|
private float defaultTimelineZoom;
|
||||||
|
|
||||||
|
private WaveformGraph waveform = null!;
|
||||||
|
|
||||||
|
private TimelineTickDisplay ticks = null!;
|
||||||
|
|
||||||
|
private TimelineControlPointDisplay controlPoints = null!;
|
||||||
|
|
||||||
|
private Container mainContent = null!;
|
||||||
|
|
||||||
|
private Bindable<float> waveformOpacity = null!;
|
||||||
|
private Bindable<bool> controlPointsVisible = null!;
|
||||||
|
private Bindable<bool> ticksVisible = null!;
|
||||||
|
|
||||||
|
private double trackLengthForZoom;
|
||||||
|
|
||||||
|
private readonly IBindable<Track> track = new Bindable<Track>();
|
||||||
|
|
||||||
public Timeline(Drawable userContent)
|
public Timeline(Drawable userContent)
|
||||||
{
|
{
|
||||||
this.userContent = userContent;
|
this.userContent = userContent;
|
||||||
@ -73,22 +102,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
ScrollbarVisible = false;
|
ScrollbarVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WaveformGraph waveform;
|
|
||||||
|
|
||||||
private TimelineTickDisplay ticks;
|
|
||||||
|
|
||||||
private TimelineControlPointDisplay controlPoints;
|
|
||||||
|
|
||||||
private Container mainContent;
|
|
||||||
|
|
||||||
private Bindable<float> waveformOpacity;
|
|
||||||
private Bindable<bool> controlPointsVisible;
|
|
||||||
private Bindable<bool> ticksVisible;
|
|
||||||
|
|
||||||
private double trackLengthForZoom;
|
|
||||||
|
|
||||||
private readonly IBindable<Track> track = new Bindable<Track>();
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours, OsuConfigManager config)
|
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
@ -178,7 +191,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
controlPointsVisible.BindValueChanged(visible =>
|
controlPointsVisible.BindValueChanged(visible =>
|
||||||
{
|
{
|
||||||
if (visible.NewValue)
|
if (visible.NewValue || alwaysShowControlPoints)
|
||||||
{
|
{
|
||||||
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
|
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
|
||||||
mainContent.MoveToY(15, 200, Easing.OutQuint);
|
mainContent.MoveToY(15, 200, Easing.OutQuint);
|
||||||
@ -318,7 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total amount of time visible on the timeline.
|
/// The total amount of time visible on the timeline.
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load()
|
||||||
{
|
{
|
||||||
// Grid with only two rows.
|
// Grid with only two rows.
|
||||||
// First is the timeline area, which should be allowed to expand as required.
|
// First is the timeline area, which should be allowed to expand as required.
|
||||||
@ -107,10 +106,18 @@ namespace osu.Game.Screens.Edit
|
|||||||
MainContent.Add(content);
|
MainContent.Add(content);
|
||||||
content.FadeInFromZero(300, Easing.OutQuint);
|
content.FadeInFromZero(300, Easing.OutQuint);
|
||||||
|
|
||||||
LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timelineContent.Add);
|
LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timeline =>
|
||||||
|
{
|
||||||
|
ConfigureTimeline(timeline);
|
||||||
|
timelineContent.Add(timeline);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void ConfigureTimeline(TimelineArea timelineArea)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract Drawable CreateMainContent();
|
protected abstract Drawable CreateMainContent();
|
||||||
|
|
||||||
protected virtual Drawable CreateTimelineContent() => new Container();
|
protected virtual Drawable CreateTimelineContent() => new Container();
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
@ -53,5 +54,12 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
|
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void ConfigureTimeline(TimelineArea timelineArea)
|
||||||
|
{
|
||||||
|
base.ConfigureTimeline(timelineArea);
|
||||||
|
|
||||||
|
timelineArea.Timeline.AlwaysShowControlPoints = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
using osu.Game.Online.Metadata;
|
using osu.Game.Online.Metadata;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -44,6 +45,9 @@ namespace osu.Game.Screens.Menu
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; } = null!;
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private INotificationOverlay? notificationOverlay { get; set; }
|
||||||
|
|
||||||
public DailyChallengeButton(string sampleName, Color4 colour, Action<MainMenuButton>? clickAction = null, params Key[] triggerKeys)
|
public DailyChallengeButton(string sampleName, Color4 colour, Action<MainMenuButton>? clickAction = null, params Key[] triggerKeys)
|
||||||
: base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys)
|
: base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys)
|
||||||
{
|
{
|
||||||
@ -100,7 +104,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
info.BindValueChanged(updateDisplay, true);
|
info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true));
|
||||||
|
dailyChallengeChanged(postNotification: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -126,27 +131,30 @@ namespace osu.Game.Screens.Menu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisplay(ValueChangedEvent<DailyChallengeInfo?> info)
|
private void dailyChallengeChanged(bool postNotification)
|
||||||
{
|
{
|
||||||
UpdateState();
|
UpdateState();
|
||||||
|
|
||||||
scheduledCountdownUpdate?.Cancel();
|
scheduledCountdownUpdate?.Cancel();
|
||||||
scheduledCountdownUpdate = null;
|
scheduledCountdownUpdate = null;
|
||||||
|
|
||||||
if (info.NewValue == null)
|
if (info.Value == null)
|
||||||
{
|
{
|
||||||
Room = null;
|
Room = null;
|
||||||
cover.OnlineInfo = TooltipContent = null;
|
cover.OnlineInfo = TooltipContent = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var roomRequest = new GetRoomRequest(info.NewValue.Value.RoomID);
|
var roomRequest = new GetRoomRequest(info.Value.Value.RoomID);
|
||||||
|
|
||||||
roomRequest.Success += room =>
|
roomRequest.Success += room =>
|
||||||
{
|
{
|
||||||
Room = room;
|
Room = room;
|
||||||
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
|
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
|
||||||
|
|
||||||
|
if (postNotification)
|
||||||
|
notificationOverlay?.Post(new NewDailyChallengeNotification(room));
|
||||||
|
|
||||||
updateCountdown();
|
updateCountdown();
|
||||||
Scheduler.AddDelayed(updateCountdown, 1000, true);
|
Scheduler.AddDelayed(updateCountdown, 1000, true);
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Audio;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -29,6 +30,7 @@ using osu.Game.Online.Metadata;
|
|||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
@ -53,6 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
private readonly Bindable<IReadOnlyList<Mod>> userMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
private readonly Bindable<IReadOnlyList<Mod>> userMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||||
|
private readonly IBindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
|
||||||
|
|
||||||
private OnlinePlayScreenWaveContainer waves = null!;
|
private OnlinePlayScreenWaveContainer waves = null!;
|
||||||
private DailyChallengeLeaderboard leaderboard = null!;
|
private DailyChallengeLeaderboard leaderboard = null!;
|
||||||
@ -97,6 +100,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private PreviewTrackManager previewTrackManager { get; set; } = null!;
|
private PreviewTrackManager previewTrackManager { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private INotificationOverlay? notificationOverlay { get; set; }
|
||||||
|
|
||||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||||
|
|
||||||
public override bool? ApplyModTrackAdjustments => true;
|
public override bool? ApplyModTrackAdjustments => true;
|
||||||
@ -106,6 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
this.room = room;
|
this.room = room;
|
||||||
playlistItem = room.Playlist.Single();
|
playlistItem = room.Playlist.Single();
|
||||||
roomManager = new RoomManager();
|
roomManager = new RoomManager();
|
||||||
|
Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
@ -168,7 +175,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
[
|
[
|
||||||
new Container
|
new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
@ -238,6 +245,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
PresentScore = presentScore,
|
PresentScore = presentScore,
|
||||||
|
SelectedMods = { BindTarget = userMods },
|
||||||
},
|
},
|
||||||
// Spacer
|
// Spacer
|
||||||
null,
|
null,
|
||||||
@ -329,10 +337,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
|
|
||||||
var rulesetInstance = rulesets.GetRuleset(playlistItem.RulesetID)!.CreateInstance();
|
var rulesetInstance = rulesets.GetRuleset(playlistItem.RulesetID)!.CreateInstance();
|
||||||
var allowedMods = playlistItem.AllowedMods.Select(m => m.ToMod(rulesetInstance));
|
var allowedMods = playlistItem.AllowedMods.Select(m => m.ToMod(rulesetInstance));
|
||||||
userModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType());
|
userModsSelectOverlay.IsValidMod = leaderboard.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet;
|
metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet;
|
||||||
|
dailyChallengeInfo.BindTo(metadataClient.DailyChallengeInfo);
|
||||||
|
|
||||||
((IBindable<MultiplayerScore?>)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore);
|
((IBindable<MultiplayerScore?>)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore);
|
||||||
}
|
}
|
||||||
@ -385,6 +394,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
|
|
||||||
apiState.BindTo(API.State);
|
apiState.BindTo(API.State);
|
||||||
apiState.BindValueChanged(onlineStateChanged, true);
|
apiState.BindValueChanged(onlineStateChanged, true);
|
||||||
|
|
||||||
|
dailyChallengeInfo.BindValueChanged(dailyChallengeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trySetDailyChallengeBeatmap()
|
private void trySetDailyChallengeBeatmap()
|
||||||
@ -402,9 +413,17 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
Schedule(forcefullyExit);
|
Schedule(forcefullyExit);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void dailyChallengeChanged(ValueChangedEvent<DailyChallengeInfo?> change)
|
||||||
|
{
|
||||||
|
if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null)
|
||||||
|
{
|
||||||
|
notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void forcefullyExit()
|
private void forcefullyExit()
|
||||||
{
|
{
|
||||||
Logger.Log($"{this} forcefully exiting due to loss of API connection");
|
Logger.Log(@$"{this} forcefully exiting due to loss of API connection");
|
||||||
|
|
||||||
// This is temporary since we don't currently have a way to force screens to be exited
|
// This is temporary since we don't currently have a way to force screens to be exited
|
||||||
// See also: `OnlinePlayScreen.forcefullyExit()`
|
// See also: `OnlinePlayScreen.forcefullyExit()`
|
||||||
@ -451,6 +470,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
{
|
{
|
||||||
base.OnResuming(e);
|
base.OnResuming(e);
|
||||||
applyLoopingToTrack();
|
applyLoopingToTrack();
|
||||||
|
// re-apply mods as they may have been changed by a child screen
|
||||||
|
// (one known instance of this is showing a replay).
|
||||||
|
updateMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnSuspending(ScreenTransitionEvent e)
|
public override void OnSuspending(ScreenTransitionEvent e)
|
||||||
|
@ -50,7 +50,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
{
|
{
|
||||||
drawable.RelativeSizeAxes = Axes.Both;
|
drawable.RelativeSizeAxes = Axes.Both;
|
||||||
drawable.Size = Vector2.One;
|
drawable.Size = Vector2.One;
|
||||||
drawable.AlwaysPresent = true;
|
|
||||||
drawable.Alpha = 0;
|
drawable.Alpha = 0;
|
||||||
|
|
||||||
base.Add(drawable);
|
base.Add(drawable);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -14,6 +15,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.SelectV2.Leaderboards;
|
using osu.Game.Screens.SelectV2.Leaderboards;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -24,6 +26,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
{
|
{
|
||||||
public IBindable<MultiplayerScore?> UserBestScore => userBestScore;
|
public IBindable<MultiplayerScore?> UserBestScore => userBestScore;
|
||||||
private readonly Bindable<MultiplayerScore?> userBestScore = new Bindable<MultiplayerScore?>();
|
private readonly Bindable<MultiplayerScore?> userBestScore = new Bindable<MultiplayerScore?>();
|
||||||
|
public Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A function determining whether each mod in the score can be selected.
|
||||||
|
/// A return value of <see langword="true"/> means that the mod can be selected in the current context.
|
||||||
|
/// A return value of <see langword="false"/> means that the mod cannot be selected in the current context.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Mod, bool> IsValidMod { get; set; } = _ => true;
|
||||||
|
|
||||||
public Action<long>? PresentScore { get; init; }
|
public Action<long>? PresentScore { get; init; }
|
||||||
|
|
||||||
@ -153,6 +163,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
Rank = index + 1,
|
Rank = index + 1,
|
||||||
IsPersonalBest = s.UserID == api.LocalUser.Value.Id,
|
IsPersonalBest = s.UserID == api.LocalUser.Value.Id,
|
||||||
Action = () => PresentScore?.Invoke(s.OnlineID),
|
Action = () => PresentScore?.Invoke(s.OnlineID),
|
||||||
|
SelectedMods = { BindTarget = SelectedMods },
|
||||||
|
IsValidMod = IsValidMod,
|
||||||
}), loaded =>
|
}), loaded =>
|
||||||
{
|
{
|
||||||
scoreFlow.Clear();
|
scoreFlow.Clear();
|
||||||
@ -171,6 +183,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
Rank = userBest.Position,
|
Rank = userBest.Position,
|
||||||
IsPersonalBest = true,
|
IsPersonalBest = true,
|
||||||
Action = () => PresentScore?.Invoke(userBest.OnlineID),
|
Action = () => PresentScore?.Invoke(userBest.OnlineID),
|
||||||
|
SelectedMods = { BindTarget = SelectedMods },
|
||||||
|
IsValidMod = IsValidMod,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -67,34 +68,61 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Queue<NewScoreEvent> newScores = new Queue<NewScoreEvent>();
|
||||||
|
|
||||||
public void AddNewScore(NewScoreEvent newScoreEvent)
|
public void AddNewScore(NewScoreEvent newScoreEvent)
|
||||||
{
|
{
|
||||||
int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1);
|
newScores.Enqueue(newScoreEvent);
|
||||||
bins[targetBin] += 1;
|
|
||||||
updateCounts();
|
|
||||||
|
|
||||||
var text = new OsuSpriteText
|
// ensure things don't get too out-of-hand.
|
||||||
|
if (newScores.Count > 25)
|
||||||
{
|
{
|
||||||
Text = newScoreEvent.TotalScore.ToString(@"N0"),
|
bins[getTargetBin(newScores.Dequeue())] += 1;
|
||||||
Anchor = Anchor.TopCentre,
|
Scheduler.AddOnce(updateCounts);
|
||||||
Origin = Anchor.BottomCentre,
|
}
|
||||||
Font = OsuFont.Default.With(size: 30),
|
}
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
X = (targetBin + 0.5f) / bin_count - 0.5f,
|
|
||||||
Alpha = 0,
|
|
||||||
};
|
|
||||||
AddInternal(text);
|
|
||||||
|
|
||||||
Scheduler.AddDelayed(() =>
|
private double lastScoreDisplay;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (Time.Current - lastScoreDisplay > 150 && newScores.TryDequeue(out var newScore))
|
||||||
{
|
{
|
||||||
float startY = ToLocalSpace(barsContainer[targetBin].CircularBar.ScreenSpaceDrawQuad.TopLeft).Y;
|
if (lastScoreDisplay < Time.Current)
|
||||||
text.FadeInFromZero()
|
lastScoreDisplay = Time.Current;
|
||||||
.ScaleTo(new Vector2(0.8f), 500, Easing.OutElasticHalf)
|
|
||||||
.MoveToY(startY)
|
int targetBin = getTargetBin(newScore);
|
||||||
.MoveToOffset(new Vector2(0, -50), 2500, Easing.OutQuint)
|
bins[targetBin] += 1;
|
||||||
.FadeOut(2500, Easing.OutQuint)
|
|
||||||
.Expire();
|
updateCounts();
|
||||||
}, 150);
|
|
||||||
|
var text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = newScore.TotalScore.ToString(@"N0"),
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Font = OsuFont.Default.With(size: 30),
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = (targetBin + 0.5f) / bin_count - 0.5f,
|
||||||
|
Alpha = 0,
|
||||||
|
};
|
||||||
|
AddInternal(text);
|
||||||
|
|
||||||
|
Scheduler.AddDelayed(() =>
|
||||||
|
{
|
||||||
|
float startY = ToLocalSpace(barsContainer[targetBin].CircularBar.ScreenSpaceDrawQuad.TopLeft).Y;
|
||||||
|
text.FadeInFromZero()
|
||||||
|
.ScaleTo(new Vector2(0.8f), 500, Easing.OutElasticHalf)
|
||||||
|
.MoveToY(startY)
|
||||||
|
.MoveToOffset(new Vector2(0, -50), 2500, Easing.OutQuint)
|
||||||
|
.FadeOut(2500, Easing.OutQuint)
|
||||||
|
.Expire();
|
||||||
|
}, 150);
|
||||||
|
|
||||||
|
lastScoreDisplay = Time.Current;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetInitialCounts(long[] counts)
|
public void SetInitialCounts(long[] counts)
|
||||||
@ -106,6 +134,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
updateCounts();
|
updateCounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getTargetBin(NewScoreEvent score) =>
|
||||||
|
(int)Math.Clamp(Math.Floor((float)score.TotalScore / 100000), 0, bin_count - 1);
|
||||||
|
|
||||||
private void updateCounts()
|
private void updateCounts()
|
||||||
{
|
{
|
||||||
long max = Math.Max(bins.Max(), 1);
|
long max = Math.Max(bins.Max(), 1);
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||||
|
{
|
||||||
|
public partial class NewDailyChallengeNotification : SimpleNotification
|
||||||
|
{
|
||||||
|
private readonly Room room;
|
||||||
|
|
||||||
|
private BeatmapCardNano card = null!;
|
||||||
|
|
||||||
|
public NewDailyChallengeNotification(Room room)
|
||||||
|
{
|
||||||
|
this.room = room;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGame? game)
|
||||||
|
{
|
||||||
|
Text = DailyChallengeStrings.ChallengeLiveNotification;
|
||||||
|
Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!));
|
||||||
|
Activated = () =>
|
||||||
|
{
|
||||||
|
game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
card.Width = Content.DrawWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,15 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
|||||||
{
|
{
|
||||||
public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip<ScoreInfo>
|
public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip<ScoreInfo>
|
||||||
{
|
{
|
||||||
|
public Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A function determining whether each mod in the score can be selected.
|
||||||
|
/// A return value of <see langword="true"/> means that the mod can be selected in the current context.
|
||||||
|
/// A return value of <see langword="false"/> means that the mod cannot be selected in the current context.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Mod, bool> IsValidMod { get; set; } = _ => true;
|
||||||
|
|
||||||
public int? Rank { get; init; }
|
public int? Rank { get; init; }
|
||||||
public bool IsPersonalBest { get; init; }
|
public bool IsPersonalBest { get; init; }
|
||||||
|
|
||||||
@ -68,9 +77,6 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private SongSelect? songSelect { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IDialogOverlay? dialogOverlay { get; set; }
|
private IDialogOverlay? dialogOverlay { get; set; }
|
||||||
|
|
||||||
@ -738,8 +744,8 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
|||||||
{
|
{
|
||||||
List<MenuItem> items = new List<MenuItem>();
|
List<MenuItem> items = new List<MenuItem>();
|
||||||
|
|
||||||
if (score.Mods.Length > 0 && songSelect != null)
|
if (score.Mods.Length > 0)
|
||||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods));
|
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => IsValidMod.Invoke(m)).ToArray()));
|
||||||
|
|
||||||
if (score.Files.Count <= 0) return items.ToArray();
|
if (score.Files.Count <= 0) return items.ToArray();
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Metadata
|
|||||||
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
|
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
|
||||||
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
|
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
|
||||||
|
|
||||||
public override IBindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
|
public override Bindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
|
||||||
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
|
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -88,7 +88,14 @@ namespace osu.Game.Tests.Visual.Metadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override Task<MultiplayerPlaylistItemStats[]> BeginWatchingMultiplayerRoom(long id)
|
public override Task<MultiplayerPlaylistItemStats[]> BeginWatchingMultiplayerRoom(long id)
|
||||||
=> Task.FromResult(new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS]);
|
{
|
||||||
|
var stats = new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS];
|
||||||
|
|
||||||
|
for (int i = 0; i < stats.Length; i++)
|
||||||
|
stats[i] = new MultiplayerPlaylistItemStats { PlaylistItemID = i };
|
||||||
|
|
||||||
|
return Task.FromResult(stats);
|
||||||
|
}
|
||||||
|
|
||||||
public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask;
|
public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
|||||||
MaxCombo = 1000,
|
MaxCombo = 1000,
|
||||||
TotalScore = 1000000,
|
TotalScore = 1000000,
|
||||||
User = new APIUser { Username = "best user" },
|
User = new APIUser { Username = "best user" },
|
||||||
|
Mods = [new APIMod { Acronym = @"DT" }],
|
||||||
Statistics = new Dictionary<HitResult, int>()
|
Statistics = new Dictionary<HitResult, int>()
|
||||||
},
|
},
|
||||||
new MultiplayerScore
|
new MultiplayerScore
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2024.517.0">
|
<PackageReference Include="ppy.LocalisationAnalyser" Version="2024.802.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
Loading…
Reference in New Issue
Block a user