大更新,可自定义note及配套特效,同步官方更新等等

This commit is contained in:
LA
2025-05-25 03:02:17 +08:00
parent 4527efd521
commit 6eee9dba80
125 changed files with 2467 additions and 781 deletions

View File

@@ -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();

View File

@@ -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);

View File

@@ -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());
}

View File

@@ -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;
}
});
}

View File

@@ -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)

View 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());
}
}
}

View File

@@ -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,
}
}

View File

@@ -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));
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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

View File

@@ -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>
{

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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--;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)

View File

@@ -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 =>

View File

@@ -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)

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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
{
}
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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 =>

View File

@@ -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();
}
}
}
}

View File

@@ -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 =>
{

View File

@@ -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();

View File

@@ -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;

View File

@@ -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,
}
}

View File

@@ -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;
});
}
}
}

View File

@@ -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(),
});

View File

@@ -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])));
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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])));
}

View File

@@ -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; }

View File

@@ -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);
}
}
}

View File

@@ -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));
}

View File

@@ -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 }
);
});

View File

@@ -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);
}
}
}

View File

@@ -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; }

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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,

View File

@@ -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
{

View File

@@ -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()

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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());

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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();
}
}

View 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; }
}
}

View File

@@ -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;

View File

@@ -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));
}
}
}

View File

@@ -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()

View File

@@ -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;
});

View File

@@ -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
}
}

View File

@@ -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++;

View File

@@ -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>

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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
{

View File

@@ -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");

View File

@@ -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),

View File

@@ -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)

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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>

View File

@@ -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()
{

View File

@@ -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;

View File

@@ -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)

View File

@@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.UI
/// </summary>
public interface IHasRecordingHandler
{
public ReplayRecorder? Recorder { set; }
public ReplayRecorder? Recorder { get; set; }
}
}

View File

@@ -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);
}
}

View File

@@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.UI
public ReplayRecorder? Recorder
{
get => recorder;
set
{
if (value == recorder)

View File

@@ -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;
}

View File

@@ -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));

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;
}
}
}

View File

@@ -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
}

View File

@@ -54,6 +54,7 @@ namespace osu.Game.Screens.Footer
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
}

View File

@@ -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>

View File

@@ -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