mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 14:02:55 +08:00
Merge branch 'master' into chat-mention-highlight
This commit is contained in:
commit
da7c6f1772
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@ -96,6 +97,8 @@ namespace osu.Desktop
|
|||||||
switch (RuntimeInfo.OS)
|
switch (RuntimeInfo.OS)
|
||||||
{
|
{
|
||||||
case RuntimeInfo.Platform.Windows:
|
case RuntimeInfo.Platform.Windows:
|
||||||
|
Debug.Assert(OperatingSystem.IsWindows());
|
||||||
|
|
||||||
return new SquirrelUpdateManager();
|
return new SquirrelUpdateManager();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Desktop.LegacyIpc;
|
using osu.Desktop.LegacyIpc;
|
||||||
@ -12,6 +13,7 @@ using osu.Framework.Logging;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Game.Tournament;
|
using osu.Game.Tournament;
|
||||||
|
using Squirrel;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
{
|
{
|
||||||
@ -24,6 +26,10 @@ namespace osu.Desktop
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
// run Squirrel first, as the app may exit after these run
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
setupSquirrel();
|
||||||
|
|
||||||
// Back up the cwd before DesktopGameHost changes it
|
// Back up the cwd before DesktopGameHost changes it
|
||||||
string cwd = Environment.CurrentDirectory;
|
string cwd = Environment.CurrentDirectory;
|
||||||
|
|
||||||
@ -104,6 +110,23 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
private static void setupSquirrel()
|
||||||
|
{
|
||||||
|
SquirrelAwareApp.HandleEvents(onInitialInstall: (version, tools) =>
|
||||||
|
{
|
||||||
|
tools.CreateShortcutForThisExe();
|
||||||
|
tools.CreateUninstallerRegistryEntry();
|
||||||
|
}, onAppUninstall: (version, tools) =>
|
||||||
|
{
|
||||||
|
tools.RemoveShortcutForThisExe();
|
||||||
|
tools.RemoveUninstallerRegistryEntry();
|
||||||
|
}, onEveryRun: (version, tools, firstRun) =>
|
||||||
|
{
|
||||||
|
tools.SetProcessAppUserModelId();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static int allowableExceptions = DebugUtils.IsDebugBuild ? 0 : 1;
|
private static int allowableExceptions = DebugUtils.IsDebugBuild ? 0 : 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -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.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -16,10 +17,11 @@ using osu.Game.Overlays.Notifications;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using Squirrel;
|
using Squirrel;
|
||||||
using LogLevel = Splat.LogLevel;
|
using Squirrel.SimpleSplat;
|
||||||
|
|
||||||
namespace osu.Desktop.Updater
|
namespace osu.Desktop.Updater
|
||||||
{
|
{
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
|
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
|
||||||
{
|
{
|
||||||
private UpdateManager updateManager;
|
private UpdateManager updateManager;
|
||||||
@ -34,12 +36,14 @@ namespace osu.Desktop.Updater
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool updatePending;
|
private bool updatePending;
|
||||||
|
|
||||||
|
private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(NotificationOverlay notification)
|
private void load(NotificationOverlay notification)
|
||||||
{
|
{
|
||||||
notificationOverlay = notification;
|
notificationOverlay = notification;
|
||||||
|
|
||||||
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
|
SquirrelLocator.CurrentMutable.Register(() => squirrelLogger, typeof(ILogger));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
||||||
@ -49,9 +53,11 @@ namespace osu.Desktop.Updater
|
|||||||
// should we schedule a retry on completion of this check?
|
// should we schedule a retry on completion of this check?
|
||||||
bool scheduleRecheck = true;
|
bool scheduleRecheck = true;
|
||||||
|
|
||||||
|
const string github_token = null; // TODO: populate.
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true).ConfigureAwait(false);
|
updateManager ??= new GithubUpdateManager(@"https://github.com/ppy/osu", false, github_token, @"osulazer");
|
||||||
|
|
||||||
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
|
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
|
||||||
|
|
||||||
@ -201,11 +207,11 @@ namespace osu.Desktop.Updater
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SquirrelLogger : Splat.ILogger, IDisposable
|
private class SquirrelLogger : ILogger, IDisposable
|
||||||
{
|
{
|
||||||
public LogLevel Level { get; set; } = LogLevel.Info;
|
public Squirrel.SimpleSplat.LogLevel Level { get; set; } = Squirrel.SimpleSplat.LogLevel.Info;
|
||||||
|
|
||||||
public void Write(string message, LogLevel logLevel)
|
public void Write(string message, Squirrel.SimpleSplat.LogLevel logLevel)
|
||||||
{
|
{
|
||||||
if (logLevel < Level)
|
if (logLevel < Level)
|
||||||
return;
|
return;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
<assemblyIdentity version="1.0.0.0" name="osu!" />
|
<assemblyIdentity version="1.0.0.0" name="osu!" />
|
||||||
|
<SquirrelAwareVersion xmlns="urn:schema-squirrel-com:asm.v1">1</SquirrelAwareVersion>
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
<security>
|
<security>
|
||||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
@ -24,10 +24,10 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
|
<PackageReference Include="Clowd.Squirrel" Version="2.8.15-pre" />
|
||||||
<PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
|
<PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
||||||
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.5" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.14">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.14">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
@ -1,25 +1,80 @@
|
|||||||
// 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 enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Database
|
namespace osu.Game.Tests.Database
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class RealmSubscriptionRegistrationTests : RealmTest
|
public class RealmSubscriptionRegistrationTests : RealmTest
|
||||||
{
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestSubscriptionCollectionAndPropertyChanges()
|
||||||
|
{
|
||||||
|
int collectionChanges = 0;
|
||||||
|
int propertyChanges = 0;
|
||||||
|
|
||||||
|
ChangeSet? lastChanges = null;
|
||||||
|
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
var registration = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>(), onChanged);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
Assert.That(collectionChanges, Is.EqualTo(1));
|
||||||
|
Assert.That(propertyChanges, Is.EqualTo(0));
|
||||||
|
Assert.That(lastChanges?.InsertedIndices, Has.One.Items);
|
||||||
|
Assert.That(lastChanges?.ModifiedIndices, Is.Empty);
|
||||||
|
Assert.That(lastChanges?.NewModifiedIndices, Is.Empty);
|
||||||
|
|
||||||
|
realm.Write(r => r.All<BeatmapSetInfo>().First().Beatmaps.First().CountdownOffset = 5);
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
Assert.That(collectionChanges, Is.EqualTo(1));
|
||||||
|
Assert.That(propertyChanges, Is.EqualTo(1));
|
||||||
|
Assert.That(lastChanges?.InsertedIndices, Is.Empty);
|
||||||
|
Assert.That(lastChanges?.ModifiedIndices, Has.One.Items);
|
||||||
|
Assert.That(lastChanges?.NewModifiedIndices, Has.One.Items);
|
||||||
|
|
||||||
|
registration.Dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
void onChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error)
|
||||||
|
{
|
||||||
|
lastChanges = changes;
|
||||||
|
|
||||||
|
if (changes == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (changes.HasCollectionChanges())
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref collectionChanges);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref propertyChanges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSubscriptionWithAsyncWrite()
|
public void TestSubscriptionWithAsyncWrite()
|
||||||
{
|
{
|
||||||
@ -47,6 +102,28 @@ namespace osu.Game.Tests.Database
|
|||||||
void onChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error) => lastChanges = changes;
|
void onChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error) => lastChanges = changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPropertyChangedSubscription()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
bool? receivedValue = null;
|
||||||
|
|
||||||
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
|
||||||
|
using (realm.SubscribeToPropertyChanged(r => r.All<BeatmapSetInfo>().First(), setInfo => setInfo.Protected, val => receivedValue = val))
|
||||||
|
{
|
||||||
|
Assert.That(receivedValue, Is.False);
|
||||||
|
|
||||||
|
realm.Write(r => r.All<BeatmapSetInfo>().First().Protected = true);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
Assert.That(receivedValue, Is.True);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSubscriptionWithContextLoss()
|
public void TestSubscriptionWithContextLoss()
|
||||||
{
|
{
|
||||||
@ -163,5 +240,41 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.That(beatmapSetInfo, Is.Null);
|
Assert.That(beatmapSetInfo, Is.Null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPropertyChangedSubscriptionWithContextLoss()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
bool? receivedValue = null;
|
||||||
|
|
||||||
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
|
||||||
|
var subscription = realm.SubscribeToPropertyChanged(
|
||||||
|
r => r.All<BeatmapSetInfo>().First(),
|
||||||
|
setInfo => setInfo.Protected,
|
||||||
|
val => receivedValue = val);
|
||||||
|
|
||||||
|
Assert.That(receivedValue, Is.Not.Null);
|
||||||
|
receivedValue = null;
|
||||||
|
|
||||||
|
using (realm.BlockAllOperations())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-registration after context restore.
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
Assert.That(receivedValue, Is.Not.Null);
|
||||||
|
|
||||||
|
subscription.Dispose();
|
||||||
|
receivedValue = null;
|
||||||
|
|
||||||
|
using (realm.BlockAllOperations())
|
||||||
|
Assert.That(receivedValue, Is.Null);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
Assert.That(receivedValue, Is.Null);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,23 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Graphics.Audio;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -118,59 +112,6 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue);
|
AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(typeof(OsuModDoubleTime), 1.5)]
|
|
||||||
[TestCase(typeof(OsuModHalfTime), 0.75)]
|
|
||||||
[TestCase(typeof(ModWindUp), 1.5)]
|
|
||||||
[TestCase(typeof(ModWindDown), 0.75)]
|
|
||||||
[TestCase(typeof(OsuModDoubleTime), 2)]
|
|
||||||
[TestCase(typeof(OsuModHalfTime), 0.5)]
|
|
||||||
[TestCase(typeof(ModWindUp), 2)]
|
|
||||||
[TestCase(typeof(ModWindDown), 0.5)]
|
|
||||||
public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate)
|
|
||||||
{
|
|
||||||
GameplayClockContainer gameplayContainer = null;
|
|
||||||
StoryboardSampleInfo sampleInfo = null;
|
|
||||||
TestDrawableStoryboardSample sample = null;
|
|
||||||
|
|
||||||
Mod testedMod = Activator.CreateInstance(expectedMod) as Mod;
|
|
||||||
|
|
||||||
switch (testedMod)
|
|
||||||
{
|
|
||||||
case ModRateAdjust m:
|
|
||||||
m.SpeedChange.Value = expectedRate;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModTimeRamp m:
|
|
||||||
m.FinalRate.Value = m.InitialRate.Value = expectedRate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddStep("setup storyboard sample", () =>
|
|
||||||
{
|
|
||||||
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this);
|
|
||||||
SelectedMods.Value = new[] { testedMod };
|
|
||||||
|
|
||||||
var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
|
||||||
|
|
||||||
Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0)
|
|
||||||
{
|
|
||||||
Child = beatmapSkinSourceContainer
|
|
||||||
});
|
|
||||||
|
|
||||||
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1))
|
|
||||||
{
|
|
||||||
Clock = gameplayContainer.GameplayClock
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("start", () => gameplayContainer.Start());
|
|
||||||
|
|
||||||
AddAssert("sample playback rate matches mod rates", () =>
|
|
||||||
testedMod != null && Precision.AlmostEquals(
|
|
||||||
sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value,
|
|
||||||
((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSamplePlaybackWithBeatmapHitsoundsOff()
|
public void TestSamplePlaybackWithBeatmapHitsoundsOff()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
// 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 Newtonsoft.Json;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.IO.Serialization;
|
||||||
|
using osu.Game.Online.Solo;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Online
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Basic testing to ensure our attribute-based naming is correctly working.
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSubmittableScoreJsonSerialization
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestScoreSerialisationViaExtensionMethod()
|
||||||
|
{
|
||||||
|
var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
|
||||||
|
|
||||||
|
string serialised = score.Serialize();
|
||||||
|
|
||||||
|
Assert.That(serialised, Contains.Substring("large_tick_hit"));
|
||||||
|
Assert.That(serialised, Contains.Substring("\"rank\": \"S\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreSerialisationWithoutSettings()
|
||||||
|
{
|
||||||
|
var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
|
||||||
|
|
||||||
|
string serialised = JsonConvert.SerializeObject(score);
|
||||||
|
|
||||||
|
Assert.That(serialised, Contains.Substring("large_tick_hit"));
|
||||||
|
Assert.That(serialised, Contains.Substring("\"rank\":\"S\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
|
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
|
||||||
storyboardContainer.Clock = decoupledClock;
|
storyboardContainer.Clock = decoupledClock;
|
||||||
|
|
||||||
storyboard = working.Storyboard.CreateDrawable(Beatmap.Value);
|
storyboard = working.Storyboard.CreateDrawable(SelectedMods.Value);
|
||||||
storyboard.Passing = false;
|
storyboard.Passing = false;
|
||||||
|
|
||||||
storyboardContainer.Add(storyboard);
|
storyboardContainer.Add(storyboard);
|
||||||
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
sb = decoder.Decode(bfr);
|
sb = decoder.Decode(bfr);
|
||||||
}
|
}
|
||||||
|
|
||||||
storyboard = sb.CreateDrawable(Beatmap.Value);
|
storyboard = sb.CreateDrawable(SelectedMods.Value);
|
||||||
|
|
||||||
storyboardContainer.Add(storyboard);
|
storyboardContainer.Add(storyboard);
|
||||||
decoupledClock.ChangeSource(Beatmap.Value.Track);
|
decoupledClock.ChangeSource(Beatmap.Value.Track);
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -19,6 +25,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
private Storyboard storyboard;
|
private Storyboard storyboard;
|
||||||
|
|
||||||
|
private IReadOnlyList<Mod> storyboardMods;
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
@ -31,42 +41,107 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20));
|
backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => storyboardMods = Array.Empty<Mod>();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestStoryboardSamplesStopDuringPause()
|
public void TestStoryboardSamplesStopDuringPause()
|
||||||
{
|
{
|
||||||
checkForFirstSamplePlayback();
|
createPlayerTest();
|
||||||
|
|
||||||
AddStep("player paused", () => Player.Pause());
|
AddStep("player paused", () => Player.Pause());
|
||||||
AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value);
|
AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value);
|
||||||
AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
|
allStoryboardSamplesStopped();
|
||||||
|
|
||||||
AddStep("player resume", () => Player.Resume());
|
AddStep("player resume", () => Player.Resume());
|
||||||
AddUntilStep("any storyboard samples playing after resume", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
waitUntilStoryboardSamplesPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestStoryboardSamplesStopOnSkip()
|
public void TestStoryboardSamplesStopOnSkip()
|
||||||
{
|
{
|
||||||
checkForFirstSamplePlayback();
|
createPlayerTest();
|
||||||
|
|
||||||
AddStep("skip intro", () => InputManager.Key(osuTK.Input.Key.Space));
|
skipIntro();
|
||||||
AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
|
allStoryboardSamplesStopped();
|
||||||
|
|
||||||
AddUntilStep("any storyboard samples playing after skip", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
waitUntilStoryboardSamplesPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForFirstSamplePlayback()
|
[TestCase(typeof(OsuModDoubleTime), 1.5)]
|
||||||
|
[TestCase(typeof(OsuModDoubleTime), 2)]
|
||||||
|
[TestCase(typeof(OsuModHalfTime), 0.75)]
|
||||||
|
[TestCase(typeof(OsuModHalfTime), 0.5)]
|
||||||
|
public void TestStoryboardSamplesPlaybackWithRateAdjustMods(Type expectedMod, double expectedRate)
|
||||||
{
|
{
|
||||||
AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null);
|
AddStep("setup mod", () =>
|
||||||
AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
{
|
||||||
|
ModRateAdjust testedMod = (ModRateAdjust)Activator.CreateInstance(expectedMod).AsNonNull();
|
||||||
|
testedMod.SpeedChange.Value = expectedRate;
|
||||||
|
storyboardMods = new[] { testedMod };
|
||||||
|
});
|
||||||
|
|
||||||
|
createPlayerTest();
|
||||||
|
skipIntro();
|
||||||
|
|
||||||
|
AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound =>
|
||||||
|
sound.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == expectedRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(typeof(ModWindUp), 0.5, 2)]
|
||||||
|
[TestCase(typeof(ModWindUp), 1.51, 2)]
|
||||||
|
[TestCase(typeof(ModWindDown), 2, 0.5)]
|
||||||
|
[TestCase(typeof(ModWindDown), 0.99, 0.5)]
|
||||||
|
public void TestStoryboardSamplesPlaybackWithTimeRampMods(Type expectedMod, double initialRate, double finalRate)
|
||||||
|
{
|
||||||
|
AddStep("setup mod", () =>
|
||||||
|
{
|
||||||
|
ModTimeRamp testedMod = (ModTimeRamp)Activator.CreateInstance(expectedMod).AsNonNull();
|
||||||
|
testedMod.InitialRate.Value = initialRate;
|
||||||
|
testedMod.FinalRate.Value = finalRate;
|
||||||
|
storyboardMods = new[] { testedMod };
|
||||||
|
});
|
||||||
|
|
||||||
|
createPlayerTest();
|
||||||
|
skipIntro();
|
||||||
|
|
||||||
|
ModTimeRamp gameplayMod = null;
|
||||||
|
|
||||||
|
AddUntilStep("mod speed change updated", () =>
|
||||||
|
{
|
||||||
|
gameplayMod = Player.GameplayState.Mods.OfType<ModTimeRamp>().Single();
|
||||||
|
return gameplayMod.SpeedChange.Value != initialRate;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound =>
|
||||||
|
sound.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == gameplayMod.SpeedChange.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPlayerTest()
|
||||||
|
{
|
||||||
|
CreateTest(null);
|
||||||
|
|
||||||
|
AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null);
|
||||||
|
waitUntilStoryboardSamplesPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitUntilStoryboardSamplesPlay() => AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
||||||
|
|
||||||
|
private void allStoryboardSamplesStopped() => AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
|
||||||
|
|
||||||
|
private void skipIntro() => AddStep("skip intro", () => InputManager.Key(Key.Space));
|
||||||
|
|
||||||
private IEnumerable<DrawableStoryboardSample> allStoryboardSamples => Player.ChildrenOfType<DrawableStoryboardSample>();
|
private IEnumerable<DrawableStoryboardSample> allStoryboardSamples => Player.ChildrenOfType<DrawableStoryboardSample>();
|
||||||
|
|
||||||
protected override bool AllowFail => false;
|
protected override bool AllowFail => false;
|
||||||
|
|
||||||
|
protected override TestPlayer CreatePlayer(Ruleset ruleset)
|
||||||
|
{
|
||||||
|
SelectedMods.Value = SelectedMods.Value.Concat(storyboardMods).ToArray();
|
||||||
|
return new TestPlayer(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false);
|
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
|
||||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio);
|
new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio);
|
||||||
|
@ -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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestScenePopupScreenTitle : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPopupScreenTitle()
|
||||||
|
{
|
||||||
|
AddStep("create content", () =>
|
||||||
|
{
|
||||||
|
Child = new PopupScreenTitle
|
||||||
|
{
|
||||||
|
Title = "Popup Screen Title",
|
||||||
|
Description = string.Join(" ", Enumerable.Repeat("This is a description.", 20)),
|
||||||
|
Close = () => { }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDisabledExit()
|
||||||
|
{
|
||||||
|
AddStep("create content", () =>
|
||||||
|
{
|
||||||
|
Child = new PopupScreenTitle
|
||||||
|
{
|
||||||
|
Title = "Popup Screen Title",
|
||||||
|
Description = "This is a description."
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -318,6 +320,66 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to the property of a realm object to watch for changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// On subscribing, unless the <paramref name="modelAccessor"/> does not match an object, an initial invocation of <paramref name="onChanged"/> will occur immediately.
|
||||||
|
/// Further invocations will occur when the value changes, but may also fire on a realm recycle with no actual value change.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="modelAccessor">A function to retrieve the relevant model from realm.</param>
|
||||||
|
/// <param name="propertyLookup">A function to traverse to the relevant property from the model.</param>
|
||||||
|
/// <param name="onChanged">A function to be invoked when a change of value occurs.</param>
|
||||||
|
/// <typeparam name="TModel">The type of the model.</typeparam>
|
||||||
|
/// <typeparam name="TProperty">The type of the property to be watched.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// A subscription token. It must be kept alive for as long as you want to receive change notifications.
|
||||||
|
/// To stop receiving notifications, call <see cref="IDisposable.Dispose"/>.
|
||||||
|
/// </returns>
|
||||||
|
public IDisposable SubscribeToPropertyChanged<TModel, TProperty>(Func<Realm, TModel?> modelAccessor, Expression<Func<TModel, TProperty>> propertyLookup, Action<TProperty> onChanged)
|
||||||
|
where TModel : RealmObjectBase
|
||||||
|
{
|
||||||
|
return RegisterCustomSubscription(r =>
|
||||||
|
{
|
||||||
|
string propertyName = getMemberName(propertyLookup);
|
||||||
|
|
||||||
|
var model = Run(modelAccessor);
|
||||||
|
var propLookupCompiled = propertyLookup.Compile();
|
||||||
|
|
||||||
|
if (model == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
model.PropertyChanged += onPropertyChanged;
|
||||||
|
|
||||||
|
// Update initial value immediately.
|
||||||
|
onChanged(propLookupCompiled(model));
|
||||||
|
|
||||||
|
return new InvokeOnDisposal(() => model.PropertyChanged -= onPropertyChanged);
|
||||||
|
|
||||||
|
void onPropertyChanged(object sender, PropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.PropertyName == propertyName)
|
||||||
|
onChanged(propLookupCompiled(model));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static string getMemberName(Expression<Func<TModel, TProperty>> expression)
|
||||||
|
{
|
||||||
|
if (!(expression is LambdaExpression lambda))
|
||||||
|
throw new ArgumentException("Outermost expression must be a lambda expression", nameof(expression));
|
||||||
|
|
||||||
|
if (!(lambda.Body is MemberExpression memberExpression))
|
||||||
|
throw new ArgumentException("Lambda body must be a member access expression", nameof(expression));
|
||||||
|
|
||||||
|
// TODO: nested access can be supported, with more iteration here
|
||||||
|
// (need to iteratively soft-cast `memberExpression.Expression` into `MemberExpression`s until `lambda.Parameters[0]` is hit)
|
||||||
|
if (memberExpression.Expression != lambda.Parameters[0])
|
||||||
|
throw new ArgumentException("Nested access expressions are not supported", nameof(expression));
|
||||||
|
|
||||||
|
return memberExpression.Member.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Run work on realm that will be run every time the update thread realm instance gets recycled.
|
/// Run work on realm that will be run every time the update thread realm instance gets recycled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
public static class RealmExtensions
|
public static class RealmExtensions
|
||||||
@ -22,5 +24,14 @@ namespace osu.Game.Database
|
|||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the provided change set has changes to the top level collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Realm subscriptions fire on both collection and property changes (including *all* nested properties).
|
||||||
|
/// Quite often we only care about changes at a collection level. This can be used to guard and early-return when no such changes are in a callback.
|
||||||
|
/// </remarks>
|
||||||
|
public static bool HasCollectionChanges(this ChangeSet changes) => changes.InsertedIndices.Length > 0 || changes.DeletedIndices.Length > 0 || changes.Moves.Length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,15 @@
|
|||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Backgrounds
|
namespace osu.Game.Graphics.Backgrounds
|
||||||
@ -20,6 +23,9 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private MusicController? musicController { get; set; }
|
private MusicController? musicController { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||||
|
|
||||||
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
||||||
: base(beatmap, fallbackTextureName)
|
: base(beatmap, fallbackTextureName)
|
||||||
{
|
{
|
||||||
@ -39,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Volume = { Value = 0 },
|
Volume = { Value = 0 },
|
||||||
Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock }
|
Child = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock }
|
||||||
}, AddInternal);
|
}, AddInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
154
osu.Game/Graphics/UserInterface/PopupScreenTitle.cs
Normal file
154
osu.Game/Graphics/UserInterface/PopupScreenTitle.cs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
public class PopupScreenTitle : CompositeDrawable
|
||||||
|
{
|
||||||
|
public LocalisableString Title
|
||||||
|
{
|
||||||
|
set => titleSpriteText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString Description
|
||||||
|
{
|
||||||
|
set => descriptionText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action? Close
|
||||||
|
{
|
||||||
|
get => closeButton.Action;
|
||||||
|
set => closeButton.Action = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const float corner_radius = 14;
|
||||||
|
private const float main_area_height = 70;
|
||||||
|
|
||||||
|
private readonly Container underlayContainer;
|
||||||
|
private readonly Box underlayBackground;
|
||||||
|
private readonly Container contentContainer;
|
||||||
|
private readonly Box contentBackground;
|
||||||
|
private readonly OsuSpriteText titleSpriteText;
|
||||||
|
private readonly OsuTextFlowContainer descriptionText;
|
||||||
|
private readonly IconButton closeButton;
|
||||||
|
|
||||||
|
public PopupScreenTitle()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChild = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 70,
|
||||||
|
Top = -corner_radius
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
underlayContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = main_area_height + 2 * corner_radius,
|
||||||
|
CornerRadius = corner_radius,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
Child = underlayBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = main_area_height + corner_radius,
|
||||||
|
CornerRadius = corner_radius,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Colour4.Black.Opacity(0.1f),
|
||||||
|
Offset = new Vector2(0, 1),
|
||||||
|
Radius = 3
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
contentBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Margin = new MarginPadding { Top = corner_radius },
|
||||||
|
Padding = new MarginPadding { Horizontal = 100 },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
titleSpriteText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 20)
|
||||||
|
},
|
||||||
|
descriptionText = new OsuTextFlowContainer(t =>
|
||||||
|
{
|
||||||
|
t.Font = OsuFont.Default.With(size: 12);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeButton = new IconButton
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.Times,
|
||||||
|
Scale = new Vector2(0.6f),
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Right = 21,
|
||||||
|
Top = corner_radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
underlayContainer.BorderColour = ColourInfo.GradientVertical(Colour4.Black, colourProvider.Dark4);
|
||||||
|
underlayBackground.Colour = colourProvider.Dark4;
|
||||||
|
|
||||||
|
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Dark3, colourProvider.Dark1);
|
||||||
|
contentBackground.Colour = colourProvider.Dark3;
|
||||||
|
|
||||||
|
closeButton.IconHoverColour = colourProvider.Highlight1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
set => Component.Text = value;
|
set => Component.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Container TabbableContentContainer
|
public CompositeDrawable TabbableContentContainer
|
||||||
{
|
{
|
||||||
set => Component.TabbableContentContainer = value;
|
set => Component.TabbableContentContainer = value;
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,6 @@ namespace osu.Game.Online.Solo
|
|||||||
[JsonProperty("mods")]
|
[JsonProperty("mods")]
|
||||||
public APIMod[] Mods { get; set; }
|
public APIMod[] Mods { get; set; }
|
||||||
|
|
||||||
[JsonProperty("user")]
|
|
||||||
public APIUser User { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("statistics")]
|
[JsonProperty("statistics")]
|
||||||
public Dictionary<HitResult, int> Statistics { get; set; }
|
public Dictionary<HitResult, int> Statistics { get; set; }
|
||||||
|
|
||||||
@ -67,7 +64,6 @@ namespace osu.Game.Online.Solo
|
|||||||
RulesetID = score.RulesetID;
|
RulesetID = score.RulesetID;
|
||||||
Passed = score.Passed;
|
Passed = score.Passed;
|
||||||
Mods = score.APIMods;
|
Mods = score.APIMods;
|
||||||
User = score.User;
|
|
||||||
Statistics = score.Statistics;
|
Statistics = score.Statistics;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Scoring
|
namespace osu.Game.Rulesets.Scoring
|
||||||
@ -16,6 +17,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// Indicates that the object has not been judged yet.
|
/// Indicates that the object has not been judged yet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description(@"")]
|
[Description(@"")]
|
||||||
|
[EnumMember(Value = "none")]
|
||||||
[Order(14)]
|
[Order(14)]
|
||||||
None,
|
None,
|
||||||
|
|
||||||
@ -27,32 +29,39 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time).
|
/// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time).
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Description(@"Miss")]
|
[Description(@"Miss")]
|
||||||
|
[EnumMember(Value = "miss")]
|
||||||
[Order(5)]
|
[Order(5)]
|
||||||
Miss,
|
Miss,
|
||||||
|
|
||||||
[Description(@"Meh")]
|
[Description(@"Meh")]
|
||||||
|
[EnumMember(Value = "meh")]
|
||||||
[Order(4)]
|
[Order(4)]
|
||||||
Meh,
|
Meh,
|
||||||
|
|
||||||
[Description(@"OK")]
|
[Description(@"OK")]
|
||||||
|
[EnumMember(Value = "ok")]
|
||||||
[Order(3)]
|
[Order(3)]
|
||||||
Ok,
|
Ok,
|
||||||
|
|
||||||
[Description(@"Good")]
|
[Description(@"Good")]
|
||||||
|
[EnumMember(Value = "good")]
|
||||||
[Order(2)]
|
[Order(2)]
|
||||||
Good,
|
Good,
|
||||||
|
|
||||||
[Description(@"Great")]
|
[Description(@"Great")]
|
||||||
|
[EnumMember(Value = "great")]
|
||||||
[Order(1)]
|
[Order(1)]
|
||||||
Great,
|
Great,
|
||||||
|
|
||||||
[Description(@"Perfect")]
|
[Description(@"Perfect")]
|
||||||
|
[EnumMember(Value = "perfect")]
|
||||||
[Order(0)]
|
[Order(0)]
|
||||||
Perfect,
|
Perfect,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates small tick miss.
|
/// Indicates small tick miss.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[EnumMember(Value = "small_tick_miss")]
|
||||||
[Order(11)]
|
[Order(11)]
|
||||||
SmallTickMiss,
|
SmallTickMiss,
|
||||||
|
|
||||||
@ -60,12 +69,14 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// Indicates a small tick hit.
|
/// Indicates a small tick hit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description(@"S Tick")]
|
[Description(@"S Tick")]
|
||||||
|
[EnumMember(Value = "small_tick_hit")]
|
||||||
[Order(7)]
|
[Order(7)]
|
||||||
SmallTickHit,
|
SmallTickHit,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates a large tick miss.
|
/// Indicates a large tick miss.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[EnumMember(Value = "large_tick_miss")]
|
||||||
[Order(10)]
|
[Order(10)]
|
||||||
LargeTickMiss,
|
LargeTickMiss,
|
||||||
|
|
||||||
@ -73,6 +84,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// Indicates a large tick hit.
|
/// Indicates a large tick hit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description(@"L Tick")]
|
[Description(@"L Tick")]
|
||||||
|
[EnumMember(Value = "large_tick_hit")]
|
||||||
[Order(6)]
|
[Order(6)]
|
||||||
LargeTickHit,
|
LargeTickHit,
|
||||||
|
|
||||||
@ -80,6 +92,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// Indicates a small bonus.
|
/// Indicates a small bonus.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("S Bonus")]
|
[Description("S Bonus")]
|
||||||
|
[EnumMember(Value = "small_bonus")]
|
||||||
[Order(9)]
|
[Order(9)]
|
||||||
SmallBonus,
|
SmallBonus,
|
||||||
|
|
||||||
@ -87,18 +100,21 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// Indicates a large bonus.
|
/// Indicates a large bonus.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("L Bonus")]
|
[Description("L Bonus")]
|
||||||
|
[EnumMember(Value = "large_bonus")]
|
||||||
[Order(8)]
|
[Order(8)]
|
||||||
LargeBonus,
|
LargeBonus,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates a miss that should be ignored for scoring purposes.
|
/// Indicates a miss that should be ignored for scoring purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[EnumMember(Value = "ignore_miss")]
|
||||||
[Order(13)]
|
[Order(13)]
|
||||||
IgnoreMiss,
|
IgnoreMiss,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates a hit that should be ignored for scoring purposes.
|
/// Indicates a hit that should be ignored for scoring purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[EnumMember(Value = "ignore_hit")]
|
||||||
[Order(12)]
|
[Order(12)]
|
||||||
IgnoreHit,
|
IgnoreHit,
|
||||||
}
|
}
|
||||||
@ -133,6 +149,30 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
public static bool AffectsAccuracy(this HitResult result)
|
public static bool AffectsAccuracy(this HitResult result)
|
||||||
=> IsScorable(result) && !IsBonus(result);
|
=> IsScorable(result) && !IsBonus(result);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a <see cref="HitResult"/> is a non-tick and non-bonus result.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsBasic(this HitResult result)
|
||||||
|
=> IsScorable(result) && !IsTick(result) && !IsBonus(result);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a <see cref="HitResult"/> should be counted as a tick.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsTick(this HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
case HitResult.LargeTickMiss:
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
case HitResult.SmallTickMiss:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether a <see cref="HitResult"/> should be counted as bonus score.
|
/// Whether a <see cref="HitResult"/> should be counted as bonus score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -75,9 +75,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
FillFlowContainer flow;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = 200,
|
Width = 200,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
@ -94,6 +96,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bank.TabbableContentContainer = flow;
|
||||||
|
volume.TabbableContentContainer = flow;
|
||||||
|
|
||||||
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||||
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||||
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
||||||
|
@ -32,6 +32,11 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
set => slider.KeyboardStep = value;
|
set => slider.KeyboardStep = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompositeDrawable TabbableContentContainer
|
||||||
|
{
|
||||||
|
set => textBox.TabbableContentContainer = value;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly BindableWithCurrent<T?> current = new BindableWithCurrent<T?>();
|
private readonly BindableWithCurrent<T?> current = new BindableWithCurrent<T?>();
|
||||||
|
|
||||||
public Bindable<T?> Current
|
public Bindable<T?> Current
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
@ -18,6 +20,8 @@ namespace osu.Game.Screens.Play
|
|||||||
public Container OverlayLayerContainer { get; private set; }
|
public Container OverlayLayerContainer { get; private set; }
|
||||||
|
|
||||||
private readonly Storyboard storyboard;
|
private readonly Storyboard storyboard;
|
||||||
|
private readonly IReadOnlyList<Mod> mods;
|
||||||
|
|
||||||
private DrawableStoryboard drawableStoryboard;
|
private DrawableStoryboard drawableStoryboard;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -28,9 +32,10 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IBindable<bool> HasStoryboardEnded = new BindableBool(true);
|
public IBindable<bool> HasStoryboardEnded = new BindableBool(true);
|
||||||
|
|
||||||
public DimmableStoryboard(Storyboard storyboard)
|
public DimmableStoryboard(Storyboard storyboard, IReadOnlyList<Mod> mods)
|
||||||
{
|
{
|
||||||
this.storyboard = storyboard;
|
this.storyboard = storyboard;
|
||||||
|
this.mods = mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -57,7 +62,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!ShowStoryboard.Value && !IgnoreUserSettings.Value)
|
if (!ShowStoryboard.Value && !IgnoreUserSettings.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
drawableStoryboard = storyboard.CreateDrawable();
|
drawableStoryboard = storyboard.CreateDrawable(mods);
|
||||||
HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded);
|
HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded);
|
||||||
|
|
||||||
if (async)
|
if (async)
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -86,26 +85,10 @@ namespace osu.Game.Screens.Play
|
|||||||
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
|
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
|
||||||
userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true);
|
userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true);
|
||||||
|
|
||||||
beatmapOffsetSubscription = realm.RegisterCustomSubscription(r =>
|
beatmapOffsetSubscription = realm.SubscribeToPropertyChanged(
|
||||||
{
|
r => r.Find<BeatmapInfo>(beatmap.BeatmapInfo.ID)?.UserSettings,
|
||||||
var userSettings = r.Find<BeatmapInfo>(beatmap.BeatmapInfo.ID)?.UserSettings;
|
settings => settings.Offset,
|
||||||
|
val => userBeatmapOffsetClock.Offset = val);
|
||||||
if (userSettings == null) // only the case for tests.
|
|
||||||
return null;
|
|
||||||
|
|
||||||
void onUserSettingsOnPropertyChanged(object sender, PropertyChangedEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.PropertyName == nameof(BeatmapUserSettings.Offset))
|
|
||||||
updateOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOffset();
|
|
||||||
userSettings.PropertyChanged += onUserSettingsOnPropertyChanged;
|
|
||||||
|
|
||||||
return new InvokeOnDisposal(() => userSettings.PropertyChanged -= onUserSettingsOnPropertyChanged);
|
|
||||||
|
|
||||||
void updateOffset() => userBeatmapOffsetClock.Offset = userSettings.Offset;
|
|
||||||
});
|
|
||||||
|
|
||||||
// sane default provided by ruleset.
|
// sane default provided by ruleset.
|
||||||
startOffset = gameplayStartTime;
|
startOffset = gameplayStartTime;
|
||||||
|
@ -359,7 +359,7 @@ namespace osu.Game.Screens.Play
|
|||||||
protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart);
|
protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart);
|
||||||
|
|
||||||
private Drawable createUnderlayComponents() =>
|
private Drawable createUnderlayComponents() =>
|
||||||
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both };
|
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay)
|
private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay)
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -120,24 +119,10 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
|
|
||||||
ReferenceScore.BindValueChanged(scoreChanged, true);
|
ReferenceScore.BindValueChanged(scoreChanged, true);
|
||||||
|
|
||||||
beatmapOffsetSubscription = realm.RegisterCustomSubscription(r =>
|
beatmapOffsetSubscription = realm.SubscribeToPropertyChanged(
|
||||||
{
|
r => r.Find<BeatmapInfo>(beatmap.Value.BeatmapInfo.ID)?.UserSettings,
|
||||||
var userSettings = r.Find<BeatmapInfo>(beatmap.Value.BeatmapInfo.ID)?.UserSettings;
|
settings => settings.Offset,
|
||||||
|
val => Current.Value = val);
|
||||||
if (userSettings == null) // only the case for tests.
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Current.Value = userSettings.Offset;
|
|
||||||
userSettings.PropertyChanged += onUserSettingsOnPropertyChanged;
|
|
||||||
|
|
||||||
return new InvokeOnDisposal(() => userSettings.PropertyChanged -= onUserSettingsOnPropertyChanged);
|
|
||||||
|
|
||||||
void onUserSettingsOnPropertyChanged(object sender, PropertyChangedEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.PropertyName == nameof(BeatmapUserSettings.Offset))
|
|
||||||
Current.Value = userSettings.Offset;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Current.BindValueChanged(currentChanged);
|
Current.BindValueChanged(currentChanged);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
public class SetPanelContent : CompositeDrawable
|
public class SetPanelContent : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
// Disallow interacting with difficulty icons on a panel until the panel has been selected.
|
||||||
|
public override bool PropagatePositionalInputSubTree => carouselSet.State.Value == CarouselItemState.Selected;
|
||||||
|
|
||||||
private readonly CarouselBeatmapSet carouselSet;
|
private readonly CarouselBeatmapSet carouselSet;
|
||||||
|
|
||||||
public SetPanelContent(CarouselBeatmapSet carouselSet)
|
public SetPanelContent(CarouselBeatmapSet carouselSet)
|
||||||
|
@ -191,6 +191,11 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// This subscription may fire from changes to linked beatmaps, which we don't care about.
|
||||||
|
// It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications.
|
||||||
|
if (changes?.HasCollectionChanges() == false)
|
||||||
|
return;
|
||||||
|
|
||||||
var scores = sender.AsEnumerable();
|
var scores = sender.AsEnumerable();
|
||||||
|
|
||||||
if (filterMods && !mods.Value.Any())
|
if (filterMods && !mods.Value.Any())
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -11,6 +13,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Stores;
|
using osu.Game.Stores;
|
||||||
|
|
||||||
@ -50,14 +53,18 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
|
|
||||||
private double? lastEventEndTime;
|
private double? lastEventEndTime;
|
||||||
|
|
||||||
|
[Cached(typeof(IReadOnlyList<Mod>))]
|
||||||
|
public IReadOnlyList<Mod> Mods { get; }
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||||
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
public DrawableStoryboard(Storyboard storyboard)
|
public DrawableStoryboard(Storyboard storyboard, IReadOnlyList<Mod> mods = null)
|
||||||
{
|
{
|
||||||
Storyboard = storyboard;
|
Storyboard = storyboard;
|
||||||
|
Mods = mods ?? Array.Empty<Mod>();
|
||||||
|
|
||||||
Size = new Vector2(640, 480);
|
Size = new Vector2(640, 480);
|
||||||
|
|
||||||
|
@ -28,19 +28,22 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
LifetimeStart = sampleInfo.StartTime;
|
LifetimeStart = sampleInfo.StartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved(CanBeNull = true)]
|
||||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
private IReadOnlyList<Mod> mods { get; set; }
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin)
|
protected override void SkinChanged(ISkinSource skin)
|
||||||
{
|
{
|
||||||
base.SkinChanged(skin);
|
base.SkinChanged(skin);
|
||||||
|
|
||||||
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
|
if (mods != null)
|
||||||
|
{
|
||||||
|
foreach (var mod in mods.OfType<IApplicableToSample>())
|
||||||
{
|
{
|
||||||
foreach (var sample in DrawableSamples)
|
foreach (var sample in DrawableSamples)
|
||||||
mod.ApplyToSample(sample);
|
mod.ApplyToSample(sample);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void SamplePlaybackDisabledChanged(ValueChangedEvent<bool> disabled)
|
protected override void SamplePlaybackDisabledChanged(ValueChangedEvent<bool> disabled)
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
@ -90,8 +91,8 @@ namespace osu.Game.Storyboards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableStoryboard CreateDrawable(IWorkingBeatmap working = null) =>
|
public DrawableStoryboard CreateDrawable(IReadOnlyList<Mod> mods = null) =>
|
||||||
new DrawableStoryboard(this);
|
new DrawableStoryboard(this, mods);
|
||||||
|
|
||||||
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
|
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
|
||||||
{
|
{
|
||||||
|
@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mods from *player* (not OsuScreen).
|
|
||||||
/// </summary>
|
|
||||||
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
|
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
|
||||||
|
|
||||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||||
|
Loading…
Reference in New Issue
Block a user