1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-13 08:32:57 +08:00

Merge branch 'master' into remove-hold-note-ticks

This commit is contained in:
Bartłomiej Dach 2023-10-11 19:47:51 +02:00
commit 063623b4e6
No known key found for this signature in database
46 changed files with 573 additions and 152 deletions

View File

@ -2,10 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
@ -137,5 +140,53 @@ namespace osu.Game.Rulesets.Catch.Difficulty
if (increaseCombo)
combo++;
}
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
{
bool scoreV2 = mods.Any(m => m is ModScoreV2);
double multiplier = 1.0;
foreach (var mod in mods)
{
switch (mod)
{
case CatchModNoFail:
multiplier *= scoreV2 ? 1.0 : 0.5;
break;
case CatchModEasy:
multiplier *= 0.5;
break;
case CatchModHalfTime:
case CatchModDaycore:
multiplier *= 0.3;
break;
case CatchModHidden:
multiplier *= scoreV2 ? 1.0 : 1.06;
break;
case CatchModHardRock:
multiplier *= 1.12;
break;
case CatchModDoubleTime:
case CatchModNightcore:
multiplier *= 1.06;
break;
case CatchModFlashlight:
multiplier *= 1.12;
break;
case CatchModRelax:
return 0;
}
}
return multiplier;
}
}
}

View File

@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Argon
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.TopCentre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Catch.UI;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
@ -22,6 +23,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
public DefaultCatcher()
{
Anchor = Anchor.TopCentre;
RelativeSizeAxes = Axes.Both;
InternalChild = sprite = new Sprite
{
@ -32,6 +34,15 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
};
}
protected override void Update()
{
base.Update();
// matches stable's origin position since we're using the same catcher sprite.
// see LegacyCatcher for more information.
OriginPosition = new Vector2(DrawWidth / 2, 16f);
}
[BackgroundDependencyLoader]
private void load(TextureStore store, Bindable<CatcherAnimationState> currentState)
{

View File

@ -0,0 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public abstract partial class LegacyCatcher : CompositeDrawable
{
protected LegacyCatcher()
{
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
// in stable, catcher sprites are displayed in their raw size. stable also has catcher sprites displayed with the following scale factors applied:
// 1. 0.5x, affecting all sprites in the playfield, computed here based on lazer's catch playfield dimensions (see WIDTH/HEIGHT constants in CatchPlayfield),
// source: https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/GameplayElements/HitObjectManager.cs#L483-L494
// 2. 0.7x, a constant scale applied to all catcher sprites on construction.
AutoSizeAxes = Axes.Both;
Scale = new Vector2(0.5f * 0.7f);
}
protected override void Update()
{
base.Update();
// stable sets the Y origin position of the catcher to 16px in order for the catching range and OD scaling to align with the top of the catcher's plate in the default skin.
OriginPosition = new Vector2(DrawWidth / 2, 16f);
}
}
}

View File

@ -7,14 +7,12 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public partial class LegacyCatcherNew : CompositeDrawable
public partial class LegacyCatcherNew : LegacyCatcher
{
[Resolved]
private Bindable<CatcherAnimationState> currentState { get; set; } = null!;
@ -23,25 +21,12 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
private Drawable currentDrawable = null!;
public LegacyCatcherNew()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
foreach (var state in Enum.GetValues<CatcherAnimationState>())
{
AddInternal(drawables[state] = getDrawableFor(state).With(d =>
{
d.Anchor = Anchor.TopCentre;
d.Origin = Anchor.TopCentre;
d.RelativeSizeAxes = Axes.Both;
d.Size = Vector2.One;
d.FillMode = FillMode.Fit;
d.Alpha = 0;
}));
AddInternal(drawables[state] = getDrawableFor(state).With(d => d.Alpha = 0));
}
currentDrawable = drawables[CatcherAnimationState.Idle];

View File

@ -3,30 +3,21 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public partial class LegacyCatcherOld : CompositeDrawable
public partial class LegacyCatcherOld : LegacyCatcher
{
public LegacyCatcherOld()
{
RelativeSizeAxes = Axes.Both;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
InternalChild = (skin.GetAnimation(@"fruit-ryuuta", true, true, true) ?? Empty()).With(d =>
{
d.Anchor = Anchor.TopCentre;
d.Origin = Anchor.TopCentre;
d.RelativeSizeAxes = Axes.Both;
d.Size = Vector2.One;
d.FillMode = FillMode.Fit;
});
InternalChild = skin.GetAnimation(@"fruit-ryuuta", true, true, true) ?? Empty();
}
}
}

