mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 10:42:54 +08:00
Merge branch 'master' into chat-mention-highlight
This commit is contained in:
commit
da7c6f1772
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@ -96,6 +97,8 @@ namespace osu.Desktop
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
Debug.Assert(OperatingSystem.IsWindows());
|
||||
|
||||
return new SquirrelUpdateManager();
|
||||
|
||||
default:
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Desktop.LegacyIpc;
|
||||
@ -12,6 +13,7 @@ using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Tournament;
|
||||
using Squirrel;
|
||||
|
||||
namespace osu.Desktop
|
||||
{
|
||||
@ -24,6 +26,10 @@ namespace osu.Desktop
|
||||
[STAThread]
|
||||
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
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -16,10 +17,11 @@ using osu.Game.Overlays.Notifications;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using Squirrel;
|
||||
using LogLevel = Splat.LogLevel;
|
||||
using Squirrel.SimpleSplat;
|
||||
|
||||
namespace osu.Desktop.Updater
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
|
||||
{
|
||||
private UpdateManager updateManager;
|
||||
@ -34,12 +36,14 @@ namespace osu.Desktop.Updater
|
||||
/// </summary>
|
||||
private bool updatePending;
|
||||
|
||||
private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(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);
|
||||
@ -49,9 +53,11 @@ namespace osu.Desktop.Updater
|
||||
// should we schedule a retry on completion of this check?
|
||||
bool scheduleRecheck = true;
|
||||
|
||||
const string github_token = null; // TODO: populate.
|
||||
|
||||
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);
|
||||
|
||||
@ -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)
|
||||
return;
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?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">
|
||||
<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">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
|
@ -24,10 +24,10 @@
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Clowd.Squirrel" Version="2.8.15-pre" />
|
||||
<PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.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.Design" Version="5.0.14">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -1,25 +1,80 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tests.Resources;
|
||||
using Realms;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Tests.Database
|
||||
{
|
||||
[TestFixture]
|
||||
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]
|
||||
public void TestSubscriptionWithAsyncWrite()
|
||||
{
|
||||
@ -47,6 +102,28 @@ namespace osu.Game.Tests.Database
|
||||
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]
|
||||
public void TestSubscriptionWithContextLoss()
|
||||
{
|
||||
@ -163,5 +240,41 @@ namespace osu.Game.Tests.Database
|
||||
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.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
@ -118,59 +112,6 @@ namespace osu.Game.Tests.Gameplay
|
||||
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]
|
||||
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 };
|
||||
storyboardContainer.Clock = decoupledClock;
|
||||
|
||||
storyboard = working.Storyboard.CreateDrawable(Beatmap.Value);
|
||||
storyboard = working.Storyboard.CreateDrawable(SelectedMods.Value);
|
||||
storyboard.Passing = false;
|
||||
|
||||
storyboardContainer.Add(storyboard);
|
||||
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
sb = decoder.Decode(bfr);
|
||||
}
|
||||
|
||||
storyboard = sb.CreateDrawable(Beatmap.Value);
|
||||
storyboard = sb.CreateDrawable(SelectedMods.Value);
|
||||
|
||||
storyboardContainer.Add(storyboard);
|
||||
decoupledClock.ChangeSource(Beatmap.Value.Track);
|
||||
|
@ -1,17 +1,23 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
@ -19,6 +25,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
private Storyboard storyboard;
|
||||
|
||||
private IReadOnlyList<Mod> storyboardMods;
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
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));
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => storyboardMods = Array.Empty<Mod>();
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardSamplesStopDuringPause()
|
||||
{
|
||||
checkForFirstSamplePlayback();
|
||||
createPlayerTest();
|
||||
|
||||
AddStep("player paused", () => Player.Pause());
|
||||
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());
|
||||
AddUntilStep("any storyboard samples playing after resume", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
||||
waitUntilStoryboardSamplesPlay();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardSamplesStopOnSkip()
|
||||
{
|
||||
checkForFirstSamplePlayback();
|
||||
createPlayerTest();
|
||||
|
||||
AddStep("skip intro", () => InputManager.Key(osuTK.Input.Key.Space));
|
||||
AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
|
||||
skipIntro();
|
||||
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);
|
||||
AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
||||
AddStep("setup mod", () =>
|
||||
{
|
||||
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>();
|
||||
|
||||
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 TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false);
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
|
||||
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.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
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>
|
||||
/// Run work on realm that will be run every time the update thread realm instance gets recycled.
|
||||
/// </summary>
|
||||
|
@ -4,6 +4,8 @@
|
||||
using System;
|
||||
using Realms;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
public static class RealmExtensions
|
||||
@ -22,5 +24,14 @@ namespace osu.Game.Database
|
||||
transaction.Commit();
|
||||
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
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Graphics.Backgrounds
|
||||
@ -20,6 +23,9 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
[Resolved(CanBeNull = true)]
|
||||
private MusicController? musicController { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
||||
: base(beatmap, fallbackTextureName)
|
||||
{
|
||||
@ -39,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Volume = { Value = 0 },
|
||||
Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock }
|
||||
Child = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock }
|
||||
}, 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;
|
||||
}
|
||||
|
||||
public Container TabbableContentContainer
|
||||
public CompositeDrawable TabbableContentContainer
|
||||
{
|
||||
set => Component.TabbableContentContainer = value;
|
||||
}
|
||||
|
@ -46,9 +46,6 @@ namespace osu.Game.Online.Solo
|
||||
[JsonProperty("mods")]
|
||||
public APIMod[] Mods { get; set; }
|
||||
|
||||
[JsonProperty("user")]
|
||||
public APIUser User { get; set; }
|
||||
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<HitResult, int> Statistics { get; set; }
|
||||
|
||||
@ -67,7 +64,6 @@ namespace osu.Game.Online.Solo
|
||||
RulesetID = score.RulesetID;
|
||||
Passed = score.Passed;
|
||||
Mods = score.APIMods;
|
||||
User = score.User;
|
||||
Statistics = score.Statistics;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
@ -16,6 +17,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Indicates that the object has not been judged yet.
|
||||
/// </summary>
|
||||
[Description(@"")]
|
||||
[EnumMember(Value = "none")]
|
||||
[Order(14)]
|
||||
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).
|
||||
/// </remarks>
|
||||
[Description(@"Miss")]
|
||||
[EnumMember(Value = "miss")]
|
||||
[Order(5)]
|
||||
Miss,
|
||||
|
||||
[Description(@"Meh")]
|
||||
[EnumMember(Value = "meh")]
|
||||
[Order(4)]
|
||||
Meh,
|
||||
|
||||
[Description(@"OK")]
|
||||
[EnumMember(Value = "ok")]
|
||||
[Order(3)]
|
||||
Ok,
|
||||
|
||||
[Description(@"Good")]
|
||||
[EnumMember(Value = "good")]
|
||||
[Order(2)]
|
||||
Good,
|
||||
|
||||
[Description(@"Great")]
|
||||
[EnumMember(Value = "great")]
|
||||
[Order(1)]
|
||||
Great,
|
||||
|
||||
[Description(@"Perfect")]
|
||||
[EnumMember(Value = "perfect")]
|
||||
[Order(0)]
|
||||
Perfect,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates small tick miss.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "small_tick_miss")]
|
||||
[Order(11)]
|
||||
SmallTickMiss,
|
||||
|
||||
@ -60,12 +69,14 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Indicates a small tick hit.
|
||||
/// </summary>
|
||||
[Description(@"S Tick")]
|
||||
[EnumMember(Value = "small_tick_hit")]
|
||||
[Order(7)]
|
||||
SmallTickHit,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a large tick miss.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "large_tick_miss")]
|
||||
[Order(10)]
|
||||
LargeTickMiss,
|
||||
|
||||
@ -73,6 +84,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Indicates a large tick hit.
|
||||
/// </summary>
|
||||
[Description(@"L Tick")]
|
||||
[EnumMember(Value = "large_tick_hit")]
|
||||
[Order(6)]
|
||||
LargeTickHit,
|
||||
|
||||
@ -80,6 +92,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Indicates a small bonus.
|
||||
/// </summary>
|
||||
[Description("S Bonus")]
|
||||
[EnumMember(Value = "small_bonus")]
|
||||
[Order(9)]
|
||||
SmallBonus,
|
||||
|
||||
@ -87,18 +100,21 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Indicates a large bonus.
|
||||
/// </summary>
|
||||
[Description("L Bonus")]
|
||||
[EnumMember(Value = "large_bonus")]
|
||||
[Order(8)]
|
||||
LargeBonus,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a miss that should be ignored for scoring purposes.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "ignore_miss")]
|
||||
[Order(13)]
|
||||
IgnoreMiss,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a hit that should be ignored for scoring purposes.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "ignore_hit")]
|
||||
[Order(12)]
|
||||
IgnoreHit,
|
||||
}
|
||||
@ -133,6 +149,30 @@ namespace osu.Game.Rulesets.Scoring
|
||||
public static bool AffectsAccuracy(this HitResult 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>
|
||||
/// Whether a <see cref="HitResult"/> should be counted as bonus score.
|
||||
/// </summary>
|
||||
|
@ -75,9 +75,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
FillFlowContainer flow;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
flow = new FillFlowContainer
|
||||
{
|
||||
Width = 200,
|
||||
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 an unselected object, operate on that object alone, independently of the selection.
|
||||
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;
|
||||
}
|
||||
|
||||
public CompositeDrawable TabbableContentContainer
|
||||
{
|
||||
set => textBox.TabbableContentContainer = value;
|
||||
}
|
||||
|
||||
private readonly BindableWithCurrent<T?> current = new BindableWithCurrent<T?>();
|
||||
|
||||
public Bindable<T?> Current
|
||||
|
@ -1,10 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
@ -18,6 +20,8 @@ namespace osu.Game.Screens.Play
|
||||
public Container OverlayLayerContainer { get; private set; }
|
||||
|
||||
private readonly Storyboard storyboard;
|
||||
private readonly IReadOnlyList<Mod> mods;
|
||||
|
||||
private DrawableStoryboard drawableStoryboard;
|
||||
|
||||
/// <summary>
|
||||
@ -28,9 +32,10 @@ namespace osu.Game.Screens.Play
|
||||
/// </remarks>
|
||||
public IBindable<bool> HasStoryboardEnded = new BindableBool(true);
|
||||
|
||||
public DimmableStoryboard(Storyboard storyboard)
|
||||
public DimmableStoryboard(Storyboard storyboard, IReadOnlyList<Mod> mods)
|
||||
{
|
||||
this.storyboard = storyboard;
|
||||
this.mods = mods;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -57,7 +62,7 @@ namespace osu.Game.Screens.Play
|
||||
if (!ShowStoryboard.Value && !IgnoreUserSettings.Value)
|
||||
return;
|
||||
|
||||
drawableStoryboard = storyboard.CreateDrawable();
|
||||
drawableStoryboard = storyboard.CreateDrawable(mods);
|
||||
HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded);
|
||||
|
||||
if (async)
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -86,26 +85,10 @@ namespace osu.Game.Screens.Play
|
||||
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
|
||||
userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true);
|
||||
|
||||
beatmapOffsetSubscription = realm.RegisterCustomSubscription(r =>
|
||||
{
|
||||
var userSettings = r.Find<BeatmapInfo>(beatmap.BeatmapInfo.ID)?.UserSettings;
|
||||
|
||||
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;
|
||||
});
|
||||
beatmapOffsetSubscription = realm.SubscribeToPropertyChanged(
|
||||
r => r.Find<BeatmapInfo>(beatmap.BeatmapInfo.ID)?.UserSettings,
|
||||
settings => settings.Offset,
|
||||
val => userBeatmapOffsetClock.Offset = val);
|
||||
|
||||
// sane default provided by ruleset.
|
||||
startOffset = gameplayStartTime;
|
||||
|
@ -359,7 +359,7 @@ namespace osu.Game.Screens.Play
|
||||
protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart);
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -120,24 +119,10 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
||||
|
||||
ReferenceScore.BindValueChanged(scoreChanged, true);
|
||||
|
||||
beatmapOffsetSubscription = realm.RegisterCustomSubscription(r =>
|
||||
{
|
||||
var userSettings = r.Find<BeatmapInfo>(beatmap.Value.BeatmapInfo.ID)?.UserSettings;
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
beatmapOffsetSubscription = realm.SubscribeToPropertyChanged(
|
||||
r => r.Find<BeatmapInfo>(beatmap.Value.BeatmapInfo.ID)?.UserSettings,
|
||||
settings => settings.Offset,
|
||||
val => Current.Value = val);
|
||||
|
||||
Current.BindValueChanged(currentChanged);
|
||||
}
|
||||
|
@ -16,6 +16,9 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
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;
|
||||
|
||||
public SetPanelContent(CarouselBeatmapSet carouselSet)
|
||||
|
@ -191,6 +191,11 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
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();
|
||||
|
||||
if (filterMods && !mods.Value.Any())
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osuTK;
|
||||
@ -11,6 +13,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Stores;
|
||||
|
||||
@ -50,14 +53,18 @@ namespace osu.Game.Storyboards.Drawables
|
||||
|
||||
private double? lastEventEndTime;
|
||||
|
||||
[Cached(typeof(IReadOnlyList<Mod>))]
|
||||
public IReadOnlyList<Mod> Mods { get; }
|
||||
|
||||
private DependencyContainer dependencies;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
public DrawableStoryboard(Storyboard storyboard)
|
||||
public DrawableStoryboard(Storyboard storyboard, IReadOnlyList<Mod> mods = null)
|
||||
{
|
||||
Storyboard = storyboard;
|
||||
Mods = mods ?? Array.Empty<Mod>();
|
||||
|
||||
Size = new Vector2(640, 480);
|
||||
|
||||
|
@ -28,19 +28,22 @@ namespace osu.Game.Storyboards.Drawables
|
||||
LifetimeStart = sampleInfo.StartTime;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IReadOnlyList<Mod> mods { get; set; }
|
||||
|
||||
protected override void SkinChanged(ISkinSource 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)
|
||||
mod.ApplyToSample(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SamplePlaybackDisabledChanged(ValueChangedEvent<bool> disabled)
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
@ -90,8 +91,8 @@ namespace osu.Game.Storyboards
|
||||
}
|
||||
}
|
||||
|
||||
public DrawableStoryboard CreateDrawable(IWorkingBeatmap working = null) =>
|
||||
new DrawableStoryboard(this);
|
||||
public DrawableStoryboard CreateDrawable(IReadOnlyList<Mod> mods = null) =>
|
||||
new DrawableStoryboard(this, mods);
|
||||
|
||||
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
|
||||
{
|
||||
|
@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
||||
|
||||
/// <summary>
|
||||
/// Mods from *player* (not OsuScreen).
|
||||
/// </summary>
|
||||
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
|
||||
|
||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||
|
Loading…
Reference in New Issue
Block a user