mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 07:23:14 +08:00
Merge branch 'master' into chat-command
This commit is contained in:
commit
d9b4fae4e5
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--filter *" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -14,7 +14,7 @@
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net5.0" />
|
||||
<method v="2">
|
||||
<option name="Build" enabled="true" />
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@ -31,12 +31,11 @@ If you are looking to install or test osu! without setting up a development envi
|
||||
|
||||
**Latest build:**
|
||||
|
||||
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
|
||||
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
||||
|
||||
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
|
||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||
|
||||
## Developing a custom ruleset
|
||||
|
@ -14,9 +14,9 @@ namespace osu.Game.Benchmarks
|
||||
[Params(1, 10, 100)]
|
||||
public int Times { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
mod = new OsuModDoubleTime();
|
||||
}
|
||||
|
||||
|
62
osu.Game.Benchmarks/BenchmarkRuleset.cs
Normal file
62
osu.Game.Benchmarks/BenchmarkRuleset.cs
Normal file
@ -0,0 +1,62 @@
|
||||
// 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 BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkRuleset : BenchmarkTest
|
||||
{
|
||||
private OsuRuleset ruleset;
|
||||
private APIMod apiModDoubleTime;
|
||||
private APIMod apiModDifficultyAdjust;
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
ruleset = new OsuRuleset();
|
||||
apiModDoubleTime = new APIMod { Acronym = "DT" };
|
||||
apiModDifficultyAdjust = new APIMod { Acronym = "DA" };
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkToModDoubleTime()
|
||||
{
|
||||
apiModDoubleTime.ToMod(ruleset);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkToModDifficultyAdjust()
|
||||
{
|
||||
apiModDifficultyAdjust.ToMod(ruleset);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetAllMods()
|
||||
{
|
||||
ruleset.CreateAllMods().Consume(new Consumer());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetAllModsForReference()
|
||||
{
|
||||
ruleset.AllMods.Consume(new Consumer());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetForAcronym()
|
||||
{
|
||||
ruleset.CreateModFromAcronym("DT");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetForType()
|
||||
{
|
||||
ruleset.CreateMod<ModDoubleTime>();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
@ -11,7 +12,7 @@ namespace osu.Game.Benchmarks
|
||||
{
|
||||
BenchmarkSwitcher
|
||||
.FromAssembly(typeof(Program).Assembly)
|
||||
.Run(args);
|
||||
.Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
@ -13,6 +14,10 @@ using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[Resolved]
|
||||
private RulesetConfigCache configCache { get; set; }
|
||||
|
||||
private readonly Bindable<bool> configTimingBasedNoteColouring = new Bindable<bool>();
|
||||
private Bindable<bool> configTimingBasedNoteColouring;
|
||||
|
||||
protected override void LoadComplete()
|
||||
private ManualClock clock;
|
||||
private DrawableManiaRuleset drawableRuleset;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("setup hierarchy", () => Child = new Container
|
||||
{
|
||||
Clock = new FramedClock(clock = new ManualClock()),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new[]
|
||||
{
|
||||
drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
|
||||
}
|
||||
});
|
||||
AddStep("retrieve config bindable", () =>
|
||||
{
|
||||
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
|
||||
configTimingBasedNoteColouring = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSimple()
|
||||
{
|
||||
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
|
||||
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleOffScreen()
|
||||
{
|
||||
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
|
||||
|
||||
seekTo(10000);
|
||||
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
|
||||
seekTo(0);
|
||||
AddAssert("all notes not coloured", () => this.ChildrenOfType<DrawableNote>().All(note => note.Colour == Colour4.White));
|
||||
|
||||
seekTo(10000);
|
||||
AddStep("enable again", () => configTimingBasedNoteColouring.Value = true);
|
||||
seekTo(0);
|
||||
AddAssert("some notes coloured", () => this.ChildrenOfType<DrawableNote>().Any(note => note.Colour != Colour4.White));
|
||||
}
|
||||
|
||||
private void seekTo(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => clock.CurrentTime = time);
|
||||
AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1));
|
||||
}
|
||||
|
||||
private ManiaBeatmap createTestBeatmap()
|
||||
{
|
||||
const double beat_length = 500;
|
||||
|
||||
var ruleset = new ManiaRuleset();
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
|
||||
{
|
||||
HitObjects =
|
||||
@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new Note { StartTime = beat_length }
|
||||
},
|
||||
ControlPointInfo = new ControlPointInfo(),
|
||||
BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
|
||||
BeatmapInfo = { Ruleset = Ruleset.Value },
|
||||
};
|
||||
|
||||
foreach (var note in beatmap.HitObjects)
|
||||
@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
BeatLength = beat_length
|
||||
});
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
Clock = new FramedClock(new ManualClock()),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new[]
|
||||
{
|
||||
ruleset.CreateDrawableRulesetWith(beatmap)
|
||||
}
|
||||
};
|
||||
|
||||
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
|
||||
config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
||||
|
||||
AddStep("Enable", () => configTimingBasedNoteColouring.Value = true);
|
||||
AddStep("Disable", () => configTimingBasedNoteColouring.Value = false);
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
updateSnapColour();
|
||||
}
|
||||
|
||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||
{
|
||||
base.OnDirectionChanged(e);
|
||||
|
@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
||||
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
||||
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
||||
Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate);
|
||||
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
||||
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Mods
|
||||
@ -11,26 +12,42 @@ namespace osu.Game.Tests.Mods
|
||||
public class ModSettingsEqualityComparison
|
||||
{
|
||||
[Test]
|
||||
public void Test()
|
||||
public void TestAPIMod()
|
||||
{
|
||||
var apiMod1 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.25 } });
|
||||
var apiMod2 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
|
||||
var apiMod3 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
|
||||
|
||||
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod3));
|
||||
Assert.That(apiMod3, Is.EqualTo(apiMod2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMod()
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } };
|
||||
var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
|
||||
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
|
||||
var apiMod1 = new APIMod(mod1);
|
||||
var apiMod2 = new APIMod(mod2);
|
||||
var apiMod3 = new APIMod(mod3);
|
||||
|
||||
var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
|
||||
var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
|
||||
var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
|
||||
|
||||
Assert.That(mod1, Is.Not.EqualTo(mod2));
|
||||
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
|
||||
Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod2));
|
||||
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod3));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod3));
|
||||
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3));
|
||||
|
||||
Assert.That(mod3, Is.EqualTo(mod2));
|
||||
Assert.That(apiMod3, Is.EqualTo(apiMod2));
|
||||
Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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.Configuration;
|
||||
@ -71,7 +70,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var working = CreateWorkingBeatmap(rulesetInfo);
|
||||
|
||||
Beatmap.Value = working;
|
||||
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
|
||||
SelectedMods.Value = new[] { ruleset.CreateMod<ModNoFail>() };
|
||||
|
||||
Player = CreatePlayer(ruleset);
|
||||
|
||||
|
@ -130,6 +130,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Type = { Value = MatchType.HeadToHead },
|
||||
}));
|
||||
|
||||
AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType<RecentParticipantsList>().Any());
|
||||
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
|
||||
AddStep("set password", () => room.Password.Value = "password");
|
||||
|
@ -51,6 +51,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("room join password correct", () => lastJoinedPassword == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPopoverHidesOnBackButton()
|
||||
{
|
||||
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
|
||||
AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
|
||||
|
||||
AddStep("hit escape", () => InputManager.Key(Key.Escape));
|
||||
AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox);
|
||||
|
||||
AddStep("hit escape", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPopoverHidesOnLeavingScreen()
|
||||
{
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -388,6 +389,19 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExitGameFromSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
exitViaEscapeAndConfirm();
|
||||
|
||||
pushEscape(); // returns to osu! logo
|
||||
|
||||
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
|
||||
AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles);
|
||||
AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
private void pushEscape() =>
|
||||
AddStep("Press escape", () => InputManager.Key(Key.Escape));
|
||||
|
||||
|
@ -5,10 +5,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -17,10 +17,11 @@ using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneBeatmapListingOverlay : OsuTestScene
|
||||
public class TestSceneBeatmapListingOverlay : OsuManualInputManagerTestScene
|
||||
{
|
||||
private readonly List<APIBeatmapSet> setsForResponse = new List<APIBeatmapSet>();
|
||||
|
||||
@ -28,27 +29,33 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType<BeatmapListingSearchControl>().Single();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = req =>
|
||||
AddStep("setup overlay", () =>
|
||||
{
|
||||
if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
|
||||
|
||||
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
|
||||
{
|
||||
BeatmapSets = setsForResponse,
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
|
||||
setsForResponse.Clear();
|
||||
});
|
||||
|
||||
AddStep("initialize dummy", () =>
|
||||
{
|
||||
var api = (DummyAPIAccess)API;
|
||||
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
|
||||
|
||||
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
|
||||
{
|
||||
BeatmapSets = setsForResponse,
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// non-supporter user
|
||||
((DummyAPIAccess)API).LocalUser.Value = new User
|
||||
api.LocalUser.Value = new User
|
||||
{
|
||||
Username = "TestBot",
|
||||
Id = API.LocalUser.Value.Id + 1,
|
||||
@ -56,6 +63,51 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideViaBack()
|
||||
{
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
AddStep("hide", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideViaBackWithSearch()
|
||||
{
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("search something", () => overlay.ChildrenOfType<SearchTextBox>().First().Text = "search");
|
||||
|
||||
AddStep("kill search", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddAssert("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType<SearchTextBox>().First().Text));
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("hide", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideViaBackWithScrolledSearch()
|
||||
{
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, 100).ToArray()));
|
||||
|
||||
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
|
||||
|
||||
AddStep("scroll to bottom", () => overlay.ChildrenOfType<OverlayScrollContainer>().First().ScrollToEnd());
|
||||
|
||||
AddStep("kill search", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddUntilStep("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType<SearchTextBox>().First().Text));
|
||||
AddUntilStep("is scrolled to top", () => overlay.ChildrenOfType<OverlayScrollContainer>().First().Current == 0);
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("hide", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoBeatmapsPlaceholder()
|
||||
{
|
||||
@ -63,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
|
||||
|
||||
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
|
||||
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any());
|
||||
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
|
||||
|
||||
AddStep("fetch for 0 beatmaps", () => fetchFor());
|
||||
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
|
||||
@ -193,13 +245,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
noPlaceholderShown();
|
||||
}
|
||||
|
||||
private static int searchCount;
|
||||
|
||||
private void fetchFor(params BeatmapSetInfo[] beatmaps)
|
||||
{
|
||||
setsForResponse.Clear();
|
||||
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
|
||||
|
||||
// trigger arbitrary change for fetching.
|
||||
searchControl.Query.TriggerChange();
|
||||
searchControl.Query.Value = $"search {searchCount++}";
|
||||
}
|
||||
|
||||
private void setRankAchievedFilter(ScoreRank[] ranks)
|
||||
@ -229,8 +283,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
private void noPlaceholderShown()
|
||||
{
|
||||
AddUntilStep("no placeholder shown", () =>
|
||||
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any()
|
||||
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any());
|
||||
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any(d => d.IsPresent)
|
||||
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
|
||||
}
|
||||
|
||||
private class TestAPIBeatmapSet : APIBeatmapSet
|
||||
|
@ -21,6 +21,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
protected override bool UseOnlineAPI => true;
|
||||
|
||||
private int nextBeatmapSetId = 1;
|
||||
|
||||
public TestSceneBeatmapSetOverlay()
|
||||
{
|
||||
Add(overlay = new TestBeatmapSetOverlay());
|
||||
@ -240,12 +242,23 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("show explicit map", () =>
|
||||
{
|
||||
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
var beatmapSet = getBeatmapSet();
|
||||
beatmapSet.OnlineInfo.HasExplicitContent = true;
|
||||
overlay.ShowBeatmapSet(beatmapSet);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFeaturedBeatmap()
|
||||
{
|
||||
AddStep("show featured map", () =>
|
||||
{
|
||||
var beatmapSet = getBeatmapSet();
|
||||
beatmapSet.OnlineInfo.TrackId = 1;
|
||||
overlay.ShowBeatmapSet(beatmapSet);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHide()
|
||||
{
|
||||
@ -308,6 +321,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
};
|
||||
}
|
||||
|
||||
private BeatmapSetInfo getBeatmapSet()
|
||||
{
|
||||
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
// Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`).
|
||||
beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++;
|
||||
return beatmapSet;
|
||||
}
|
||||
|
||||
private void downloadAssert(bool shown)
|
||||
{
|
||||
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown);
|
||||
|
@ -99,16 +99,23 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets)
|
||||
{
|
||||
var normal = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
var normal = getBeatmapSet();
|
||||
normal.OnlineInfo.HasVideo = true;
|
||||
normal.OnlineInfo.HasStoryboard = true;
|
||||
|
||||
var undownloadable = getUndownloadableBeatmapSet();
|
||||
var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
|
||||
|
||||
var explicitMap = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
var explicitMap = getBeatmapSet();
|
||||
explicitMap.OnlineInfo.HasExplicitContent = true;
|
||||
|
||||
var featuredMap = getBeatmapSet();
|
||||
featuredMap.OnlineInfo.TrackId = 1;
|
||||
|
||||
var explicitFeaturedMap = getBeatmapSet();
|
||||
explicitFeaturedMap.OnlineInfo.HasExplicitContent = true;
|
||||
explicitFeaturedMap.OnlineInfo.TrackId = 2;
|
||||
|
||||
Child = new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -125,13 +132,19 @@ namespace osu.Game.Tests.Visual.Online
|
||||
new GridBeatmapPanel(undownloadable),
|
||||
new GridBeatmapPanel(manyDifficulties),
|
||||
new GridBeatmapPanel(explicitMap),
|
||||
new GridBeatmapPanel(featuredMap),
|
||||
new GridBeatmapPanel(explicitFeaturedMap),
|
||||
new ListBeatmapPanel(normal),
|
||||
new ListBeatmapPanel(undownloadable),
|
||||
new ListBeatmapPanel(manyDifficulties),
|
||||
new ListBeatmapPanel(explicitMap)
|
||||
new ListBeatmapPanel(explicitMap),
|
||||
new ListBeatmapPanel(featuredMap),
|
||||
new ListBeatmapPanel(explicitFeaturedMap)
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,12 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType<RoomsContainer>().First();
|
||||
|
||||
[Test]
|
||||
public void TestManyRooms()
|
||||
{
|
||||
AddStep("add rooms", () => RoomManager.AddRooms(500));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollByDraggingRooms()
|
||||
{
|
||||
|
@ -203,6 +203,71 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyboardNavigation()
|
||||
{
|
||||
var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 };
|
||||
var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 };
|
||||
var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 };
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add scores and select middle", () =>
|
||||
{
|
||||
// order of addition purposefully scrambled.
|
||||
list.AddScore(middleScore);
|
||||
list.AddScore(lowestScore);
|
||||
list.AddScore(highestScore);
|
||||
list.SelectedScore.Value = middleScore;
|
||||
});
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, true);
|
||||
assertScoreState(lowestScore, false);
|
||||
|
||||
AddStep("press left", () => InputManager.Key(Key.Left));
|
||||
|
||||
assertScoreState(highestScore, true);
|
||||
assertScoreState(middleScore, false);
|
||||
assertScoreState(lowestScore, false);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press left at start of list", () => InputManager.Key(Key.Left));
|
||||
|
||||
assertScoreState(highestScore, true);
|
||||
assertScoreState(middleScore, false);
|
||||
assertScoreState(lowestScore, false);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press right", () => InputManager.Key(Key.Right));
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, true);
|
||||
assertScoreState(lowestScore, false);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press right again", () => InputManager.Key(Key.Right));
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, false);
|
||||
assertScoreState(lowestScore, true);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press right at end of list", () => InputManager.Key(Key.Right));
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, false);
|
||||
assertScoreState(lowestScore, true);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press left", () => InputManager.Key(Key.Left));
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, true);
|
||||
assertScoreState(lowestScore, false);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
private void createListStep(Func<ScorePanelList> creationFunc)
|
||||
{
|
||||
AddStep("create list", () => Child = list = creationFunc().With(d =>
|
||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("select EZ mod", () =>
|
||||
{
|
||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||
SelectedMods.Value = new[] { ruleset.GetAllMods().OfType<ModEasy>().Single() };
|
||||
SelectedMods.Value = new[] { ruleset.CreateMod<ModEasy>() };
|
||||
});
|
||||
|
||||
AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue));
|
||||
@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("select HR mod", () =>
|
||||
{
|
||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||
SelectedMods.Value = new[] { ruleset.GetAllMods().OfType<ModHardRock>().Single() };
|
||||
SelectedMods.Value = new[] { ruleset.CreateMod<ModHardRock>() };
|
||||
});
|
||||
|
||||
AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue));
|
||||
@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("select unchanged Difficulty Adjust mod", () =>
|
||||
{
|
||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||
var difficultyAdjustMod = ruleset.GetAllMods().OfType<ModDifficultyAdjust>().Single();
|
||||
var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>();
|
||||
difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty);
|
||||
SelectedMods.Value = new[] { difficultyAdjustMod };
|
||||
});
|
||||
@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("select changed Difficulty Adjust mod", () =>
|
||||
{
|
||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||
var difficultyAdjustMod = ruleset.GetAllMods().OfType<OsuModDifficultyAdjust>().Single();
|
||||
var difficultyAdjustMod = ruleset.CreateMod<OsuModDifficultyAdjust>();
|
||||
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
|
||||
|
||||
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
|
||||
|
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
AddStep("setup display", () =>
|
||||
{
|
||||
var randomMods = Ruleset.Value.CreateInstance().GetAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList();
|
||||
var randomMods = Ruleset.Value.CreateInstance().CreateAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList();
|
||||
|
||||
OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) };
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Width = 200,
|
||||
Current =
|
||||
{
|
||||
Value = new OsuRuleset().GetAllMods().ToArray(),
|
||||
Value = new OsuRuleset().CreateAllMods().ToArray(),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -1,7 +1,9 @@
|
||||
// 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.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
@ -17,5 +19,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
|
||||
AddStep("change mod", () => icon.Mod = new OsuModEasy());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInterfaceModType()
|
||||
{
|
||||
ModIcon icon = null;
|
||||
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT")));
|
||||
AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
var mania = new ManiaRuleset();
|
||||
|
||||
testModsWithSameBaseType(
|
||||
mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)),
|
||||
mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden)));
|
||||
mania.CreateMod<ManiaModFadeIn>(),
|
||||
mania.CreateMod<ManiaModHidden>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
private void success(APIBeatmap apiBeatmap)
|
||||
{
|
||||
beatmap = apiBeatmap.ToBeatmap(rulesets);
|
||||
var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods();
|
||||
var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods;
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -49,7 +48,7 @@ namespace osu.Game.Tournament.Components
|
||||
}
|
||||
|
||||
var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0);
|
||||
var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym);
|
||||
var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym);
|
||||
|
||||
if (modIcon == null)
|
||||
return;
|
||||
|
@ -93,6 +93,12 @@ namespace osu.Game.Beatmaps
|
||||
public bool WidescreenStoryboard { get; set; }
|
||||
public bool EpilepsyWarning { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not sound samples should change rate when playing with speed-changing mods.
|
||||
/// TODO: only read/write supported for now, requires implementation in gameplay.
|
||||
/// </summary>
|
||||
public bool SamplesMatchPlaybackRate { get; set; }
|
||||
|
||||
public CountdownType Countdown { get; set; } = CountdownType.Normal;
|
||||
|
||||
/// <summary>
|
||||
|
@ -129,6 +129,7 @@ namespace osu.Game.Beatmaps
|
||||
Ruleset = ruleset,
|
||||
Metadata = metadata,
|
||||
WidescreenStoryboard = true,
|
||||
SamplesMatchPlaybackRate = true,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -90,6 +90,12 @@ namespace osu.Game.Beatmaps
|
||||
/// The song language of this beatmap set.
|
||||
/// </summary>
|
||||
public BeatmapSetOnlineLanguage Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The track ID of this beatmap set.
|
||||
/// Non-null only if the track is linked to a featured artist track entry.
|
||||
/// </summary>
|
||||
public int? TrackId { get; set; }
|
||||
}
|
||||
|
||||
public class BeatmapSetOnlineGenre
|
||||
|
@ -180,6 +180,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"SamplesMatchPlaybackRate":
|
||||
beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"Countdown":
|
||||
beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value);
|
||||
break;
|
||||
|
@ -105,8 +105,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (beatmap.BeatmapInfo.RulesetID == 3)
|
||||
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}"));
|
||||
// if (b.SamplesMatchPlaybackRate)
|
||||
// writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
|
||||
if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate)
|
||||
writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
|
||||
}
|
||||
|
||||
private void handleEditor(TextWriter writer)
|
||||
|
@ -4,6 +4,7 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@ -167,9 +168,21 @@ namespace osu.Game.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, (SettingSourceAttribute, PropertyInfo)[]> property_info_cache = new ConcurrentDictionary<Type, (SettingSourceAttribute, PropertyInfo)[]>();
|
||||
|
||||
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj)
|
||||
{
|
||||
foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
|
||||
var type = obj.GetType();
|
||||
|
||||
if (!property_info_cache.TryGetValue(type, out var properties))
|
||||
property_info_cache[type] = properties = getSettingsSourceProperties(type).ToArray();
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static IEnumerable<(SettingSourceAttribute, PropertyInfo)> getSettingsSourceProperties(Type type)
|
||||
{
|
||||
foreach (var property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
var attr = property.GetCustomAttribute<SettingSourceAttribute>(true);
|
||||
|
||||
|
@ -87,23 +87,25 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
private void createLink(IEnumerable<Drawable> drawables, LinkDetails link, string tooltipText, Action action = null)
|
||||
{
|
||||
AddInternal(new DrawableLinkCompiler(drawables.OfType<SpriteText>().ToList())
|
||||
var linkCompiler = CreateLinkCompiler(drawables.OfType<SpriteText>());
|
||||
linkCompiler.RelativeSizeAxes = Axes.Both;
|
||||
linkCompiler.TooltipText = tooltipText;
|
||||
linkCompiler.Action = () =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
TooltipText = tooltipText,
|
||||
Action = () =>
|
||||
{
|
||||
if (action != null)
|
||||
action();
|
||||
else if (game != null)
|
||||
game.HandleLink(link);
|
||||
// fallback to handle cases where OsuGame is not available, ie. tournament client.
|
||||
else if (link.Action == LinkAction.External)
|
||||
host.OpenUrlExternally(link.Argument);
|
||||
},
|
||||
});
|
||||
if (action != null)
|
||||
action();
|
||||
else if (game != null)
|
||||
game.HandleLink(link);
|
||||
// fallback to handle cases where OsuGame is not available, ie. tournament client.
|
||||
else if (link.Action == LinkAction.External)
|
||||
host.OpenUrlExternally(link.Argument);
|
||||
};
|
||||
|
||||
AddInternal(linkCompiler);
|
||||
}
|
||||
|
||||
protected virtual DrawableLinkCompiler CreateLinkCompiler(IEnumerable<SpriteText> parts) => new DrawableLinkCompiler(parts);
|
||||
|
||||
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
|
||||
// However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation.
|
||||
// Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow.
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
public virtual bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (!HasFocus) return false;
|
||||
|
||||
|
@ -4,14 +4,17 @@
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public class OsuPopover : Popover
|
||||
public class OsuPopover : Popover, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private const float fade_duration = 250;
|
||||
private const double scale_duration = 500;
|
||||
@ -51,5 +54,23 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
this.ScaleTo(0.7f, scale_duration, Easing.OutQuint);
|
||||
this.FadeOut(fade_duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return false;
|
||||
|
||||
if (action == GlobalAction.Back)
|
||||
{
|
||||
Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(GlobalAction action)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
515
osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs
generated
Normal file
515
osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs
generated
Normal file
@ -0,0 +1,515 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
[DbContext(typeof(OsuDbContext))]
|
||||
[Migration("20210912144011_AddSamplesMatchPlaybackRate")]
|
||||
partial class AddSamplesMatchPlaybackRate
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<float>("ApproachRate");
|
||||
|
||||
b.Property<float>("CircleSize");
|
||||
|
||||
b.Property<float>("DrainRate");
|
||||
|
||||
b.Property<float>("OverallDifficulty");
|
||||
|
||||
b.Property<double>("SliderMultiplier");
|
||||
|
||||
b.Property<double>("SliderTickRate");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapDifficulty");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<double>("AudioLeadIn");
|
||||
|
||||
b.Property<double>("BPM");
|
||||
|
||||
b.Property<int>("BaseDifficultyID");
|
||||
|
||||
b.Property<int>("BeatDivisor");
|
||||
|
||||
b.Property<int>("BeatmapSetInfoID");
|
||||
|
||||
b.Property<int>("Countdown");
|
||||
|
||||
b.Property<int>("CountdownOffset");
|
||||
|
||||
b.Property<double>("DistanceSpacing");
|
||||
|
||||
b.Property<bool>("EpilepsyWarning");
|
||||
|
||||
b.Property<int>("GridSize");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<bool>("Hidden");
|
||||
|
||||
b.Property<double>("Length");
|
||||
|
||||
b.Property<bool>("LetterboxInBreaks");
|
||||
|
||||
b.Property<string>("MD5Hash");
|
||||
|
||||
b.Property<int?>("MetadataID");
|
||||
|
||||
b.Property<int?>("OnlineBeatmapID");
|
||||
|
||||
b.Property<string>("Path");
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<bool>("SamplesMatchPlaybackRate");
|
||||
|
||||
b.Property<bool>("SpecialStyle");
|
||||
|
||||
b.Property<float>("StackLeniency");
|
||||
|
||||
b.Property<double>("StarDifficulty");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("StoredBookmarks");
|
||||
|
||||
b.Property<double>("TimelineZoom");
|
||||
|
||||
b.Property<string>("Version");
|
||||
|
||||
b.Property<bool>("WidescreenStoryboard");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BaseDifficultyID");
|
||||
|
||||
b.HasIndex("BeatmapSetInfoID");
|
||||
|
||||
b.HasIndex("Hash");
|
||||
|
||||
b.HasIndex("MD5Hash");
|
||||
|
||||
b.HasIndex("MetadataID");
|
||||
|
||||
b.HasIndex("OnlineBeatmapID")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RulesetID");
|
||||
|
||||
b.ToTable("BeatmapInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Artist");
|
||||
|
||||
b.Property<string>("ArtistUnicode");
|
||||
|
||||
b.Property<string>("AudioFile");
|
||||
|
||||
b.Property<int>("AuthorID")
|
||||
.HasColumnName("AuthorID");
|
||||
|
||||
b.Property<string>("AuthorString")
|
||||
.HasColumnName("Author");
|
||||
|
||||
b.Property<string>("BackgroundFile");
|
||||
|
||||
b.Property<int>("PreviewTime");
|
||||
|
||||
b.Property<string>("Source");
|
||||
|
||||
b.Property<string>("Tags");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("TitleUnicode");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("BeatmapSetInfoID");
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BeatmapSetInfoID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.ToTable("BeatmapSetFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset>("DateAdded");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int?>("MetadataID");
|
||||
|
||||
b.Property<int?>("OnlineBeatmapSetID");
|
||||
|
||||
b.Property<bool>("Protected");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("DeletePending");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("MetadataID");
|
||||
|
||||
b.HasIndex("OnlineBeatmapSetID")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("BeatmapSetInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Key")
|
||||
.HasColumnName("Key");
|
||||
|
||||
b.Property<int?>("RulesetID");
|
||||
|
||||
b.Property<int?>("SkinInfoID");
|
||||
|
||||
b.Property<string>("StringValue")
|
||||
.HasColumnName("Value");
|
||||
|
||||
b.Property<int?>("Variant");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("SkinInfoID");
|
||||
|
||||
b.HasIndex("RulesetID", "Variant");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int>("ReferenceCount");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ReferenceCount");
|
||||
|
||||
b.ToTable("FileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("IntAction")
|
||||
.HasColumnName("Action");
|
||||
|
||||
b.Property<string>("KeysString")
|
||||
.HasColumnName("Keys");
|
||||
|
||||
b.Property<int?>("RulesetID");
|
||||
|
||||
b.Property<int?>("Variant");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("IntAction");
|
||||
|
||||
b.HasIndex("RulesetID", "Variant");
|
||||
|
||||
b.ToTable("KeyBinding");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
|
||||
{
|
||||
b.Property<int?>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("ShortName");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Available");
|
||||
|
||||
b.HasIndex("ShortName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RulesetInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int?>("ScoreInfoID");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.HasIndex("ScoreInfoID");
|
||||
|
||||
b.ToTable("ScoreFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<double>("Accuracy")
|
||||
.HasColumnType("DECIMAL(1,4)");
|
||||
|
||||
b.Property<int>("BeatmapInfoID");
|
||||
|
||||
b.Property<int>("Combo");
|
||||
|
||||
b.Property<DateTimeOffset>("Date");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int>("MaxCombo");
|
||||
|
||||
b.Property<string>("ModsJson")
|
||||
.HasColumnName("Mods");
|
||||
|
||||
b.Property<long?>("OnlineScoreID");
|
||||
|
||||
b.Property<double?>("PP");
|
||||
|
||||
b.Property<int>("Rank");
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<string>("StatisticsJson")
|
||||
.HasColumnName("Statistics");
|
||||
|
||||
b.Property<long>("TotalScore");
|
||||
|
||||
b.Property<int?>("UserID")
|
||||
.HasColumnName("UserID");
|
||||
|
||||
b.Property<string>("UserString")
|
||||
.HasColumnName("User");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BeatmapInfoID");
|
||||
|
||||
b.HasIndex("OnlineScoreID")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RulesetID");
|
||||
|
||||
b.ToTable("ScoreInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int>("SkinInfoID");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.HasIndex("SkinInfoID");
|
||||
|
||||
b.ToTable("SkinFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Creator");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("DeletePending");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SkinInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
|
||||
.WithMany()
|
||||
.HasForeignKey("BaseDifficultyID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
|
||||
.WithMany("Beatmaps")
|
||||
.HasForeignKey("BeatmapSetInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||
.WithMany("Beatmaps")
|
||||
.HasForeignKey("MetadataID");
|
||||
|
||||
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||
.WithMany()
|
||||
.HasForeignKey("RulesetID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("BeatmapSetInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||
.WithMany("BeatmapSets")
|
||||
.HasForeignKey("MetadataID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||
.WithMany("Settings")
|
||||
.HasForeignKey("SkinInfoID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Scoring.ScoreInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ScoreInfoID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
|
||||
.WithMany("Scores")
|
||||
.HasForeignKey("BeatmapInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||
.WithMany()
|
||||
.HasForeignKey("RulesetID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("SkinInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
public partial class AddSamplesMatchPlaybackRate : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SamplesMatchPlaybackRate",
|
||||
table: "BeatmapInfo",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SamplesMatchPlaybackRate",
|
||||
table: "BeatmapInfo");
|
||||
}
|
||||
}
|
||||
}
|
@ -81,6 +81,8 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<bool>("SamplesMatchPlaybackRate");
|
||||
|
||||
b.Property<bool>("SpecialStyle");
|
||||
|
||||
b.Property<float>("StackLeniency");
|
||||
|
@ -16,7 +16,7 @@ using osu.Game.Utils;
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
[MessagePackObject]
|
||||
public class APIMod : IMod, IEquatable<APIMod>
|
||||
public class APIMod : IEquatable<APIMod>
|
||||
{
|
||||
[JsonProperty("acronym")]
|
||||
[Key(0)]
|
||||
@ -48,31 +48,31 @@ namespace osu.Game.Online.API
|
||||
|
||||
public Mod ToMod(Ruleset ruleset)
|
||||
{
|
||||
Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == Acronym);
|
||||
Mod resultMod = ruleset.CreateModFromAcronym(Acronym);
|
||||
|
||||
if (resultMod == null)
|
||||
throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}.");
|
||||
|
||||
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
|
||||
if (Settings.Count > 0)
|
||||
{
|
||||
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
|
||||
continue;
|
||||
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
|
||||
{
|
||||
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
|
||||
continue;
|
||||
|
||||
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
|
||||
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
|
||||
}
|
||||
}
|
||||
|
||||
return resultMod;
|
||||
}
|
||||
|
||||
public bool Equals(IMod other) => other is APIMod them && Equals(them);
|
||||
|
||||
public bool Equals(APIMod other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
return Acronym == other.Acronym &&
|
||||
Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
|
||||
return Acronym == other.Acronym && Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests
|
||||
private readonly BeatmapInfo beatmap;
|
||||
private readonly BeatmapLeaderboardScope scope;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly IEnumerable<Mod> mods;
|
||||
private readonly IEnumerable<IMod> mods;
|
||||
|
||||
public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<Mod> mods = null)
|
||||
public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod> mods = null)
|
||||
{
|
||||
if (!beatmap.OnlineBeatmapID.HasValue)
|
||||
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
|
||||
@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests
|
||||
this.beatmap = beatmap;
|
||||
this.scope = scope;
|
||||
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
|
||||
this.mods = mods ?? Array.Empty<Mod>();
|
||||
this.mods = mods ?? Array.Empty<IMod>();
|
||||
|
||||
Success += onSuccess;
|
||||
}
|
||||
|
@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"ratings")]
|
||||
private int[] ratings { get; set; }
|
||||
|
||||
[JsonProperty(@"track_id")]
|
||||
private int? trackId { get; set; }
|
||||
|
||||
[JsonProperty(@"user_id")]
|
||||
private int creatorId
|
||||
{
|
||||
@ -106,7 +109,8 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
Availability = availability,
|
||||
HasFavourited = hasFavourited,
|
||||
Genre = genre,
|
||||
Language = language
|
||||
Language = language,
|
||||
TrackId = trackId
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -23,10 +23,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty<Mod>();
|
||||
var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty<Mod>();
|
||||
|
||||
// all API scores provided by this class are considered to be legacy.
|
||||
mods = mods.Append(rulesetInstance.GetAllMods().OfType<ModClassic>().Single()).ToArray();
|
||||
mods = mods.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
|
||||
|
||||
var scoreInfo = new ScoreInfo
|
||||
{
|
||||
|
@ -3,21 +3,22 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
@ -117,7 +118,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
textBox = new BeatmapSearchTextBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
TypingStarted = () => TypingStarted?.Invoke(),
|
||||
TextChanged = () => TypingStarted?.Invoke(),
|
||||
},
|
||||
new ReverseChildIDFillFlowContainer<Drawable>
|
||||
{
|
||||
@ -167,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
/// <summary>
|
||||
/// Any time the text box receives key events (even while masked).
|
||||
/// </summary>
|
||||
public Action TypingStarted;
|
||||
public Action TextChanged;
|
||||
|
||||
protected override Color4 SelectionColour => Color4.Gray;
|
||||
|
||||
@ -181,7 +182,16 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
if (!base.OnKeyDown(e))
|
||||
return false;
|
||||
|
||||
TypingStarted?.Invoke();
|
||||
TextChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (!base.OnPressed(action))
|
||||
return false;
|
||||
|
||||
TextChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
private const float horizontal_padding = 10;
|
||||
private const float vertical_padding = 5;
|
||||
|
||||
private FillFlowContainer bottomPanel, statusContainer, titleContainer;
|
||||
private FillFlowContainer bottomPanel, statusContainer, titleContainer, artistContainer;
|
||||
private PlayButton playButton;
|
||||
private Box progressBar;
|
||||
|
||||
@ -89,11 +89,19 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
artistContainer = new FillFlowContainer
|
||||
{
|
||||
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
|
||||
},
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new Container
|
||||
@ -213,6 +221,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
});
|
||||
}
|
||||
|
||||
if (SetInfo.OnlineInfo?.TrackId != null)
|
||||
{
|
||||
artistContainer.Add(new FeaturedArtistBeatmapPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Left = 10f, Top = 2f },
|
||||
});
|
||||
}
|
||||
|
||||
if (SetInfo.OnlineInfo?.HasVideo ?? false)
|
||||
{
|
||||
statusContainer.Add(new IconPill(FontAwesome.Solid.Film));
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
private const float vertical_padding = 5;
|
||||
private const float height = 70;
|
||||
|
||||
private FillFlowContainer statusContainer, titleContainer;
|
||||
private FillFlowContainer statusContainer, titleContainer, artistContainer;
|
||||
protected BeatmapPanelDownloadButton DownloadButton;
|
||||
private PlayButton playButton;
|
||||
private Box progressBar;
|
||||
@ -112,10 +112,18 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
artistContainer = new FillFlowContainer
|
||||
{
|
||||
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -227,6 +235,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
});
|
||||
}
|
||||
|
||||
if (SetInfo.OnlineInfo?.TrackId != null)
|
||||
{
|
||||
artistContainer.Add(new FeaturedArtistBeatmapPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Left = 10f, Top = 2f },
|
||||
});
|
||||
}
|
||||
|
||||
if (SetInfo.OnlineInfo?.HasVideo ?? false)
|
||||
{
|
||||
statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
||||
|
@ -75,6 +75,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Masking = true,
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -186,21 +187,16 @@ namespace osu.Game.Overlays
|
||||
|
||||
if (lastContent != null)
|
||||
{
|
||||
var transform = lastContent.FadeOut(100, Easing.OutQuint);
|
||||
lastContent.FadeOut(100, Easing.OutQuint);
|
||||
|
||||
if (lastContent == notFoundContent || lastContent == supporterRequiredContent)
|
||||
{
|
||||
// the placeholders may be used multiple times, so don't expire/dispose them.
|
||||
transform.Schedule(() => panelTarget.Remove(lastContent));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Consider the case when the new content is smaller than the last content.
|
||||
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
|
||||
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
|
||||
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
|
||||
lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => lastContent.Expire());
|
||||
}
|
||||
// Consider the case when the new content is smaller than the last content.
|
||||
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
|
||||
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
|
||||
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
|
||||
var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y);
|
||||
|
||||
if (lastContent != notFoundContent && lastContent != supporterRequiredContent)
|
||||
sequence.Then().Schedule(() => lastContent.Expire());
|
||||
}
|
||||
|
||||
if (!content.IsAlive)
|
||||
@ -208,6 +204,9 @@ namespace osu.Game.Overlays
|
||||
|
||||
content.FadeInFromZero(200, Easing.OutQuint);
|
||||
currentContent = content;
|
||||
// currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out.
|
||||
// restore to the initial state.
|
||||
currentContent.BypassAutoSizeAxes = Axes.None;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -37,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
private readonly OsuSpriteText title, artist;
|
||||
private readonly AuthorInfo author;
|
||||
private readonly ExplicitContentBeatmapPill explicitContentPill;
|
||||
private readonly FeaturedArtistBeatmapPill featuredArtistPill;
|
||||
private readonly FillFlowContainer downloadButtonsContainer;
|
||||
private readonly BeatmapAvailability beatmapAvailability;
|
||||
private readonly BeatmapSetOnlineStatusPill onlineStatusPill;
|
||||
@ -129,10 +130,25 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
}
|
||||
}
|
||||
},
|
||||
artist = new OsuSpriteText
|
||||
new FillFlowContainer
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true),
|
||||
Margin = new MarginPadding { Bottom = 20 }
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Bottom = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
artist = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true),
|
||||
},
|
||||
featuredArtistPill = new FeaturedArtistBeatmapPill
|
||||
{
|
||||
Alpha = 0f,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Left = 10 }
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -233,6 +249,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist);
|
||||
|
||||
explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0;
|
||||
featuredArtistPill.Alpha = setInfo.NewValue.OnlineInfo.TrackId != null ? 1 : 0;
|
||||
|
||||
onlineStatusPill.FadeIn(500, Easing.OutQuint);
|
||||
onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status;
|
||||
|
47
osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs
Normal file
47
osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public class FeaturedArtistBeatmapPill : CompositeDrawable
|
||||
{
|
||||
public FeaturedArtistBeatmapPill()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuColour colours, OverlayColourProvider colourProvider)
|
||||
{
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider?.Background5 ?? colours.Gray2,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f },
|
||||
Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(),
|
||||
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
|
||||
Colour = OverlayColourProvider.Blue.Colour1,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public class LeaderboardModSelector : CompositeDrawable
|
||||
{
|
||||
public readonly BindableList<Mod> SelectedMods = new BindableList<Mod>();
|
||||
public readonly BindableList<IMod> SelectedMods = new BindableList<IMod>();
|
||||
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
private readonly FillFlowContainer<ModButton> modsContainer;
|
||||
@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
return;
|
||||
|
||||
modsContainer.Add(new ModButton(new ModNoMod()));
|
||||
modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.UserPlayable).Select(m => new ModButton(m)));
|
||||
modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m)));
|
||||
|
||||
modsContainer.ForEach(button =>
|
||||
{
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
updateHighlighted();
|
||||
}
|
||||
|
||||
private void selectionChanged(Mod mod, bool selected)
|
||||
private void selectionChanged(IMod mod, bool selected)
|
||||
{
|
||||
if (selected)
|
||||
SelectedMods.Add(mod);
|
||||
@ -101,9 +101,9 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
private const int duration = 200;
|
||||
|
||||
public readonly BindableBool Highlighted = new BindableBool();
|
||||
public Action<Mod, bool> OnSelectionChanged;
|
||||
public Action<IMod, bool> OnSelectionChanged;
|
||||
|
||||
public ModButton(Mod mod)
|
||||
public ModButton(IMod mod)
|
||||
: base(mod)
|
||||
{
|
||||
Scale = new Vector2(0.4f);
|
||||
|
@ -26,9 +26,6 @@ namespace osu.Game.Overlays.Changelog
|
||||
private const float image_container_width = 164;
|
||||
private const float heart_size = 75;
|
||||
|
||||
private readonly FillFlowContainer textContainer;
|
||||
private readonly Container imageContainer;
|
||||
|
||||
public ChangelogSupporterPromo()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -38,6 +35,12 @@ namespace osu.Game.Overlays.Changelog
|
||||
Vertical = 20,
|
||||
Horizontal = 50,
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colour, TextureStore textures, OverlayColourProvider colourProvider)
|
||||
{
|
||||
SupporterPromoLinkFlowContainer supportLinkText;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Changelog
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black.Opacity(0.3f),
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -68,7 +71,7 @@ namespace osu.Game.Overlays.Changelog
|
||||
Padding = new MarginPadding { Horizontal = 75 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
textContainer = new FillFlowContainer
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@ -76,91 +79,84 @@ namespace osu.Game.Overlays.Changelog
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding { Right = 50 + image_container_width },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = ChangelogStrings.SupportHeading,
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
|
||||
Margin = new MarginPadding { Bottom = 20 },
|
||||
},
|
||||
supportLinkText = new SupporterPromoLinkFlowContainer(t =>
|
||||
{
|
||||
t.Font = t.Font.With(size: 14);
|
||||
t.Colour = colour.PinkLighter;
|
||||
})
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
new OsuTextFlowContainer(t =>
|
||||
{
|
||||
t.Font = t.Font.With(size: 12);
|
||||
t.Colour = colour.PinkLighter;
|
||||
})
|
||||
{
|
||||
Text = ChangelogStrings.SupportText2.ToString(),
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
}
|
||||
},
|
||||
},
|
||||
imageContainer = new Container
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = image_container_width,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 28 },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill,
|
||||
Texture = textures.Get(@"Online/supporter-pippi"),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(heart_size),
|
||||
Margin = new MarginPadding { Top = 70 },
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = colour.Pink,
|
||||
Radius = 10,
|
||||
Roundness = heart_size / 2,
|
||||
},
|
||||
Child = new Sprite
|
||||
{
|
||||
Size = new Vector2(heart_size),
|
||||
Texture = textures.Get(@"Online/supporter-heart"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colour, TextureStore textures)
|
||||
{
|
||||
SupporterPromoLinkFlowContainer supportLinkText;
|
||||
textContainer.Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = ChangelogStrings.SupportHeading,
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
|
||||
Margin = new MarginPadding { Bottom = 20 },
|
||||
},
|
||||
supportLinkText = new SupporterPromoLinkFlowContainer(t =>
|
||||
{
|
||||
t.Font = t.Font.With(size: 14);
|
||||
t.Colour = colour.PinkLighter;
|
||||
})
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
new OsuTextFlowContainer(t =>
|
||||
{
|
||||
t.Font = t.Font.With(size: 12);
|
||||
t.Colour = colour.PinkLighter;
|
||||
})
|
||||
{
|
||||
Text = ChangelogStrings.SupportText2.ToString(),
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
}
|
||||
};
|
||||
|
||||
supportLinkText.AddText("Support further development of osu! and ");
|
||||
supportLinkText.AddLink("become and osu!supporter", "https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold));
|
||||
supportLinkText.AddLink("become an osu!supporter", @"https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold));
|
||||
supportLinkText.AddText(" today!");
|
||||
|
||||
imageContainer.Children = new Drawable[]
|
||||
{
|
||||
new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 28 },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill,
|
||||
Texture = textures.Get(@"Online/supporter-pippi"),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(heart_size),
|
||||
Margin = new MarginPadding { Top = 70 },
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = colour.Pink,
|
||||
Radius = 10,
|
||||
Roundness = heart_size / 2,
|
||||
},
|
||||
Child = new Sprite
|
||||
{
|
||||
Size = new Vector2(heart_size),
|
||||
Texture = textures.Get(@"Online/supporter-heart"),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private class SupporterPromoLinkFlowContainer : LinkFlowContainer
|
||||
@ -170,27 +166,18 @@ namespace osu.Game.Overlays.Changelog
|
||||
{
|
||||
}
|
||||
|
||||
public new void AddLink(string text, string url, Action<SpriteText> creationParameters) =>
|
||||
AddInternal(new SupporterPromoLinkCompiler(AddText(text, creationParameters)) { Url = url });
|
||||
protected override DrawableLinkCompiler CreateLinkCompiler(IEnumerable<SpriteText> parts) => new SupporterPromoLinkCompiler(parts);
|
||||
|
||||
private class SupporterPromoLinkCompiler : DrawableLinkCompiler
|
||||
{
|
||||
[Resolved(CanBeNull = true)]
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
public string Url;
|
||||
|
||||
public SupporterPromoLinkCompiler(IEnumerable<Drawable> parts)
|
||||
: base(parts)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colour)
|
||||
{
|
||||
TooltipText = Url;
|
||||
Action = () => game?.HandleLink(Url);
|
||||
IdleColour = colour.PinkDark;
|
||||
HoverColour = Color4.White;
|
||||
}
|
||||
|
@ -107,9 +107,9 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
var incompatibleTypes = mod.IncompatibleMods;
|
||||
|
||||
var allMods = ruleset.Value.CreateInstance().GetAllMods();
|
||||
var allMods = ruleset.Value.CreateInstance().AllMods;
|
||||
|
||||
incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList();
|
||||
incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList();
|
||||
incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods";
|
||||
}
|
||||
}
|
||||
|
@ -355,6 +355,12 @@ namespace osu.Game.Overlays.Volume
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
State = SelectionState.Selected;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@ -11,7 +11,37 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// <summary>
|
||||
/// The shortened name of this mod.
|
||||
/// </summary>
|
||||
[JsonProperty("acronym")]
|
||||
string Acronym { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this mod.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user readable description of this mod.
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of this mod.
|
||||
/// </summary>
|
||||
ModType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon of this mod.
|
||||
/// </summary>
|
||||
IconUsage? Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mod is playable by an end user.
|
||||
/// Should be <c>false</c> for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example).
|
||||
/// </summary>
|
||||
bool UserPlayable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a fresh <see cref="Mod"/> instance based on this mod.
|
||||
/// </summary>
|
||||
Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType());
|
||||
}
|
||||
}
|
||||
|
@ -22,32 +22,17 @@ namespace osu.Game.Rulesets.Mods
|
||||
[ExcludeFromDynamicCompile]
|
||||
public abstract class Mod : IMod, IEquatable<Mod>, IDeepCloneable<Mod>
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of this mod.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The shortened name of this mod.
|
||||
/// </summary>
|
||||
public abstract string Acronym { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon of this mod.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public virtual IconUsage? Icon => null;
|
||||
|
||||
/// <summary>
|
||||
/// The type of this mod.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public virtual ModType Type => ModType.Fun;
|
||||
|
||||
/// <summary>
|
||||
/// The user readable description of this mod.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public abstract string Description { get; }
|
||||
|
||||
@ -106,10 +91,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
[JsonIgnore]
|
||||
public virtual bool HasImplementation => this is IApplicableMod;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mod is playable by an end user.
|
||||
/// Should be <c>false</c> for cases where the user is not interacting with the game (so it can be excluded from mutliplayer selection, for example).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public virtual bool UserPlayable => true;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
@ -38,13 +39,58 @@ namespace osu.Game.Rulesets
|
||||
{
|
||||
public RulesetInfo RulesetInfo { get; internal set; }
|
||||
|
||||
public IEnumerable<Mod> GetAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>()
|
||||
// Confine all mods of each mod type into a single IEnumerable<Mod>
|
||||
.SelectMany(GetModsFor)
|
||||
// Filter out all null mods
|
||||
.Where(mod => mod != null)
|
||||
// Resolve MultiMods as their .Mods property
|
||||
.SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod });
|
||||
private static readonly ConcurrentDictionary<int, IMod[]> mod_reference_cache = new ConcurrentDictionary<int, IMod[]>();
|
||||
|
||||
/// <summary>
|
||||
/// A queryable source containing all available mods.
|
||||
/// Call <see cref="IMod.CreateInstance"/> for consumption purposes.
|
||||
/// </summary>
|
||||
public IEnumerable<IMod> AllMods
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!(RulesetInfo.ID is int id))
|
||||
return CreateAllMods();
|
||||
|
||||
if (!mod_reference_cache.TryGetValue(id, out var mods))
|
||||
mod_reference_cache[id] = mods = CreateAllMods().Cast<IMod>().ToArray();
|
||||
|
||||
return mods;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns fresh instances of all mods.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings)
|
||||
/// use <see cref="AllMods"/> instead.
|
||||
/// </remarks>
|
||||
public IEnumerable<Mod> CreateAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>()
|
||||
// Confine all mods of each mod type into a single IEnumerable<Mod>
|
||||
.SelectMany(GetModsFor)
|
||||
// Filter out all null mods
|
||||
.Where(mod => mod != null)
|
||||
// Resolve MultiMods as their .Mods property
|
||||
.SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod });
|
||||
|
||||
/// <summary>
|
||||
/// Returns a fresh instance of the mod matching the specified acronym.
|
||||
/// </summary>
|
||||
/// <param name="acronym">The acronym to query for .</param>
|
||||
public Mod CreateModFromAcronym(string acronym)
|
||||
{
|
||||
return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a fresh instance of the mod matching the specified type.
|
||||
/// </summary>
|
||||
public T CreateMod<T>()
|
||||
where T : Mod
|
||||
{
|
||||
return AllMods.FirstOrDefault(m => m is T)?.CreateInstance() as T;
|
||||
}
|
||||
|
||||
public abstract IEnumerable<Mod> GetModsFor(ModType type);
|
||||
|
||||
@ -126,7 +172,7 @@ namespace osu.Game.Rulesets
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().FirstOrDefault();
|
||||
public ModAutoplay GetAutoplayMod() => CreateMod<ModAutoplay>();
|
||||
|
||||
public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null;
|
||||
|
||||
|
@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
private const float size = 80;
|
||||
|
||||
public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null;
|
||||
public virtual LocalisableString TooltipText => showTooltip ? ((mod as Mod)?.IconTooltip ?? mod.Name) : null;
|
||||
|
||||
private Mod mod;
|
||||
private IMod mod;
|
||||
private readonly bool showTooltip;
|
||||
|
||||
public Mod Mod
|
||||
public IMod Mod
|
||||
{
|
||||
get => mod;
|
||||
set
|
||||
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
/// <param name="mod">The mod to be displayed</param>
|
||||
/// <param name="showTooltip">Whether a tooltip describing the mod should display on hover.</param>
|
||||
public ModIcon(Mod mod, bool showTooltip = true)
|
||||
public ModIcon(IMod mod, bool showTooltip = true)
|
||||
{
|
||||
this.mod = mod ?? throw new ArgumentNullException(nameof(mod));
|
||||
this.showTooltip = showTooltip;
|
||||
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI
|
||||
updateMod(mod);
|
||||
}
|
||||
|
||||
private void updateMod(Mod value)
|
||||
private void updateMod(IMod value)
|
||||
{
|
||||
modAcronym.Text = value.Acronym;
|
||||
modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question;
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Scoring.Legacy
|
||||
|
||||
// lazer replays get a really high version number.
|
||||
if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION)
|
||||
scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetAllMods().OfType<ModClassic>().Single()).ToArray();
|
||||
scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.CreateMod<ModClassic>()).ToArray();
|
||||
|
||||
currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
|
||||
scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
|
||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
private LabelledSwitchButton widescreenSupport;
|
||||
private LabelledSwitchButton epilepsyWarning;
|
||||
private LabelledSwitchButton letterboxDuringBreaks;
|
||||
private LabelledSwitchButton samplesMatchPlaybackRate;
|
||||
|
||||
public override LocalisableString Title => "Design";
|
||||
|
||||
@ -79,6 +80,12 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
Label = "Letterbox during breaks",
|
||||
Description = "Adds horizontal letterboxing to give a cinematic look during breaks.",
|
||||
Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks }
|
||||
},
|
||||
samplesMatchPlaybackRate = new LabelledSwitchButton
|
||||
{
|
||||
Label = "Samples match playback rate",
|
||||
Description = "When enabled, all samples will speed up or slow down when rate-changing mods are enabled.",
|
||||
Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate }
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -96,6 +103,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
widescreenSupport.Current.BindValueChanged(_ => updateBeatmap());
|
||||
epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap());
|
||||
letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap());
|
||||
samplesMatchPlaybackRate.Current.BindValueChanged(_ => updateBeatmap());
|
||||
}
|
||||
|
||||
private void updateCountdownSettingsVisibility() => CountdownSettings.FadeTo(EnableCountdown.Current.Value ? 1 : 0);
|
||||
@ -115,6 +123,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value;
|
||||
Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value;
|
||||
Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value;
|
||||
Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,9 @@ namespace osu.Game.Screens.Menu
|
||||
if (setInfo == null)
|
||||
return false;
|
||||
|
||||
return (initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0])) != null;
|
||||
initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
|
||||
|
||||
return UsingThemedIntro = initialBeatmap != null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,7 +167,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack();
|
||||
|
||||
protected void StartTrack()
|
||||
protected virtual void StartTrack()
|
||||
{
|
||||
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
|
||||
if (UsingThemedIntro)
|
||||
@ -184,7 +186,6 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
beatmap.Value = initialBeatmap;
|
||||
Track = initialBeatmap.Track;
|
||||
UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice;
|
||||
|
||||
// ensure the track starts at maximum volume
|
||||
musicController.CurrentTrack.FinishTransforms();
|
||||
|
@ -41,6 +41,9 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private Sample welcome;
|
||||
|
||||
private DecoupleableInterpolatingFramedClock decoupledClock;
|
||||
private TrianglesIntroSequence intro;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -56,10 +59,18 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
PrepareMenuLoad();
|
||||
|
||||
LoadComponentAsync(new TrianglesIntroSequence(logo, background)
|
||||
decoupledClock = new DecoupleableInterpolatingFramedClock
|
||||
{
|
||||
IsCoupled = false
|
||||
};
|
||||
|
||||
if (UsingThemedIntro)
|
||||
decoupledClock.ChangeSource(Track);
|
||||
|
||||
LoadComponentAsync(intro = new TrianglesIntroSequence(logo, background)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Clock = new FramedClock(UsingThemedIntro ? Track : null),
|
||||
Clock = decoupledClock,
|
||||
LoadMenu = LoadMenu
|
||||
}, t =>
|
||||
{
|
||||
@ -72,12 +83,25 @@ namespace osu.Game.Screens.Menu
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSuspending(IScreen next)
|
||||
{
|
||||
base.OnSuspending(next);
|
||||
|
||||
// important as there is a clock attached to a track which will likely be disposed before returning to this screen.
|
||||
intro.Expire();
|
||||
}
|
||||
|
||||
public override void OnResuming(IScreen last)
|
||||
{
|
||||
base.OnResuming(last);
|
||||
background.FadeOut(100);
|
||||
}
|
||||
|
||||
protected override void StartTrack()
|
||||
{
|
||||
decoupledClock.Start();
|
||||
}
|
||||
|
||||
private class TrianglesIntroSequence : CompositeDrawable
|
||||
{
|
||||
private readonly OsuLogo logo;
|
||||
|
@ -43,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
private PasswordProtectedIcon passwordIcon;
|
||||
private EndDateInfo endDateInfo;
|
||||
|
||||
private DelayedLoadWrapper wrapper;
|
||||
|
||||
public DrawableRoom(Room room)
|
||||
{
|
||||
Room = room;
|
||||
@ -63,6 +65,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
ButtonsContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X
|
||||
};
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
|
||||
@ -75,155 +85,153 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
d.RelativeSizeAxes = Axes.Both;
|
||||
}),
|
||||
new Container
|
||||
{
|
||||
Name = @"Room content",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// This negative padding resolves 1px gaps between this background and the background above.
|
||||
Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
|
||||
Child = new Container
|
||||
wrapper = new DelayedLoadWrapper(() =>
|
||||
new Container
|
||||
{
|
||||
Name = @"Room content",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
// This negative padding resolves 1px gaps between this background and the background above.
|
||||
Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
|
||||
Child = new Container
|
||||
{
|
||||
new GridContainer
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
new GridContainer
|
||||
{
|
||||
new Dimension(GridSizeMode.Relative, 0.2f)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Background5,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = @"Left details",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Left = 20,
|
||||
Vertical = 5
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomStatusPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
specialCategoryPill = new RoomSpecialCategoryPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
endDateInfo = new EndDateInfo
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 3 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomNameText(),
|
||||
new RoomHostText(),
|
||||
}
|
||||
}
|
||||
},
|
||||
new Dimension(GridSizeMode.Relative, 0.2f)
|
||||
},
|
||||
new FillFlowContainer
|
||||
Content = new[]
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
new Drawable[]
|
||||
{
|
||||
new PlaylistCountPill
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Background5,
|
||||
},
|
||||
new StarRatingRangeDisplay
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.8f)
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = @"Left details",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Left = 20,
|
||||
Vertical = 5
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomStatusPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
specialCategoryPill = new RoomSpecialCategoryPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
endDateInfo = new EndDateInfo
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 3 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomNameText(),
|
||||
new RoomHostText(),
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PlaylistCountPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new StarRatingRangeDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.8f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Name = "Right content",
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Right = 10,
|
||||
Vertical = 20,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
new FillFlowContainer
|
||||
{
|
||||
ButtonsContainer = new Container
|
||||
Name = "Right content",
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X
|
||||
Right = 10,
|
||||
Vertical = 20,
|
||||
},
|
||||
recentParticipantsList = new RecentParticipantsList
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
NumberOfCircles = NumberOfAvatars
|
||||
ButtonsContainer,
|
||||
recentParticipantsList = new RecentParticipantsList
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
NumberOfCircles = NumberOfAvatars
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
|
||||
},
|
||||
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
|
||||
},
|
||||
},
|
||||
},
|
||||
}, 0)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -231,23 +239,28 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
roomCategory.BindTo(Room.Category);
|
||||
roomCategory.BindValueChanged(c =>
|
||||
wrapper.DelayedLoadComplete += _ =>
|
||||
{
|
||||
if (c.NewValue == RoomCategory.Spotlight)
|
||||
specialCategoryPill.Show();
|
||||
else
|
||||
specialCategoryPill.Hide();
|
||||
}, true);
|
||||
wrapper.FadeInFromZero(200);
|
||||
|
||||
roomType.BindTo(Room.Type);
|
||||
roomType.BindValueChanged(t =>
|
||||
{
|
||||
endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0;
|
||||
}, true);
|
||||
roomCategory.BindTo(Room.Category);
|
||||
roomCategory.BindValueChanged(c =>
|
||||
{
|
||||
if (c.NewValue == RoomCategory.Spotlight)
|
||||
specialCategoryPill.Show();
|
||||
else
|
||||
specialCategoryPill.Hide();
|
||||
}, true);
|
||||
|
||||
hasPassword.BindTo(Room.HasPassword);
|
||||
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
|
||||
roomType.BindTo(Room.Type);
|
||||
roomType.BindValueChanged(t =>
|
||||
{
|
||||
endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0;
|
||||
}, true);
|
||||
|
||||
hasPassword.BindTo(Room.HasPassword);
|
||||
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
|
||||
};
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
|
@ -83,12 +83,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (matchingFilter)
|
||||
this.FadeInFromZero(transition_duration);
|
||||
else
|
||||
Alpha = 0;
|
||||
Alpha = matchingFilter ? 1 : 0;
|
||||
selectionBox.Alpha = SelectedRoom.Value == Room ? 1 : 0;
|
||||
|
||||
SelectedRoom.BindValueChanged(updateSelectedRoom, true);
|
||||
SelectedRoom.BindValueChanged(updateSelectedRoom);
|
||||
}
|
||||
|
||||
private void updateSelectedRoom(ValueChangedEvent<Room> selected)
|
||||
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -306,18 +307,18 @@ namespace osu.Game.Screens.Ranking
|
||||
if (expandedPanel == null)
|
||||
return base.OnKeyDown(e);
|
||||
|
||||
var expandedPanelIndex = flow.GetPanelIndex(expandedPanel.Score);
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Left:
|
||||
if (expandedPanelIndex > 0)
|
||||
SelectedScore.Value = flow.Children[expandedPanelIndex - 1].Panel.Score;
|
||||
var previousScore = flow.GetPreviousScore(expandedPanel.Score);
|
||||
if (previousScore != null)
|
||||
SelectedScore.Value = previousScore;
|
||||
return true;
|
||||
|
||||
case Key.Right:
|
||||
if (expandedPanelIndex < flow.Count - 1)
|
||||
SelectedScore.Value = flow.Children[expandedPanelIndex + 1].Panel.Score;
|
||||
var nextScore = flow.GetNextScore(expandedPanel.Score);
|
||||
if (nextScore != null)
|
||||
SelectedScore.Value = nextScore;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -336,6 +337,12 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count();
|
||||
|
||||
[CanBeNull]
|
||||
public ScoreInfo GetPreviousScore(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).LastOrDefault()?.Panel.Score;
|
||||
|
||||
[CanBeNull]
|
||||
public ScoreInfo GetNextScore(ScoreInfo score) => applySorting(Children).SkipWhile(s => s.Panel.Score != score).ElementAtOrDefault(1)?.Panel.Score;
|
||||
|
||||
private IEnumerable<ScorePanelTrackingContainer> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanelTrackingContainer>()
|
||||
.OrderByDescending(GetLayoutPosition)
|
||||
.ThenBy(s => s.Panel.Score.OnlineScoreID);
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
protected void TestToLegacy(LegacyMods expectedLegacyMods, Type[] providedModTypes)
|
||||
{
|
||||
var ruleset = CreateRuleset();
|
||||
var modInstances = ruleset.GetAllMods()
|
||||
var modInstances = ruleset.CreateAllMods()
|
||||
.Where(mod => providedModTypes.Contains(mod.GetType()))
|
||||
.ToArray();
|
||||
var actualLegacyMods = ruleset.ConvertToLegacyMods(modInstances);
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Tests
|
||||
RulesetID = ruleset.ID ?? 0;
|
||||
|
||||
Mods = excessMods
|
||||
? ruleset.CreateInstance().GetAllMods().ToArray()
|
||||
? ruleset.CreateInstance().CreateAllMods().ToArray()
|
||||
: new Mod[] { new TestModHardRock(), new TestModDoubleTime() };
|
||||
|
||||
TotalScore = 2845370;
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
if (!AllowFail)
|
||||
{
|
||||
var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail);
|
||||
var noFailMod = ruleset.CreateMod<ModNoFail>();
|
||||
if (noFailMod != null)
|
||||
SelectedMods.Value = new[] { noFailMod };
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user