View File

@ -17,12 +17,13 @@ namespace osu.Game.Rulesets.Catch.UI
public CatchPlayfieldAdjustmentContainer()
{
// because we are using centre anchor/origin, we will need to limit visibility in the future
// to ensure tall windows do not get a readability advantage.
// it may be possible to bake the catch-specific offsets (-100..340 mentioned below) into new values
// which are compatible with TopCentre alignment.
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
// playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable.
// we can match that in lazer by using relative coordinates for Y and considering window height to be 1, and playfield height to be 0.8.
RelativePositionAxes = Axes.Y;
Y = (1 - playfield_size_adjust) / 4 * 3;
Size = new Vector2(playfield_size_adjust);
@ -42,18 +43,28 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
private partial class ScalingContainer : Container
{
public ScalingContainer()
{
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
}
protected override void Update()
{
base.Update();
// in stable, fruit fall vertically from -100 to 340.
// to emulate this, we want to make our playfield 440 gameplay pixels high.
// we then offset it -100 vertically in the position set below.
const float stable_v_offset_ratio = 440 / 384f;
// in stable, fruit fall vertically from 100 pixels above the playfield top down to the catcher's Y position (i.e. -100 to 340),
// see: https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/GameplayElements/HitObjects/Fruits/HitCircleFruits.cs#L65
// we already have the playfield positioned similar to stable (see CatchPlayfieldAdjustmentContainer constructor),
// so we only need to increase this container's height 100 pixels above the playfield, and offset it to have the bottom at 340 rather than 384.
const float stable_fruit_start_position = -100;
const float stable_catcher_y_position = 340;
const float playfield_v_size_adjustment = (stable_catcher_y_position - stable_fruit_start_position) / CatchPlayfield.HEIGHT;
const float playfield_v_catcher_offset = stable_catcher_y_position - CatchPlayfield.HEIGHT;
Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH);
Position = new Vector2(0, -100 * stable_v_offset_ratio + Scale.X);
Size = Vector2.Divide(new Vector2(1, stable_v_offset_ratio), Scale);
Scale = new Vector2(Parent!.ChildSize.X / CatchPlayfield.WIDTH);
Position = new Vector2(0f, playfield_v_catcher_offset * Scale.Y);
Size = Vector2.Divide(new Vector2(1, playfield_v_size_adjustment), Scale);
}
}
}

View File

@ -29,6 +29,13 @@ namespace osu.Game.Rulesets.Catch.UI
/// <summary>
/// The size of the catcher at 1x scale.
/// </summary>
/// <remarks>
/// This is mainly used to compute catching range, the actual catcher size may differ based on skin implementation and sprite textures.
/// This is also equivalent to the "catcherWidth" property in osu-stable when the game field and beatmap difficulty are set to default values.
/// </remarks>
/// <seealso cref="CatchPlayfield.WIDTH"/>
/// <seealso cref="CatchPlayfield.HEIGHT"/>
/// <seealso cref="IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY"/>
public const float BASE_SIZE = 106.75f;
/// <summary>

View File

@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
@ -26,8 +25,8 @@ namespace osu.Game.Rulesets.Catch.UI
: base(new CatchSkinComponentLookup(CatchSkinComponents.Catcher), _ => new DefaultCatcher())
{
Anchor = Anchor.TopCentre;
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
OriginPosition = new Vector2(0.5f, 0.06f) * Catcher.BASE_SIZE;
Origin = Anchor.TopCentre;
CentreComponent = false;
}
}
}

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Utils;
using osuTK;
@ -43,39 +44,41 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
: base(beatmap, ruleset)
{
IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
TargetColumns = GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap));
double roundedCircleSize = Math.Round(beatmap.Difficulty.CircleSize);
double roundedOverallDifficulty = Math.Round(beatmap.Difficulty.OverallDifficulty);
if (IsForCurrentRuleset)
if (IsForCurrentRuleset && TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
{
TargetColumns = GetColumnCountForNonConvert(beatmap.BeatmapInfo);
if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
{
TargetColumns /= 2;
Dual = true;
}
}
else
{
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
TargetColumns = roundedOverallDifficulty > 5 ? 7 : 6;
else if (percentSliderOrSpinner > 0.6)
TargetColumns = roundedOverallDifficulty > 4 ? 5 : 4;
else
TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
TargetColumns /= 2;
Dual = true;
}
originalTargetColumns = TargetColumns;
}
public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo)
public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty)
{
double roundedCircleSize = Math.Round(beatmapInfo.Difficulty.CircleSize);
if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset))
return GetColumnCountForNonConvert(difficulty);
double roundedCircleSize = Math.Round(difficulty.CircleSize);
double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty);
int countSliderOrSpinner = difficulty.TotalObjectCount - difficulty.CircleCount;
float percentSpecialObjects = (float)countSliderOrSpinner / difficulty.TotalObjectCount;
if (percentSpecialObjects < 0.2)
return 7;
if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5)
return roundedOverallDifficulty > 5 ? 7 : 6;
if (percentSpecialObjects > 0.6)
return roundedOverallDifficulty > 4 ? 5 : 4;
return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
}
public static int GetColumnCountForNonConvert(IBeatmapDifficultyInfo difficulty)
{
double roundedCircleSize = Math.Round(difficulty.CircleSize);
return (int)Math.Max(1, roundedCircleSize);
}

