mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
大更新,可自定义note及配套特效,同步官方更新等等
This commit is contained in:
@@ -17,6 +17,7 @@ using osu.Framework.Logging;
|
||||
using osu.Game.Updater;
|
||||
using osu.Desktop.Windows;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@@ -33,6 +34,8 @@ namespace osu.Desktop
|
||||
[Cached(typeof(IHighPerformanceSessionManager))]
|
||||
private readonly HighPerformanceSessionManager highPerformanceSessionManager = new HighPerformanceSessionManager();
|
||||
|
||||
public bool IsFirstRun { get; init; }
|
||||
|
||||
public OsuGameDesktop(string[]? args = null)
|
||||
: base(args)
|
||||
{
|
||||
@@ -104,6 +107,14 @@ namespace osu.Desktop
|
||||
|
||||
protected override UpdateManager CreateUpdateManager()
|
||||
{
|
||||
// If this is the first time we've run the game, ie it is being installed,
|
||||
// reset the user's release stream to "lazer".
|
||||
//
|
||||
// This ensures that if a user is trying to recover from a failed startup on an unstable release stream,
|
||||
// the game doesn't immediately try and update them back to the release stream after starting up.
|
||||
if (IsFirstRun)
|
||||
LocalConfig.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
|
||||
|
||||
if (IsPackageManaged)
|
||||
return new NoActionUpdateManager();
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@ namespace osu.Desktop
|
||||
|
||||
private static LegacyTcpIpcProvider? legacyIpc;
|
||||
|
||||
// [DllImport("kernel32.dll", SetLastError = true)]
|
||||
// private static extern bool SetDllDirectory(string lpPathName);
|
||||
private static bool isFirstRun;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
@@ -61,8 +60,6 @@ namespace osu.Desktop
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// SetDllDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "C:\\Users\\12456\\.nuget\\packages\\bass.asio\\1.3.1.2\\build\\native\\x64"));
|
||||
}
|
||||
|
||||
// NVIDIA profiles are based on the executable name of a process.
|
||||
@@ -140,7 +137,12 @@ namespace osu.Desktop
|
||||
if (tournamentClient)
|
||||
host.Run(new TournamentGame());
|
||||
else
|
||||
host.Run(new OsuGameDesktop(args));
|
||||
{
|
||||
host.Run(new OsuGameDesktop(args)
|
||||
{
|
||||
IsFirstRun = isFirstRun
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +184,8 @@ namespace osu.Desktop
|
||||
|
||||
var app = VelopackApp.Build();
|
||||
|
||||
app.WithFirstRun(_ => isFirstRun = true);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
configureWindows(app);
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Play;
|
||||
@@ -16,8 +18,8 @@ namespace osu.Desktop.Updater
|
||||
{
|
||||
public partial class VelopackUpdateManager : Game.Updater.UpdateManager
|
||||
{
|
||||
private readonly UpdateManager updateManager;
|
||||
private INotificationOverlay notificationOverlay = null!;
|
||||
[Resolved]
|
||||
private INotificationOverlay notificationOverlay { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; } = null!;
|
||||
@@ -25,22 +27,32 @@ namespace osu.Desktop.Updater
|
||||
[Resolved]
|
||||
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager osuConfigManager { get; set; } = null!;
|
||||
|
||||
private bool isInGameplay => localUserInfo?.PlayingState.Value != LocalUserPlayingState.NotPlaying;
|
||||
|
||||
private readonly Bindable<ReleaseStream> releaseStream = new Bindable<ReleaseStream>();
|
||||
private UpdateManager? updateManager;
|
||||
private UpdateInfo? pendingUpdate;
|
||||
|
||||
public VelopackUpdateManager()
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", null, false), new UpdateOptions
|
||||
// Used by the base implementation.
|
||||
osuConfigManager.BindWith(OsuSetting.ReleaseStream, releaseStream);
|
||||
releaseStream.BindValueChanged(_ => onReleaseStreamChanged(), true);
|
||||
|
||||
base.LoadComplete();
|
||||
}
|
||||
|
||||
private void onReleaseStreamChanged()
|
||||
{
|
||||
updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", null, releaseStream.Value == ReleaseStream.Tachyon), new UpdateOptions
|
||||
{
|
||||
AllowVersionDowngrade = true,
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(INotificationOverlay notifications)
|
||||
{
|
||||
notificationOverlay = notifications;
|
||||
Schedule(() => Task.Run(CheckForUpdateAsync));
|
||||
}
|
||||
|
||||
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
||||
@@ -76,6 +88,12 @@ namespace osu.Desktop.Updater
|
||||
return true;
|
||||
}
|
||||
|
||||
if (updateManager == null)
|
||||
{
|
||||
scheduleRecheck = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
pendingUpdate = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
|
||||
|
||||
// No update is available. We'll check again later.
|
||||
@@ -141,6 +159,9 @@ namespace osu.Desktop.Updater
|
||||
|
||||
private async Task restartToApplyUpdate()
|
||||
{
|
||||
if (updateManager == null)
|
||||
return;
|
||||
|
||||
await updateManager.WaitExitThenApplyUpdatesAsync(pendingUpdate?.TargetFullRelease).ConfigureAwait(false);
|
||||
Schedule(() => game.AttemptExit());
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
foreach (var holdNote in CreatedDrawables.SelectMany(d => d.ChildrenOfType<DrawableHoldNote>()))
|
||||
{
|
||||
((Bindable<bool>)holdNote.IsHitting).Value = v;
|
||||
((Bindable<bool>)holdNote.IsHolding).Value = v;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,8 +46,10 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public void TestRecording()
|
||||
{
|
||||
seekTo(0);
|
||||
AddStep("press space", () => InputManager.Key(Key.Space));
|
||||
AddAssert("button press recorded to replay", () => Player.Score.Replay.Frames.OfType<ManiaReplayFrame>().Any(f => f.Actions.SequenceEqual([ManiaAction.Key1])));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
seekTo(15);
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
AddUntilStep("button press recorded to replay", () => Player.Score.Replay.Frames.OfType<ManiaReplayFrame>().Any(f => f.Actions.SequenceEqual([ManiaAction.Key1])));
|
||||
}
|
||||
|
||||
private void seekTo(double time)
|
||||
|
||||
125
osu.Game.Rulesets.Mania.Tests/TestSceneReplayRewinding.cs
Normal file
125
osu.Game.Rulesets.Mania.Tests/TestSceneReplayRewinding.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public partial class TestSceneReplayRewinding : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
private ReplayPlayer currentPlayer = null!;
|
||||
|
||||
[Test]
|
||||
public void TestRewindingToMiddleOfHoldNote()
|
||||
{
|
||||
Score score = null!;
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(4))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 500,
|
||||
EndTime = 1500,
|
||||
Column = 2
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddStep(@"create replay", () => score = new Score
|
||||
{
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(500, ManiaAction.Key3),
|
||||
new ManiaReplayFrame(1500),
|
||||
}
|
||||
},
|
||||
ScoreInfo = new ScoreInfo()
|
||||
});
|
||||
|
||||
AddStep(@"set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(beatmap));
|
||||
AddStep(@"set ruleset", () => Ruleset.Value = beatmap.BeatmapInfo.Ruleset);
|
||||
AddStep(@"push player", () => LoadScreen(currentPlayer = new ReplayPlayer(score)));
|
||||
|
||||
AddUntilStep(@"wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep(@"wait for hold to be judged", () => currentPlayer.ChildrenOfType<IFrameStableClock>().Single().CurrentTime, () => Is.GreaterThan(1600));
|
||||
AddStep(@"seek to middle of hold note", () => currentPlayer.Seek(1000));
|
||||
AddUntilStep(@"wait for gameplay to complete", () => currentPlayer.GameplayState.HasCompleted);
|
||||
AddAssert(@"no misses registered", () => currentPlayer.GameplayState.ScoreProcessor.Statistics.GetValueOrDefault(HitResult.Miss), () => Is.Zero);
|
||||
|
||||
AddStep(@"exit player", () => currentPlayer.Exit());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCorrectComboAccountingForConcurrentObjects()
|
||||
{
|
||||
Score score = null!;
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(4))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = 500,
|
||||
Column = 0,
|
||||
},
|
||||
new Note
|
||||
{
|
||||
StartTime = 500,
|
||||
Column = 2,
|
||||
},
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 1500,
|
||||
Column = 1,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddStep(@"create replay", () => score = new Score
|
||||
{
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(500, ManiaAction.Key1, ManiaAction.Key3),
|
||||
new ManiaReplayFrame(520),
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key2),
|
||||
new ManiaReplayFrame(1500),
|
||||
}
|
||||
},
|
||||
ScoreInfo = new ScoreInfo()
|
||||
});
|
||||
|
||||
AddStep(@"set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(beatmap));
|
||||
AddStep(@"set ruleset", () => Ruleset.Value = beatmap.BeatmapInfo.Ruleset);
|
||||
AddStep(@"push player", () => LoadScreen(currentPlayer = new ReplayPlayer(score)));
|
||||
|
||||
AddUntilStep(@"wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep(@"wait for objects to be judged", () => currentPlayer.ChildrenOfType<IFrameStableClock>().Single().CurrentTime, () => Is.GreaterThan(1600));
|
||||
AddStep(@"stop gameplay", () => currentPlayer.ChildrenOfType<GameplayClockContainer>().Single().Stop());
|
||||
AddStep(@"seek to start", () => currentPlayer.Seek(0));
|
||||
AddAssert(@"combo is 0", () => currentPlayer.GameplayState.ScoreProcessor.Combo.Value, () => Is.Zero);
|
||||
|
||||
AddStep(@"exit player", () => currentPlayer.Exit());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Configuration.Tracking;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
@@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
SetDefault(ManiaRulesetSetting.ScrollSpeed, 200, 1.0, 401.0, current_scroll_speed_precision);
|
||||
SetDefault(ManiaRulesetSetting.ScrollBaseSpeed, 500, 100, 1000, 1.0);
|
||||
SetDefault(ManiaRulesetSetting.ScrollTimePerSpeed, 5, 1.0, 40, 1.0);
|
||||
SetDefault(ManiaRulesetSetting.ScrollStyle, ManiaScrollingStyle.ScrollTimeStyleFixed);
|
||||
SetDefault(ManiaRulesetSetting.ScrollStyle, EzManiaScrollingStyle.ScrollTimeStyleFixed);
|
||||
SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
|
||||
SetDefault(ManiaRulesetSetting.ScrollPerKeyMode, false);
|
||||
SetDefault(ManiaRulesetSetting.PerspectiveAngle, 90.0f, 30.0f, 90.0f);
|
||||
@@ -81,4 +82,18 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
TimingBasedNoteColouring,
|
||||
MobileLayout,
|
||||
}
|
||||
|
||||
public enum EzManiaScrollingStyle
|
||||
{
|
||||
// [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionUp))]
|
||||
[Description("40速 通配速度风格(不可用)")]
|
||||
ScrollSpeedStyle,
|
||||
|
||||
// [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionDown))]
|
||||
[Description("ms值 恒定速度")]
|
||||
ScrollTimeStyle,
|
||||
|
||||
[Description("ms值 恒定时间")]
|
||||
ScrollTimeStyleFixed,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Judgements
|
||||
{
|
||||
public class HoldNoteJudgementResult : JudgementResult
|
||||
{
|
||||
private Stack<(double time, bool holding)> holdingState { get; } = new Stack<(double, bool)>();
|
||||
|
||||
public HoldNoteJudgementResult(HoldNote hitObject, Judgement judgement)
|
||||
: base(hitObject, judgement)
|
||||
{
|
||||
holdingState.Push((double.NegativeInfinity, false));
|
||||
}
|
||||
|
||||
private (double time, bool holding) getLastReport(double currentTime)
|
||||
{
|
||||
while (holdingState.Peek().time > currentTime)
|
||||
holdingState.Pop();
|
||||
|
||||
return holdingState.Peek();
|
||||
}
|
||||
|
||||
public bool IsHolding(double currentTime) => getLastReport(currentTime).holding;
|
||||
|
||||
public bool DroppedHoldAfter(double time)
|
||||
{
|
||||
foreach (var state in holdingState)
|
||||
{
|
||||
if (state.time >= time && !state.holding)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ReportHoldState(double currentTime, bool holding)
|
||||
{
|
||||
var lastReport = getLastReport(currentTime);
|
||||
if (holding != lastReport.holding)
|
||||
holdingState.Push((currentTime, holding));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@@ -45,5 +46,129 @@ namespace osu.Game.Rulesets.Mania.LAsEZMania
|
||||
private static readonly Color4 colour_scratch = new Color4(20, 0, 0, 255);
|
||||
private static readonly Color4 colour_panel = new Color4(0, 20, 0, 255);
|
||||
private static readonly Color4 colour_alpha = new Color4(0, 0, 0, 0);
|
||||
|
||||
// 颜色定义
|
||||
private static readonly Color4 colour_special = new Color4(206, 6, 3, 255);
|
||||
|
||||
private static readonly Color4 colour_green = new Color4(100, 192, 92, 255);
|
||||
private static readonly Color4 colour_red = new Color4(206, 6, 3, 255);
|
||||
|
||||
private static readonly Color4 colour_withe = new Color4(222, 222, 222, 255);
|
||||
private static readonly Color4 colour_blue = new Color4(55, 155, 255, 255);
|
||||
|
||||
private const int total_colours = 3;
|
||||
|
||||
private static readonly Color4 colour_cyan = new Color4(72, 198, 255, 255);
|
||||
private static readonly Color4 colour_pink = new Color4(213, 35, 90, 255);
|
||||
private static readonly Color4 colour_purple = new Color4(203, 60, 236, 255);
|
||||
|
||||
public static Color4 GetColourForLayout(this StageDefinition stage, int columnIndex)
|
||||
{
|
||||
columnIndex %= stage.Columns;
|
||||
|
||||
switch (stage.Columns)
|
||||
{
|
||||
case 4:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 => colour_green,
|
||||
1 => colour_red,
|
||||
2 => colour_blue,
|
||||
3 => colour_cyan,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 5:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 => colour_green,
|
||||
1 => colour_blue,
|
||||
2 => colour_red,
|
||||
3 => colour_cyan,
|
||||
4 => colour_purple,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 7:
|
||||
return columnIndex switch
|
||||
{
|
||||
1 or 5 => colour_withe,
|
||||
0 or 2 or 4 or 6 => colour_blue,
|
||||
3 => colour_green,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 8:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 4 => colour_red,
|
||||
2 or 6 => colour_withe,
|
||||
1 or 3 or 5 or 7 => colour_blue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 9:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 6 or 7 => colour_red,
|
||||
2 or 4 => colour_withe,
|
||||
1 or 3 or 5 => colour_blue,
|
||||
8 => colour_green,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 10:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 9 => colour_green,
|
||||
2 or 4 or 5 or 7 => colour_withe,
|
||||
1 or 3 or 6 or 8 => colour_blue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 12:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 11 => colour_red,
|
||||
1 or 3 or 5 or 6 or 8 or 10 => colour_withe,
|
||||
2 or 4 or 7 or 9 => colour_blue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 14:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 12 or 13 => colour_red,
|
||||
1 or 3 or 5 or 7 or 9 or 11 => colour_withe,
|
||||
2 or 4 or 8 or 10 => colour_blue,
|
||||
6 => colour_green,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 16:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 6 or 7 or 8 or 9 or 15 => colour_red,
|
||||
1 or 3 or 5 or 10 or 12 or 14 => colour_withe,
|
||||
2 or 4 or 11 or 13 => colour_blue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
// 后备逻辑保持不变
|
||||
if (stage.EzIsSpecialColumn(columnIndex))
|
||||
return colour_special;
|
||||
|
||||
switch (columnIndex % total_colours)
|
||||
{
|
||||
case 0: return colour_cyan;
|
||||
|
||||
case 1: return colour_pink;
|
||||
|
||||
case 2: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,12 @@ using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Mods.LAsMods;
|
||||
using osu.Game.Rulesets.Mania.Mods.YuLiangSSSMods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Ez2;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Argon;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Ez2;
|
||||
using osu.Game.Rulesets.Mania.Skinning.EzStylePro;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
||||
using osu.Game.Rulesets.Mania.Skinning.SbI;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
@@ -83,10 +84,16 @@ namespace osu.Game.Rulesets.Mania
|
||||
return new ManiaArgonSkinTransformer(skin, beatmap);
|
||||
|
||||
case Ez2Skin:
|
||||
if (GlobalConfigStore.Config == null || GlobalConfigStore.EZConfig == null)
|
||||
if (GlobalConfigStore.Config == null)
|
||||
throw new ArgumentNullException(nameof(GlobalConfigStore.Config));
|
||||
|
||||
return new ManiaEz2SkinTransformer(skin, beatmap, GlobalConfigStore.Config, GlobalConfigStore.EZConfig);
|
||||
return new ManiaEz2SkinTransformer(skin, beatmap, GlobalConfigStore.Config);
|
||||
|
||||
case EzStyleProSkin:
|
||||
if (GlobalConfigStore.Config == null)
|
||||
throw new ArgumentNullException(nameof(GlobalConfigStore.Config));
|
||||
|
||||
return new ManiaEzStyleProSkinTransformer(skin, beatmap, GlobalConfigStore.Config);
|
||||
|
||||
case SbISkin:
|
||||
return new ManiaSbISkinTransformer(skin, beatmap);
|
||||
@@ -304,7 +311,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
return new Mod[]
|
||||
{
|
||||
new ManiaModEz2Settings(),
|
||||
new ManiaHitModeConvertor(),
|
||||
new EzManiaHitModeConvertor(),
|
||||
// new ManiaModJudgmentStyle(),
|
||||
new ManiaModNiceBPM(),
|
||||
new ManiaModSpaceBody(),
|
||||
@@ -375,8 +382,6 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
for (int i = 1; i <= MAX_STAGE_KEYS; i++)
|
||||
yield return (int)PlayfieldType.Single + i;
|
||||
// for (int i = 2; i <= MAX_STAGE_KEYS * 2; i += 2)
|
||||
// yield return (int)PlayfieldType.Dual + i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,9 +391,6 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
case PlayfieldType.Single:
|
||||
return new SingleStageVariantGenerator(variant).GenerateMappings();
|
||||
|
||||
// case PlayfieldType.Dual:
|
||||
// return new DualStageVariantGenerator(getDualStageKeyCount(variant)).GenerateMappings();
|
||||
}
|
||||
|
||||
return Array.Empty<KeyBinding>();
|
||||
@@ -400,21 +402,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
default:
|
||||
return $"{variant}K";
|
||||
|
||||
// case PlayfieldType.Dual:
|
||||
// {
|
||||
// int keys = getDualStageKeyCount(variant);
|
||||
// return $"{keys}K + {keys}K";
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Finds the number of keys for each stage in a <see cref="PlayfieldType.Dual"/> variant.
|
||||
// /// </summary>
|
||||
// /// <param name="variant">The variant.</param>
|
||||
// private int getDualStageKeyCount(int variant) => (variant - (int)PlayfieldType.Dual) / 2;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="PlayfieldType"/> that corresponds to a variant value.
|
||||
/// </summary>
|
||||
@@ -442,25 +432,6 @@ namespace osu.Game.Rulesets.Mania
|
||||
};
|
||||
}
|
||||
|
||||
// public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
||||
// {
|
||||
// new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
|
||||
// {
|
||||
// RelativeSizeAxes = Axes.X,
|
||||
// AutoSizeAxes = Axes.Y
|
||||
// }),
|
||||
// new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents) //判定偏移
|
||||
// {
|
||||
// RelativeSizeAxes = Axes.X,
|
||||
// Height = 200
|
||||
// }, true),
|
||||
// new StatisticItem("Statistics", () => new SimpleStatisticTable(2, new SimpleStatisticItem[]
|
||||
// {
|
||||
// new AverageHitError(score.HitEvents),
|
||||
// new UnstableRate(score.HitEvents)
|
||||
// }), true),
|
||||
// };
|
||||
|
||||
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) //单轨道判定分布
|
||||
{
|
||||
var hitEventsByColumn = score.HitEvents
|
||||
|
||||
@@ -29,33 +29,15 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// new SettingsSlider<double>
|
||||
// {
|
||||
// LabelText = "Column Width(Not Active)",
|
||||
// Current = config.GetBindable<double>(ManiaRulesetSetting.ColumnWidth),
|
||||
// KeyboardStep = 1,
|
||||
// },
|
||||
// new SettingsSlider<double>
|
||||
// {
|
||||
// LabelText = "Special Column Width Factor(Not Active)",
|
||||
// Current = config.GetBindable<double>(ManiaRulesetSetting.SpecialFactor),
|
||||
// KeyboardStep = 1,
|
||||
// },
|
||||
// new SettingsEnumDropdown<MUGHitMode>
|
||||
// {
|
||||
// ClassicDefault = MUGHitMode.EZ2AC,
|
||||
// LabelText = "MUG HitMode(No Active)",
|
||||
// Current = config.GetBindable<MUGHitMode>(ManiaRulesetSetting.HitMode)
|
||||
// },
|
||||
new SettingsEnumDropdown<ManiaScrollingDirection>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollingDirection,
|
||||
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||
},
|
||||
new SettingsEnumDropdown<ManiaScrollingStyle>
|
||||
new SettingsEnumDropdown<EzManiaScrollingStyle>
|
||||
{
|
||||
LabelText = "Scrolling style",
|
||||
Current = config.GetBindable<ManiaScrollingStyle>(ManiaRulesetSetting.ScrollStyle)
|
||||
Current = config.GetBindable<EzManiaScrollingStyle>(ManiaRulesetSetting.ScrollStyle)
|
||||
},
|
||||
new SettingsSlider<double, ManiaScrollSlider>
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
// apply perfect once the tail is reached
|
||||
if (HoldNote.HoldStartTime != null && timeOffset >= 0)
|
||||
if (HoldNote.IsHolding.Value && timeOffset >= 0)
|
||||
ApplyResult(GetCappedResult(HitResult.Perfect));
|
||||
else
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Mania.Mods.YuLiangSSSMods
|
||||
{
|
||||
public new bool HasHoldBreak => false;
|
||||
|
||||
internal override void TriggerResult(bool hit)
|
||||
internal new void TriggerResult(bool hit)
|
||||
{
|
||||
if (AllJudged) return;
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Mania.Mods.YuLiangSSSMods
|
||||
return;
|
||||
}
|
||||
|
||||
if (HoldNote.IsHitting.Value)
|
||||
if (HoldNote.IsHolding.Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (HoldNote.HoldStartTime != null && Math.Abs(timeOffset) <= ReleaseOffset)
|
||||
if (Math.Abs(timeOffset) <= ReleaseOffset)
|
||||
ApplyResult(GetCappedResult(HitResult.Perfect));
|
||||
else
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
|
||||
@@ -256,27 +256,27 @@ namespace osu.Game.Rulesets.Mania.Mods.YuLiangSSSMods
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (HoldNote.HoldStartTime != null && userTriggered && RemedyGreat > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Perfect)))
|
||||
if (HoldNote.Head.IsHit && userTriggered && RemedyGreat > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Perfect)))
|
||||
{
|
||||
RemedyGreat--;
|
||||
ApplyResult(GetCappedResult(HitResult.Perfect));
|
||||
}
|
||||
else if (HoldNote.HoldStartTime != null && userTriggered && RemedyGood > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Great)))
|
||||
else if (HoldNote.Head.IsHit && userTriggered && RemedyGood > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Great)))
|
||||
{
|
||||
RemedyGood--;
|
||||
ApplyResult(GetCappedResult(HitResult.Great));
|
||||
}
|
||||
else if (HoldNote.HoldStartTime != null && userTriggered && RemedyOk > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Good)))
|
||||
else if (HoldNote.Head.IsHit && userTriggered && RemedyOk > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Good)))
|
||||
{
|
||||
RemedyOk--;
|
||||
ApplyResult(GetCappedResult(HitResult.Good));
|
||||
}
|
||||
else if (HoldNote.HoldStartTime != null && userTriggered && RemedyMeh > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Ok)))
|
||||
else if (HoldNote.Head.IsHit && userTriggered && RemedyMeh > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Ok)))
|
||||
{
|
||||
RemedyMeh--;
|
||||
ApplyResult(GetCappedResult(HitResult.Ok));
|
||||
}
|
||||
else if (HoldNote.HoldStartTime != null && userTriggered && RemedyMiss > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Meh))
|
||||
else if (HoldNote.Head.IsHit && userTriggered && RemedyMiss > 0 && Math.Abs(timeOffset) > Math.Abs(HitWindows.WindowFor(HitResult.Meh))
|
||||
&& Math.Abs(timeOffset) <= Math.Abs(HitWindows.WindowFor(HitResult.Miss)))
|
||||
{
|
||||
RemedyMiss--;
|
||||
|
||||
@@ -11,6 +11,8 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@@ -29,9 +31,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public IBindable<bool> IsHitting => isHitting;
|
||||
public IBindable<bool> IsHolding => isHolding;
|
||||
|
||||
private readonly Bindable<bool> isHitting = new Bindable<bool>();
|
||||
private readonly Bindable<bool> isHolding = new Bindable<bool>();
|
||||
|
||||
public DrawableHoldNoteHead Head => headContainer.Child;
|
||||
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
||||
@@ -55,16 +57,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
private SkinnableDrawable bodyPiece;
|
||||
|
||||
/// <summary>
|
||||
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
||||
/// </summary>
|
||||
public double? HoldStartTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to decide whether to visually clamp the hold note to the judgement line.
|
||||
/// </summary>
|
||||
private double? releaseTime;
|
||||
|
||||
public DrawableHoldNote()
|
||||
: this(null)
|
||||
{
|
||||
@@ -126,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
isHitting.BindValueChanged(updateSlidingSample, true);
|
||||
isHolding.BindValueChanged(updateSlidingSample, true);
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
@@ -134,8 +126,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
base.OnApply();
|
||||
|
||||
sizingContainer.Size = Vector2.One;
|
||||
HoldStartTime = null;
|
||||
releaseTime = null;
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
@@ -214,11 +204,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (Time.Current < releaseTime)
|
||||
releaseTime = null;
|
||||
|
||||
if (Time.Current < HoldStartTime)
|
||||
endHold();
|
||||
isHolding.Value = Result.IsHolding(Time.Current);
|
||||
|
||||
// Pad the full size container so its contents (i.e. the masking container) reach under the tail.
|
||||
// This is required for the tail to not be masked away, since it lies outside the bounds of the hold note.
|
||||
@@ -249,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
//
|
||||
// As per stable, this should not apply for early hits, waiting until the object starts to touch the
|
||||
// judgement area first.
|
||||
if (Head.IsHit && releaseTime == null && DrawHeight > 0)
|
||||
if (Head.IsHit && !Result.DroppedHoldAfter(HitObject.StartTime) && DrawHeight > 0)
|
||||
{
|
||||
// How far past the hit target this hold note is.
|
||||
float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;
|
||||
@@ -260,6 +246,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
sizingContainer.Height = 1;
|
||||
}
|
||||
|
||||
protected override JudgementResult CreateResult(Judgement judgement) => new HoldNoteJudgementResult(HitObject, judgement);
|
||||
|
||||
public new HoldNoteJudgementResult Result => (HoldNoteJudgementResult)base.Result;
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (Tail.AllJudged)
|
||||
@@ -274,7 +264,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
Body.TriggerResult(Tail.IsHit);
|
||||
|
||||
// Important that this is always called when a result is applied.
|
||||
endHold();
|
||||
Result.ReportHoldState(Time.Current, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +273,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
base.MissForcefully();
|
||||
|
||||
// Important that this is always called when a result is applied.
|
||||
endHold();
|
||||
Result.ReportHoldState(Time.Current, false);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||
@@ -317,8 +307,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss))
|
||||
return;
|
||||
|
||||
HoldStartTime = Time.Current;
|
||||
isHitting.Value = true;
|
||||
Result.ReportHoldState(Time.Current, true);
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||
@@ -337,22 +326,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
// the user has released too early (before the tail).
|
||||
//
|
||||
// In such a case, we want to record this against the DrawableHoldNoteBody.
|
||||
if (HoldStartTime != null)
|
||||
if (isHolding.Value)
|
||||
{
|
||||
Tail.UpdateResult();
|
||||
Body.TriggerResult(Tail.IsHit);
|
||||
|
||||
endHold();
|
||||
releaseTime = Time.Current;
|
||||
Result.ReportHoldState(Time.Current, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void endHold()
|
||||
{
|
||||
HoldStartTime = null;
|
||||
isHitting.Value = false;
|
||||
}
|
||||
|
||||
protected override void LoadSamples()
|
||||
{
|
||||
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void TriggerResult(bool hit)
|
||||
internal void TriggerResult(bool hit)
|
||||
{
|
||||
if (AllJudged) return;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
|
||||
public partial class Ez2AcDrawableHoldNoteBody : DrawableHoldNoteBody
|
||||
{
|
||||
internal override void TriggerResult(bool hit)
|
||||
internal new void TriggerResult(bool hit)
|
||||
{
|
||||
if (AllJudged) return;
|
||||
|
||||
@@ -91,13 +91,13 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (HoldNote.IsHitting.Value && timeOffset >= 0)
|
||||
if (HoldNote.IsHolding.Value && timeOffset >= 0)
|
||||
{
|
||||
ApplyResult(GetCappedResult(HitResult.Perfect));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HoldNote.IsHitting.Value && timeOffset < 0)
|
||||
if (!HoldNote.IsHolding.Value && timeOffset < 0)
|
||||
{
|
||||
ApplyResult(GetCappedResult(HitResult.ComboBreak));
|
||||
return;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
public new bool HasHoldBreak => false;
|
||||
|
||||
internal override void TriggerResult(bool hit)
|
||||
internal new void TriggerResult(bool hit)
|
||||
{
|
||||
if (AllJudged) return;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
return;
|
||||
}
|
||||
|
||||
if (HoldNote.IsHitting.Value)
|
||||
if (HoldNote.IsHolding.Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
// apply perfect once the tail is reached
|
||||
if (HoldNote.HoldStartTime != null && timeOffset >= 0)
|
||||
if (HoldNote.Head.IsHit && timeOffset >= 0)
|
||||
ApplyResult(GetCappedResult(HitResult.Perfect));
|
||||
else
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
|
||||
@@ -22,7 +22,7 @@ using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
public class ManiaHitModeConvertor : Mod, IApplicableAfterBeatmapConversion, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||
public class EzManiaHitModeConvertor : Mod, IApplicableAfterBeatmapConversion, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||
{
|
||||
public override string Name => "Mania Hit Mode";
|
||||
public override LocalisableString Description => "LaMod: To Use Setting";
|
||||
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
// private readonly BindableDouble configColumnWidth = new BindableDouble();
|
||||
// private readonly BindableDouble configSpecialFactor = new BindableDouble();
|
||||
|
||||
public ManiaHitModeConvertor()
|
||||
public EzManiaHitModeConvertor()
|
||||
{
|
||||
// if (GlobalConfigStore.Config != null)
|
||||
// HitMode.Value = GlobalConfigStore.Config.Get<MUGHitMode>(OsuSetting.HitMode);
|
||||
@@ -5,12 +5,12 @@ using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
public class ManiaHitTimingInfo
|
||||
public class EzManiaHitTimingInfo
|
||||
{
|
||||
public double HitTime { get; set; }
|
||||
public HitResult Result { get; set; }
|
||||
|
||||
public ManiaHitTimingInfo(double hitTime, HitResult result)
|
||||
public EzManiaHitTimingInfo(double hitTime, HitResult result)
|
||||
{
|
||||
HitTime = hitTime;
|
||||
Result = result;
|
||||
@@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
public partial class ManiaScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public List<ManiaHitTimingInfo> HitTimings { get; private set; } = new List<ManiaHitTimingInfo>();
|
||||
public List<EzManiaHitTimingInfo> HitTimings { get; private set; } = new List<EzManiaHitTimingInfo>();
|
||||
|
||||
public void AddHitTiming(double hitTime, HitResult result)
|
||||
{
|
||||
HitTimings.Add(new ManiaHitTimingInfo(hitTime, result));
|
||||
HitTimings.Add(new EzManiaHitTimingInfo(hitTime, result));
|
||||
}
|
||||
|
||||
public double CalculateScoreWithParameters(double comboProgress, double accuracyProgress, double bonusPortion, Dictionary<HitResult, int> customHitProportionScore)
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
AccentColour.BindTo(holdNote.AccentColour);
|
||||
hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHolding);
|
||||
}
|
||||
|
||||
AccentColour.BindValueChanged(colour =>
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
hittingLayer.AccentColour.BindTo(holdNoteTail.HoldNote.AccentColour);
|
||||
|
||||
hittingLayer.IsHitting.UnbindBindings();
|
||||
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNoteTail.HoldNote.IsHitting);
|
||||
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNoteTail.HoldNote.IsHolding);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning
|
||||
{
|
||||
public static class BindableExtensions
|
||||
{
|
||||
public static Bindable<float> ConvertToFloatBindable(this IBindable<double> doubleBindable)
|
||||
{
|
||||
var floatBindable = new Bindable<float>();
|
||||
// 将 double 绑定到 float 绑定上,初始绑定时转换一次,并且之后值更新时同步
|
||||
doubleBindable.BindValueChanged(e => floatBindable.Value = (float)e.NewValue, true);
|
||||
return floatBindable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
|
||||
var holdNote = (DrawableHoldNote)drawableObject;
|
||||
|
||||
AccentColour.BindTo(drawableObject.AccentColour);
|
||||
IsHitting.BindTo(holdNote.IsHitting);
|
||||
IsHitting.BindTo(holdNote.IsHolding);
|
||||
}
|
||||
|
||||
AccentColour.BindValueChanged(onAccentChanged, true);
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
{
|
||||
public partial class EzComComboCounter : ComboCounter
|
||||
{
|
||||
[SettingSource("Font", "Font", SettingControlType = typeof(EzEnumListSelector))]
|
||||
public Bindable<OffsetNumberName> NameDropdown { get; } = new Bindable<OffsetNumberName>((OffsetNumberName)4);
|
||||
[SettingSource("Font", "Font", SettingControlType = typeof(EzSelectorEnumList))]
|
||||
public Bindable<EzSelectorNameSet> NameDropdown { get; } = new Bindable<EzSelectorNameSet>((EzSelectorNameSet)4);
|
||||
|
||||
[SettingSource("Effect Type", "Effect Type")]
|
||||
public Bindable<EffectType> Effect { get; } = new Bindable<EffectType>(EffectType.Scale);
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
{
|
||||
public partial class EzComComboSprite : HitErrorMeter
|
||||
{
|
||||
[SettingSource("Combo Text Font", "Combo Text Font", SettingControlType = typeof(EzEnumListSelector))]
|
||||
public Bindable<OffsetNumberName> NameDropdown { get; } = new Bindable<OffsetNumberName>((OffsetNumberName)4);
|
||||
[SettingSource("Combo Text Font", "Combo Text Font", SettingControlType = typeof(EzSelectorEnumList))]
|
||||
public Bindable<EzSelectorNameSet> NameDropdown { get; } = new Bindable<EzSelectorNameSet>((EzSelectorNameSet)4);
|
||||
|
||||
[SettingSource("Effect Type", "Effect Type")]
|
||||
public Bindable<EffectType> Effect { get; } = new Bindable<EffectType>(EffectType.Scale);
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
[Cached]
|
||||
public partial class EzComHitTiming : HitErrorMeter
|
||||
{
|
||||
[SettingSource("Offset Number Font", "Offset Number Font", SettingControlType = typeof(EzEnumListSelector))]
|
||||
public Bindable<OffsetNumberName> NumberNameDropdown { get; } = new Bindable<OffsetNumberName>((OffsetNumberName)28);
|
||||
[SettingSource("Offset Number Font", "Offset Number Font", SettingControlType = typeof(EzSelectorEnumList))]
|
||||
public Bindable<EzSelectorNameSet> NumberNameDropdown { get; } = new Bindable<EzSelectorNameSet>((EzSelectorNameSet)28);
|
||||
|
||||
[SettingSource("Offset Text Font", "Offset Text Font", SettingControlType = typeof(OffsetTextNameSelector))]
|
||||
public Bindable<OffsetNumberName> TextNameDropdown { get; } = new Bindable<OffsetNumberName>((OffsetNumberName)28);
|
||||
public Bindable<EzSelectorNameSet> TextNameDropdown { get; } = new Bindable<EzSelectorNameSet>((EzSelectorNameSet)28);
|
||||
|
||||
[SettingSource("AloneShow", "Show only Early or: Late separately")]
|
||||
public Bindable<AloneShowMenu> AloneShow { get; } = new Bindable<AloneShowMenu>(AloneShowMenu.None);
|
||||
@@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
None,
|
||||
}
|
||||
|
||||
public partial class OffsetTextNameSelector : EzEnumListSelector
|
||||
public partial class OffsetTextNameSelector : EzSelectorEnumList
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
||||
private float getRelativeJudgementPosition(double value)
|
||||
{
|
||||
float moveRange = MoveHeight.Value;
|
||||
return (float)Math.Clamp((value / HitWindows.WindowFor(HitResult.Miss)) * moveRange, -moveRange, moveRange);
|
||||
double missWindow = HitWindows.WindowFor(HitResult.Miss);
|
||||
|
||||
if (moveRange == 0 || missWindow == 0)
|
||||
return 0;
|
||||
|
||||
float pos = (float)(value / (missWindow * moveRange));
|
||||
return Math.Clamp(pos, -moveRange, moveRange);
|
||||
}
|
||||
|
||||
private int getColumnIndex(JudgementResult judgement)
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = colour.NewValue,
|
||||
Roundness = Ez2NotePiece.NoteHeight,
|
||||
Roundness = NoteHeight,
|
||||
Radius = 50,
|
||||
};
|
||||
}, true);
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
AccentColour.BindTo(holdNote.AccentColour);
|
||||
hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHolding);
|
||||
}
|
||||
|
||||
AccentColour.BindValueChanged(colour =>
|
||||
|
||||
@@ -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;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -12,9 +11,9 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.LAsEZMania;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
private float columnWidth { get; set; }
|
||||
|
||||
public ManiaEz2SkinTransformer(ISkin skin, IBeatmap beatmap, OsuConfigManager config, EzSkinSettings ezSkinSettings)
|
||||
public ManiaEz2SkinTransformer(ISkin skin, IBeatmap beatmap, OsuConfigManager config)
|
||||
: base(skin)
|
||||
{
|
||||
this.beatmap = (ManiaBeatmap)beatmap;
|
||||
@@ -190,9 +189,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
// return new Ez2JudgementPiece(resultComponent.Component);
|
||||
return Drawable.Empty();
|
||||
// return new DefaultSkinComponentsContainer(container =>
|
||||
// {
|
||||
// });
|
||||
// return new DefaultSkinComponentsContainer(container =>
|
||||
// {
|
||||
// });
|
||||
|
||||
case ManiaSkinComponentLookup maniaComponent:
|
||||
switch (maniaComponent.Component)
|
||||
@@ -206,23 +205,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
return new Ez2ColumnBackground();
|
||||
|
||||
case ManiaSkinComponents.HoldNoteBody:
|
||||
return new Ez2HoldBodyPiece();
|
||||
|
||||
case ManiaSkinComponents.HoldNoteTail:
|
||||
return new Ez2HoldNoteTailPiece();
|
||||
|
||||
case ManiaSkinComponents.HoldNoteHead:
|
||||
return new Ez2HoldNoteHeadPiece();
|
||||
|
||||
case ManiaSkinComponents.Note:
|
||||
return new Ez2NotePiece();
|
||||
case ManiaSkinComponents.KeyArea:
|
||||
return new Ez2KeyArea();
|
||||
|
||||
case ManiaSkinComponents.HitTarget:
|
||||
return new Ez2HitTarget();
|
||||
|
||||
case ManiaSkinComponents.KeyArea:
|
||||
return new Ez2KeyArea();
|
||||
case ManiaSkinComponents.Note:
|
||||
return new Ez2NotePiece();
|
||||
|
||||
case ManiaSkinComponents.HoldNoteTail:
|
||||
return new Ez2HoldNoteTailPiece();
|
||||
|
||||
case ManiaSkinComponents.HoldNoteBody:
|
||||
return new Ez2HoldBodyPiece();
|
||||
|
||||
case ManiaSkinComponents.HoldNoteHead:
|
||||
return new Ez2HoldNoteHeadPiece();
|
||||
|
||||
case ManiaSkinComponents.HitExplosion:
|
||||
return new Ez2HitExplosion();
|
||||
@@ -251,6 +250,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
switch (maniaLookup.Lookup)
|
||||
{
|
||||
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(columnWidth));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.HitPosition:
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(hitPosition));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||
|
||||
var colour = stage.GetColourForLayout(columnIndex);
|
||||
|
||||
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.BarLineHeight:
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(1));
|
||||
|
||||
@@ -263,145 +274,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.StagePaddingTop:
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(0));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.HitPosition:
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(hitPosition));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(columnWidth));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||
|
||||
var colour = getColourForLayout(columnIndex, stage);
|
||||
|
||||
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
||||
}
|
||||
}
|
||||
|
||||
return base.GetConfig<TLookup, TValue>(lookup);
|
||||
}
|
||||
|
||||
private static readonly Color4 colour_special = new Color4(206, 6, 3, 255);
|
||||
|
||||
private static readonly Color4 colour_green = new Color4(100, 192, 92, 255);
|
||||
private static readonly Color4 colour_red = new Color4(206, 6, 3, 255);
|
||||
|
||||
private static readonly Color4 colour_withe = new Color4(222, 222, 222, 255);
|
||||
private static readonly Color4 colour_blue = new Color4(55, 155, 255, 255);
|
||||
|
||||
private const int total_colours = 3;
|
||||
|
||||
private static readonly Color4 colour_cyan = new Color4(72, 198, 255, 255);
|
||||
private static readonly Color4 colour_pink = new Color4(213, 35, 90, 255);
|
||||
private static readonly Color4 colour_purple = new Color4(203, 60, 236, 255);
|
||||
|
||||
private Color4 getColourForLayout(int columnIndex, StageDefinition stage)
|
||||
{
|
||||
columnIndex %= stage.Columns;
|
||||
|
||||
switch (stage.Columns)
|
||||
{
|
||||
case 4:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 => colour_green,
|
||||
1 => colour_red,
|
||||
2 => colour_blue,
|
||||
3 => colour_cyan,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 5:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 => colour_green,
|
||||
1 => colour_blue,
|
||||
2 => colour_red,
|
||||
3 => colour_cyan,
|
||||
4 => colour_purple,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 7:
|
||||
return columnIndex switch
|
||||
{
|
||||
1 or 5 => colour_withe,
|
||||
0 or 2 or 4 or 6 => colour_blue,
|
||||
3 => colour_green,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 8:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 4 => colour_red,
|
||||
2 or 6 => colour_withe,
|
||||
1 or 3 or 5 or 7 => colour_blue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 9:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 6 or 7 => colour_red,
|
||||
2 or 4 => colour_withe,
|
||||
1 or 3 or 5 => colour_blue,
|
||||
8 => colour_green,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 10:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 9 => colour_green,
|
||||
2 or 4 or 5 or 7 => colour_withe,
|
||||
1 or 3 or 6 or 8 => colour_blue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 12:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 11 => colour_red,
|
||||
1 or 3 or 5 or 6 or 8 or 10 => colour_withe,
|
||||
2 or 4 or 7 or 9 => colour_blue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 14:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 12 or 13 => colour_red,
|
||||
1 or 3 or 5 or 7 or 9 or 11 => colour_withe,
|
||||
2 or 4 or 8 or 10 => colour_blue,
|
||||
6 => colour_green,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
case 16:
|
||||
return columnIndex switch
|
||||
{
|
||||
0 or 6 or 7 or 8 or 9 or 15 => colour_red,
|
||||
1 or 3 or 5 or 10 or 12 or 14 => colour_withe,
|
||||
2 or 4 or 11 or 13 => colour_blue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
// 后备逻辑保持不变
|
||||
if (stage.EzIsSpecialColumn(columnIndex))
|
||||
return colour_special;
|
||||
|
||||
switch (columnIndex % total_colours)
|
||||
{
|
||||
case 0: return colour_cyan;
|
||||
|
||||
case 1: return colour_pink;
|
||||
|
||||
case 2: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
var wrapMode = bodyStyle == LegacyNoteBodyStyle.Stretch ? WrapMode.ClampToEdge : WrapMode.Repeat;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
isHitting.BindTo(holdNote.IsHitting);
|
||||
isHitting.BindTo(holdNote.IsHolding);
|
||||
|
||||
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30)?.With(d =>
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.SbI
|
||||
combo1.Origin = Anchor.Centre;
|
||||
combo1.Y = 200;
|
||||
combo1.Effect.Value = EffectType.None;
|
||||
combo1.NameDropdown.Value = (OffsetNumberName)43;
|
||||
combo1.NameDropdown.Value = (EzSelectorNameSet)43;
|
||||
}
|
||||
|
||||
var hitErrorMeter = container.OfType<BarHitErrorMeter>().FirstOrDefault();
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
|
||||
|
||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
||||
private readonly Bindable<ManiaScrollingStyle> scrollingStyle = new Bindable<ManiaScrollingStyle>();
|
||||
private readonly Bindable<EzManiaScrollingStyle> scrollingStyle = new Bindable<EzManiaScrollingStyle>();
|
||||
private readonly BindableDouble configBaseMs = new BindableDouble();
|
||||
private readonly BindableDouble configTimePerSpeed = new BindableDouble();
|
||||
private readonly BindableDouble configScrollSpeed = new BindableDouble();
|
||||
@@ -186,13 +186,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
switch (scrollingStyle.Value)
|
||||
{
|
||||
case ManiaScrollingStyle.ScrollSpeedStyle:
|
||||
case ManiaScrollingStyle.ScrollTimeStyle:
|
||||
case EzManiaScrollingStyle.ScrollSpeedStyle:
|
||||
case EzManiaScrollingStyle.ScrollTimeStyle:
|
||||
// Preserve the scroll speed as the scroll length varies from changes to the hit position.
|
||||
scale = lengthToHitPosition / length_to_default_hit_position;
|
||||
break;
|
||||
|
||||
case ManiaScrollingStyle.ScrollTimeStyleFixed:
|
||||
case EzManiaScrollingStyle.ScrollTimeStyleFixed:
|
||||
// Ensure the travel time from the top of the screen to the hit position remains constant.
|
||||
scale = length_to_default_hit_position / lengthToHitPosition;
|
||||
break;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public enum ManiaScrollingStyle
|
||||
{
|
||||
// [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionUp))]
|
||||
[Description("40速 通配速度风格(不可用)")]
|
||||
ScrollSpeedStyle,
|
||||
|
||||
// [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionDown))]
|
||||
[Description("ms值 恒定速度")]
|
||||
ScrollTimeStyle,
|
||||
|
||||
[Description("ms值 恒定时间")]
|
||||
ScrollTimeStyleFixed,
|
||||
}
|
||||
}
|
||||
@@ -49,5 +49,59 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestRewind()
|
||||
{
|
||||
bool seekedBack = false;
|
||||
bool missRecorded = false;
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModStrictTracking(),
|
||||
Autoplay = false,
|
||||
CreateBeatmap = () => new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath
|
||||
{
|
||||
ControlPoints =
|
||||
{
|
||||
new PathControlPoint(),
|
||||
new PathControlPoint(new Vector2(0, 100))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(0, new Vector2(100, 0)),
|
||||
new OsuReplayFrame(1000, new Vector2(100, 0)),
|
||||
new OsuReplayFrame(1050, new Vector2()),
|
||||
new OsuReplayFrame(1100, new Vector2(), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1750, new Vector2(0, 100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1751, new Vector2(0, 100)),
|
||||
},
|
||||
PassCondition = () => seekedBack && !missRecorded,
|
||||
});
|
||||
AddStep("subscribe to new judgements", () => Player.ScoreProcessor.NewJudgement += j =>
|
||||
{
|
||||
if (!j.IsHit)
|
||||
missRecorded = true;
|
||||
});
|
||||
AddUntilStep("wait for gameplay completion", () => Player.GameplayState.HasCompleted);
|
||||
AddAssert("no misses", () => missRecorded, () => Is.False);
|
||||
AddStep("seek back", () =>
|
||||
{
|
||||
Player.GameplayClockContainer.Stop();
|
||||
Player.Seek(1040);
|
||||
seekedBack = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
@@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Cached]
|
||||
private OsuRulesetConfigManager config = new OsuRulesetConfigManager(null, new OsuRuleset().RulesetInfo);
|
||||
|
||||
private readonly StopwatchClock clock = new StopwatchClock();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
@@ -35,7 +38,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
new OsuPlayfieldAdjustmentContainer
|
||||
{
|
||||
Child = analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay()),
|
||||
Child = analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay())
|
||||
{
|
||||
Clock = new FramedClock(clock)
|
||||
},
|
||||
},
|
||||
settings = new ReplayAnalysisSettings(config),
|
||||
};
|
||||
@@ -55,11 +61,23 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
settings.ShowAimMarkers.Value = true;
|
||||
settings.ShowCursorPath.Value = true;
|
||||
});
|
||||
AddToggleStep("toggle pause", running =>
|
||||
{
|
||||
if (running)
|
||||
clock.Stop();
|
||||
else
|
||||
clock.Start();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitMarkers()
|
||||
{
|
||||
AddStep("stop at 2000", () =>
|
||||
{
|
||||
clock.Stop();
|
||||
clock.Seek(2000);
|
||||
});
|
||||
AddStep("enable hit markers", () => settings.ShowClickMarkers.Value = true);
|
||||
AddUntilStep("hit markers visible", () => analysisContainer.HitMarkersVisible);
|
||||
AddStep("disable hit markers", () => settings.ShowClickMarkers.Value = false);
|
||||
@@ -69,6 +87,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestAimMarker()
|
||||
{
|
||||
AddStep("stop at 2000", () =>
|
||||
{
|
||||
clock.Stop();
|
||||
clock.Seek(2000);
|
||||
});
|
||||
AddStep("enable aim markers", () => settings.ShowAimMarkers.Value = true);
|
||||
AddUntilStep("aim markers visible", () => analysisContainer.AimMarkersVisible);
|
||||
AddStep("disable aim markers", () => settings.ShowAimMarkers.Value = false);
|
||||
@@ -78,6 +101,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestAimLines()
|
||||
{
|
||||
AddStep("stop at 2000", () =>
|
||||
{
|
||||
clock.Stop();
|
||||
clock.Seek(2000);
|
||||
});
|
||||
AddStep("enable aim lines", () => settings.ShowCursorPath.Value = true);
|
||||
AddUntilStep("aim lines visible", () => analysisContainer.AimLinesVisible);
|
||||
AddStep("disable aim lines", () => settings.ShowCursorPath.Value = false);
|
||||
@@ -87,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private Replay fabricateReplay()
|
||||
{
|
||||
var frames = new List<ReplayFrame>();
|
||||
var random = new Random();
|
||||
var random = new Random(20250522);
|
||||
int posX = 250;
|
||||
int posY = 250;
|
||||
|
||||
@@ -109,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
frames.Add(new OsuReplayFrame
|
||||
{
|
||||
Time = Time.Current + i * 15,
|
||||
Time = i * 15,
|
||||
Position = new Vector2(posX, posY),
|
||||
Actions = actions.ToList(),
|
||||
});
|
||||
|
||||
@@ -58,17 +58,23 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
seekTo(0);
|
||||
AddStep("move cursor to circle", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.Single()));
|
||||
AddStep("press X", () => InputManager.Key(Key.X));
|
||||
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||
seekTo(15);
|
||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||
AddAssert("right button press recorded to replay", () => Player.Score.Replay.Frames.OfType<OsuReplayFrame>().Any(f => f.Actions.SequenceEqual([OsuAction.RightButton])));
|
||||
|
||||
seekTo(5000);
|
||||
AddStep("move cursor to circle", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.Single()));
|
||||
AddStep("press Z", () => InputManager.Key(Key.Z));
|
||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||
seekTo(5015);
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
AddAssert("left button press recorded to replay", () => Player.Score.Replay.Frames.OfType<OsuReplayFrame>().Any(f => f.Actions.SequenceEqual([OsuAction.LeftButton])));
|
||||
|
||||
seekTo(10000);
|
||||
AddStep("move cursor to circle", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.Single()));
|
||||
AddStep("press C", () => InputManager.Key(Key.C));
|
||||
AddStep("press C", () => InputManager.PressKey(Key.C));
|
||||
seekTo(10015);
|
||||
AddStep("release C", () => InputManager.ReleaseKey(Key.C));
|
||||
AddAssert("smoke button press recorded to replay", () => Player.Score.Replay.Frames.OfType<OsuReplayFrame>().Any(f => f.Actions.SequenceEqual([OsuAction.Smoke])));
|
||||
}
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
[CanBeNull]
|
||||
public SnapResult TrySnapToDistanceGrid(Vector2 screenSpacePosition, double? fixedTime = null)
|
||||
{
|
||||
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True || distanceSnapGrid == null)
|
||||
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True || distanceSnapGrid?.IsLoaded != true)
|
||||
return null;
|
||||
|
||||
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
||||
|
||||
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
@@ -39,6 +40,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
if (slider.Time.Current < slider.HitObject.StartTime)
|
||||
return;
|
||||
|
||||
if ((slider.Clock as IGameplayClock)?.IsRewinding == true)
|
||||
return;
|
||||
|
||||
var tail = slider.NestedHitObjects.OfType<StrictTrackingDrawableSliderTail>().First();
|
||||
|
||||
if (!tail.Judged)
|
||||
|
||||
@@ -40,19 +40,27 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
public void TestRecording()
|
||||
{
|
||||
seekTo(0);
|
||||
AddStep("press D", () => InputManager.Key(Key.D));
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
seekTo(15);
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddAssert("left rim press recorded to replay", () => Player.Score.Replay.Frames.OfType<TaikoReplayFrame>().Any(f => f.Actions.SequenceEqual([TaikoAction.LeftRim])));
|
||||
|
||||
seekTo(5000);
|
||||
AddStep("press F", () => InputManager.Key(Key.F));
|
||||
AddStep("press F", () => InputManager.PressKey(Key.F));
|
||||
seekTo(5015);
|
||||
AddStep("release F", () => InputManager.ReleaseKey(Key.F));
|
||||
AddAssert("left centre press recorded to replay", () => Player.Score.Replay.Frames.OfType<TaikoReplayFrame>().Any(f => f.Actions.SequenceEqual([TaikoAction.LeftCentre])));
|
||||
|
||||
seekTo(10000);
|
||||
AddStep("press J", () => InputManager.Key(Key.J));
|
||||
AddStep("press J", () => InputManager.PressKey(Key.J));
|
||||
seekTo(10015);
|
||||
AddStep("release J", () => InputManager.ReleaseKey(Key.J));
|
||||
AddAssert("right centre press recorded to replay", () => Player.Score.Replay.Frames.OfType<TaikoReplayFrame>().Any(f => f.Actions.SequenceEqual([TaikoAction.RightCentre])));
|
||||
|
||||
seekTo(10000);
|
||||
AddStep("press K", () => InputManager.Key(Key.K));
|
||||
seekTo(15000);
|
||||
AddStep("press K", () => InputManager.PressKey(Key.K));
|
||||
seekTo(15015);
|
||||
AddStep("release K", () => InputManager.ReleaseKey(Key.K));
|
||||
AddAssert("right rim press recorded to replay", () => Player.Score.Replay.Frames.OfType<TaikoReplayFrame>().Any(f => f.Actions.SequenceEqual([TaikoAction.RightRim])));
|
||||
}
|
||||
|
||||
|
||||
@@ -421,6 +421,65 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.SH));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestComboAccounting([Values] bool shuffleResults)
|
||||
{
|
||||
var testBeatmap = new Beatmap
|
||||
{
|
||||
HitObjects = Enumerable.Range(1, 40).Select(i => new TestHitObject(HitResult.Perfect, HitResult.Miss)).ToList<HitObject>(),
|
||||
};
|
||||
scoreProcessor.ApplyBeatmap(testBeatmap);
|
||||
|
||||
var results = new List<JudgementResult>();
|
||||
JudgementResult judgementResult;
|
||||
|
||||
for (int i = 0; i < 25; ++i)
|
||||
{
|
||||
judgementResult = new JudgementResult(testBeatmap.HitObjects[i], new TestJudgement(HitResult.Perfect, HitResult.Miss))
|
||||
{
|
||||
Type = HitResult.Perfect
|
||||
};
|
||||
results.Add(judgementResult);
|
||||
scoreProcessor.ApplyResult(judgementResult);
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(i + 1));
|
||||
}
|
||||
|
||||
judgementResult = new JudgementResult(testBeatmap.HitObjects[25], new TestJudgement(HitResult.Perfect, HitResult.Miss))
|
||||
{
|
||||
Type = HitResult.Miss
|
||||
};
|
||||
results.Add(judgementResult);
|
||||
scoreProcessor.ApplyResult(judgementResult);
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||
|
||||
for (int i = 26; i < 40; ++i)
|
||||
{
|
||||
judgementResult = new JudgementResult(testBeatmap.HitObjects[i], new TestJudgement(HitResult.Perfect, HitResult.Miss))
|
||||
{
|
||||
Type = HitResult.Perfect
|
||||
};
|
||||
results.Add(judgementResult);
|
||||
scoreProcessor.ApplyResult(judgementResult);
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(i - 25));
|
||||
}
|
||||
|
||||
Assert.That(scoreProcessor.MaximumStatistics[HitResult.Perfect], Is.EqualTo(40));
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(14));
|
||||
Assert.That(scoreProcessor.HighestCombo.Value, Is.EqualTo(25));
|
||||
|
||||
// random shuffle is VERY extreme and overkill.
|
||||
// it might not work correctly for any other `ScoreProcessor` property, and the intermediate results likely make no sense.
|
||||
// the goal is only to demonstrate idempotency to zero when reverting all results.
|
||||
var random = new Random(20250519);
|
||||
var toRevert = shuffleResults ? results.OrderBy(_ => random.Next()).ToList() : Enumerable.Reverse(results);
|
||||
|
||||
foreach (var result in toRevert)
|
||||
scoreProcessor.RevertResult(result);
|
||||
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.Zero);
|
||||
Assert.That(scoreProcessor.HighestCombo.Value, Is.Zero);
|
||||
}
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
{
|
||||
public override HitResult MaxResult { get; }
|
||||
|
||||
@@ -16,6 +16,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
@@ -63,7 +64,11 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
withStatistics.NominationStatus = new BeatmapSetNominationStatus
|
||||
{
|
||||
Current = 1,
|
||||
Required = 2
|
||||
RequiredMeta =
|
||||
{
|
||||
MainRuleset = 2,
|
||||
NonMainRuleset = 1,
|
||||
}
|
||||
};
|
||||
|
||||
var undownloadable = getUndownloadableBeatmapSet();
|
||||
@@ -78,7 +83,11 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
someDifficulties.NominationStatus = new BeatmapSetNominationStatus
|
||||
{
|
||||
Current = 2,
|
||||
Required = 2
|
||||
RequiredMeta =
|
||||
{
|
||||
MainRuleset = 2,
|
||||
NonMainRuleset = 1,
|
||||
}
|
||||
};
|
||||
|
||||
var manyDifficulties = getManyDifficultiesBeatmapSet(100);
|
||||
@@ -220,6 +229,9 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
}
|
||||
|
||||
private Drawable createContent(OverlayColourScheme colourScheme, Func<APIBeatmapSet, Drawable> creationFunc)
|
||||
=> createContent(colourScheme, testCases.Select(creationFunc).ToArray());
|
||||
|
||||
private Drawable createContent(OverlayColourScheme colourScheme, Drawable[] cards)
|
||||
{
|
||||
var colourProvider = new OverlayColourProvider(colourScheme);
|
||||
|
||||
@@ -247,7 +259,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
Direction = FillDirection.Full,
|
||||
Padding = new MarginPadding(10),
|
||||
Spacing = new Vector2(10),
|
||||
ChildrenEnumerable = testCases.Select(creationFunc)
|
||||
ChildrenEnumerable = cards
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,5 +332,54 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
|
||||
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNominations()
|
||||
{
|
||||
AddStep("create cards", () =>
|
||||
{
|
||||
var singleRuleset = CreateAPIBeatmapSet(Ruleset.Value);
|
||||
singleRuleset.HypeStatus = new BeatmapSetHypeStatus();
|
||||
singleRuleset.NominationStatus = new BeatmapSetNominationStatus
|
||||
{
|
||||
Current = 4,
|
||||
RequiredMeta =
|
||||
{
|
||||
MainRuleset = 5,
|
||||
NonMainRuleset = 1,
|
||||
}
|
||||
};
|
||||
|
||||
var multipleRulesets = getManyDifficultiesBeatmapSet(3);
|
||||
multipleRulesets.HypeStatus = new BeatmapSetHypeStatus();
|
||||
multipleRulesets.NominationStatus = new BeatmapSetNominationStatus
|
||||
{
|
||||
Current = 4,
|
||||
RequiredMeta =
|
||||
{
|
||||
MainRuleset = 5,
|
||||
NonMainRuleset = 1,
|
||||
}
|
||||
};
|
||||
|
||||
Child = createContent(OverlayColourScheme.Blue, new Drawable[]
|
||||
{
|
||||
new BeatmapCardNormal(singleRuleset),
|
||||
new BeatmapCardNormal(multipleRulesets),
|
||||
});
|
||||
});
|
||||
|
||||
// first card: only has main ruleset, required nominations = main_ruleset = 5
|
||||
AddAssert("first card has single ruleset", () => firstCard().BeatmapSet.Beatmaps.GroupBy(b => b.Ruleset).Count(), () => Is.EqualTo(1));
|
||||
AddAssert("first card nominations = 4/5", () => firstCard().ChildrenOfType<NominationsStatistic>().Single().TooltipText.ToString(), () => Is.EqualTo("Nominations: 4/5"));
|
||||
|
||||
// second card: has non-main rulesets, required nominations = main_ruleset + non_main_ruleset * (count of non-main rulesets) = 5 + 1 * 2 = 7
|
||||
AddAssert("second card has three rulesets", () => secondCard().BeatmapSet.Beatmaps.GroupBy(b => b.Ruleset).Count(), () => Is.EqualTo(3));
|
||||
AddAssert("second card nominations = 4/7", () => secondCard().ChildrenOfType<NominationsStatistic>().Single().TooltipText.ToString(), () => Is.EqualTo("Nominations: 4/7"));
|
||||
|
||||
// order is reversed due to the cards being inside a reverse child-id fill flow.
|
||||
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().ElementAt(1);
|
||||
BeatmapCardNormal secondCard() => this.ChildrenOfType<BeatmapCardNormal>().ElementAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
public partial class TestSceneBeatmapSetOnlineStatusPill : ThemeComparisonTestScene
|
||||
{
|
||||
private bool showUnknownStatus;
|
||||
private bool animated = true;
|
||||
|
||||
protected override Drawable CreateContent() => new FillFlowContainer
|
||||
{
|
||||
@@ -37,10 +38,11 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
ShowUnknownStatus = showUnknownStatus,
|
||||
Animated = animated,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Status = status
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -64,6 +66,12 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
CreateThemedContent(OverlayColourScheme.Red);
|
||||
});
|
||||
|
||||
AddStep("toggle animate", () =>
|
||||
{
|
||||
animated = !animated;
|
||||
CreateThemedContent(OverlayColourScheme.Red);
|
||||
});
|
||||
|
||||
AddStep("unset fixed width", () => statusPills.ForEach(pill => pill.AutoSizeAxes = Axes.Both));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
TotalScore = 10_000 * (100 - i),
|
||||
Position = i,
|
||||
}).ToArray(),
|
||||
1337,
|
||||
null
|
||||
);
|
||||
});
|
||||
@@ -83,6 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
TotalScore = 600_000 + 10_000 * (40 - i),
|
||||
Position = i,
|
||||
}).ToArray(),
|
||||
1337,
|
||||
null
|
||||
);
|
||||
});
|
||||
@@ -129,6 +131,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
TotalScore = 500_000 + 10_000 * (50 - i),
|
||||
Position = i
|
||||
}).ToArray(),
|
||||
1337,
|
||||
new ScoreInfo { TotalScore = 200_000 }
|
||||
);
|
||||
});
|
||||
|
||||
@@ -86,5 +86,39 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("change local user", () => ((DummyAPIAccess)API).LocalUser.Value = new GuestUser());
|
||||
AddUntilStep("display hidden", () => display.Alpha, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTwoPlayers()
|
||||
{
|
||||
AddStep("create content", () =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies =
|
||||
[
|
||||
(typeof(IGameplayLeaderboardProvider), leaderboardProvider = new TestSceneGameplayLeaderboard.TestGameplayLeaderboardProvider()),
|
||||
(typeof(GameplayState), gameplayState = TestGameplayState.Create(new OsuRuleset()))
|
||||
],
|
||||
Child = display = new MultiplayerPositionDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
score = leaderboardProvider.CreateLeaderboardScore(new BindableLong(), API.LocalUser.Value, true);
|
||||
score.Position.BindTo(position);
|
||||
|
||||
var r = leaderboardProvider.CreateRandomScore(new APIUser());
|
||||
r.Position.Value = 1;
|
||||
});
|
||||
|
||||
AddStep("first place", () => position.Value = 1);
|
||||
AddStep("second place", () => position.Value = 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
|
||||
@@ -14,6 +17,19 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
private ScreenFooter screenFooter => this.ChildrenOfType<ScreenFooter>().Single();
|
||||
|
||||
[Test]
|
||||
public void TestFooterButtonsOnScreenTransitions()
|
||||
{
|
||||
PushAndConfirm(() => new TestScreenOne());
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
PushAndConfirm(() => new TestScreenTwo());
|
||||
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
|
||||
|
||||
AddStep("exit screen", () => Game.ScreenStack.Exit());
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterHidesOldBackButton()
|
||||
{
|
||||
@@ -38,6 +54,32 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("old back button shown", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
|
||||
private partial class TestScreenOne : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button One" },
|
||||
};
|
||||
}
|
||||
|
||||
private partial class TestScreenTwo : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button Two" },
|
||||
};
|
||||
}
|
||||
|
||||
private partial class TestScreen : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter { get; }
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
AddStep("show results", () => LoadScreen(new SoloResultsScreen(localScore)));
|
||||
AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded);
|
||||
AddAssert("local score is #16", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16));
|
||||
AddUntilStep("local score is #16", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
AddStep("show results", () => LoadScreen(new SoloResultsScreen(localScore)));
|
||||
AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded);
|
||||
AddAssert("local score is #16", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16));
|
||||
AddUntilStep("local score is #16", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
LoadScreen(new SoloResultsScreen(localScore));
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded);
|
||||
AddAssert("local score is #16", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16));
|
||||
AddUntilStep("local score is #16", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
LoadScreen(new SoloResultsScreen(localScore));
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded);
|
||||
AddAssert("local score is #16", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16));
|
||||
AddUntilStep("local score is #16", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16));
|
||||
AddAssert("previous user best not shown", () => this.ChildrenOfType<ScorePanel>().All(p => p.Score.OnlineID != 123456));
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
LoadScreen(new SoloResultsScreen(localScore));
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded);
|
||||
AddAssert("local score is #31", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(31));
|
||||
AddUntilStep("local score is #31", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(31));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -306,7 +306,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded);
|
||||
AddAssert("local score has no position", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.Null);
|
||||
AddAssert("previous user best shown at same position", () => this.ChildrenOfType<ScorePanel>().Any(p => p.Score.OnlineID == 123456 && p.ScorePosition.Value == 133_337));
|
||||
AddUntilStep("previous user best shown at same position", () => this.ChildrenOfType<ScorePanel>().Any(p => p.Score.OnlineID == 123456 && p.ScorePosition.Value == 133_337));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -411,7 +411,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
LoadScreen(new SoloResultsScreen(localScore));
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded);
|
||||
AddAssert("local score is #36", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(36));
|
||||
AddUntilStep("local score is #36", () => this.ChildrenOfType<ScorePanelList>().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(36));
|
||||
AddAssert("previous user best not shown", () => this.ChildrenOfType<ScorePanel>().All(p => p.Score.OnlineID != 123456));
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded);
|
||||
AddAssert("only one score with ID 12345", () => this.ChildrenOfType<ScorePanel>().Count(s => s.Score.OnlineID == 12345), () => Is.EqualTo(1));
|
||||
AddAssert("user best position preserved", () => this.ChildrenOfType<ScorePanel>().Any(p => p.ScorePosition.Value == 133_337));
|
||||
AddUntilStep("user best position preserved", () => this.ChildrenOfType<ScorePanel>().Any(p => p.ScorePosition.Value == 133_337));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null, IEnumerable<Mod>? mods = null, CancellationToken cancellationToken = default)
|
||||
public override async Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null, IEnumerable<Mod>? mods = null, CancellationToken cancellationToken = default, int debounceDelay = 0)
|
||||
{
|
||||
if (blockCalculation)
|
||||
{
|
||||
@@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
await calculationBlocker.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
|
||||
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken, 0).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
public Func<IEnumerable<BeatmapInfo>, BeatmapInfo>? BeatmapRecommendationFunction { get; set; }
|
||||
|
||||
private OsuTextFlowContainer stats = null!;
|
||||
|
||||
private int beatmapCount;
|
||||
@@ -69,6 +71,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
AddStep("create components", () =>
|
||||
{
|
||||
BeatmapRecommendationFunction = null;
|
||||
NewItemsPresentedInvocationCount = 0;
|
||||
|
||||
Box topBox;
|
||||
@@ -105,6 +108,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Carousel = new TestBeatmapCarousel
|
||||
{
|
||||
NewItemsPresented = () => NewItemsPresentedInvocationCount++,
|
||||
ChooseRecommendedBeatmap = beatmaps => BeatmapRecommendationFunction?.Invoke(beatmaps) ?? beatmaps.First(),
|
||||
BleedTop = 50,
|
||||
BleedBottom = 50,
|
||||
Anchor = Anchor.Centre,
|
||||
|
||||
@@ -189,10 +189,24 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
private void updateFooter(IScreen? _, IScreen? newScreen)
|
||||
{
|
||||
if (newScreen is IOsuScreen osuScreen && osuScreen.ShowFooter)
|
||||
if (newScreen is OsuScreen osuScreen && osuScreen.ShowFooter)
|
||||
{
|
||||
Footer.Show();
|
||||
Footer.SetButtons(osuScreen.CreateFooterButtons());
|
||||
|
||||
if (osuScreen.IsLoaded)
|
||||
updateFooterButtons();
|
||||
else
|
||||
osuScreen.OnLoadComplete += _ => updateFooterButtons();
|
||||
|
||||
void updateFooterButtons()
|
||||
{
|
||||
var buttons = osuScreen.CreateFooterButtons();
|
||||
|
||||
osuScreen.LoadComponentsAgainstScreenDependencies(buttons);
|
||||
|
||||
Footer.SetButtons(buttons);
|
||||
Footer.Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
@@ -62,6 +64,26 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddStep("enable masking", () => Scroll.Masking = true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Explicit]
|
||||
public void TestRandomStatus()
|
||||
{
|
||||
SortBy(SortMode.Title);
|
||||
AddStep("add beatmaps", () =>
|
||||
{
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||
set.Status = Enum.GetValues<BeatmapOnlineStatus>().MinBy(_ => RNG.Next());
|
||||
|
||||
if (i % 2 == 0)
|
||||
set.Status = BeatmapOnlineStatus.None;
|
||||
|
||||
BeatmapSets.Add(set);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Explicit]
|
||||
public void TestPerformanceWithManyBeatmaps()
|
||||
|
||||
@@ -290,5 +290,26 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
CheckDisplayedBeatmapsCount(local_set_count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFirstDifficultyFiltered()
|
||||
{
|
||||
AddBeatmaps(2, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectNextGroup();
|
||||
WaitForSelection(0, 0);
|
||||
|
||||
CheckDisplayedBeatmapsCount(6);
|
||||
|
||||
ApplyToFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
|
||||
WaitForFiltering();
|
||||
|
||||
CheckDisplayedBeatmapsCount(4);
|
||||
|
||||
SelectNextGroup();
|
||||
SelectPrevGroup();
|
||||
WaitForSelection(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +261,40 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
WaitForSelection(1, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRecommendedSelection()
|
||||
{
|
||||
AddBeatmaps(5, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
AddStep("set recommendation algorithm", () => BeatmapRecommendationFunction = beatmaps => beatmaps.Last());
|
||||
|
||||
SelectPrevGroup();
|
||||
|
||||
// check recommended was selected
|
||||
SelectNextGroup();
|
||||
WaitForSelection(0, 2);
|
||||
|
||||
// change away from recommended
|
||||
SelectPrevPanel();
|
||||
Select();
|
||||
WaitForSelection(0, 1);
|
||||
|
||||
// next set, check recommended
|
||||
SelectNextGroup();
|
||||
WaitForSelection(1, 2);
|
||||
|
||||
// next set, check recommended
|
||||
SelectNextGroup();
|
||||
WaitForSelection(2, 2);
|
||||
|
||||
// go back to first set and ensure user selection was retained
|
||||
// todo: we don't do that yet. not sure if we will continue to have this.
|
||||
// SelectPrevGroup();
|
||||
// SelectPrevGroup();
|
||||
// WaitForSelection(0, 1);
|
||||
}
|
||||
|
||||
private void checkSelectionIterating(bool isIterating)
|
||||
{
|
||||
object? selection = null;
|
||||
|
||||
@@ -312,7 +312,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Username = @"waaiiru",
|
||||
CountryCode = CountryCode.ES,
|
||||
},
|
||||
});
|
||||
Date = DateTimeOffset.Now,
|
||||
}, 1234567);
|
||||
}
|
||||
|
||||
private void showPersonalBest()
|
||||
@@ -332,8 +333,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Id = 6602580,
|
||||
Username = @"waaiiru",
|
||||
CountryCode = CountryCode.ES,
|
||||
}
|
||||
});
|
||||
},
|
||||
Date = DateTimeOffset.Now,
|
||||
}, 1234567);
|
||||
}
|
||||
|
||||
private void setScope(BeatmapLeaderboardScope scope)
|
||||
@@ -364,7 +366,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
private partial class TestBeatmapLeaderboardWedge : BeatmapLeaderboardWedge
|
||||
{
|
||||
public new void SetState(LeaderboardState state) => base.SetState(state);
|
||||
public new void SetScores(IEnumerable<ScoreInfo> scores, ScoreInfo? userScore = null) => base.SetScores(scores, userScore);
|
||||
public new void SetScores(IEnumerable<ScoreInfo> scores, ScoreInfo? userScore = null, int? totalCount = null) => base.SetScores(scores, userScore, totalCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
@@ -24,30 +25,37 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
if (set.ID == currentOnlineSet?.OnlineID)
|
||||
{
|
||||
set.TriggerSuccess(currentOnlineSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Child = wedge = new BeatmapMetadataWedge
|
||||
{
|
||||
State = { Value = Visibility.Visible },
|
||||
};
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("register request handling", () =>
|
||||
{
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
if (set.ID == currentOnlineSet?.OnlineID)
|
||||
{
|
||||
set.TriggerSuccess(currentOnlineSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowHide()
|
||||
{
|
||||
@@ -205,6 +213,74 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoading()
|
||||
{
|
||||
AddStep("override request handling", () =>
|
||||
{
|
||||
currentOnlineSet = null;
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
Scheduler.AddDelayed(() => set.TriggerSuccess(currentOnlineSet!), 500);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
onlineSet.RelatedTags![0].Name = "other/tag";
|
||||
onlineSet.RelatedTags[1].Name = "another/tag";
|
||||
onlineSet.RelatedTags[2].Name = "some/tag";
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
AddStep("no user tags", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
onlineSet.Beatmaps.Single().TopTags = null;
|
||||
onlineSet.RelatedTags = null;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
AddStep("no user tags", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
onlineSet.Beatmaps.Single().TopTags = null;
|
||||
onlineSet.RelatedTags = null;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
}
|
||||
|
||||
private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
|
||||
{
|
||||
var working = CreateWorkingBeatmap(Ruleset.Value);
|
||||
|
||||
@@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -66,7 +65,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddStep("set local scope", () =>
|
||||
{
|
||||
var current = LeaderboardManager.CurrentCriteria!;
|
||||
LeaderboardManager.FetchWithCriteria(new LeaderboardCriteria(current.Beatmap, current.Ruleset, BeatmapLeaderboardScope.Local, null));
|
||||
LeaderboardManager.FetchWithCriteria(current with
|
||||
{
|
||||
Scope = BeatmapLeaderboardScope.Local,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for score panel", () => SongSelect.ChildrenOfType<BeatmapLeaderboardScore>().Any());
|
||||
|
||||
@@ -246,6 +246,28 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
checkMatchedBeatmaps(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideBeatmap()
|
||||
{
|
||||
LoadSongSelect();
|
||||
ImportBeatmapForRuleset(0);
|
||||
|
||||
checkMatchedBeatmaps(3);
|
||||
|
||||
// song select should automatically select the beatmap for us but this is not implemented yet.
|
||||
// todo: remove when that's the case.
|
||||
AddAssert("no beatmap selected", () => Beatmap.IsDefault);
|
||||
AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First()));
|
||||
|
||||
AddStep("hide", () => Beatmaps.Hide(Beatmap.Value.BeatmapInfo));
|
||||
|
||||
checkMatchedBeatmaps(2);
|
||||
|
||||
AddStep("restore", () => Beatmaps.Restore(Beatmap.Value.BeatmapInfo));
|
||||
|
||||
checkMatchedBeatmaps(3);
|
||||
}
|
||||
|
||||
private NoResultsPlaceholder? getPlaceholder() => SongSelect.ChildrenOfType<NoResultsPlaceholder>().FirstOrDefault();
|
||||
|
||||
private void checkMatchedBeatmaps(int expected) => AddUntilStep($"{expected} matching shown", () => Carousel.MatchedBeatmapsCount, () => Is.EqualTo(expected));
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
public StarDifficulty? Difficulty { get; set; }
|
||||
|
||||
public override Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null, IEnumerable<Mod>? mods = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
CancellationToken cancellationToken = default, int computationDelay = 0)
|
||||
=> Task.FromResult(Difficulty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,12 +98,17 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="BeatmapInfo"/>.</param>
|
||||
/// <param name="computationDelay">A delay in milliseconds before performing the </param>
|
||||
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated).</returns>
|
||||
public IBindable<StarDifficulty?> GetBindableDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
|
||||
public IBindable<StarDifficulty?> GetBindableDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default, int computationDelay = 0)
|
||||
{
|
||||
var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken);
|
||||
var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken)
|
||||
{
|
||||
// Start with an approximate known value instead of zero.
|
||||
Value = new StarDifficulty(beatmapInfo.StarRating, 0)
|
||||
};
|
||||
|
||||
updateBindable(bindable, currentRuleset.Value, currentMods.Value, cancellationToken);
|
||||
updateBindable(bindable, currentRuleset.Value, currentMods.Value, cancellationToken, computationDelay);
|
||||
|
||||
lock (bindableUpdateLock)
|
||||
trackedBindables.Add(bindable);
|
||||
@@ -118,13 +123,14 @@ namespace osu.Game.Beatmaps
|
||||
/// <param name="rulesetInfo">The <see cref="IRulesetInfo"/> to get the difficulty with.</param>
|
||||
/// <param name="mods">The <see cref="Mod"/>s to get the difficulty with.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops computing the star difficulty.</param>
|
||||
/// <param name="computationDelay">In the case a cached lookup was not possible, a value in milliseconds of to wait until performing potentially intensive lookup.</param>
|
||||
/// <returns>
|
||||
/// The requested <see cref="StarDifficulty"/>, if non-<see langword="null"/>.
|
||||
/// A <see langword="null"/> return value indicates that the difficulty process failed or was interrupted early,
|
||||
/// and as such there is no usable star difficulty value to be returned.
|
||||
/// </returns>
|
||||
public virtual Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null,
|
||||
IEnumerable<Mod>? mods = null, CancellationToken cancellationToken = default)
|
||||
public virtual Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null, IEnumerable<Mod>? mods = null,
|
||||
CancellationToken cancellationToken = default, int computationDelay = 0)
|
||||
{
|
||||
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
|
||||
rulesetInfo ??= beatmapInfo.Ruleset;
|
||||
@@ -139,7 +145,7 @@ namespace osu.Game.Beatmaps
|
||||
return Task.FromResult<StarDifficulty?>(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0));
|
||||
}
|
||||
|
||||
return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken);
|
||||
return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken, computationDelay);
|
||||
}
|
||||
|
||||
protected override Task<StarDifficulty?> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken cancellationToken = default)
|
||||
@@ -206,11 +212,12 @@ namespace osu.Game.Beatmaps
|
||||
/// <param name="rulesetInfo">The <see cref="IRulesetInfo"/> to update with.</param>
|
||||
/// <param name="mods">The <see cref="Mod"/>s to update with.</param>
|
||||
/// <param name="cancellationToken">A token that may be used to cancel this update.</param>
|
||||
private void updateBindable(BindableStarDifficulty bindable, IRulesetInfo? rulesetInfo, IEnumerable<Mod>? mods, CancellationToken cancellationToken = default)
|
||||
/// <param name="computationDelay">In the case a cached lookup was not possible, a value in milliseconds of to wait until performing potentially intensive lookup.</param>
|
||||
private void updateBindable(BindableStarDifficulty bindable, IRulesetInfo? rulesetInfo, IEnumerable<Mod>? mods, CancellationToken cancellationToken = default, int computationDelay = 0)
|
||||
{
|
||||
// GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available
|
||||
// (contrary to GetAsync)
|
||||
GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken)
|
||||
GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken, computationDelay)
|
||||
.ContinueWith(task =>
|
||||
{
|
||||
// We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events.
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// The number of nominations required so that the map is eligible for qualification.
|
||||
/// </summary>
|
||||
[JsonProperty(@"required")]
|
||||
public int Required { get; set; }
|
||||
[JsonProperty(@"required_meta")]
|
||||
public BeatmapSetNominationRequiredMeta RequiredMeta { get; set; } = new BeatmapSetNominationRequiredMeta();
|
||||
}
|
||||
}
|
||||
|
||||
25
osu.Game/Beatmaps/BeatmapSetNominationStatusRequiredMeta.cs
Normal file
25
osu.Game/Beatmaps/BeatmapSetNominationStatusRequiredMeta.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about the number of nominations required for a beatmap set.
|
||||
/// </summary>
|
||||
public class BeatmapSetNominationRequiredMeta
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of nominations required for difficulties of the main ruleset.
|
||||
/// </summary>
|
||||
[JsonProperty(@"main_ruleset")]
|
||||
public int MainRuleset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of nominations required for difficulties of each non-main ruleset.
|
||||
/// </summary>
|
||||
[JsonProperty(@"non_main_ruleset")]
|
||||
public int NonMainRuleset { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,11 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </summary>
|
||||
public bool ShowUnknownStatus { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether changing status performs transition transforms.
|
||||
/// </summary>
|
||||
public bool Animated { get; init; } = true;
|
||||
|
||||
public BeatmapOnlineStatus Status
|
||||
{
|
||||
get => status;
|
||||
@@ -98,9 +103,11 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
double duration = Animated ? animation_duration : 0;
|
||||
|
||||
if (Status == BeatmapOnlineStatus.None && !ShowUnknownStatus)
|
||||
{
|
||||
this.FadeOut(animation_duration, Easing.OutQuint);
|
||||
this.FadeOut(duration, Easing.OutQuint);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,15 +116,16 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
// after we have a valid size.
|
||||
if (Height > 0)
|
||||
{
|
||||
AutoSizeDuration = (float)animation_duration;
|
||||
AutoSizeDuration = (float)duration;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
}
|
||||
|
||||
this.FadeIn(animation_duration, Easing.OutQuint);
|
||||
this.FadeIn(duration, Easing.OutQuint);
|
||||
|
||||
// Handle the case where transition from hidden to non-hidden may cause
|
||||
// a fade from a colour that doesn't make sense (due to not being able to see the previous colour).
|
||||
double duration = Alpha > 0 ? animation_duration : 0;
|
||||
if (Alpha == 0)
|
||||
duration = 0;
|
||||
|
||||
Color4 statusTextColour;
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// 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.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
|
||||
@@ -12,16 +14,27 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
|
||||
/// </summary>
|
||||
public partial class NominationsStatistic : BeatmapCardStatistic
|
||||
{
|
||||
private NominationsStatistic(BeatmapSetNominationStatus nominationStatus)
|
||||
private NominationsStatistic(int current, int required)
|
||||
{
|
||||
Icon = FontAwesome.Solid.ThumbsUp;
|
||||
Text = nominationStatus.Current.ToLocalisableString();
|
||||
TooltipText = BeatmapsStrings.NominationsRequiredText(nominationStatus.Current.ToLocalisableString(), nominationStatus.Required.ToLocalisableString());
|
||||
Text = current.ToLocalisableString();
|
||||
TooltipText = BeatmapsStrings.NominationsRequiredText(current.ToLocalisableString(), required.ToLocalisableString());
|
||||
}
|
||||
|
||||
public static NominationsStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetOnlineInfo)
|
||||
public static NominationsStatistic? CreateFor(APIBeatmapSet beatmapSet)
|
||||
{
|
||||
// web does not show nominations unless hypes are also present.
|
||||
// see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443
|
||||
=> beatmapSetOnlineInfo.HypeStatus == null || beatmapSetOnlineInfo.NominationStatus == null ? null : new NominationsStatistic(beatmapSetOnlineInfo.NominationStatus);
|
||||
if (beatmapSet.HypeStatus == null || beatmapSet.NominationStatus == null)
|
||||
return null;
|
||||
|
||||
int current = beatmapSet.NominationStatus.Current;
|
||||
int requiredMainRuleset = beatmapSet.NominationStatus.RequiredMeta.MainRuleset;
|
||||
int requiredNonMainRuleset = beatmapSet.NominationStatus.RequiredMeta.NonMainRuleset;
|
||||
|
||||
int rulesets = beatmapSet.Beatmaps.GroupBy(b => b.Ruleset).Count();
|
||||
|
||||
return new NominationsStatistic(current, requiredMainRuleset + requiredNonMainRuleset * (rulesets - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets;
|
||||
@@ -20,7 +19,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public partial class DifficultyIcon : CompositeDrawable, IHasCustomTooltip<DifficultyIconTooltipContent>, IHasCurrentValue<StarDifficulty>
|
||||
public partial class DifficultyIcon : CompositeDrawable, IHasCustomTooltip<DifficultyIconTooltipContent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Size of this difficulty icon.
|
||||
@@ -46,8 +45,12 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private readonly Container iconContainer;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private readonly BindableWithCurrent<StarDifficulty> difficulty = new BindableWithCurrent<StarDifficulty>();
|
||||
|
||||
// TODO: remove this after old song select is gone.
|
||||
public virtual Bindable<StarDifficulty> Current
|
||||
{
|
||||
get => difficulty.Current;
|
||||
@@ -64,28 +67,19 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// <param name="mods">An array of mods to account for in the calculations</param>
|
||||
/// <param name="ruleset">An optional ruleset to be used for the icon display, in place of the beatmap's ruleset.</param>
|
||||
public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null)
|
||||
: this(ruleset ?? beatmap.Ruleset)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
this.mods = mods;
|
||||
this.ruleset = ruleset ?? beatmap.Ruleset;
|
||||
|
||||
Current.Value = new StarDifficulty(beatmap.StarRating, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DifficultyIcon"/> without an associated beatmap.
|
||||
/// </summary>
|
||||
/// <param name="ruleset">The ruleset to be used for the icon display.</param>
|
||||
public DifficultyIcon(IRulesetInfo ruleset)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
InternalChild = iconContainer = new Container { Size = new Vector2(20f) };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load()
|
||||
{
|
||||
iconContainer.Children = new Drawable[]
|
||||
{
|
||||
@@ -115,8 +109,18 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Icon = getRulesetIcon()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Current.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars), true);
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(difficulty =>
|
||||
{
|
||||
background.FadeColour(colours.ForStarDifficulty(difficulty.NewValue.Stars), 200);
|
||||
}, true);
|
||||
|
||||
background.FinishTransforms();
|
||||
}
|
||||
|
||||
private Drawable getRulesetIcon()
|
||||
|
||||
@@ -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;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@@ -23,8 +24,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </summary>
|
||||
public partial class StarRatingDisplay : CompositeDrawable, IHasCurrentValue<StarDifficulty>
|
||||
{
|
||||
public const double TRANSFORM_DURATION = 750;
|
||||
|
||||
private readonly bool animated;
|
||||
private readonly Box background;
|
||||
private readonly SpriteIcon starIcon;
|
||||
@@ -147,7 +146,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Current.BindValueChanged(c =>
|
||||
{
|
||||
if (animated)
|
||||
this.TransformBindableTo(displayedStars, c.NewValue.Stars, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
// Animation roughly matches `StarCounter`'s implementation.
|
||||
this.TransformBindableTo(displayedStars, c.NewValue.Stars, 100 + 80 * Math.Abs(c.NewValue.Stars - c.OldValue.Stars), Easing.OutQuint);
|
||||
else
|
||||
displayedStars.Value = c.NewValue.Stars;
|
||||
});
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
public enum ReleaseStream
|
||||
{
|
||||
Lazer,
|
||||
//Stable40,
|
||||
//Beta40,
|
||||
//Stable
|
||||
|
||||
[Description("Tachyon (Unstable)")]
|
||||
Tachyon
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,9 @@ namespace osu.Game.Database
|
||||
/// Retrieve the cached value for the given lookup.
|
||||
/// </summary>
|
||||
/// <param name="lookup">The lookup to retrieve.</param>
|
||||
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
||||
protected async Task<TValue?> GetAsync(TLookup lookup, CancellationToken token = default)
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
||||
/// <param name="computationDelay">In the case a cached lookup was not possible, a value in milliseconds of to wait until performing potentially intensive lookup.</param>
|
||||
protected async Task<TValue?> GetAsync(TLookup lookup, CancellationToken cancellationToken = default, int computationDelay = 0)
|
||||
{
|
||||
if (CheckExists(lookup, out TValue? existing))
|
||||
{
|
||||
@@ -44,7 +45,10 @@ namespace osu.Game.Database
|
||||
return existing;
|
||||
}
|
||||
|
||||
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
|
||||
if (computationDelay > 0)
|
||||
await Task.Delay(computationDelay, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var computed = await ComputeValueAsync(lookup, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
statistics.Value.MissCount++;
|
||||
|
||||
|
||||
@@ -79,6 +79,18 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString LearnMoreAboutLazerTooltip => new TranslatableString(getKey(@"check_out_the_feature_comparison"), @"Check out the feature comparison and FAQ");
|
||||
|
||||
/// <summary>
|
||||
/// "Are you sure you want to run a potentially unstable version of the game?"
|
||||
/// </summary>
|
||||
public static LocalisableString ChangeReleaseStreamConfirmation => new TranslatableString(getKey(@"change_release stream_confirmation"),
|
||||
@"Are you sure you want to run a potentially unstable version of the game?");
|
||||
|
||||
/// <summary>
|
||||
/// "If you run into issues starting the game, you can usually run the installer from the official site to recover."
|
||||
/// </summary>
|
||||
public static LocalisableString ChangeReleaseStreamConfirmationInfo => new TranslatableString(getKey(@"change_release stream_confirmation_info"),
|
||||
@"If you run into issues starting the game, you can usually run the installer from the official site to recover.");
|
||||
|
||||
/// <summary>
|
||||
/// "You are running the latest release ({0})"
|
||||
/// </summary>
|
||||
|
||||
@@ -10,6 +10,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
public class APIScoresCollection
|
||||
{
|
||||
[JsonProperty(@"score_count")]
|
||||
public int ScoresCount;
|
||||
|
||||
[JsonProperty(@"scores")]
|
||||
public List<SoloScoreInfo> Scores;
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
return s;
|
||||
})
|
||||
.ToArray(),
|
||||
response.ScoresCount,
|
||||
response.UserScore?.CreateScoreInfo(rulesets, newCriteria.Beatmap)
|
||||
);
|
||||
inFlightOnlineRequest = null;
|
||||
@@ -181,7 +182,8 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
newScores = newScores.Detach().OrderByTotalScore();
|
||||
|
||||
scores.Value = LeaderboardScores.Success(newScores.ToArray(), null);
|
||||
var newScoresArray = newScores.ToArray();
|
||||
scores.Value = LeaderboardScores.Success(newScoresArray, newScoresArray.Length, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +197,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
public record LeaderboardScores
|
||||
{
|
||||
public ICollection<ScoreInfo> TopScores { get; }
|
||||
public int TotalScores { get; }
|
||||
public ScoreInfo? UserScore { get; }
|
||||
public LeaderboardFailState? FailState { get; }
|
||||
|
||||
@@ -210,15 +213,16 @@ namespace osu.Game.Online.Leaderboards
|
||||
}
|
||||
}
|
||||
|
||||
private LeaderboardScores(ICollection<ScoreInfo> topScores, ScoreInfo? userScore, LeaderboardFailState? failState)
|
||||
private LeaderboardScores(ICollection<ScoreInfo> topScores, int totalScores, ScoreInfo? userScore, LeaderboardFailState? failState)
|
||||
{
|
||||
TopScores = topScores;
|
||||
TotalScores = totalScores;
|
||||
UserScore = userScore;
|
||||
FailState = failState;
|
||||
}
|
||||
|
||||
public static LeaderboardScores Success(ICollection<ScoreInfo> topScores, ScoreInfo? userScore) => new LeaderboardScores(topScores, userScore, null);
|
||||
public static LeaderboardScores Failure(LeaderboardFailState failState) => new LeaderboardScores([], null, failState);
|
||||
public static LeaderboardScores Success(ICollection<ScoreInfo> topScores, int totalScores, ScoreInfo? userScore) => new LeaderboardScores(topScores, totalScores, userScore, null);
|
||||
public static LeaderboardScores Failure(LeaderboardFailState failState) => new LeaderboardScores([], 0, null, failState);
|
||||
}
|
||||
|
||||
public enum LeaderboardFailState
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace osu.Game.Online.Spectator
|
||||
|
||||
private readonly Queue<FrameDataBundle> pendingFrameBundles = new Queue<FrameDataBundle>();
|
||||
|
||||
private readonly Queue<LegacyReplayFrame> pendingFrames = new Queue<LegacyReplayFrame>();
|
||||
private readonly List<LegacyReplayFrame> pendingFrames = new List<LegacyReplayFrame>();
|
||||
|
||||
private double lastPurgeTime;
|
||||
|
||||
@@ -244,7 +244,18 @@ namespace osu.Game.Online.Spectator
|
||||
if (frame is IConvertibleReplayFrame convertible)
|
||||
{
|
||||
Debug.Assert(currentBeatmap != null);
|
||||
pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap));
|
||||
|
||||
var convertedFrame = convertible.ToLegacy(currentBeatmap);
|
||||
|
||||
// only keep the last recorded frame for a given timestamp.
|
||||
// this reduces redundancy of frames in the resulting replay.
|
||||
//
|
||||
// this is also done at `ReplayRecorded`, but needs to be done here as well
|
||||
// due to the flow being handled differently.
|
||||
if (pendingFrames.LastOrDefault()?.Time == convertedFrame.Time)
|
||||
pendingFrames[^1] = convertedFrame;
|
||||
else
|
||||
pendingFrames.Add(convertedFrame);
|
||||
}
|
||||
|
||||
if (pendingFrames.Count > max_pending_frames)
|
||||
|
||||
@@ -1740,7 +1740,12 @@ namespace osu.Game
|
||||
BackButton.Hide();
|
||||
ScreenFooter.Show();
|
||||
|
||||
newOsuScreen.OnLoadComplete += _ =>
|
||||
if (newOsuScreen.IsLoaded)
|
||||
updateFooterButtons();
|
||||
else
|
||||
newOsuScreen.OnLoadComplete += _ => updateFooterButtons();
|
||||
|
||||
void updateFooterButtons()
|
||||
{
|
||||
var buttons = newScreen.CreateFooterButtons();
|
||||
|
||||
@@ -1748,7 +1753,7 @@ namespace osu.Game
|
||||
|
||||
ScreenFooter.SetButtons(buttons);
|
||||
ScreenFooter.Show();
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -61,6 +61,7 @@ using osu.Game.Resources;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Utils;
|
||||
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
||||
@@ -168,6 +169,12 @@ namespace osu.Game
|
||||
|
||||
protected Storage Storage { get; set; }
|
||||
|
||||
protected EzSkinSettingsManager EzSkinSettingsManager { get; private set; }
|
||||
|
||||
protected EzLocalNoteFactory EzLocalNoteFactory { get; private set; }
|
||||
|
||||
// protected EzNoteFactory EzNoteFactory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The language in which the game is currently displayed in.
|
||||
/// </summary>
|
||||
@@ -431,26 +438,35 @@ namespace osu.Game
|
||||
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
|
||||
LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown";
|
||||
LocalConfig.LookupKeyBindings = l => KeyBindingStore.GetBindingsStringFor(l);
|
||||
|
||||
// 初始化并注册EzSkinSettingsManager
|
||||
dependencies.Cache(EzSkinSettingsManager = new EzSkinSettingsManager(Storage));
|
||||
// dependencies.CacheAs<IEzSkinSettings>(EzSkinSettingsManager);
|
||||
dependencies.Cache(EzLocalNoteFactory = new EzLocalNoteFactory(Storage));
|
||||
// EzNoteFactory = new EzNoteFactory(Storage);
|
||||
// dependencies.CacheAs(EzNoteFactory);
|
||||
// dependencies.Cache(EzNoteFactory = new EzNoteFactory());
|
||||
}
|
||||
|
||||
private void updateLanguage() => CurrentLanguage.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value);
|
||||
|
||||
private void addFilesWarning()
|
||||
{
|
||||
var realmStore = new RealmFileStore(realm, Storage);
|
||||
|
||||
const string filename = "IMPORTANT READ ME.txt";
|
||||
|
||||
if (!realmStore.Storage.Exists(filename))
|
||||
if (!Storage.Exists(filename))
|
||||
{
|
||||
using (var stream = realmStore.Storage.CreateFileSafely(filename))
|
||||
using (var stream = Storage.CreateFileSafely(filename))
|
||||
using (var textWriter = new StreamWriter(stream))
|
||||
{
|
||||
textWriter.WriteLine(@"This folder contains all your user files (beatmaps, skins, replays etc.)");
|
||||
textWriter.WriteLine(@"Please do not touch or delete this folder!!");
|
||||
textWriter.WriteLine(@"This folder contains all your user files and configuration.");
|
||||
textWriter.WriteLine(@"Please DO NOT make manual changes to this folder.");
|
||||
textWriter.WriteLine();
|
||||
textWriter.WriteLine(@"If you are really looking to completely delete user data, please delete");
|
||||
textWriter.WriteLine(@"the parent folder including all other files and directories");
|
||||
textWriter.WriteLine(@"- If you want to back up your game files, please back up THE ENTIRETY OF THIS DIRECTORY.");
|
||||
textWriter.WriteLine(@"- If you want to delete all of your game files, please delete THE ENTIRETY OF THIS DIRECTORY.");
|
||||
textWriter.WriteLine();
|
||||
textWriter.WriteLine(@"To be very clear, the ""files/"" directory inside this directory stores all the raw pieces of your beatmaps, skins, and replays.");
|
||||
textWriter.WriteLine(@"Importantly, it is NOT the only directory you need a backup of to avoid losing data. If you copy only the ""files/"" directory, YOU WILL LOSE DATA.");
|
||||
textWriter.WriteLine();
|
||||
textWriter.WriteLine(@"For more information on how these files are organised,");
|
||||
textWriter.WriteLine(@"see https://github.com/ppy/osu/wiki/User-file-storage");
|
||||
|
||||
@@ -13,7 +13,6 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
@@ -295,7 +294,6 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
icon = new DifficultyIcon(beatmapInfo, ruleset)
|
||||
{
|
||||
TooltipType = DifficultyIconTooltipType.None,
|
||||
Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) },
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(size - tile_icon_padding * 2),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
@@ -13,6 +14,7 @@ using osu.Framework.Statistics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||
using osu.Game.Updater;
|
||||
@@ -27,6 +29,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
|
||||
private SettingsButton checkForUpdatesButton = null!;
|
||||
|
||||
private readonly Bindable<ReleaseStream> configReleaseStream = new Bindable<ReleaseStream>();
|
||||
private SettingsEnumDropdown<ReleaseStream> releaseStreamDropdown = null!;
|
||||
|
||||
[Resolved]
|
||||
private UpdateManager? updateManager { get; set; }
|
||||
|
||||
@@ -40,21 +45,46 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
private void load(OsuConfigManager config, IDialogOverlay? dialogOverlay)
|
||||
{
|
||||
Add(new SettingsEnumDropdown<ReleaseStream>
|
||||
{
|
||||
LabelText = GeneralSettingsStrings.ReleaseStream,
|
||||
Current = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream),
|
||||
});
|
||||
config.BindWith(OsuSetting.ReleaseStream, configReleaseStream);
|
||||
|
||||
if (updateManager?.CanCheckForUpdate == true)
|
||||
{
|
||||
Add(releaseStreamDropdown = new SettingsEnumDropdown<ReleaseStream>
|
||||
{
|
||||
LabelText = GeneralSettingsStrings.ReleaseStream,
|
||||
Current = { Value = configReleaseStream.Value },
|
||||
});
|
||||
|
||||
Add(checkForUpdatesButton = new SettingsButton
|
||||
{
|
||||
Text = GeneralSettingsStrings.CheckUpdate,
|
||||
Action = () => checkForUpdates().FireAndForget()
|
||||
});
|
||||
|
||||
releaseStreamDropdown.Current.BindValueChanged(stream =>
|
||||
{
|
||||
if (stream.NewValue == ReleaseStream.Tachyon)
|
||||
{
|
||||
dialogOverlay?.Push(new ConfirmDialog(GeneralSettingsStrings.ChangeReleaseStreamConfirmation,
|
||||
() =>
|
||||
{
|
||||
configReleaseStream.Value = ReleaseStream.Tachyon;
|
||||
},
|
||||
() =>
|
||||
{
|
||||
releaseStreamDropdown.Current.Value = ReleaseStream.Lazer;
|
||||
})
|
||||
{
|
||||
BodyText = GeneralSettingsStrings.ChangeReleaseStreamConfirmationInfo
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
configReleaseStream.Value = stream.NewValue;
|
||||
});
|
||||
}
|
||||
|
||||
if (RuntimeInfo.IsDesktop)
|
||||
|
||||
@@ -127,6 +127,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
dropdownItems.Clear();
|
||||
|
||||
dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.EZ2_SKIN).ToLive(realm));
|
||||
dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.EZ_STYLE_PRO_SKIN).ToLive(realm));
|
||||
dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.SBI_SKIN).ToLive(realm));
|
||||
dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_SKIN).ToLive(realm));
|
||||
dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_PRO_SKIN).ToLive(realm));
|
||||
|
||||
@@ -70,8 +70,18 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
float rotation = fraction * 360 - 90;
|
||||
|
||||
// The case where a hand is completing a rotation.
|
||||
// Animate and then move back one full rotation so we don't need to track outside of 0..360
|
||||
if (Math.Abs(hand.Rotation - rotation) > 180)
|
||||
hand.RotateTo(rotation);
|
||||
{
|
||||
float animRotation = rotation;
|
||||
while (animRotation < hand.Rotation)
|
||||
animRotation += 180;
|
||||
|
||||
hand.RotateTo(animRotation, duration, Easing.OutElastic)
|
||||
.Then()
|
||||
.RotateTo(rotation);
|
||||
}
|
||||
else
|
||||
hand.RotateTo(rotation, duration, Easing.OutElastic);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,14 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
private int? lastSecond;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
UpdateDisplay(DateTimeOffset.Now);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
@@ -74,6 +74,11 @@ namespace osu.Game.Rulesets.Judgements
|
||||
/// </summary>
|
||||
public int HighestComboAtJudgement { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The highest combo achieved after this <see cref="JudgementResult"/> occurred.
|
||||
/// </summary>
|
||||
public int HighestComboAfterJudgement { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The health prior to this <see cref="JudgementResult"/> occurring.
|
||||
/// </summary>
|
||||
|
||||
@@ -781,7 +781,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// Creates the <see cref="JudgementResult"/> that represents the scoring result for this <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="judgement">The <see cref="Judgement"/> that provides the scoring information.</param>
|
||||
protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement);
|
||||
protected internal virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement);
|
||||
|
||||
private void ensureEntryHasResult()
|
||||
{
|
||||
|
||||
@@ -215,7 +215,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
|
||||
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
|
||||
Accuracy.ValueChanged += _ => updateRank();
|
||||
|
||||
Mods.ValueChanged += mods =>
|
||||
@@ -251,7 +250,10 @@ namespace osu.Game.Rulesets.Scoring
|
||||
else if (result.Type.BreaksCombo())
|
||||
Combo.Value = 0;
|
||||
|
||||
HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value);
|
||||
|
||||
result.ComboAfterJudgement = Combo.Value;
|
||||
result.HighestComboAfterJudgement = HighestCombo.Value;
|
||||
|
||||
if (result.Judgement.MaxResult.AffectsAccuracy())
|
||||
{
|
||||
@@ -294,8 +296,11 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (!TrackHitEvents)
|
||||
throw new InvalidOperationException(@$"Rewind is not supported when {nameof(TrackHitEvents)} is disabled.");
|
||||
|
||||
Combo.Value = result.ComboAtJudgement;
|
||||
HighestCombo.Value = result.HighestComboAtJudgement;
|
||||
// the reason this is written so funnily rather than just using `ComboAtJudgement`
|
||||
// is to nullify impact of ordering when reverting concurrent judgement results
|
||||
// (think mania and multiple judgements within a frame).
|
||||
Combo.Value -= (result.ComboAfterJudgement - result.ComboAtJudgement);
|
||||
HighestCombo.Value -= (result.HighestComboAfterJudgement - result.HighestComboAtJudgement);
|
||||
|
||||
if (result.FailedAtJudgement && !ApplyNewJudgementsWhenFailed)
|
||||
return;
|
||||
|
||||
@@ -300,6 +300,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
if (score == null)
|
||||
{
|
||||
NewResult -= emitImportantFrame;
|
||||
recordingInputManager.Recorder = null;
|
||||
return;
|
||||
}
|
||||
@@ -311,7 +312,10 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield;
|
||||
|
||||
NewResult += emitImportantFrame;
|
||||
recordingInputManager.Recorder = recorder;
|
||||
|
||||
void emitImportantFrame(JudgementResult judgementResult) => recordingInputManager.Recorder?.RecordFrame(true);
|
||||
}
|
||||
|
||||
public override void SetReplayScore(Score replayScore)
|
||||
|
||||
@@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
public interface IHasRecordingHandler
|
||||
{
|
||||
public ReplayRecorder? Recorder { set; }
|
||||
public ReplayRecorder? Recorder { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,29 +51,29 @@ namespace osu.Game.Rulesets.UI
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
recordFrame(false);
|
||||
RecordFrame(false);
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
recordFrame(false);
|
||||
RecordFrame(false);
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<T> e)
|
||||
{
|
||||
pressedActions.Add(e.Action);
|
||||
recordFrame(true);
|
||||
RecordFrame(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
||||
{
|
||||
pressedActions.Remove(e.Action);
|
||||
recordFrame(true);
|
||||
RecordFrame(true);
|
||||
}
|
||||
|
||||
private void recordFrame(bool important)
|
||||
public override void RecordFrame(bool important)
|
||||
{
|
||||
var last = target.Replay.Frames.LastOrDefault();
|
||||
|
||||
@@ -86,8 +86,15 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
if (frame != null)
|
||||
{
|
||||
target.Replay.Frames.Add(frame);
|
||||
// only keep the last recorded frame for a given timestamp.
|
||||
// this reduces redundancy of frames in the resulting replay.
|
||||
if (last?.Time == frame.Time)
|
||||
target.Replay.Frames[^1] = frame;
|
||||
else
|
||||
target.Replay.Frames.Add(frame);
|
||||
|
||||
// the above de-duplication is done at `FrameDataBundle` level in `SpectatorClient`.
|
||||
// it's not 100% matching because of the possibility of duplicated frames crossing a bundle boundary, but it's close and simple enough.
|
||||
spectatorClient?.HandleFrame(frame);
|
||||
}
|
||||
}
|
||||
@@ -98,5 +105,7 @@ namespace osu.Game.Rulesets.UI
|
||||
public abstract partial class ReplayRecorder : Component
|
||||
{
|
||||
public Func<Vector2, Vector2> ScreenSpaceToGamefield;
|
||||
|
||||
public abstract void RecordFrame(bool important);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public ReplayRecorder? Recorder
|
||||
{
|
||||
get => recorder;
|
||||
set
|
||||
{
|
||||
if (value == recorder)
|
||||
|
||||
@@ -113,9 +113,7 @@ namespace osu.Game.Screens.Backgrounds
|
||||
}
|
||||
|
||||
b.Depth = newDepth;
|
||||
b.Anchor = b.Origin = Anchor.Centre;
|
||||
b.FadeInFromZero(500, Easing.OutQuint);
|
||||
b.ScaleTo(1.02f).ScaleTo(1, 3500, Easing.OutQuint);
|
||||
dimmable.Background = Background = b;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace osu.Game.Screens.Backgrounds
|
||||
{
|
||||
case BackgroundSource.WebmSource:
|
||||
{
|
||||
const string relative_path = @"Resource\Webm";
|
||||
const string relative_path = @"EzResources\Webm";
|
||||
string dataFolderPath = storage.GetFullPath(relative_path);
|
||||
Debug.Assert(!string.IsNullOrEmpty(dataFolderPath));
|
||||
|
||||
|
||||
@@ -36,14 +36,8 @@ namespace osu.Game.Screens.Backgrounds
|
||||
//下面只用于注册全局设置
|
||||
GlobalConfigStore.Config = config;
|
||||
|
||||
var skinSettings = new EzSkinSettings();
|
||||
GlobalConfigStore.EZConfig = skinSettings;
|
||||
}
|
||||
|
||||
public void InjectDependencies(IReadOnlyDependencyContainer dependencies, DependencyContainer newDependencies)
|
||||
{
|
||||
// 在这里注册 EzSkinSettings
|
||||
newDependencies.CacheAs(new EzSkinSettings());
|
||||
var ezskinSettings = new EzSkinSettings();
|
||||
GlobalConfigStore.EZConfig = ezskinSettings;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
private void applyRotation(bool shouldSnap)
|
||||
{
|
||||
float newRotation = shouldSnap ? snap(rawCumulativeRotation, snap_step) : MathF.Round(rawCumulativeRotation);
|
||||
newRotation = (newRotation - 180) % 360 + 180;
|
||||
newRotation = ((newRotation + 360 + 180) % 360) - 180;
|
||||
if (MathF.Abs(newRotation) == 180)
|
||||
newRotation = 180;
|
||||
|
||||
cumulativeRotation.Value = newRotation;
|
||||
|
||||
|
||||
@@ -490,8 +490,6 @@ namespace osu.Game.Screens.Edit
|
||||
Mode.Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose;
|
||||
Mode.BindValueChanged(onModeChanged, true);
|
||||
|
||||
musicController.TrackChanged += onTrackChanged;
|
||||
|
||||
MutationTracker.InProgress.BindValueChanged(_ =>
|
||||
{
|
||||
foreach (var item in saveRelatedMenuItems)
|
||||
@@ -503,6 +501,7 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
// redundant (should have happened via a `resetTrack()` call in `OnExiting()`), but done for safety
|
||||
musicController.TrackChanged -= onTrackChanged;
|
||||
}
|
||||
|
||||
@@ -845,14 +844,14 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
base.OnEntering(e);
|
||||
setUpBackground();
|
||||
resetTrack(true);
|
||||
setUpTrack(seekToStart: true);
|
||||
}
|
||||
|
||||
public override void OnResuming(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnResuming(e);
|
||||
setUpBackground();
|
||||
clock.BindAdjustments();
|
||||
setUpTrack();
|
||||
}
|
||||
|
||||
private void setUpBackground()
|
||||
@@ -899,8 +898,9 @@ namespace osu.Game.Screens.Edit
|
||||
beatmap.EditorTimestamp = clock.CurrentTime;
|
||||
});
|
||||
|
||||
// `resetTrack()` MUST happen before `refetchBeatmap()`, because along other things, `refetchBeatmap()` causes a global working beatmap change,
|
||||
// which would cause `EditorClock` to reload the track and automatically reapply adjustments to it if not preceded by `resetTrack()`.
|
||||
resetTrack();
|
||||
|
||||
refetchBeatmap();
|
||||
|
||||
return base.OnExiting(e);
|
||||
@@ -909,12 +909,11 @@ namespace osu.Game.Screens.Edit
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnSuspending(e);
|
||||
clock.Stop();
|
||||
|
||||
// `resetTrack()` MUST happen before `refetchBeatmap()`, because along other things, `refetchBeatmap()` causes a global working beatmap change,
|
||||
// which would cause `EditorClock` to reload the track and automatically reapply adjustments to it if not preceded by `resetTrack()`.
|
||||
resetTrack();
|
||||
refetchBeatmap();
|
||||
// unfortunately ordering matters here.
|
||||
// this unbind MUST happen after `refetchBeatmap()`, because along other things, `refetchBeatmap()` causes a global working beatmap change,
|
||||
// which causes `EditorClock` to reload the track and automatically reapply adjustments to it.
|
||||
clock.UnbindAdjustments();
|
||||
}
|
||||
|
||||
private void refetchBeatmap()
|
||||
@@ -1038,7 +1037,7 @@ namespace osu.Game.Screens.Edit
|
||||
editorBeatmap.PreviewTime.Value = (int)clock.CurrentTime;
|
||||
}
|
||||
|
||||
private void resetTrack(bool seekToStart = false)
|
||||
private void setUpTrack(bool seekToStart = false)
|
||||
{
|
||||
clock.Stop();
|
||||
|
||||
@@ -1059,6 +1058,16 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
clock.Seek(Math.Max(0, targetTime));
|
||||
}
|
||||
|
||||
clock.BindAdjustments();
|
||||
musicController.TrackChanged += onTrackChanged;
|
||||
}
|
||||
|
||||
private void resetTrack()
|
||||
{
|
||||
clock.Stop();
|
||||
clock.UnbindAdjustments();
|
||||
musicController.TrackChanged -= onTrackChanged;
|
||||
}
|
||||
|
||||
private void onModeChanged(ValueChangedEvent<EditorScreenMode> e)
|
||||
|
||||
@@ -14,6 +14,7 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
@@ -75,6 +76,11 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects, editorState.Time))
|
||||
{
|
||||
var judgement = hitObject.Judgement;
|
||||
// this is very dodgy because there's no guarantee that `JudgementResult` is the correct result type for the object.
|
||||
// however, instantiating the correct one is difficult here, because `JudgementResult`s are constructed by DHOs
|
||||
// and because of pooling we don't *have* a DHO to use here.
|
||||
// this basically mostly attempts to fill holes in `ScoreProcessor` tallies
|
||||
// so that gameplay can actually complete at the end of the map when entering gameplay test midway through it, and not much else.
|
||||
var result = new JudgementResult(hitObject, judgement)
|
||||
{
|
||||
Type = judgement.MaxResult,
|
||||
@@ -109,30 +115,28 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var drawableObjectEntry in enumerateDrawableEntries(
|
||||
DrawableRuleset.Playfield.AllHitObjects
|
||||
.Select(ho => ho.Entry)
|
||||
.Where(e => e != null)
|
||||
.Cast<HitObjectLifetimeEntry>(), editorState.Time))
|
||||
foreach (var drawableObject in enumerateDrawableObjects(DrawableRuleset.Playfield.AllHitObjects, editorState.Time))
|
||||
{
|
||||
drawableObjectEntry.Result = new JudgementResult(drawableObjectEntry.HitObject, drawableObjectEntry.HitObject.Judgement)
|
||||
{
|
||||
Type = drawableObjectEntry.HitObject.Judgement.MaxResult
|
||||
};
|
||||
if (drawableObject.Entry == null)
|
||||
continue;
|
||||
|
||||
var result = drawableObject.CreateResult(drawableObject.HitObject.Judgement);
|
||||
result.Type = result.Judgement.MaxResult;
|
||||
drawableObject.Entry.Result = result;
|
||||
}
|
||||
|
||||
static IEnumerable<HitObjectLifetimeEntry> enumerateDrawableEntries(IEnumerable<HitObjectLifetimeEntry> entries, double cutoffTime)
|
||||
static IEnumerable<DrawableHitObject> enumerateDrawableObjects(IEnumerable<DrawableHitObject> drawableObjects, double cutoffTime)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
foreach (var drawableObject in drawableObjects)
|
||||
{
|
||||
foreach (var nested in enumerateDrawableEntries(entry.NestedEntries, cutoffTime))
|
||||
foreach (var nested in enumerateDrawableObjects(drawableObject.NestedHitObjects, cutoffTime))
|
||||
{
|
||||
if (nested.HitObject.GetEndTime() < cutoffTime)
|
||||
yield return nested;
|
||||
}
|
||||
|
||||
if (entry.HitObject.GetEndTime() < cutoffTime)
|
||||
yield return entry;
|
||||
if (drawableObject.HitObject.GetEndTime() < cutoffTime)
|
||||
yield return drawableObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
@@ -13,6 +21,7 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@@ -20,17 +29,30 @@ namespace osu.Game.Screens
|
||||
{
|
||||
public partial class EzSkinSettings : EditorSidebarSection
|
||||
{
|
||||
public BindableNumber<double>? NonSquareNoteHeight;
|
||||
public Bindable<double>? VirtualHitPosition;
|
||||
private Bindable<double>? columnWidth;
|
||||
private Bindable<double>? specialFactor;
|
||||
private readonly Bindable<bool> dynamicTracking = new Bindable<bool>(false);
|
||||
private readonly Bindable<bool> dynamicTracking = new Bindable<bool>();
|
||||
|
||||
private readonly Bindable<EzSelectorNameSet> globalTextureName = new Bindable<EzSelectorNameSet>((EzSelectorNameSet)4);
|
||||
|
||||
private readonly Bindable<string> selectedNoteSet = new Bindable<string>();
|
||||
|
||||
private readonly List<string> availableNoteSets = new List<string>();
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skinManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Storage storage { get; set; } = null!;
|
||||
|
||||
public EzSkinSettings()
|
||||
: base("EZ Skin Settings")
|
||||
{
|
||||
@@ -43,25 +65,53 @@ namespace osu.Game.Screens
|
||||
specialFactor = config.GetBindable<double>(OsuSetting.SpecialFactor);
|
||||
VirtualHitPosition = config.GetBindable<double>(OsuSetting.VirtualHitPosition);
|
||||
|
||||
var configBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight);
|
||||
|
||||
NonSquareNoteHeight = new BindableNumber<double>
|
||||
{
|
||||
Value = configBindable.Value,
|
||||
MinValue = 1,
|
||||
MaxValue = 100,
|
||||
Precision = 1f,
|
||||
};
|
||||
|
||||
NonSquareNoteHeight.ValueChanged += e => configBindable.Value = e.NewValue;
|
||||
configBindable.ValueChanged += e => NonSquareNoteHeight.Value = e.NewValue;
|
||||
// NonSquareNoteHeight.ValueChanged += onSettingsValueChanged;
|
||||
|
||||
globalTextureName.Value = (EzSelectorNameSet)ezSkinConfig.GetBindable<int>(EzSkinSetting.GlobalTextureName).Value;
|
||||
globalTextureName.ValueChanged += onTextureNameChanged;
|
||||
|
||||
dynamicTracking.BindTo(ezSkinConfig.GetBindable<bool>(EzSkinSetting.DynamicTracking));
|
||||
dynamicTracking.ValueChanged += tracking =>
|
||||
{
|
||||
// 根据开关状态取消或添加监听器
|
||||
if (tracking.NewValue)
|
||||
{
|
||||
// 启用动态追踪,添加监听器
|
||||
columnWidth!.ValueChanged += OnSettingsValueChanged;
|
||||
specialFactor!.ValueChanged += OnSettingsValueChanged;
|
||||
VirtualHitPosition!.ValueChanged += OnSettingsValueChanged;
|
||||
columnWidth!.ValueChanged += onSettingsValueChanged;
|
||||
specialFactor!.ValueChanged += onSettingsValueChanged;
|
||||
VirtualHitPosition!.ValueChanged += onSettingsValueChanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 禁用动态追踪,移除监听器
|
||||
columnWidth!.ValueChanged -= OnSettingsValueChanged;
|
||||
specialFactor!.ValueChanged -= OnSettingsValueChanged;
|
||||
VirtualHitPosition!.ValueChanged -= OnSettingsValueChanged;
|
||||
columnWidth!.ValueChanged -= onSettingsValueChanged;
|
||||
specialFactor!.ValueChanged -= onSettingsValueChanged;
|
||||
VirtualHitPosition!.ValueChanged -= onSettingsValueChanged;
|
||||
}
|
||||
|
||||
ezSkinConfig.SetValue(EzSkinSetting.DynamicTracking, tracking.NewValue);
|
||||
};
|
||||
|
||||
loadAvailableNoteSets();
|
||||
|
||||
// 从配置中加载上次选择的note套图,如果有的话
|
||||
string configuredNoteSet = ezSkinConfig.Get<string>(EzSkinSetting.NoteSetName);
|
||||
if (!string.IsNullOrEmpty(configuredNoteSet) && availableNoteSets.Contains(configuredNoteSet))
|
||||
selectedNoteSet.Value = configuredNoteSet;
|
||||
else if (availableNoteSets.Count > 0)
|
||||
selectedNoteSet.Value = availableNoteSets[1];
|
||||
|
||||
selectedNoteSet.ValueChanged += onNoteSetChanged;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
@@ -96,22 +146,57 @@ namespace osu.Game.Screens
|
||||
Current = VirtualHitPosition,
|
||||
KeyboardStep = 0.1f,
|
||||
},
|
||||
new EzGlobalTextureNameSelector
|
||||
{
|
||||
LabelText = "(全局纹理名称)Global Texture Name",
|
||||
Current = globalTextureName,
|
||||
TooltipText = "统一修改当前皮肤中所有组件的纹理名称"
|
||||
},
|
||||
new SettingsDropdown<string>
|
||||
{
|
||||
LabelText = "(Note套图)Note Set",
|
||||
Current = selectedNoteSet,
|
||||
Items = availableNoteSets,
|
||||
TooltipText = "选择不同的Note套图,影响音符和打击光效果"
|
||||
},
|
||||
new SettingsSlider<double>
|
||||
{
|
||||
LabelText = "(note高)Note Height",
|
||||
Current = NonSquareNoteHeight,
|
||||
KeyboardStep = 0.1f,
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
Action = RefreshSkin,
|
||||
}.WithTwoLineText("(刷新并保存皮肤)", "Refresh & Save Skin")
|
||||
}.WithTwoLineText("(刷新&保存皮肤)", "Refresh & Save Skin")
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void OnSettingsValueChanged(ValueChangedEvent<double> _) => ScheduleRefresh();
|
||||
#region 追踪处理
|
||||
|
||||
private void onTextureNameChanged(ValueChangedEvent<EzSelectorNameSet> textureName)
|
||||
{
|
||||
updateAllSkinComponentsTextureNames(textureName.NewValue);
|
||||
|
||||
ezSkinConfig.SetValue(EzSkinSetting.GlobalTextureName, (int)textureName.NewValue);
|
||||
}
|
||||
|
||||
private void onNoteSetChanged(ValueChangedEvent<string> e)
|
||||
{
|
||||
ezSkinConfig.SetValue(EzSkinSetting.NoteSetName, e.NewValue);
|
||||
|
||||
if (dynamicTracking.Value)
|
||||
{
|
||||
ScheduleRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
private ScheduledDelegate? scheduledRefresh;
|
||||
|
||||
/// <summary>
|
||||
/// 使用防抖技术,延迟刷新皮肤,避免滑动滑块时频繁刷新导致卡顿
|
||||
/// </summary>
|
||||
private void onSettingsValueChanged(ValueChangedEvent<double> _) => ScheduleRefresh();
|
||||
|
||||
public void ScheduleRefresh()
|
||||
{
|
||||
scheduledRefresh?.Cancel();
|
||||
@@ -122,8 +207,105 @@ namespace osu.Game.Screens
|
||||
{
|
||||
skinManager.CurrentSkinInfo.TriggerChange();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MyRegion
|
||||
|
||||
private void updateAllSkinComponentsTextureNames(EzSelectorNameSet textureName)
|
||||
{
|
||||
var scoreTexts = this.ChildrenOfType<EzScoreText>().ToList();
|
||||
var comboTexts = this.ChildrenOfType<EzComboText>().ToList();
|
||||
|
||||
if (Parent != null)
|
||||
{
|
||||
var parentScoreTexts = Parent.ChildrenOfType<EzScoreText>().ToList();
|
||||
var parentComboTexts = Parent.ChildrenOfType<EzComboText>().ToList();
|
||||
|
||||
scoreTexts.AddRange(parentScoreTexts);
|
||||
comboTexts.AddRange(parentComboTexts);
|
||||
}
|
||||
|
||||
var root = this as Drawable;
|
||||
|
||||
while (root.Parent != null)
|
||||
{
|
||||
root = root.Parent;
|
||||
}
|
||||
|
||||
var rootScoreTexts = root.ChildrenOfType<EzScoreText>().ToList();
|
||||
var rootComboTexts = root.ChildrenOfType<EzComboText>().ToList();
|
||||
|
||||
scoreTexts.AddRange(rootScoreTexts);
|
||||
comboTexts.AddRange(rootComboTexts);
|
||||
|
||||
var hitResultScores = this.ChildrenOfType<EzComHitResultScore>().ToList();
|
||||
|
||||
if (Parent != null)
|
||||
{
|
||||
var parentHitResultScores = Parent.ChildrenOfType<EzComHitResultScore>().ToList();
|
||||
hitResultScores.AddRange(parentHitResultScores);
|
||||
}
|
||||
|
||||
var rootHitResultScores = root.ChildrenOfType<EzComHitResultScore>().ToList();
|
||||
hitResultScores.AddRange(rootHitResultScores);
|
||||
|
||||
foreach (var scoreText in scoreTexts)
|
||||
{
|
||||
scoreText.FontName.Value = textureName;
|
||||
}
|
||||
|
||||
foreach (var comboText in comboTexts)
|
||||
{
|
||||
comboText.FontName.Value = textureName;
|
||||
}
|
||||
|
||||
foreach (var hitResultScore in hitResultScores)
|
||||
{
|
||||
hitResultScore.NameDropdown.Value = textureName;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void loadAvailableNoteSets()
|
||||
{
|
||||
availableNoteSets.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
const string relative_path = @"EzResources\note";
|
||||
string dataFolderPath = storage.GetFullPath(relative_path);
|
||||
Debug.Assert(!string.IsNullOrEmpty(dataFolderPath));
|
||||
|
||||
if (!Directory.Exists(dataFolderPath))
|
||||
{
|
||||
Directory.CreateDirectory(dataFolderPath);
|
||||
Logger.Log($"EzSkinSettings create Note Path: {dataFolderPath}");
|
||||
}
|
||||
|
||||
// 获取所有子文件夹作为note套图选项
|
||||
string[] directories = Directory.GetDirectories(dataFolderPath);
|
||||
|
||||
foreach (string dir in directories)
|
||||
{
|
||||
string dirName = Path.GetFileName(dir);
|
||||
availableNoteSets.Add(dirName);
|
||||
}
|
||||
|
||||
Logger.Log($"EzSkinSettings Find {dataFolderPath} to {availableNoteSets.Count} Note Sets", LoggingTarget.Runtime, LogLevel.Debug);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "EzSkinSettings Load NoteSets Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EzGlobalTextureNameSelector : EzSelectorEnumList;
|
||||
|
||||
#region 拓展按钮的多行文本显示
|
||||
|
||||
public static class SettingsButtonExtensions
|
||||
{
|
||||
public static SettingsButton WithTwoLineText(this SettingsButton button, string topText, string bottomText, int fontSize = 14)
|
||||
@@ -173,9 +355,5 @@ namespace osu.Game.Screens
|
||||
}
|
||||
}
|
||||
|
||||
public enum EditorMode
|
||||
{
|
||||
Default,
|
||||
EzSettings
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
public LocalisableString Text
|
||||
{
|
||||
get => text.Text;
|
||||
set => text.Text = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -158,13 +158,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return;
|
||||
}
|
||||
|
||||
float relativePosition = (float)(position.Value.Value - 1) / scores.Count;
|
||||
float relativePosition = Math.Clamp((float)(position.Value.Value - 1) / Math.Max(scores.Count - 1, 1), 0, 1);
|
||||
|
||||
positionText.Current.Value = position.Value.Value;
|
||||
positionText.FadeTo(min_alpha + (max_alpha - min_alpha) * (1 - relativePosition), 1000, Easing.OutPow10);
|
||||
|
||||
localPlayerMarker.FadeIn();
|
||||
localPlayerMarker.MoveToX(marker_size / 2 + Math.Min(relativePosition * (width - marker_size / 2), width - marker_size / 2), 1000, Easing.OutPow10);
|
||||
float markerWidth = Math.Max(marker_size, width / scores.Count);
|
||||
localPlayerMarker.ResizeWidthTo(markerWidth, 1000, Easing.OutPow10);
|
||||
localPlayerMarker.MoveToX(markerWidth / 2 + (width - markerWidth) * relativePosition, 1000, Easing.OutPow10);
|
||||
}
|
||||
|
||||
private partial class PositionCounter : RollingCounter<int>
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
public partial class LAsAnalysisOptionsPopover : OsuPopover
|
||||
{
|
||||
private readonly BeatmapInfo beatmapInfo;
|
||||
// private readonly BeatmapInfo beatmapInfo;
|
||||
|
||||
public LAsAnalysisOptionsPopover(BeatmapInfo beatmapInfo)
|
||||
: base(false)
|
||||
{
|
||||
this.beatmapInfo = beatmapInfo;
|
||||
// this.beatmapInfo = beatmapInfo;
|
||||
|
||||
Body.CornerRadius = 4;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user