mirror of
https://github.com/ppy/osu.git
synced 2026-05-13 23:23:32 +08:00
Compare commits
19 Commits
pp-dev
...
2024.1115.1
@@ -523,7 +523,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("restart completed", () => getCurrentPlayer() != null && getCurrentPlayer() != previousPlayer);
|
||||
AddStep("release quick retry key", () => InputManager.ReleaseKey(Key.Tilde));
|
||||
|
||||
AddUntilStep("wait for player", () => getCurrentPlayer()?.LoadState == LoadState.Ready);
|
||||
AddUntilStep("wait for player", () => getCurrentPlayer()?.LoadState >= LoadState.Ready);
|
||||
|
||||
AddUntilStep("time reached zero", () => getCurrentPlayer()?.GameplayClockContainer.CurrentTime > 0);
|
||||
AddUntilStep("skip button not visible", () => !checkSkipButtonVisible());
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected partial class OutroPlayer : TestPlayer
|
||||
{
|
||||
public void ExitViaPause() => PerformExit(true);
|
||||
public void ExitViaPause() => PerformExitWithConfirmation();
|
||||
|
||||
public new FailOverlay FailOverlay => base.FailOverlay;
|
||||
|
||||
|
||||
@@ -40,6 +40,13 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddSliderStep("height", 0.0f, 1000.0f, height.Value, height.Set);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestZeroEvents()
|
||||
{
|
||||
createTest(new List<HitEvent>());
|
||||
AddStep("update offset", () => graph.UpdateOffset(10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManyDistributedEventsOffset()
|
||||
{
|
||||
|
||||
@@ -1,12 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -23,7 +20,6 @@ using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
@@ -35,15 +31,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapInfoWedge : OsuTestScene
|
||||
{
|
||||
private RulesetStore rulesets;
|
||||
private TestBeatmapInfoWedge infoWedge;
|
||||
private readonly List<IBeatmap> beatmaps = new List<IBeatmap>();
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets)
|
||||
{
|
||||
this.rulesets = rulesets;
|
||||
}
|
||||
private TestBeatmapInfoWedge infoWedge = null!;
|
||||
private readonly List<IBeatmap> beatmaps = new List<IBeatmap>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
@@ -156,7 +148,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm });
|
||||
|
||||
OsuModDoubleTime doubleTime = null;
|
||||
OsuModDoubleTime doubleTime = null!;
|
||||
|
||||
selectBeatmap(beatmap);
|
||||
checkDisplayedBPM($"{bpm}");
|
||||
@@ -173,7 +165,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[TestCase(120, 120.4, null, "120")]
|
||||
[TestCase(120, 120.6, "DT", "180-182 (mostly 180)")]
|
||||
[TestCase(120, 120.4, "DT", "180")]
|
||||
public void TestVaryingBPM(double commonBpm, double otherBpm, string mod, string expectedDisplay)
|
||||
public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay)
|
||||
{
|
||||
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm });
|
||||
@@ -203,7 +195,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
double drain = beatmap.CalculateDrainLength();
|
||||
beatmap.BeatmapInfo.Length = drain;
|
||||
|
||||
OsuModDoubleTime doubleTime = null;
|
||||
OsuModDoubleTime doubleTime = null!;
|
||||
|
||||
selectBeatmap(beatmap);
|
||||
checkDisplayedLength(drain);
|
||||
@@ -221,14 +213,15 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddUntilStep($"check map drain ({displayedLength})", () =>
|
||||
{
|
||||
var label = infoWedge.DisplayedContent.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength));
|
||||
var label = infoWedge.DisplayedContent.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>()
|
||||
.Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength));
|
||||
return label.Statistic.Content == displayedLength.ToString();
|
||||
});
|
||||
}
|
||||
|
||||
private void setRuleset(RulesetInfo rulesetInfo)
|
||||
{
|
||||
Container containerBefore = null;
|
||||
Container? containerBefore = null;
|
||||
|
||||
AddStep("set ruleset", () =>
|
||||
{
|
||||
@@ -242,9 +235,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
|
||||
}
|
||||
|
||||
private void selectBeatmap([CanBeNull] IBeatmap b)
|
||||
private void selectBeatmap(IBeatmap? b)
|
||||
{
|
||||
Container containerBefore = null;
|
||||
Container? containerBefore = null;
|
||||
|
||||
AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () =>
|
||||
{
|
||||
@@ -307,11 +300,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
public new WedgeInfoText Info => base.Info;
|
||||
}
|
||||
|
||||
private class TestHitObject : ConvertHitObject, IHasPosition
|
||||
{
|
||||
public float X => 0;
|
||||
public float Y => 0;
|
||||
public Vector2 Position { get; } = Vector2.Zero;
|
||||
}
|
||||
private class TestHitObject : ConvertHitObject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
@@ -209,11 +207,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
public new WedgeInfoText? Info => base.Info;
|
||||
}
|
||||
|
||||
private class TestHitObject : ConvertHitObject, IHasPosition
|
||||
{
|
||||
public float X => 0;
|
||||
public float Y => 0;
|
||||
public Vector2 Position { get; } = Vector2.Zero;
|
||||
}
|
||||
private class TestHitObject : ConvertHitObject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
internal static RulesetStore? RulesetStore;
|
||||
|
||||
private Beatmap beatmap = null!;
|
||||
|
||||
private ConvertHitObjectParser? parser;
|
||||
private ConvertHitObjectParser parser = null!;
|
||||
|
||||
private LegacySampleBank defaultSampleBank;
|
||||
private int defaultSampleVolume = 100;
|
||||
@@ -80,6 +79,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion;
|
||||
parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
|
||||
applyLegacyDefaults(this.beatmap.BeatmapInfo);
|
||||
|
||||
@@ -162,7 +162,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
if (hitObject is IHasRepeats hasRepeats)
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) ?? SampleControlPoint.DEFAULT;
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1)
|
||||
?? SampleControlPoint.DEFAULT;
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
|
||||
@@ -175,7 +176,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
else
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) ?? SampleControlPoint.DEFAULT;
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY)
|
||||
?? SampleControlPoint.DEFAULT;
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
}
|
||||
}
|
||||
@@ -263,29 +265,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"Mode":
|
||||
int rulesetID = Parsing.ParseInt(pair.Value);
|
||||
|
||||
beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(rulesetID) ?? throw new ArgumentException("Ruleset is not available locally.");
|
||||
|
||||
switch (rulesetID)
|
||||
{
|
||||
case 0:
|
||||
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
break;
|
||||
}
|
||||
|
||||
beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(Parsing.ParseInt(pair.Value)) ?? throw new ArgumentException("Ruleset is not available locally.");
|
||||
break;
|
||||
|
||||
case @"LetterboxInBreaks":
|
||||
@@ -617,17 +597,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
private void handleHitObject(string line)
|
||||
{
|
||||
// If the ruleset wasn't specified, assume the osu!standard ruleset.
|
||||
parser ??= new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
|
||||
var obj = parser.Parse(line);
|
||||
obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
beatmap.HitObjects.Add(obj);
|
||||
}
|
||||
beatmap.HitObjects.Add(obj);
|
||||
}
|
||||
|
||||
private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0);
|
||||
|
||||
@@ -6,7 +6,7 @@ using System;
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
{
|
||||
[Flags]
|
||||
internal enum LegacyHitObjectType
|
||||
public enum LegacyHitObjectType
|
||||
{
|
||||
Circle = 1,
|
||||
Slider = 1 << 1,
|
||||
|
||||
@@ -74,6 +74,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
return 0.0;
|
||||
|
||||
double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical
|
||||
|
||||
if (consistentTopStrain == 0)
|
||||
return ObjectStrains.Count;
|
||||
|
||||
// Use a weighted sum of all strains. Constants are arbitrary and give nice values
|
||||
return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88))));
|
||||
}
|
||||
|
||||
@@ -1,20 +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 osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!catch Hit-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertHit : ConvertHitObject, IHasPosition
|
||||
{
|
||||
public float X => Position.X;
|
||||
|
||||
public float Y => Position.Y;
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,63 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osuTK;
|
||||
using osu.Game.Audio;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObjectParser to parse legacy osu!catch Beatmaps.
|
||||
/// </summary>
|
||||
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
|
||||
{
|
||||
private ConvertHitObject lastObject;
|
||||
|
||||
public ConvertHitObjectParser(double offset, int formatVersion)
|
||||
: base(offset, formatVersion)
|
||||
{
|
||||
}
|
||||
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
return lastObject = new ConvertHit
|
||||
{
|
||||
Position = position,
|
||||
NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo,
|
||||
ComboOffset = newCombo ? comboOffset : 0
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
return lastObject = new ConvertSlider
|
||||
{
|
||||
Position = position,
|
||||
NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo,
|
||||
ComboOffset = newCombo ? comboOffset : 0,
|
||||
Path = new SliderPath(controlPoints, length),
|
||||
NodeSamples = nodeSamples,
|
||||
RepeatCount = repeatCount
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return lastObject = new ConvertSpinner
|
||||
{
|
||||
Duration = duration,
|
||||
NewCombo = newCombo
|
||||
// Spinners cannot have combo offset.
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return lastObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +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 osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!catch Slider-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition
|
||||
{
|
||||
public float X => Position.X;
|
||||
|
||||
public float Y => Position.Y;
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +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 osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!catch Spinner-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition
|
||||
{
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public double Duration { get; set; }
|
||||
|
||||
public float X => 256; // Required for CatchBeatmapConverter
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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.
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy "HitCircle" hit object type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for parsing beatmaps and not gameplay.
|
||||
/// </remarks>
|
||||
internal sealed class ConvertHitCircle : ConvertHitObject;
|
||||
}
|
||||
@@ -1,21 +1,34 @@
|
||||
// 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.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// A hit object only used for conversion, not actual gameplay.
|
||||
/// Represents a legacy hit object.
|
||||
/// </summary>
|
||||
internal abstract class ConvertHitObject : HitObject, IHasCombo
|
||||
/// <remarks>
|
||||
/// Only used for parsing beatmaps and not gameplay.
|
||||
/// </remarks>
|
||||
internal abstract class ConvertHitObject : HitObject, IHasCombo, IHasPosition, IHasLegacyHitObjectType
|
||||
{
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
|
||||
public float X => Position.X;
|
||||
|
||||
public float Y => Position.Y;
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public LegacyHitObjectType LegacyType { get; set; }
|
||||
|
||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
@@ -1,8 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using System;
|
||||
@@ -11,7 +9,6 @@ using System.IO;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Audio;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
@@ -24,24 +21,32 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <summary>
|
||||
/// A HitObjectParser to parse legacy Beatmaps.
|
||||
/// </summary>
|
||||
public abstract class ConvertHitObjectParser : HitObjectParser
|
||||
public class ConvertHitObjectParser : HitObjectParser
|
||||
{
|
||||
/// <summary>
|
||||
/// The offset to apply to all time values.
|
||||
/// </summary>
|
||||
protected readonly double Offset;
|
||||
private readonly double offset;
|
||||
|
||||
/// <summary>
|
||||
/// The .osu format (beatmap) version.
|
||||
/// </summary>
|
||||
protected readonly int FormatVersion;
|
||||
private readonly int formatVersion;
|
||||
|
||||
protected bool FirstObject { get; private set; } = true;
|
||||
/// <summary>
|
||||
/// Whether the current hitobject is the first hitobject in the beatmap.
|
||||
/// </summary>
|
||||
private bool firstObject = true;
|
||||
|
||||
protected ConvertHitObjectParser(double offset, int formatVersion)
|
||||
/// <summary>
|
||||
/// The last parsed hitobject.
|
||||
/// </summary>
|
||||
private ConvertHitObject? lastObject;
|
||||
|
||||
internal ConvertHitObjectParser(double offset, int formatVersion)
|
||||
{
|
||||
Offset = offset;
|
||||
FormatVersion = formatVersion;
|
||||
this.offset = offset;
|
||||
this.formatVersion = formatVersion;
|
||||
}
|
||||
|
||||
public override HitObject Parse(string text)
|
||||
@@ -49,11 +54,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
string[] split = text.Split(',');
|
||||
|
||||
Vector2 pos =
|
||||
FormatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION
|
||||
formatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION
|
||||
? new Vector2(Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE))
|
||||
: new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));
|
||||
|
||||
double startTime = Parsing.ParseDouble(split[2]) + Offset;
|
||||
double startTime = Parsing.ParseDouble(split[2]) + offset;
|
||||
|
||||
LegacyHitObjectType type = (LegacyHitObjectType)Parsing.ParseInt(split[3]);
|
||||
|
||||
@@ -66,11 +71,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
|
||||
var bankInfo = new SampleBankInfo();
|
||||
|
||||
HitObject result = null;
|
||||
ConvertHitObject? result = null;
|
||||
|
||||
if (type.HasFlag(LegacyHitObjectType.Circle))
|
||||
{
|
||||
result = CreateHit(pos, combo, comboOffset);
|
||||
result = createHitCircle(pos, combo, comboOffset);
|
||||
|
||||
if (split.Length > 5)
|
||||
readCustomSampleBanks(split[5], bankInfo);
|
||||
@@ -145,13 +150,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
|
||||
|
||||
result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
|
||||
result = createSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
|
||||
}
|
||||
else if (type.HasFlag(LegacyHitObjectType.Spinner))
|
||||
{
|
||||
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);
|
||||
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + offset - startTime);
|
||||
|
||||
result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration);
|
||||
result = createSpinner(new Vector2(512, 384) / 2, combo, duration);
|
||||
|
||||
if (split.Length > 6)
|
||||
readCustomSampleBanks(split[6], bankInfo);
|
||||
@@ -169,18 +174,19 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo);
|
||||
}
|
||||
|
||||
result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime);
|
||||
result = createHold(pos, endTime + offset - startTime);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
throw new InvalidDataException($"Unknown hit object type: {split[3]}");
|
||||
|
||||
result.StartTime = startTime;
|
||||
result.LegacyType = type;
|
||||
|
||||
if (result.Samples.Count == 0)
|
||||
result.Samples = convertSoundType(soundType, bankInfo);
|
||||
|
||||
FirstObject = false;
|
||||
firstObject = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -200,10 +206,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
if (!Enum.IsDefined(addBank))
|
||||
addBank = LegacySampleBank.Normal;
|
||||
|
||||
string stringBank = bank.ToString().ToLowerInvariant();
|
||||
string? stringBank = bank.ToString().ToLowerInvariant();
|
||||
string? stringAddBank = addBank.ToString().ToLowerInvariant();
|
||||
|
||||
if (stringBank == @"none")
|
||||
stringBank = null;
|
||||
string stringAddBank = addBank.ToString().ToLowerInvariant();
|
||||
|
||||
if (stringAddBank == @"none")
|
||||
{
|
||||
@@ -357,7 +364,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
int endPointLength = endPoint == null ? 0 : 1;
|
||||
|
||||
if (FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
|
||||
if (formatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
|
||||
{
|
||||
if (vertices.Length + endPointLength != 3)
|
||||
type = PathType.BEZIER;
|
||||
@@ -393,7 +400,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
// Legacy CATMULL sliders don't support multiple segments, so adjacent CATMULL segments should be treated as a single one.
|
||||
// Importantly, this is not applied to the first control point, which may duplicate the slider path's position
|
||||
// resulting in a duplicate (0,0) control point in the resultant list.
|
||||
if (type == PathType.CATMULL && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
|
||||
if (type == PathType.CATMULL && endIndex > 1 && formatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
|
||||
continue;
|
||||
|
||||
// The last control point of each segment is not allowed to start a new implicit segment.
|
||||
@@ -442,7 +449,15 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <returns>The hit object.</returns>
|
||||
protected abstract HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset);
|
||||
private ConvertHitObject createHitCircle(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
return lastObject = new ConvertHitCircle
|
||||
{
|
||||
Position = position,
|
||||
NewCombo = firstObject || lastObject is ConvertSpinner || newCombo,
|
||||
ComboOffset = newCombo ? comboOffset : 0
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creats a legacy Slider-type hit object.
|
||||
@@ -455,27 +470,51 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <param name="repeatCount">The slider repeat count.</param>
|
||||
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
|
||||
/// <returns>The hit object.</returns>
|
||||
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
IList<IList<HitSampleInfo>> nodeSamples);
|
||||
private ConvertHitObject createSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
return lastObject = new ConvertSlider
|
||||
{
|
||||
Position = position,
|
||||
NewCombo = firstObject || lastObject is ConvertSpinner || newCombo,
|
||||
ComboOffset = newCombo ? comboOffset : 0,
|
||||
Path = new SliderPath(controlPoints, length),
|
||||
NodeSamples = nodeSamples,
|
||||
RepeatCount = repeatCount
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a legacy Spinner-type hit object.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the hit object.</param>
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <param name="duration">The spinner duration.</param>
|
||||
/// <returns>The hit object.</returns>
|
||||
protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration);
|
||||
private ConvertHitObject createSpinner(Vector2 position, bool newCombo, double duration)
|
||||
{
|
||||
return lastObject = new ConvertSpinner
|
||||
{
|
||||
Position = position,
|
||||
Duration = duration,
|
||||
NewCombo = newCombo
|
||||
// Spinners cannot have combo offset.
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a legacy Hold-type hit object.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the hit object.</param>
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <param name="duration">The hold duration.</param>
|
||||
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration);
|
||||
private ConvertHitObject createHold(Vector2 position, double duration)
|
||||
{
|
||||
return lastObject = new ConvertHold
|
||||
{
|
||||
Position = position,
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
|
||||
private List<HitSampleInfo> convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo)
|
||||
{
|
||||
@@ -511,21 +550,19 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <summary>
|
||||
/// An optional overriding filename which causes all bank/sample specifications to be ignored.
|
||||
/// </summary>
|
||||
public string Filename;
|
||||
public string? Filename;
|
||||
|
||||
/// <summary>
|
||||
/// The bank identifier to use for the base ("hitnormal") sample.
|
||||
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
public string BankForNormal;
|
||||
public string? BankForNormal;
|
||||
|
||||
/// <summary>
|
||||
/// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap").
|
||||
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
public string BankForAdditions;
|
||||
public string? BankForAdditions;
|
||||
|
||||
/// <summary>
|
||||
/// Hit sample volume (0-100).
|
||||
@@ -548,8 +585,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class LegacyHitSampleInfo : HitSampleInfo, IEquatable<LegacyHitSampleInfo>
|
||||
{
|
||||
public readonly int CustomSampleBank;
|
||||
@@ -577,13 +612,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
IsLayered = isLayered;
|
||||
}
|
||||
|
||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
|
||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default,
|
||||
Optional<bool> newEditorAutoBank = default)
|
||||
=> With(newName, newBank, newVolume, newEditorAutoBank);
|
||||
|
||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default,
|
||||
Optional<int> newCustomSampleBank = default,
|
||||
Optional<bool> newIsLayered = default)
|
||||
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
|
||||
Optional<bool> newEditorAutoBank = default, Optional<int> newCustomSampleBank = default, Optional<bool> newIsLayered = default)
|
||||
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank),
|
||||
newIsLayered.GetOr(IsLayered));
|
||||
|
||||
public bool Equals(LegacyHitSampleInfo? other)
|
||||
// The additions to equality checks here are *required* to ensure that pooling works correctly.
|
||||
@@ -615,9 +651,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
Path.ChangeExtension(Filename, null)
|
||||
};
|
||||
|
||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default,
|
||||
Optional<int> newCustomSampleBank = default,
|
||||
Optional<bool> newIsLayered = default)
|
||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
|
||||
Optional<bool> newEditorAutoBank = default, Optional<int> newCustomSampleBank = default, Optional<bool> newIsLayered = default)
|
||||
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
||||
|
||||
public bool Equals(FileHitSampleInfo? other)
|
||||
|
||||
+6
-5
@@ -3,17 +3,18 @@
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!mania Spinner-type, used for parsing Beatmaps.
|
||||
/// Legacy "Hold" hit object type. Generally only valid in the mania ruleset.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition
|
||||
/// <remarks>
|
||||
/// Only used for parsing beatmaps and not gameplay.
|
||||
/// </remarks>
|
||||
internal sealed class ConvertHold : ConvertHitObject, IHasDuration
|
||||
{
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public float X { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
@@ -13,7 +11,13 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity
|
||||
/// <summary>
|
||||
/// Legacy "Slider" hit object type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for parsing beatmaps and not gameplay.
|
||||
/// </remarks>
|
||||
internal class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks
|
||||
{
|
||||
/// <summary>
|
||||
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
||||
@@ -50,6 +54,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
set => SliderVelocityMultiplierBindable.Value = value;
|
||||
}
|
||||
|
||||
public bool GenerateTicks { get; set; } = true;
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
+5
-2
@@ -3,11 +3,14 @@
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!taiko Spinner-type, used for parsing Beatmaps.
|
||||
/// Legacy "Spinner" hit object type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for parsing beatmaps and not gameplay.
|
||||
/// </remarks>
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration
|
||||
{
|
||||
public double Duration { get; set; }
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Game.Beatmaps.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// A hit object from a legacy beatmap representation.
|
||||
/// </summary>
|
||||
public interface IHasLegacyHitObjectType
|
||||
{
|
||||
/// <summary>
|
||||
/// The hit object type.
|
||||
/// </summary>
|
||||
LegacyHitObjectType LegacyType { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +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 osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!mania Hit-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertHit : ConvertHitObject, IHasXPosition
|
||||
{
|
||||
public float X { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,58 +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 osuTK;
|
||||
using osu.Game.Audio;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObjectParser to parse legacy osu!mania Beatmaps.
|
||||
/// </summary>
|
||||
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
|
||||
{
|
||||
public ConvertHitObjectParser(double offset, int formatVersion)
|
||||
: base(offset, formatVersion)
|
||||
{
|
||||
}
|
||||
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
return new ConvertHit
|
||||
{
|
||||
X = position.X
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
return new ConvertSlider
|
||||
{
|
||||
X = position.X,
|
||||
Path = new SliderPath(controlPoints, length),
|
||||
NodeSamples = nodeSamples,
|
||||
RepeatCount = repeatCount
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return new ConvertSpinner
|
||||
{
|
||||
X = position.X,
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return new ConvertHold
|
||||
{
|
||||
X = position.X,
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +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 osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
{
|
||||
internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration
|
||||
{
|
||||
public float X { get; set; }
|
||||
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double EndTime => StartTime + Duration;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +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 osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!mania Slider-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition
|
||||
{
|
||||
public float X { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +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 osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu! Hit-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertHit : ConvertHitObject, IHasPosition
|
||||
{
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public float X => Position.X;
|
||||
|
||||
public float Y => Position.Y;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osuTK;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Audio;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObjectParser to parse legacy osu! Beatmaps.
|
||||
/// </summary>
|
||||
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
|
||||
{
|
||||
private ConvertHitObject lastObject;
|
||||
|
||||
public ConvertHitObjectParser(double offset, int formatVersion)
|
||||
: base(offset, formatVersion)
|
||||
{
|
||||
}
|
||||
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
return lastObject = new ConvertHit
|
||||
{
|
||||
Position = position,
|
||||
NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo,
|
||||
ComboOffset = newCombo ? comboOffset : 0
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
return lastObject = new ConvertSlider
|
||||
{
|
||||
Position = position,
|
||||
NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo,
|
||||
ComboOffset = newCombo ? comboOffset : 0,
|
||||
Path = new SliderPath(controlPoints, length),
|
||||
NodeSamples = nodeSamples,
|
||||
RepeatCount = repeatCount
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return lastObject = new ConvertSpinner
|
||||
{
|
||||
Position = position,
|
||||
Duration = duration,
|
||||
NewCombo = newCombo
|
||||
// Spinners cannot have combo offset.
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return lastObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +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 osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu! Slider-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasGenerateTicks
|
||||
{
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public float X => Position.X;
|
||||
|
||||
public float Y => Position.Y;
|
||||
|
||||
public bool GenerateTicks { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +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 osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu! Spinner-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition
|
||||
{
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public float X => Position.X;
|
||||
|
||||
public float Y => Position.Y;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +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.
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!taiko Hit-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertHit : ConvertHitObject
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,51 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osuTK;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Audio;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObjectParser to parse legacy osu!taiko Beatmaps.
|
||||
/// </summary>
|
||||
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
|
||||
{
|
||||
public ConvertHitObjectParser(double offset, int formatVersion)
|
||||
: base(offset, formatVersion)
|
||||
{
|
||||
}
|
||||
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
return new ConvertHit();
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||
IList<IList<HitSampleInfo>> nodeSamples)
|
||||
{
|
||||
return new ConvertSlider
|
||||
{
|
||||
Path = new SliderPath(controlPoints, length),
|
||||
NodeSamples = nodeSamples,
|
||||
RepeatCount = repeatCount
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return new ConvertSpinner
|
||||
{
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +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.
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!taiko Slider-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important);
|
||||
|
||||
Schedule(() => PerformExit(false));
|
||||
Schedule(() => PerformExit());
|
||||
}
|
||||
|
||||
private void onGameplayStarted() => Scheduler.Add(() =>
|
||||
|
||||
@@ -86,6 +86,7 @@ namespace osu.Game.Screens.Play
|
||||
public Action<bool> RestartRequested;
|
||||
|
||||
private bool isRestarting;
|
||||
private bool skipExitTransition;
|
||||
|
||||
private Bindable<bool> mouseWheelDisabled;
|
||||
|
||||
@@ -289,7 +290,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
SaveReplay = async () => await prepareAndImportScoreAsync(true).ConfigureAwait(false),
|
||||
OnRetry = Configuration.AllowUserInteraction ? () => Restart() : null,
|
||||
OnQuit = () => PerformExit(true),
|
||||
OnQuit = () => PerformExitWithConfirmation(),
|
||||
},
|
||||
new HotkeyExitOverlay
|
||||
{
|
||||
@@ -297,10 +298,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
|
||||
if (PerformExit(false))
|
||||
// The hotkey overlay dims the screen.
|
||||
// If the operation succeeds, we want to make sure we stay dimmed to keep continuity.
|
||||
fadeOut(true);
|
||||
PerformExit(skipTransition: true);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -318,10 +316,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
|
||||
if (Restart(true))
|
||||
// The hotkey overlay dims the screen.
|
||||
// If the operation succeeds, we want to make sure we stay dimmed to keep continuity.
|
||||
fadeOut(true);
|
||||
Restart(true);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -448,7 +443,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
HoldToQuit =
|
||||
{
|
||||
Action = () => PerformExit(true),
|
||||
Action = () => PerformExitWithConfirmation(),
|
||||
IsPaused = { BindTarget = GameplayClockContainer.IsPaused },
|
||||
ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded },
|
||||
},
|
||||
@@ -485,7 +480,7 @@ namespace osu.Game.Screens.Play
|
||||
OnResume = Resume,
|
||||
Retries = RestartCount,
|
||||
OnRetry = () => Restart(),
|
||||
OnQuit = () => PerformExit(true),
|
||||
OnQuit = () => PerformExitWithConfirmation(),
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -588,25 +583,24 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to complete a user request to exit gameplay.
|
||||
/// Attempts to complete a user request to exit gameplay, with confirmation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>This should only be called in response to a user interaction. Exiting is not guaranteed.</item>
|
||||
/// <item>This will interrupt any pending progression to the results screen, even if the transition has begun.</item>
|
||||
/// </list>
|
||||
///
|
||||
/// This method will show the pause or fail dialog before performing an exit.
|
||||
/// If a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead.
|
||||
/// </remarks>
|
||||
/// <param name="showDialogFirst">
|
||||
/// Whether the pause or fail dialog should be shown before performing an exit.
|
||||
/// If <see langword="true"/> and a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead.
|
||||
/// </param>
|
||||
/// <returns>Whether this call resulted in a final exit.</returns>
|
||||
protected bool PerformExit(bool showDialogFirst)
|
||||
protected bool PerformExitWithConfirmation()
|
||||
{
|
||||
bool pauseOrFailDialogVisible =
|
||||
PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible;
|
||||
|
||||
if (showDialogFirst && !pauseOrFailDialogVisible)
|
||||
if (!pauseOrFailDialogVisible)
|
||||
{
|
||||
// if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion).
|
||||
if (ValidForResume && GameplayState.HasFailed)
|
||||
@@ -625,6 +619,22 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
}
|
||||
|
||||
return PerformExit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to complete a user request to exit gameplay.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>This should only be called in response to a user interaction. Exiting is not guaranteed.</item>
|
||||
/// <item>This will interrupt any pending progression to the results screen, even if the transition has begun.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <param name="skipTransition">Whether the exit should perform without a transition, because the screen had faded to black already.</param>
|
||||
/// <returns>Whether this call resulted in a final exit.</returns>
|
||||
protected bool PerformExit(bool skipTransition = false)
|
||||
{
|
||||
// Matching osu!stable behaviour, if the results screen is pending and the user requests an exit,
|
||||
// show the results instead.
|
||||
if (GameplayState.HasPassed && !isRestarting)
|
||||
@@ -639,6 +649,8 @@ namespace osu.Game.Screens.Play
|
||||
// Screen may not be current if a restart has been performed.
|
||||
if (this.IsCurrentScreen())
|
||||
{
|
||||
skipExitTransition = skipTransition;
|
||||
|
||||
// The actual exit is performed if
|
||||
// - the pause / fail dialog was not requested
|
||||
// - the pause / fail dialog was requested but is already displayed (user showing intention to exit).
|
||||
@@ -707,9 +719,14 @@ namespace osu.Game.Screens.Play
|
||||
// stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader.
|
||||
musicController.Stop();
|
||||
|
||||
RestartRequested?.Invoke(quickRestart);
|
||||
if (RestartRequested != null)
|
||||
{
|
||||
skipExitTransition = quickRestart;
|
||||
RestartRequested?.Invoke(quickRestart);
|
||||
return true;
|
||||
}
|
||||
|
||||
return PerformExit(false);
|
||||
return PerformExit(quickRestart);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1254,10 +1271,10 @@ namespace osu.Game.Screens.Play
|
||||
ShowUserStatistics = true,
|
||||
};
|
||||
|
||||
private void fadeOut(bool instant = false)
|
||||
private void fadeOut()
|
||||
{
|
||||
float fadeOutDuration = instant ? 0 : 250;
|
||||
this.FadeOut(fadeOutDuration);
|
||||
if (!skipExitTransition)
|
||||
this.FadeOut(250);
|
||||
|
||||
if (this.IsCurrentScreen())
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.Input;
|
||||
@@ -51,6 +52,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public override bool? AllowGlobalTrackControl => false;
|
||||
|
||||
public override float BackgroundParallaxAmount => quickRestart ? 0 : 1;
|
||||
|
||||
// Here because IsHovered will not update unless we do so.
|
||||
public override bool HandlePositionalInput => true;
|
||||
|
||||
@@ -86,9 +89,13 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private SkinnableSound sampleRestart = null!;
|
||||
|
||||
private Box? quickRestartBlackLayer;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private const double quick_restart_initial_delay = 500;
|
||||
|
||||
protected bool BackgroundBrightnessReduction
|
||||
{
|
||||
set
|
||||
@@ -305,6 +312,9 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.OnSuspending(e);
|
||||
|
||||
quickRestartBlackLayer?.FadeOut(500, Easing.OutQuint).Expire();
|
||||
quickRestartBlackLayer = null;
|
||||
|
||||
BackgroundBrightnessReduction = false;
|
||||
|
||||
// we're moving to player, so a period of silence is upcoming.
|
||||
@@ -348,7 +358,14 @@ namespace osu.Game.Screens.Play
|
||||
if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint);
|
||||
|
||||
logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint);
|
||||
logo.FadeIn(350);
|
||||
|
||||
if (quickRestart)
|
||||
{
|
||||
logo.Delay(quick_restart_initial_delay)
|
||||
.FadeIn(350);
|
||||
}
|
||||
else
|
||||
logo.FadeIn(350);
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
@@ -387,7 +404,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
// We need to perform this check here rather than in OnHover as any number of children of VisualSettings
|
||||
// may also be handling the hover events.
|
||||
if (inputManager.HoveredDrawables.Contains(VisualSettings))
|
||||
if (inputManager.HoveredDrawables.Contains(VisualSettings) || quickRestart)
|
||||
{
|
||||
// Preview user-defined background dim and blur when hovered on the visual settings panel.
|
||||
ApplyToBackground(b =>
|
||||
@@ -454,28 +471,45 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
MetadataInfo.Loading = true;
|
||||
|
||||
content.FadeInFromZero(500, Easing.OutQuint);
|
||||
|
||||
if (quickRestart)
|
||||
{
|
||||
// A quick restart starts by triggering a fade to black
|
||||
AddInternal(quickRestartBlackLayer = new Box
|
||||
{
|
||||
Colour = Color4.Black,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MaxValue
|
||||
});
|
||||
|
||||
quickRestartBlackLayer
|
||||
.Delay(50)
|
||||
.FadeOut(5000, Easing.OutQuint);
|
||||
|
||||
prepareNewPlayer();
|
||||
content.ScaleTo(1, 650, Easing.OutQuint);
|
||||
|
||||
content
|
||||
.Delay(quick_restart_initial_delay)
|
||||
.ScaleTo(1)
|
||||
.FadeInFromZero(500, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
content.FadeInFromZero(500, Easing.OutQuint);
|
||||
|
||||
content
|
||||
.ScaleTo(0.7f)
|
||||
.ScaleTo(1, 650, Easing.OutQuint)
|
||||
.Then()
|
||||
.Schedule(prepareNewPlayer);
|
||||
}
|
||||
|
||||
using (BeginDelayedSequence(delayBeforeSideDisplays))
|
||||
{
|
||||
settingsScroll.FadeInFromZero(500, Easing.Out)
|
||||
.MoveToX(0, 500, Easing.OutQuint);
|
||||
using (BeginDelayedSequence(delayBeforeSideDisplays))
|
||||
{
|
||||
settingsScroll.FadeInFromZero(500, Easing.Out)
|
||||
.MoveToX(0, 500, Easing.OutQuint);
|
||||
|
||||
disclaimers.FadeInFromZero(500, Easing.Out)
|
||||
.MoveToX(0, 500, Easing.OutQuint);
|
||||
disclaimers.FadeInFromZero(500, Easing.Out)
|
||||
.MoveToX(0, 500, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
AddRangeInternal(new[]
|
||||
@@ -539,33 +573,36 @@ namespace osu.Game.Screens.Play
|
||||
highPerformanceSession ??= highPerformanceSessionManager?.BeginSession();
|
||||
|
||||
scheduledPushPlayer = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
// ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
|
||||
var consumedPlayer = consumePlayer();
|
||||
|
||||
ContentOut();
|
||||
|
||||
TransformSequence<PlayerLoader> pushSequence = this.Delay(0);
|
||||
|
||||
// This goes hand-in-hand with the restoration of low pass filter in contentOut().
|
||||
this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic);
|
||||
|
||||
pushSequence.Schedule(() =>
|
||||
{
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
// ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
|
||||
var consumedPlayer = consumePlayer();
|
||||
|
||||
LoadTask = null;
|
||||
ContentOut();
|
||||
|
||||
// By default, we want to load the player and never be returned to.
|
||||
// Note that this may change if the player we load requested a re-run.
|
||||
ValidForResume = false;
|
||||
TransformSequence<PlayerLoader> pushSequence = this.Delay(0);
|
||||
|
||||
if (consumedPlayer.LoadedBeatmapSuccessfully)
|
||||
this.Push(consumedPlayer);
|
||||
else
|
||||
this.Exit();
|
||||
});
|
||||
}, 500);
|
||||
// This goes hand-in-hand with the restoration of low pass filter in contentOut().
|
||||
this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic);
|
||||
|
||||
pushSequence.Schedule(() =>
|
||||
{
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
|
||||
LoadTask = null;
|
||||
|
||||
// By default, we want to load the player and never be returned to.
|
||||
// Note that this may change if the player we load requested a re-run.
|
||||
ValidForResume = false;
|
||||
|
||||
if (consumedPlayer.LoadedBeatmapSuccessfully)
|
||||
this.Push(consumedPlayer);
|
||||
else
|
||||
this.Exit();
|
||||
});
|
||||
},
|
||||
// When a quick restart is activated, the metadata content will display some time later if it's taking too long.
|
||||
// To avoid it appearing too briefly, if it begins to fade in let's induce a standard delay.
|
||||
quickRestart && content.Alpha == 0 ? 0 : 500);
|
||||
}
|
||||
|
||||
private void cancelLoad()
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
for (int i = 1; i <= axis_points; i++)
|
||||
{
|
||||
double axisValue = i * axisValueStep;
|
||||
float position = (float)(axisValue / maxValue);
|
||||
float position = maxValue == 0 ? 0 : (float)(axisValue / maxValue);
|
||||
float alpha = 1f - position * 0.8f;
|
||||
|
||||
axisFlow.Add(new OsuSpriteText
|
||||
@@ -348,7 +348,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private float offsetForValue(float value) => (1 - minimum_height) * value / maxValue;
|
||||
private float offsetForValue(float value) => maxValue == 0 ? 0 : (1 - minimum_height) * value / maxValue;
|
||||
|
||||
private float heightForValue(float value) => minimum_height + offsetForValue(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user