View File

@ -1,7 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring.Legacy;
namespace osu.Game.Rulesets.Mania.Difficulty
@ -12,5 +17,50 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
return new LegacyScoreAttributes { ComboScore = 1000000 };
}
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
{
bool scoreV2 = mods.Any(m => m is ModScoreV2);
double multiplier = 1.0;
foreach (var mod in mods)
{
switch (mod)
{
case ManiaModNoFail:
multiplier *= scoreV2 ? 1.0 : 0.5;
break;
case ManiaModEasy:
multiplier *= 0.5;
break;
case ManiaModHalfTime:
case ManiaModDaycore:
multiplier *= 0.5;
break;
}
}
if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset))
return multiplier;
// Apply key mod multipliers.
int originalColumns = ManiaBeatmapConverter.GetColumnCount(difficulty);
int actualColumns = originalColumns;
actualColumns = mods.OfType<ManiaKeyMod>().SingleOrDefault()?.KeyCount ?? actualColumns;
if (mods.Any(m => m is ManiaModDualStages))
actualColumns *= 2;
if (actualColumns > originalColumns)
multiplier *= 0.9;
else if (actualColumns < originalColumns)
multiplier *= 0.9 - 0.04 * (originalColumns - actualColumns);
return multiplier;
}
}
}

View File

@ -8,6 +8,8 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
@ -23,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Edit
/// <summary>
/// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
/// </summary>
public partial class ManiaBeatSnapGrid : Component
public partial class ManiaBeatSnapGrid : CompositeComponent
{
private const double visible_range = 750;
@ -53,6 +55,8 @@ namespace osu.Game.Rulesets.Mania.Edit
private readonly List<ScrollingHitObjectContainer> grids = new List<ScrollingHitObjectContainer>();
private readonly DrawablePool<DrawableGridLine> linesPool = new DrawablePool<DrawableGridLine>(50);
private readonly Cached lineCache = new Cached();
private (double start, double end)? selectionTimeRange;
@ -60,6 +64,8 @@ namespace osu.Game.Rulesets.Mania.Edit
[BackgroundDependencyLoader]
private void load(HitObjectComposer composer)
{
AddInternal(linesPool);
foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages)
{
foreach (var column in stage.Columns)
@ -85,17 +91,10 @@ namespace osu.Game.Rulesets.Mania.Edit
}
}
private readonly Stack<DrawableGridLine> availableLines = new Stack<DrawableGridLine>();
private void createLines()
{
foreach (var grid in grids)
{
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
availableLines.Push(line);
grid.Clear();
}
if (selectionTimeRange == null)
return;
@ -131,10 +130,13 @@ namespace osu.Game.Rulesets.Mania.Edit
foreach (var grid in grids)
{
if (!availableLines.TryPop(out var line))
line = new DrawableGridLine();
var line = linesPool.Get();
line.Apply(new HitObject
{
StartTime = time
});
line.HitObject.StartTime = time;
line.Colour = colour;
grid.Add(line);

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania
public bool Matches(BeatmapInfo beatmapInfo)
{
return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo.Difficulty)));
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)

View File

@ -2,11 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;
@ -174,5 +177,58 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (increaseCombo)
combo++;
}
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
{
bool scoreV2 = mods.Any(m => m is ModScoreV2);
double multiplier = 1.0;
foreach (var mod in mods)
{
switch (mod)
{
case OsuModNoFail:
multiplier *= scoreV2 ? 1.0 : 0.5;
break;
case OsuModEasy:
multiplier *= 0.5;
break;
case OsuModHalfTime:
case OsuModDaycore:
multiplier *= 0.3;
break;
case OsuModHidden:
multiplier *= 1.06;
break;
case OsuModHardRock:
multiplier *= scoreV2 ? 1.10 : 1.06;
break;
case OsuModDoubleTime:
case OsuModNightcore:
multiplier *= scoreV2 ? 1.20 : 1.12;
break;
case OsuModFlashlight:
multiplier *= 1.12;
break;
case OsuModSpunOut:
multiplier *= 0.9;
break;
case OsuModRelax:
case OsuModAutopilot:
return 0;
}
}
return multiplier;
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private DrawableHitObject drawableObject { get; set; } = null!;
public LegacyApproachCircle()
: base("Gameplay/osu/approachcircle", OsuHitObject.OBJECT_DIMENSIONS)
: base("Gameplay/osu/approachcircle", OsuHitObject.OBJECT_DIMENSIONS * 2)
{
}

View File

@ -2,13 +2,16 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
@ -194,5 +197,47 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (increaseCombo)
combo++;
}
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
{
bool scoreV2 = mods.Any(m => m is ModScoreV2);
double multiplier = 1.0;
foreach (var mod in mods)
{
switch (mod)
{
case TaikoModNoFail:
multiplier *= scoreV2 ? 1.0 : 0.5;
break;
case TaikoModEasy:
multiplier *= 0.5;
break;
case TaikoModHalfTime:
case TaikoModDaycore:
multiplier *= 0.3;
break;
case TaikoModHidden:
case TaikoModHardRock:
multiplier *= 1.06;
break;
case TaikoModDoubleTime:
case TaikoModNightcore:
case TaikoModFlashlight:
multiplier *= 1.12;
break;
case TaikoModRelax:
return 0;
}
}
return multiplier;
}
}
}

View File

@ -33,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapVersion()
{
using (var resStream = TestResources.OpenResource("beatmap-version.osu"))
using (var resStream = TestResources.OpenResource("beatmap-version-6.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var decoder = Decoder.GetDecoder<Beatmap>(stream);
@ -45,6 +45,25 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
[TestCase(false)]
[TestCase(true)]
public void TestPreviewPointWithOffsets(bool applyOffsets)
{
using (var resStream = TestResources.OpenResource("beatmap-version-4.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var decoder = Decoder.GetDecoder<Beatmap>(stream);
((LegacyBeatmapDecoder)decoder).ApplyOffsets = applyOffsets;
var working = new TestWorkingBeatmap(decoder.Decode(stream));
Assert.AreEqual(4, working.BeatmapInfo.BeatmapVersion);
Assert.AreEqual(4, working.Beatmap.BeatmapInfo.BeatmapVersion);
Assert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapInfo.BeatmapVersion);
Assert.AreEqual(-1, working.BeatmapInfo.Metadata.PreviewTime);
}
}
[Test]
public void TestDecodeBeatmapGeneral()
{
@ -915,10 +934,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
[Test]
public void TestLegacyDefaultsPreserved()
[TestCase(false)]
[TestCase(true)]
public void TestLegacyDefaultsPreserved(bool applyOffsets)
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = applyOffsets };
using (var memoryStream = new MemoryStream())
using (var stream = new LineBufferedReader(memoryStream))

View File

@ -127,8 +127,9 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestScoreUpgradeSuccess()
[TestCase(30000002)]
[TestCase(30000003)]
public void TestScoreUpgradeSuccess(int scoreVersion)
{
ScoreInfo scoreInfo = null!;
@ -138,7 +139,7 @@ namespace osu.Game.Tests.Database
{
r.Add(scoreInfo = new ScoreInfo(ruleset: r.All<RulesetInfo>().First(), beatmap: r.All<BeatmapInfo>().First())
{
TotalScoreVersion = 30000002,
TotalScoreVersion = scoreVersion,
LegacyTotalScore = 123456,
IsLegacyScore = true,
});

View File

@ -0,0 +1,4 @@
osu file format v4
[General]
PreviewTime: -1

View File

@ -15,6 +15,9 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@ -26,6 +29,7 @@ using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@ -98,6 +102,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find<BeatmapInfo>(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed);
AddStep("reset last played", () => Realm.Write(r => r.Find<BeatmapInfo>(Beatmap.Value.BeatmapInfo.ID)!.LastPlayed = null));
AddAssert("last played is null", () => getLastPlayed() == null);
CreateTest();
@ -150,6 +155,40 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
}
[Test]
public void TestGuestScoreIsStoredAsGuest()
{
AddStep("set up API", () => ((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
case GetUserRequest userRequest:
userRequest.TriggerSuccess(new APIUser
{
Username = "Guest",
CountryCode = CountryCode.JP,
Id = 1234
});
return true;
default:
return false;
}
});
AddStep("log out", () => API.Logout());
CreateTest();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
AddStep("log back in", () => API.Login("username", "password"));
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
AddAssert("score is not associated with online user", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID))!.UserID == APIUser.SYSTEM_USER_ID);
}
[Test]
public void TestReplayExport()
{

View File

@ -11,6 +11,7 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -28,6 +29,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
});
AddStep("Start track playing", () =>
{
Beatmap.Value.Track.Start();
});
AddStep("initialise gameplay", () =>
{
Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo)
@ -37,7 +43,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);
AddAssert("gameplay clock is paused", () => player.ChildrenOfType<GameplayClockContainer>().Single().IsPaused.Value);
AddAssert("gameplay clock is not running", () => !player.ChildrenOfType<GameplayClockContainer>().Single().IsRunning);
AddStep("start gameplay", () => ((IMultiplayerClient)MultiplayerClient).GameplayStarted());
AddUntilStep("gameplay clock is not paused", () => !player.ChildrenOfType<GameplayClockContainer>().Single().IsPaused.Value);
AddAssert("gameplay clock is running", () => player.ChildrenOfType<GameplayClockContainer>().Single().IsRunning);
}
[Test]

View File

@ -84,6 +84,7 @@ namespace osu.Game.Tournament.Components
{
Colour = colours.Gray3,
RelativeSizeAxes = Axes.Both,
Alpha = 0.4f,
},
flow = new FillFlowContainer
{

View File

@ -235,7 +235,9 @@ namespace osu.Game
Logger.Log("Querying for scores that need total score conversion...");
HashSet<Guid> scoreIds = realmAccess.Run(r => new HashSet<Guid>(r.All<ScoreInfo>()
.Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null && s.TotalScoreVersion == 30000002)
.Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null
&& (s.TotalScoreVersion == 30000002
|| s.TotalScoreVersion == 30000003))
.AsEnumerable().Select(s => s.ID)));
Logger.Log($"Found {scoreIds.Count} scores which require total score conversion.");

View File

@ -196,7 +196,8 @@ namespace osu.Game.Beatmaps.Formats
break;
case @"PreviewTime":
metadata.PreviewTime = getOffsetTime(Parsing.ParseInt(pair.Value));
int time = Parsing.ParseInt(pair.Value);
metadata.PreviewTime = time == -1 ? time : getOffsetTime(time);
break;
case @"SampleSet":

View File

@ -10,7 +10,6 @@ using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.IO.Legacy;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@ -225,28 +224,30 @@ namespace osu.Game.Database
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
return ConvertFromLegacyTotalScore(score, attributes);
return ConvertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
}
/// <summary>
/// Converts from <see cref="ScoreInfo.LegacyTotalScore"/> to the new standardised scoring of <see cref="ScoreProcessor"/>.
/// </summary>
/// <param name="score">The score to convert the total score of.</param>
/// <param name="attributes">Difficulty attributes providing the legacy scoring values
/// (<see cref="DifficultyAttributes.LegacyAccuracyScore"/>, <see cref="DifficultyAttributes.LegacyComboScore"/>, and <see cref="DifficultyAttributes.LegacyBonusScoreRatio"/>)
/// for the beatmap which the score was set on.</param>
/// <param name="difficulty">The beatmap difficulty.</param>
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
/// <returns>The standardised total score.</returns>
public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyScoreAttributes attributes)
public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{
if (!score.IsLegacyScore)
return score.TotalScore;
Debug.Assert(score.LegacyTotalScore != null);
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
Ruleset ruleset = score.Ruleset.CreateInstance();
if (ruleset is not ILegacyRuleset legacyRuleset)
return score.TotalScore;
double legacyModMultiplier = legacyRuleset.CreateLegacyScoreSimulator().GetLegacyScoreMultiplier(score.Mods, difficulty);
int maximumLegacyAccuracyScore = attributes.AccuracyScore;
long maximumLegacyComboScore = (long)Math.Round(attributes.ComboScore * modMultiplier);
long maximumLegacyComboScore = (long)Math.Round(attributes.ComboScore * legacyModMultiplier);
double maximumLegacyBonusRatio = attributes.BonusScoreRatio;
// The part of total score that doesn't include bonus.
@ -258,6 +259,8 @@ namespace osu.Game.Database
// The bonus proportion makes up the rest of the score that exceeds maximumLegacyBaseScore.
double bonusProportion = Math.Max(0, ((long)score.LegacyTotalScore - maximumLegacyBaseScore) * maximumLegacyBonusRatio);
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
switch (score.Ruleset.OnlineID)
{
case 0:

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.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Scoring.Legacy
{
@ -16,5 +18,13 @@ namespace osu.Game.Rulesets.Scoring.Legacy
/// <param name="workingBeatmap">The working beatmap.</param>
/// <param name="playableBeatmap">A playable version of the beatmap for the ruleset.</param>
LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap);
/// <summary>
/// Returns the legacy score multiplier for the mods. This is only used during legacy score conversion.
/// </summary>
/// <param name="mods">The mods.</param>
/// <param name="difficulty">Extra difficulty parameters.</param>
/// <returns>The legacy multiplier.</returns>
double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty);
}
}

View File

@ -0,0 +1,67 @@
// 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.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Scoring.Legacy
{
/// <summary>
/// A set of properties that are required to facilitate beatmap conversion between legacy rulesets.
/// </summary>
public class LegacyBeatmapConversionDifficultyInfo : IBeatmapDifficultyInfo
{
/// <summary>
/// The beatmap's ruleset.
/// </summary>
public IRulesetInfo SourceRuleset { get; set; } = new RulesetInfo();
/// <summary>
/// The beatmap circle size.
/// </summary>
public float CircleSize { get; set; }
/// <summary>
/// The beatmap overall difficulty.
/// </summary>
public float OverallDifficulty { get; set; }
/// <summary>
/// The count of hitcircles in the beatmap.
/// </summary>
/// <remarks>
/// When converting from osu! ruleset beatmaps, this is equivalent to the sum of sliders and spinners in the beatmap.
/// </remarks>
public int CircleCount { get; set; }
/// <summary>
/// The total count of hitobjects in the beatmap.
/// </summary>
public int TotalObjectCount { get; set; }
float IBeatmapDifficultyInfo.DrainRate => 0;
float IBeatmapDifficultyInfo.ApproachRate => 0;
double IBeatmapDifficultyInfo.SliderMultiplier => 0;
double IBeatmapDifficultyInfo.SliderTickRate => 0;
public static LegacyBeatmapConversionDifficultyInfo FromAPIBeatmap(APIBeatmap apiBeatmap) => new LegacyBeatmapConversionDifficultyInfo
{
SourceRuleset = apiBeatmap.Ruleset,
CircleSize = apiBeatmap.CircleSize,
OverallDifficulty = apiBeatmap.OverallDifficulty,
CircleCount = apiBeatmap.CircleCount,
TotalObjectCount = apiBeatmap.SliderCount + apiBeatmap.SpinnerCount + apiBeatmap.CircleCount
};
public static LegacyBeatmapConversionDifficultyInfo FromBeatmap(IBeatmap beatmap) => new LegacyBeatmapConversionDifficultyInfo
{
SourceRuleset = beatmap.BeatmapInfo.Ruleset,
CircleSize = beatmap.Difficulty.CircleSize,
OverallDifficulty = beatmap.Difficulty.OverallDifficulty,
CircleCount = beatmap.HitObjects.Count(h => h is not IHasDuration),
TotalObjectCount = beatmap.HitObjects.Count
};
}
}

View File

@ -30,9 +30,10 @@ namespace osu.Game.Scoring.Legacy
/// <item><description>30000001: Appends <see cref="LegacyReplaySoloScoreInfo"/> to the end of scores.</description></item>
/// <item><description>30000002: Score stored to replay calculated using the Score V2 algorithm. Legacy scores on this version are candidate to Score V1 -> V2 conversion.</description></item>
/// <item><description>30000003: First version after converting legacy total score to standardised.</description></item>
/// <item><description>30000004: Fixed mod multipliers during legacy score conversion. Reconvert all scores.</description></item>
/// </list>
/// </remarks>
public const int LATEST_VERSION = 30000003;
public const int LATEST_VERSION = 30000004;
/// <summary>
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.

View File

@ -190,6 +190,9 @@ namespace osu.Game.Scoring
/// </summary>
private void populateUserDetails(ScoreInfo model)
{
if (model.RealmUser.OnlineID == APIUser.SYSTEM_USER_ID)
return;
string username = model.RealmUser.Username;
if (usernameLookupCache.TryGetValue(username, out var existing))

View File

@ -456,6 +456,7 @@ namespace osu.Game.Screens.OnlinePlay
private IEnumerable<Drawable> createButtons() => new[]
{
beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap),
showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie)
{
Size = new Vector2(30, 30),
@ -463,7 +464,6 @@ namespace osu.Game.Screens.OnlinePlay
Alpha = AllowShowingResults ? 1 : 0,
TooltipText = "View results"
},
beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap),
editButton = new PlaylistEditButton
{
Size = new Vector2(30, 30),

View File

@ -148,6 +148,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
loadingDisplay.Show();
client.ChangeState(MultiplayerUserState.ReadyForGameplay);
}
// This will pause the clock, pending the gameplay started callback from the server.
GameplayClockContainer.Reset();
}
private void failAndBail(string message = null)

View File

@ -138,12 +138,16 @@ namespace osu.Game.Screens.Play
/// Resets this <see cref="GameplayClockContainer"/> and the source to an initial state ready for gameplay.
/// </summary>
/// <param name="time">The time to seek to on resetting. If <c>null</c>, the existing <see cref="StartTime"/> will be used.</param>
/// <param name="startClock">Whether to start the clock immediately, if not already started.</param>
/// <param name="startClock">Whether to start the clock immediately. If <c>false</c> and the clock was already paused, the clock will remain paused after this call.
/// </param>
public void Reset(double? time = null, bool startClock = false)
{
bool wasPaused = isPaused.Value;
Stop();
// The intention of the Reset method is to get things into a known sane state.
// As such, we intentionally stop the underlying clock directly here, bypassing Stop/StopGameplayClock.
// This is to avoid any kind of isPaused state checks and frequency ramping (as provided by MasterGameplayClockContainer).
GameplayClock.Stop();
if (time != null)
StartTime = time.Value;

View File

@ -15,8 +15,6 @@ using osu.Framework.Layout;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
@ -75,7 +73,7 @@ namespace osu.Game.Screens.Play.HUD
return;
glowBarValue = value;
updatePathVertices();
Scheduler.AddOnce(updatePathVertices);
}
}
@ -90,7 +88,7 @@ namespace osu.Game.Screens.Play.HUD
return;
healthBarValue = value;
updatePathVertices();
Scheduler.AddOnce(updatePathVertices);
}
}
@ -114,7 +112,7 @@ namespace osu.Game.Screens.Play.HUD
glowBar = new BarPath
{
BarColour = Color4.White,
GlowColour = OsuColour.Gray(0.5f),
GlowColour = main_bar_glow_colour,
Blending = BlendingParameters.Additive,
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White),
PathRadius = 40f,
@ -140,17 +138,7 @@ namespace osu.Game.Screens.Play.HUD
{
base.LoadComplete();
Current.BindValueChanged(v =>
{
if (v.NewValue >= GlowBarValue)
finishMissDisplay();
double time = v.NewValue > GlowBarValue ? 500 : 250;
this.TransformTo(nameof(HealthBarValue), v.NewValue, time, Easing.OutQuint);
if (resetMissBarDelegate == null)
this.TransformTo(nameof(GlowBarValue), v.NewValue, time, Easing.OutQuint);
}, true);
Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true);
BarLength.BindValueChanged(l => Width = l.NewValue, true);
BarHeight.BindValueChanged(_ => updatePath());
@ -165,6 +153,17 @@ namespace osu.Game.Screens.Play.HUD
return base.OnInvalidate(invalidation, source);
}
private void updateCurrent()
{
if (Current.Value >= GlowBarValue) finishMissDisplay();
double time = Current.Value > GlowBarValue ? 500 : 250;
// TODO: this should probably use interpolation in update.
this.TransformTo(nameof(HealthBarValue), Current.Value, time, Easing.OutQuint);
if (resetMissBarDelegate == null) this.TransformTo(nameof(GlowBarValue), Current.Value, time, Easing.OutQuint);
}
protected override void Update()
{
base.Update();
@ -173,27 +172,28 @@ namespace osu.Game.Screens.Play.HUD
glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed);
}
protected override void Flash(JudgementResult result)
protected override void Flash()
{
base.Flash(result);
base.Flash();
mainBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f))
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);
if (resetMissBarDelegate == null)
{
glowBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 100, Easing.OutQuint)
glowBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 30, Easing.OutQuint)
.Then()
.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 800, Easing.OutQuint);
.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 1000, Easing.OutQuint);
glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White)
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 800, Easing.OutQuint);
glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 30, Easing.OutQuint)
.Then()
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);
}
}
protected override void Miss(JudgementResult result)
protected override void Miss()
{
base.Miss(result);
base.Miss();
if (resetMissBarDelegate != null)
{
@ -221,6 +221,9 @@ namespace osu.Game.Screens.Play.HUD
private void finishMissDisplay()
{
if (resetMissBarDelegate == null)
return;
if (Current.Value > 0)
{
glowBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In);

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
@ -112,6 +111,13 @@ namespace osu.Game.Screens.Play.HUD
};
}
protected override void Flash()
{
fill.FadeEdgeEffectTo(Math.Min(1, fill.EdgeEffect.Colour.Linear.A + (1f - base_glow_opacity) / glow_max_hits), 50, Easing.OutQuint)
.Delay(glow_fade_delay)
.FadeEdgeEffectTo(base_glow_opacity, glow_fade_time, Easing.OutQuint);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@ -119,15 +125,6 @@ namespace osu.Game.Screens.Play.HUD
GlowColour = colours.BlueDarker;
}
protected override void Flash(JudgementResult result) => Scheduler.AddOnce(flash);
private void flash()
{
fill.FadeEdgeEffectTo(Math.Min(1, fill.EdgeEffect.Colour.Linear.A + (1f - base_glow_opacity) / glow_max_hits), 50, Easing.OutQuint)
.Delay(glow_fade_delay)
.FadeEdgeEffectTo(base_glow_opacity, glow_fade_time, Easing.OutQuint);
}
protected override void Update()
{
base.Update();

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@ -39,17 +38,17 @@ namespace osu.Game.Screens.Play.HUD
/// <summary>
/// Triggered when a <see cref="Judgement"/> is a successful hit, signaling the health display to perform a flash animation (if designed to do so).
/// Calls to this method are debounced.
/// </summary>
/// <param name="result">The judgement result.</param>
protected virtual void Flash(JudgementResult result)
protected virtual void Flash()
{
}
/// <summary>
/// Triggered when a <see cref="Judgement"/> resulted in the player losing health.
/// Calls to this method are debounced.
/// </summary>
/// <param name="result">The judgement result.</param>
protected virtual void Miss(JudgementResult result)
protected virtual void Miss()
{
}
@ -92,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD
{
double newValue = Current.Value + 0.05f;
this.TransformBindableTo(Current, newValue, increase_delay);
Flash(new JudgementResult(new HitObject(), new Judgement()));
Scheduler.AddOnce(Flash);
if (newValue >= 1)
finishInitialAnimation();
@ -101,6 +100,9 @@ namespace osu.Game.Screens.Play.HUD
private void finishInitialAnimation()
{
if (initialIncrease == null)
return;
initialIncrease?.Cancel();
initialIncrease = null;
@ -115,9 +117,9 @@ namespace osu.Game.Screens.Play.HUD
private void onNewJudgement(JudgementResult judgement)
{
if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit)
Flash(judgement);
Scheduler.AddOnce(Flash);
else if (judgement.Judgement.HealthIncreaseFor(judgement) < 0)
Miss(judgement);
Scheduler.AddOnce(Miss);
}
protected override void Dispose(bool isDisposing)

View File

@ -1078,7 +1078,7 @@ namespace osu.Game.Screens.Play
protected virtual void StartGameplay()
{
if (GameplayClockContainer.IsRunning)
throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running");
Logger.Error(new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running"), "Clock failure");
GameplayClockContainer.Reset(startClock: true);

View File

@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Utils;
using osu.Game.Rulesets.Judgements;
using osu.Game.Screens.Play.HUD;
using osu.Game.Utils;
using osuTK;
@ -80,7 +79,7 @@ namespace osu.Game.Skinning
marker.Position = fill.Position + new Vector2(fill.DrawWidth, isNewStyle ? fill.DrawHeight / 2 : 0);
}
protected override void Flash(JudgementResult result) => marker.Flash(result);
protected override void Flash() => marker.Flash();
private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"scorebar-{name}");
@ -238,7 +237,7 @@ namespace osu.Game.Skinning
});
}
public override void Flash(JudgementResult result)
public override void Flash()
{
bulgeMain();
@ -257,7 +256,7 @@ namespace osu.Game.Skinning
{
public Bindable<double> Current { get; } = new Bindable<double>();
public virtual void Flash(JudgementResult result)
public virtual void Flash()
{
}
}