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

Merge branch 'master' into di-backed-ruleset

This commit is contained in:
Dean Herbert 2018-06-29 19:33:12 +09:00 committed by GitHub
commit 9fce4eaccf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 3456 additions and 1498 deletions

View File

@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" /> <PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup> </ItemGroup>

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
@ -12,11 +13,14 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
internal class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue> [TestFixture]
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
[TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")] [TestCase("basic")]
[TestCase("spinner")]
[TestCase("spinner-and-circles")]
public new void Test(string name) public new void Test(string name)
{ {
base.Test(name); base.Test(name);
@ -27,36 +31,52 @@ namespace osu.Game.Rulesets.Catch.Tests
if (hitObject is JuiceStream stream) if (hitObject is JuiceStream stream)
{ {
foreach (var nested in stream.NestedHitObjects) foreach (var nested in stream.NestedHitObjects)
{ yield return new ConvertValue((CatchHitObject)nested);
yield return new ConvertValue
{
StartTime = nested.StartTime,
Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH
};
} }
else if (hitObject is BananaShower shower)
{
foreach (var nested in shower.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested);
} }
else else
{ yield return new ConvertValue((CatchHitObject)hitObject);
yield return new ConvertValue
{
StartTime = hitObject.StartTime,
Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH
};
}
} }
protected override Ruleset CreateRuleset() => new CatchRuleset(); protected override Ruleset CreateRuleset() => new CatchRuleset();
} }
internal struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.
/// </summary> /// </summary>
private const float conversion_lenience = 2; private const float conversion_lenience = 2;
public double StartTime; [JsonIgnore]
public float Position; public readonly CatchHitObject HitObject;
public ConvertValue(CatchHitObject hitObject)
{
HitObject = hitObject;
startTime = 0;
position = 0;
}
private double startTime;
public double StartTime
{
get => HitObject?.StartTime ?? startTime;
set => startTime = value;
}
private float position;
public float Position
{
get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position;
set => position = value;
}
public bool Equals(ConvertValue other) public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index) private DrawableFruit createDrawable(int index)
{ {
Fruit fruit = index == 5 Fruit fruit = index == 5
? new BananaShower.Banana ? new Banana
{ {
StartTime = 1000000000000, StartTime = 1000000000000,
IndexInBeatmap = index, IndexInBeatmap = index,

View File

@ -26,22 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
var positionData = obj as IHasXPosition; var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo; var comboData = obj as IHasCombo;
var endTime = obj as IHasEndTime; var endTime = obj as IHasEndTime;
var legacyOffset = obj as IHasLegacyLastTickOffset;
if (positionData == null)
{
if (endTime != null)
{
yield return new BananaShower
{
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false
};
}
yield break;
}
if (curveData != null) if (curveData != null)
{ {
@ -54,21 +39,32 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
Distance = curveData.Distance, Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples, RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,
X = positionData.X / CatchPlayfield.BASE_WIDTH, X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0
};
}
else if (endTime != null)
{
yield return new BananaShower
{
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false NewCombo = comboData?.NewCombo ?? false
}; };
yield break;
} }
else
{
yield return new Fruit yield return new Fruit
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
NewCombo = comboData?.NewCombo ?? false, NewCombo = comboData?.NewCombo ?? false,
X = positionData.X / CatchPlayfield.BASE_WIDTH X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
}; };
} }
}
protected override Beatmap<CatchHitObject> CreateBeatmap() => new CatchBeatmap(); protected override Beatmap<CatchHitObject> CreateBeatmap() => new CatchBeatmap();
} }

View File

@ -9,6 +9,7 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Catch.MathUtils;
namespace osu.Game.Rulesets.Catch.Beatmaps namespace osu.Game.Rulesets.Catch.Beatmaps
{ {
@ -21,13 +22,54 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
public override void PostProcess() public override void PostProcess()
{ {
applyPositionOffsets();
initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects); initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
base.PostProcess(); base.PostProcess();
int index = 0; int index = 0;
foreach (var obj in Beatmap.HitObjects.OfType<CatchHitObject>()) foreach (var obj in Beatmap.HitObjects.OfType<CatchHitObject>())
{
obj.IndexInBeatmap = index++; obj.IndexInBeatmap = index++;
if (obj.LastInCombo && obj.NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = true;
}
}
public const int RNG_SEED = 1337;
private void applyPositionOffsets()
{
var rng = new FastRandom(RNG_SEED);
// todo: HardRock displacement should be applied here
foreach (var obj in Beatmap.HitObjects)
{
switch (obj)
{
case BananaShower bananaShower:
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
{
banana.X = (float)rng.NextDouble();
rng.Next(); // osu!stable retrieved a random banana type
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
}
break;
case JuiceStream juiceStream:
foreach (var nested in juiceStream.NestedHitObjects)
{
var hitObject = (CatchHitObject)nested;
if (hitObject is TinyDroplet)
hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH;
else if (hitObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
}
break;
}
}
} }
private void initialiseHyperDash(List<CatchHitObject> objects) private void initialiseHyperDash(List<CatchHitObject> objects)

View File

@ -0,0 +1,36 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchBananaJudgement : CatchJudgement
{
public override bool AffectsCombo => false;
public override bool ShouldExplode => true;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 1100;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 8;
}
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchDropletJudgement : CatchJudgement
{
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 30;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 7;
}
}
}
}

View File

@ -2,11 +2,51 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements namespace osu.Game.Rulesets.Catch.Judgements
{ {
public class CatchJudgement : Judgement public class CatchJudgement : Judgement
{ {
// todo: wangs public override HitResult MaxResult => HitResult.Perfect;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 300;
}
}
/// <summary>
/// The base health increase for the result achieved.
/// </summary>
public float HealthIncrease => HealthIncreaseFor(Result);
/// <summary>
/// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also <see cref="IHasComboInformation.LastInCombo" />
/// </summary>
public virtual bool ShouldExplode => IsHit;
/// <summary>
/// Convert a <see cref="HitResult"/> to a base health increase.
/// </summary>
/// <param name="result">The value to convert.</param>
/// <returns>The base health increase.</returns>
protected virtual float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 10.2f;
}
}
} }
} }

View File

@ -0,0 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchTinyDropletJudgement : CatchJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 10;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 4;
}
}
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
namespace osu.Game.Rulesets.Catch.MathUtils
{
/// <summary>
/// A PRNG specified in http://heliosphan.org/fastrandom.html.
/// </summary>
public class FastRandom
{
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
private const uint y = 842502087;
private const uint z = 3579807591;
private const uint w = 273326509;
private uint _x, _y = y, _z = z, _w = w;
public FastRandom(int seed)
{
_x = (uint)seed;
}
public FastRandom()
: this(Environment.TickCount)
{
}
/// <summary>
/// Generates a random unsigned integer within the range [<see cref="uint.MinValue"/>, <see cref="uint.MaxValue"/>).
/// </summary>
/// <returns>The random value.</returns>
public uint NextUInt()
{
uint t = _x ^ _x << 11;
_x = _y;
_y = _z;
_z = _w;
return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
}
/// <summary>
/// Generates a random integer value within the range [0, <see cref="int.MaxValue"/>).
/// </summary>
/// <returns>The random value.</returns>
public int Next() => (int)(int_mask & NextUInt());
/// <summary>
/// Generates a random integer value within the range [0, <paramref name="upperBound"/>).
/// </summary>
/// <param name="upperBound">The upper bound.</param>
/// <returns>The random value.</returns>
public int Next(int upperBound) => (int)(NextDouble() * upperBound);
/// <summary>
/// Generates a random integer value within the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
/// </summary>
/// <param name="lowerBound">The lower bound of the range.</param>
/// <param name="upperBound">The upper bound of the range.</param>
/// <returns>The random value.</returns>
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
/// <summary>
/// Generates a random double value within the range [0, 1).
/// </summary>
/// <returns>The random value.</returns>
public double NextDouble() => int_to_real * Next();
private uint bitBuffer;
private int bitIndex = 32;
/// <summary>
/// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls.
/// </summary>
/// <returns>The random value.</returns>
public bool NextBool()
{
if (bitIndex == 32)
{
bitBuffer = NextUInt();
bitIndex = 1;
return (bitBuffer & 1) == 1;
}
bitIndex++;
return ((bitBuffer >>= 1) & 1) == 1;
}
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Catch.Objects
{
public class Banana : Fruit
{
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
@ -31,18 +30,12 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Banana AddNested(new Banana
{ {
Samples = Samples, Samples = Samples,
StartTime = i, StartTime = i
X = RNG.NextSingle()
}); });
} }
public double EndTime => StartTime + Duration; public double EndTime => StartTime + Duration;
public double Duration { get; set; } public double Duration { get; set; }
public class Banana : Fruit
{
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
}
} }
} }

View File

@ -0,0 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableBanana : DrawableFruit
{
public DrawableBanana(Banana h)
: base(h)
{
}
protected override CatchJudgement CreateJudgement() => new CatchBananaJudgement();
}
}

View File

@ -5,9 +5,7 @@ using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
foreach (var b in s.NestedHitObjects.Cast<BananaShower.Banana>()) foreach (var b in s.NestedHitObjects.Cast<Banana>())
AddNested(getVisualRepresentation?.Invoke(b)); AddNested(getVisualRepresentation?.Invoke(b));
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override bool ProvidesJudgement => false;
{
if (timeOffset >= 0)
AddJudgement(new Judgement { Result = NestedHitObjects.Cast<DrawableCatchHitObject>().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss });
}
protected override void AddNested(DrawableHitObject h) protected override void AddNested(DrawableHitObject h)
{ {

View File

@ -2,14 +2,14 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
@ -58,8 +58,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
if (CheckPosition == null) return; if (CheckPosition == null) return;
if (timeOffset >= 0) if (timeOffset >= 0)
AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss }); {
var judgement = CreateJudgement();
judgement.Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss;
AddJudgement(judgement);
} }
}
protected virtual CatchJudgement CreateJudgement() => new CatchJudgement();
protected override void SkinChanged(ISkinSource skin, bool allowFallback) protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{ {

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Masking = false; Masking = false;
} }
protected override CatchJudgement CreateJudgement() => new CatchDropletJudgement();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableTinyDroplet : DrawableDroplet
{
public DrawableTinyDroplet(Droplet h)
: base(h)
{
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8;
}
protected override CatchJudgement CreateJudgement() => new CatchTinyDropletJudgement();
}
}

View File

@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects()
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects();
createTicks(); createTicks();
} }
@ -78,6 +77,13 @@ namespace osu.Game.Rulesets.Catch.Objects
double time = spanStartTime + timeProgress * spanDuration; double time = spanStartTime + timeProgress * spanDuration;
if (LegacyLastTickOffset != null)
{
// If we're the last tick, apply the legacy offset
if (span == this.SpanCount() - 1 && d + tickDistance > length)
time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value);
}
double tinyTickInterval = time - lastDropletTime; double tinyTickInterval = time - lastDropletTime;
while (tinyTickInterval > 100) while (tinyTickInterval > 100)
tinyTickInterval /= 2; tinyTickInterval /= 2;
@ -124,9 +130,6 @@ namespace osu.Game.Rulesets.Catch.Objects
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
}); });
} }
if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = LastInCombo;
} }
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
@ -156,5 +159,7 @@ namespace osu.Game.Rulesets.Catch.Objects
get { return Curve.CurveType; } get { return Curve.CurveType; }
set { Curve.CurveType = value; } set { Curve.CurveType = value; }
} }
public double? LegacyLastTickOffset { get; set; }
} }
} }

View File

@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays
return; return;
} }
if (h is BananaShower.Banana) if (h is Banana)
{ {
// auto bananas unrealistically warp to catch 100% combo. // auto bananas unrealistically warp to catch 100% combo.
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{ {
switch (nestedObj) switch (nestedObj)
{ {
case BananaShower.Banana _: case Banana _:
case TinyDroplet _: case TinyDroplet _:
case Droplet _: case Droplet _:
case Fruit _: case Fruit _:

View File

@ -0,0 +1,65 @@
{
"Mappings": [{
"StartTime": 2589,
"Objects": [{
"StartTime": 2589,
"Position": 256
}]
},
{
"StartTime": 2915,
"Objects": [{
"StartTime": 2915,
"Position": 65
},
{
"StartTime": 2916,
"Position": 482
}
]
},
{
"StartTime": 3078,
"Objects": [{
"StartTime": 3078,
"Position": 164
},
{
"StartTime": 3079,
"Position": 315
}
]
},
{
"StartTime": 3241,
"Objects": [{
"StartTime": 3241,
"Position": 145
},
{
"StartTime": 3242,
"Position": 159
}
]
},
{
"StartTime": 3404,
"Objects": [{
"StartTime": 3404,
"Position": 310
},
{
"StartTime": 3405,
"Position": 441
}
]
},
{
"StartTime": 5197,
"Objects": [{
"StartTime": 5197,
"Position": 256
}]
}
]
}

View File

@ -0,0 +1,24 @@
osu file format v14
[General]
StackLeniency: 0.7
Mode: 2
[Difficulty]
HPDrainRate:5
CircleSize:2
OverallDifficulty:5
ApproachRate:8
SliderMultiplier:1.4
SliderTickRate:4
[TimingPoints]
2589,326.086956521739,4,2,1,70,1,0
[HitObjects]
256,192,2589,5,0,0:0:0:0:
256,192,2915,12,0,2916,0:0:0:0:
256,192,3078,12,0,3079,0:0:0:0:
256,192,3241,12,0,3242,0:0:0:0:
256,192,3404,12,0,3405,0:0:0:0:
256,192,5197,5,0,0:0:0:0:

View File

@ -0,0 +1,74 @@
{
"Mappings": [{
"StartTime": 18500,
"Objects": [{
"StartTime": 18500,
"Position": 65
},
{
"StartTime": 18559,
"Position": 482
},
{
"StartTime": 18618,
"Position": 164
},
{
"StartTime": 18678,
"Position": 315
},
{
"StartTime": 18737,
"Position": 145
},
{
"StartTime": 18796,
"Position": 159
},
{
"StartTime": 18856,
"Position": 310
},
{
"StartTime": 18915,
"Position": 441
},
{
"StartTime": 18975,
"Position": 428
},
{
"StartTime": 19034,
"Position": 243
},
{
"StartTime": 19093,
"Position": 422
},
{
"StartTime": 19153,
"Position": 481
},
{
"StartTime": 19212,
"Position": 104
},
{
"StartTime": 19271,
"Position": 473
},
{
"StartTime": 19331,
"Position": 135
},
{
"StartTime": 19390,
"Position": 360
},
{
"StartTime": 19450,
"Position": 123
}
]
}]
}

View File

@ -0,0 +1,20 @@
osu file format v14
[General]
Mode: 2
[Difficulty]
HPDrainRate:6
CircleSize:4
OverallDifficulty:7
ApproachRate:8.3
SliderMultiplier:1.6
SliderTickRate:1
[TimingPoints]
500,500,4,2,1,50,1,0
13426,-100,4,3,1,45,0,0
14884,-100,4,2,1,50,0,0
[HitObjects]
256,192,18500,12,0,19450,0:0:0:0:

View File

@ -1,10 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -17,28 +19,57 @@ namespace osu.Game.Rulesets.Catch.Scoring
{ {
} }
private float hpDrainRate;
protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap) protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap)
{ {
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
{ {
switch (obj) switch (obj)
{ {
case JuiceStream stream: case JuiceStream stream:
foreach (var _ in stream.NestedHitObjects.Cast<CatchHitObject>()) foreach (var nestedObject in stream.NestedHitObjects)
switch (nestedObject)
{
case TinyDroplet _:
AddJudgement(new CatchTinyDropletJudgement { Result = HitResult.Perfect });
break;
case Droplet _:
AddJudgement(new CatchDropletJudgement { Result = HitResult.Perfect });
break;
case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break; break;
}
break;
case BananaShower shower: case BananaShower shower:
foreach (var _ in shower.NestedHitObjects.Cast<CatchHitObject>()) foreach (var _ in shower.NestedHitObjects.Cast<CatchHitObject>())
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchBananaJudgement { Result = HitResult.Perfect });
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break; break;
case Fruit _: case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break; break;
} }
} }
}
base.SimulateAutoplay(beatmap); private const double harshness = 0.01;
protected override void OnNewJudgement(Judgement judgement)
{
base.OnNewJudgement(judgement);
if (judgement.Result == HitResult.Miss)
{
if (!judgement.IsBonus)
Health.Value -= hpDrainRate * (harshness * 2);
return;
}
if (judgement is CatchJudgement catchJudgement)
Health.Value += Math.Max(catchJudgement.HealthIncrease - hpDrainRate, 0) * harshness;
} }
} }
} }

View File

@ -38,14 +38,16 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
switch (h) switch (h)
{ {
case Banana banana:
return new DrawableBanana(banana);
case Fruit fruit: case Fruit fruit:
return new DrawableFruit(fruit); return new DrawableFruit(fruit);
case JuiceStream stream: case JuiceStream stream:
return new DrawableJuiceStream(stream, GetVisualRepresentation); return new DrawableJuiceStream(stream, GetVisualRepresentation);
case BananaShower banana: case BananaShower shower:
return new DrawableBananaShower(banana, GetVisualRepresentation); return new DrawableBananaShower(shower, GetVisualRepresentation);
case TinyDroplet tiny: case TinyDroplet tiny:
return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }; return new DrawableTinyDroplet(tiny);
case Droplet droplet: case Droplet droplet:
return new DrawableDroplet(droplet); return new DrawableDroplet(droplet);
} }

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
@ -78,12 +79,11 @@ namespace osu.Game.Rulesets.Catch.UI
if (!fruit.StaysOnPlate) if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit)); runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
} }
if (fruit.HitObject.LastInCombo) if (fruit.HitObject.LastInCombo)
{ {
if (judgement.IsHit) if (((CatchJudgement)judgement).ShouldExplode)
runAfterLoaded(() => MovableCatcher.Explode()); runAfterLoaded(() => MovableCatcher.Explode());
else else
MovableCatcher.Drop(); MovableCatcher.Drop();

View File

@ -12,7 +12,8 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
internal class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue> [TestFixture]
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests
protected override Ruleset CreateRuleset() => new ManiaRuleset(); protected override Ruleset CreateRuleset() => new ManiaRuleset();
} }
internal struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.

View File

@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
public class HoldNoteJudgement : ManiaJudgement public class HoldNoteJudgement : ManiaJudgement
{ {
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0; protected override int NumericResultFor(HitResult result) => 0;
} }
} }

View File

@ -8,17 +8,19 @@ using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
internal class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue> [TestFixture]
public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase("basic")] [TestCase("basic")]
[TestCase("colinear-perfect-curve")] [TestCase("colinear-perfect-curve")]
[TestCase("slider-ticks")]
public new void Test(string name) public new void Test(string name)
{ {
base.Test(name); base.Test(name);
@ -26,24 +28,30 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
{ {
var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192); switch (hitObject)
var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition;
yield return new ConvertValue
{ {
StartTime = hitObject.StartTime, case Slider slider:
EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, foreach (var nested in slider.NestedHitObjects)
StartX = startPosition.X, yield return createConvertValue(nested);
StartY = startPosition.Y, break;
EndX = endPosition.X, default:
EndY = endPosition.Y yield return createConvertValue(hitObject);
break;
}
ConvertValue createConvertValue(HitObject obj) => new ConvertValue
{
StartTime = obj.StartTime,
EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
X = (obj as IHasPosition)?.X ?? OsuPlayfield.BASE_SIZE.X / 2,
Y = (obj as IHasPosition)?.Y ?? OsuPlayfield.BASE_SIZE.Y / 2,
}; };
} }
protected override Ruleset CreateRuleset() => new OsuRuleset(); protected override Ruleset CreateRuleset() => new OsuRuleset();
} }
internal struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.
@ -52,17 +60,13 @@ namespace osu.Game.Rulesets.Osu.Tests
public double StartTime; public double StartTime;
public double EndTime; public double EndTime;
public float StartX; public float X;
public float StartY; public float Y;
public float EndX;
public float EndY;
public bool Equals(ConvertValue other) public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
&& Precision.AlmostEquals(StartX, other.StartX) && Precision.AlmostEquals(X, other.X, conversion_lenience)
&& Precision.AlmostEquals(StartY, other.StartY, conversion_lenience) && Precision.AlmostEquals(Y, other.Y, conversion_lenience);
&& Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
&& Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
} }
} }

View File

@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
var endTimeData = original as IHasEndTime; var endTimeData = original as IHasEndTime;
var positionData = original as IHasPosition; var positionData = original as IHasPosition;
var comboData = original as IHasCombo; var comboData = original as IHasCombo;
var legacyOffset = original as IHasLegacyLastTickOffset;
if (curveData != null) if (curveData != null)
{ {
@ -40,7 +41,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
RepeatSamples = curveData.RepeatSamples, RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero, Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false NewCombo = comboData?.NewCombo ?? false,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset
}; };
} }
else if (endTimeData != null) else if (endTimeData != null)

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -12,11 +11,6 @@ namespace osu.Game.Rulesets.Osu.Judgements
{ {
public override HitResult MaxResult => HitResult.Great; public override HitResult MaxResult => HitResult.Great;
/// <summary>
/// The positional hit offset.
/// </summary>
public Vector2 PositionOffset;
protected override int NumericResultFor(HitResult result) protected override int NumericResultFor(HitResult result)
{ {
switch (result) switch (result)

View File

@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Judgements
public class OsuSliderTailJudgement : OsuJudgement public class OsuSliderTailJudgement : OsuJudgement
{ {
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0; protected override int NumericResultFor(HitResult result) => 0;
} }
} }

View File

@ -93,7 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddJudgement(new OsuJudgement AddJudgement(new OsuJudgement
{ {
Result = result, Result = result,
PositionOffset = Vector2.Zero //todo: set to correct value
}); });
} }

View File

@ -93,6 +93,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.AccentColour = value; base.AccentColour = value;
Body.AccentColour = AccentColour; Body.AccentColour = AccentColour;
Ball.AccentColour = AccentColour; Ball.AccentColour = AccentColour;
if (HasNestedHitObjects)
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour = AccentColour;
} }
} }

View File

@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public virtual bool NewCombo { get; set; } public virtual bool NewCombo { get; set; }
public int IndexInCurrentCombo { get; set; } public virtual int IndexInCurrentCombo { get; set; }
public int ComboIndex { get; set; } public virtual int ComboIndex { get; set; }
public bool LastInCombo { get; set; } public bool LastInCombo { get; set; }

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic; using System.Collections.Generic;
@ -25,6 +26,28 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
public override Vector2 EndPosition => Position + this.CurvePositionAt(1); public override Vector2 EndPosition => Position + this.CurvePositionAt(1);
public override int ComboIndex
{
get => base.ComboIndex;
set
{
base.ComboIndex = value;
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
n.ComboIndex = value;
}
}
public override int IndexInCurrentCombo
{
get => base.IndexInCurrentCombo;
set
{
base.IndexInCurrentCombo = value;
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
n.IndexInCurrentCombo = value;
}
}
public SliderCurve Curve { get; } = new SliderCurve(); public SliderCurve Curve { get; } = new SliderCurve();
public List<Vector2> ControlPoints public List<Vector2> ControlPoints
@ -45,6 +68,8 @@ namespace osu.Game.Rulesets.Osu.Objects
set { Curve.Distance = value; } set { Curve.Distance = value; }
} }
public double? LegacyLastTickOffset { get; set; }
/// <summary> /// <summary>
/// The position of the cursor at the point of completion of this <see cref="Slider"/> if it was hit /// The position of the cursor at the point of completion of this <see cref="Slider"/> if it was hit
/// with as few movements as possible. This is set and used by difficulty calculation. /// with as few movements as possible. This is set and used by difficulty calculation.
@ -91,6 +116,9 @@ namespace osu.Game.Rulesets.Osu.Objects
createSliderEnds(); createSliderEnds();
createTicks(); createTicks();
createRepeatPoints(); createRepeatPoints();
if (LegacyLastTickOffset != null)
TailCircle.StartTime = Math.Max(StartTime + Duration / 2, TailCircle.StartTime - LegacyLastTickOffset.Value);
} }
private void createSliderEnds() private void createSliderEnds()
@ -141,7 +169,8 @@ namespace osu.Game.Rulesets.Osu.Objects
var distanceProgress = d / length; var distanceProgress = d / length;
var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL)
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var sampleList = new List<SampleInfo>(); var sampleList = new List<SampleInfo>();
if (firstSample != null) if (firstSample != null)

View File

@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Users; using osu.Game.Users;
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
/// <summary> /// <summary>
/// Constants (for spinners). /// Constants (for spinners).
/// </summary> /// </summary>
protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192); protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2;
protected const float SPIN_RADIUS = 50; protected const float SPIN_RADIUS = 50;
/// <summary> /// <summary>

View File

@ -1,124 +1,256 @@
{ {
"Mappings": [{ "Mappings": [{
"StartTime": 500, "StartTime": 500.0,
"Objects": [{ "Objects": [{
"StartTime": 500, "StartTime": 500.0,
"EndTime": 2500, "EndTime": 500.0,
"StartX": 96, "X": 96.0,
"StartY": 192, "Y": 192.0
"EndX": 96, }, {
"EndY": 192 "StartTime": 1000.0,
"EndTime": 1000.0,
"X": 256.0,
"Y": 192.0
}, {
"StartTime": 1500.0,
"EndTime": 1500.0,
"X": 416.0,
"Y": 192.0
}, {
"StartTime": 2000.0,
"EndTime": 2000.0,
"X": 256.0,
"Y": 192.0
}, {
"StartTime": 2464.0,
"EndTime": 2464.0,
"X": 96.0,
"Y": 192.0
}] }]
}, }, {
{ "StartTime": 3000.0,
"StartTime": 3000,
"Objects": [{ "Objects": [{
"StartTime": 3000, "StartTime": 3000.0,
"EndTime": 4000, "EndTime": 4000.0,
"StartX": 256, "X": 256.0,
"StartY": 192, "Y": 192.0
"EndX": 256,
"EndY": 192
}] }]
}, }, {
{ "StartTime": 4500.0,
"StartTime": 4500,
"Objects": [{ "Objects": [{
"StartTime": 4500, "StartTime": 4500.0,
"EndTime": 5500, "EndTime": 5500.0,
"StartX": 256, "X": 256.0,
"StartY": 192, "Y": 192.0
"EndX": 256,
"EndY": 192
}] }]
}, }, {
{ "StartTime": 6000.0,
"StartTime": 6000,
"Objects": [{ "Objects": [{
"StartTime": 6000, "StartTime": 6000.0,
"EndTime": 6500, "EndTime": 6500.0,
"StartX": 256, "X": 256.0,
"StartY": 192, "Y": 192.0
"EndX": 256,
"EndY": 192
}] }]
}, }, {
{ "StartTime": 7000.0,
"StartTime": 7000,
"Objects": [{ "Objects": [{
"StartTime": 7000, "StartTime": 7000.0,
"EndTime": 8000, "EndTime": 7000.0,
"StartX": 256, "X": 256.0,
"StartY": 128, "Y": 128.0
"EndX": 256, }, {
"EndY": 128 "StartTime": 7250.0,
"EndTime": 7250.0,
"X": 336.0,
"Y": 128.0
}, {
"StartTime": 7500.0,
"EndTime": 7500.0,
"X": 256.0,
"Y": 128.0
}, {
"StartTime": 7750.0,
"EndTime": 7750.0,
"X": 336.0,
"Y": 128.0
}, {
"StartTime": 7964.0,
"EndTime": 7964.0,
"X": 256.0,
"Y": 128.0
}] }]
}, }, {
{ "StartTime": 8500.0,
"StartTime": 8500,
"Objects": [{ "Objects": [{
"StartTime": 8500, "StartTime": 8500.0,
"EndTime": 10999, "EndTime": 8500.0,
"StartX": 32, "X": 32.0,
"StartY": 192, "Y": 192.0
"EndX": 508.166229, }, {
"EndY": 153.299271 "StartTime": 9000.0,
"EndTime": 9000.0,
"X": 101.81015,
"Y": 326.4915
}, {
"StartTime": 9500.0,
"EndTime": 9500.0,
"X": 237.2304,
"Y": 276.282928
}, {
"StartTime": 10000.0,
"EndTime": 10000.0,
"X": 270.339874,
"Y": 121.1423
}, {
"StartTime": 10500.0,
"EndTime": 10500.0,
"X": 401.0588,
"Y": 49.1515045
}, {
"StartTime": 10964.0,
"EndTime": 10964.0,
"X": 508.166229,
"Y": 153.299271
}] }]
}, }, {
{ "StartTime": 11500.0,
"StartTime": 11500,
"Objects": [{ "Objects": [{
"StartTime": 11500, "StartTime": 11500.0,
"EndTime": 12000, "EndTime": 12000.0,
"StartX": 256, "X": 256.0,
"StartY": 192, "Y": 192.0
"EndX": 256,
"EndY": 192
}] }]
}, }, {
{ "StartTime": 12500.0,
"StartTime": 12500,
"Objects": [{ "Objects": [{
"StartTime": 12500, "StartTime": 12500.0,
"EndTime": 16500, "EndTime": 12500.0,
"StartX": 512, "X": 512.0,
"StartY": 320, "Y": 320.0
"EndX": 291.1977, }, {
"EndY": 40.799427 "StartTime": 13000.0,
"EndTime": 13000.0,
"X": 353.235535,
"Y": 300.154449
}, {
"StartTime": 13500.0,
"EndTime": 13500.0,
"X": 194.471069,
"Y": 280.3089
}, {
"StartTime": 14000.0,
"EndTime": 14000.0,
"X": 35.7066345,
"Y": 260.463318
}, {
"StartTime": 14500.0,
"EndTime": 14500.0,
"X": 118.370323,
"Y": 219.009277
}, {
"StartTime": 15000.0,
"EndTime": 15000.0,
"X": 271.087128,
"Y": 171.285278
}, {
"StartTime": 15500.0,
"EndTime": 15500.0,
"X": 423.803925,
"Y": 123.561279
}, {
"StartTime": 16000.0,
"EndTime": 16000.0,
"X": 446.420532,
"Y": 79.60513
}, {
"StartTime": 16464.0,
"EndTime": 16464.0,
"X": 291.1977,
"Y": 40.799427
}] }]
}, }, {
{ "StartTime": 17000.0,
"StartTime": 17000,
"Objects": [{ "Objects": [{
"StartTime": 17000, "StartTime": 17000.0,
"EndTime": 18000, "EndTime": 17000.0,
"StartX": 256, "X": 256.0,
"StartY": 256, "Y": 256.0
"EndX": 256, }, {
"EndY": 256 "StartTime": 17250.0,
"EndTime": 17250.0,
"X": 176.0,
"Y": 256.0
}, {
"StartTime": 17500.0,
"EndTime": 17500.0,
"X": 256.0,
"Y": 256.0
}, {
"StartTime": 17750.0,
"EndTime": 17750.0,
"X": 176.0,
"Y": 256.0
}, {
"StartTime": 17964.0,
"EndTime": 17964.0,
"X": 256.0,
"Y": 256.0
}] }]
}, }, {
{ "StartTime": 18500.0,
"StartTime": 18500,
"Objects": [{ "Objects": [{
"StartTime": 18500, "StartTime": 18500.0,
"EndTime": 19450, "EndTime": 19450.0,
"StartX": 256, "X": 256.0,
"StartY": 192, "Y": 192.0
"EndX": 256,
"EndY": 192
}] }]
}, }, {
{ "StartTime": 19875.0,
"StartTime": 19875,
"Objects": [{ "Objects": [{
"StartTime": 19875, "StartTime": 19875.0,
"EndTime": 23874, "EndTime": 19875.0,
"StartX": 216, "X": 216.0,
"StartY": 231, "Y": 231.0
"EndX": 408.720825, }, {
"EndY": 339.810455 "StartTime": 20375.0,
"EndTime": 20375.0,
"X": 317.446747,
"Y": 171.345245
}, {
"StartTime": 20875.0,
"EndTime": 20875.0,
"X": 270.3294,
"Y": 310.4395
}, {
"StartTime": 21375.0,
"EndTime": 21375.0,
"X": 119.121056,
"Y": 322.8657
}, {
"StartTime": 21875.0,
"EndTime": 21875.0,
"X": 124.28746,
"Y": 165.224731
}, {
"StartTime": 22375.0,
"EndTime": 22375.0,
"X": 240.4715,
"Y": 62.65587
}, {
"StartTime": 22875.0,
"EndTime": 22875.0,
"X": 398.054047,
"Y": 39.064167
}, {
"StartTime": 23375.0,
"EndTime": 23375.0,
"X": 439.749878,
"Y": 183.668091
}, {
"StartTime": 23839.0,
"EndTime": 23839.0,
"X": 408.720825,
"Y": 339.810455
}]
}] }]
}
]
} }

View File

@ -1,13 +1,16 @@
{ {
"Mappings": [{ "Mappings": [{
"StartTime": 118858, "StartTime": 118858.0,
"Objects": [{ "Objects": [{
"StartTime": 118858, "StartTime": 118858.0,
"EndTime": 119088, "EndTime": 118858.0,
"StartX": 219, "X": 219.0,
"StartY": 215, "Y": 215.0
"EndX": 239.6507, }, {
"EndY": 29.1437378 "StartTime": 119052.0,
"EndTime": 119052.0,
"X": 239.6507,
"Y": 29.1437378
}] }]
}] }]
} }

View File

@ -0,0 +1,331 @@
{
"Mappings": [{
"StartTime": 500.0,
"Objects": [{
"StartTime": 500.0,
"EndTime": 500.0,
"X": 96.0,
"Y": 192.0
}, {
"StartTime": 624.0,
"EndTime": 624.0,
"X": 105.921242,
"Y": 192.0
}, {
"StartTime": 749.0,
"EndTime": 749.0,
"X": 115.922493,
"Y": 192.0
}, {
"StartTime": 874.0,
"EndTime": 874.0,
"X": 125.923737,
"Y": 192.0
}, {
"StartTime": 999.0,
"EndTime": 999.0,
"X": 135.924988,
"Y": 192.0
}, {
"StartTime": 1124.0,
"EndTime": 1124.0,
"X": 145.926239,
"Y": 192.0
}, {
"StartTime": 1249.0,
"EndTime": 1249.0,
"X": 155.92749,
"Y": 192.0
}, {
"StartTime": 1374.0,
"EndTime": 1374.0,
"X": 165.928741,
"Y": 192.0
}, {
"StartTime": 1499.0,
"EndTime": 1499.0,
"X": 175.93,
"Y": 192.0
}, {
"StartTime": 1624.0,
"EndTime": 1624.0,
"X": 185.931244,
"Y": 192.0
}, {
"StartTime": 1749.0,
"EndTime": 1749.0,
"X": 195.9325,
"Y": 192.0
}, {
"StartTime": 1874.0,
"EndTime": 1874.0,
"X": 205.933746,
"Y": 192.0
}, {
"StartTime": 1999.0,
"EndTime": 1999.0,
"X": 215.935,
"Y": 192.0
}, {
"StartTime": 2124.0,
"EndTime": 2124.0,
"X": 225.936234,
"Y": 192.0
}, {
"StartTime": 2249.0,
"EndTime": 2249.0,
"X": 235.9375,
"Y": 192.0
}, {
"StartTime": 2374.0,
"EndTime": 2374.0,
"X": 245.938751,
"Y": 192.0
}, {
"StartTime": 2499.0,
"EndTime": 2499.0,
"X": 255.94,
"Y": 192.0
}, {
"StartTime": 2624.0,
"EndTime": 2624.0,
"X": 265.941223,
"Y": 192.0
}, {
"StartTime": 2749.0,
"EndTime": 2749.0,
"X": 275.9425,
"Y": 192.0
}, {
"StartTime": 2874.0,
"EndTime": 2874.0,
"X": 285.943756,
"Y": 192.0
}, {
"StartTime": 2999.0,
"EndTime": 2999.0,
"X": 295.945,
"Y": 192.0
}, {
"StartTime": 3124.0,
"EndTime": 3124.0,
"X": 305.946259,
"Y": 192.0
}, {
"StartTime": 3249.0,
"EndTime": 3249.0,
"X": 315.9475,
"Y": 192.0
}, {
"StartTime": 3374.0,
"EndTime": 3374.0,
"X": 325.94873,
"Y": 192.0
}, {
"StartTime": 3499.0,
"EndTime": 3499.0,
"X": 335.949982,
"Y": 192.0
}, {
"StartTime": 3624.0,
"EndTime": 3624.0,
"X": 345.951233,
"Y": 192.0
}, {
"StartTime": 3749.0,
"EndTime": 3749.0,
"X": 355.952484,
"Y": 192.0
}, {
"StartTime": 3874.0,
"EndTime": 3874.0,
"X": 365.953766,
"Y": 192.0
}, {
"StartTime": 3999.0,
"EndTime": 3999.0,
"X": 375.955,
"Y": 192.0
}, {
"StartTime": 4124.0,
"EndTime": 4124.0,
"X": 385.956238,
"Y": 192.0
}, {
"StartTime": 4249.0,
"EndTime": 4249.0,
"X": 395.9575,
"Y": 192.0
}, {
"StartTime": 4374.0,
"EndTime": 4374.0,
"X": 405.95874,
"Y": 192.0
}, {
"StartTime": 4499.0,
"EndTime": 4499.0,
"X": 415.960022,
"Y": 192.0
}, {
"StartTime": 4624.0,
"EndTime": 4624.0,
"X": 406.038757,
"Y": 192.0
}, {
"StartTime": 4749.0,
"EndTime": 4749.0,
"X": 396.0375,
"Y": 192.0
}, {
"StartTime": 4874.0,
"EndTime": 4874.0,
"X": 386.036255,
"Y": 192.0
}, {
"StartTime": 4999.0,
"EndTime": 4999.0,
"X": 376.035034,
"Y": 192.0
}, {
"StartTime": 5124.0,
"EndTime": 5124.0,
"X": 366.033752,
"Y": 192.0
}, {
"StartTime": 5249.0,
"EndTime": 5249.0,
"X": 356.0325,
"Y": 192.0
}, {
"StartTime": 5374.0,
"EndTime": 5374.0,
"X": 346.03125,
"Y": 192.0
}, {
"StartTime": 5499.0,
"EndTime": 5499.0,
"X": 336.030029,
"Y": 192.0
}, {
"StartTime": 5624.0,
"EndTime": 5624.0,
"X": 326.028748,
"Y": 192.0
}, {
"StartTime": 5749.0,
"EndTime": 5749.0,
"X": 316.0275,
"Y": 192.0
}, {
"StartTime": 5874.0,
"EndTime": 5874.0,
"X": 306.026245,
"Y": 192.0
}, {
"StartTime": 5999.0,
"EndTime": 5999.0,
"X": 296.025,
"Y": 192.0
}, {
"StartTime": 6124.0,
"EndTime": 6124.0,
"X": 286.023773,
"Y": 192.0
}, {
"StartTime": 6249.0,
"EndTime": 6249.0,
"X": 276.022522,
"Y": 192.0
}, {
"StartTime": 6374.0,
"EndTime": 6374.0,
"X": 266.02124,
"Y": 192.0
}, {
"StartTime": 6499.0,
"EndTime": 6499.0,
"X": 256.02,
"Y": 192.0
}, {
"StartTime": 6624.0,
"EndTime": 6624.0,
"X": 246.018768,
"Y": 192.0
}, {
"StartTime": 6749.0,
"EndTime": 6749.0,
"X": 236.017517,
"Y": 192.0
}, {
"StartTime": 6874.0,
"EndTime": 6874.0,
"X": 226.016251,
"Y": 192.0
}, {
"StartTime": 6999.0,
"EndTime": 6999.0,
"X": 216.014984,
"Y": 192.0
}, {
"StartTime": 7124.0,
"EndTime": 7124.0,
"X": 206.013733,
"Y": 192.0
}, {
"StartTime": 7249.0,
"EndTime": 7249.0,
"X": 196.012512,
"Y": 192.0
}, {
"StartTime": 7374.0,
"EndTime": 7374.0,
"X": 186.011261,
"Y": 192.0
}, {
"StartTime": 7499.0,
"EndTime": 7499.0,
"X": 176.01,
"Y": 192.0
}, {
"StartTime": 7624.0,
"EndTime": 7624.0,
"X": 166.008728,
"Y": 192.0
}, {
"StartTime": 7749.0,
"EndTime": 7749.0,
"X": 156.0075,
"Y": 192.0
}, {
"StartTime": 7874.0,
"EndTime": 7874.0,
"X": 146.006256,
"Y": 192.0
}, {
"StartTime": 7999.0,
"EndTime": 7999.0,
"X": 136.005,
"Y": 192.0
}, {
"StartTime": 8124.0,
"EndTime": 8124.0,
"X": 126.003738,
"Y": 192.0
}, {
"StartTime": 8249.0,
"EndTime": 8249.0,
"X": 116.002518,
"Y": 192.0
}, {
"StartTime": 8374.0,
"EndTime": 8374.0,
"X": 106.001259,
"Y": 192.0
}, {
"StartTime": 8463.0,
"EndTime": 8463.0,
"X": 96.0,
"Y": 192.0
}]
}]
}

View File

@ -0,0 +1,20 @@
osu file format v14
[General]
StackLeniency: 0.7
[Difficulty]
HPDrainRate:6
CircleSize:4
OverallDifficulty:7
ApproachRate:8.3
SliderMultiplier:0.400000005960464
SliderTickRate:4
[TimingPoints]
500,500,4,2,1,50,1,0
13426,-100,4,3,1,45,0,0
14884,-100,4,2,1,50,0,0
[HitObjects]
96,192,500,6,0,L|416:192,2,320.000004768372

View File

@ -11,7 +11,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.UI namespace osu.Game.Rulesets.Osu.UI
{ {
@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject) DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject)
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition
}; };
judgementLayer.Add(explosion); judgementLayer.Add(explosion);

View File

@ -12,7 +12,8 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests namespace osu.Game.Rulesets.Taiko.Tests
{ {
internal class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue> [TestFixture]
public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override Ruleset CreateRuleset() => new TaikoRuleset(); protected override Ruleset CreateRuleset() => new TaikoRuleset();
} }
internal struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.

View File

@ -1,8 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using NUnit.Framework;
using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -10,9 +9,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using OpenTK; using OpenTK;
@ -20,10 +16,9 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[TestFixture]
public class TestCaseEditorSeekSnapping : EditorClockTestCase public class TestCaseEditorSeekSnapping : EditorClockTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(HitObjectComposer) };
public TestCaseEditorSeekSnapping() public TestCaseEditorSeekSnapping()
{ {
BeatDivisor.Value = 4; BeatDivisor.Value = 4;
@ -56,22 +51,13 @@ namespace osu.Game.Tests.Visual
Beatmap.Value = new TestWorkingBeatmap(testBeatmap); Beatmap.Value = new TestWorkingBeatmap(testBeatmap);
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
testSeekNoSnapping();
testSeekSnappingOnBeat();
testSeekSnappingInBetweenBeat();
testSeekForwardNoSnapping();
testSeekForwardSnappingOnBeat();
testSeekForwardSnappingFromInBetweenBeat();
testSeekBackwardSnappingOnBeat();
testSeekBackwardSnappingFromInBetweenBeat();
testSeekingWithFloatingPointBeatLength();
} }
/// <summary> /// <summary>
/// Tests whether time is correctly seeked without snapping. /// Tests whether time is correctly seeked without snapping.
/// </summary> /// </summary>
private void testSeekNoSnapping() [Test]
public void TestSeekNoSnapping()
{ {
reset(); reset();
@ -94,7 +80,8 @@ namespace osu.Game.Tests.Visual
/// Tests whether seeking to exact beat times puts us on the beat time. /// Tests whether seeking to exact beat times puts us on the beat time.
/// These are the white/yellow ticks on the graph. /// These are the white/yellow ticks on the graph.
/// </summary> /// </summary>
private void testSeekSnappingOnBeat() [Test]
public void TestSeekSnappingOnBeat()
{ {
reset(); reset();
@ -117,9 +104,9 @@ namespace osu.Game.Tests.Visual
/// <summary> /// <summary>
/// Tests whether seeking to somewhere in the middle between beats puts us on the expected beats. /// Tests whether seeking to somewhere in the middle between beats puts us on the expected beats.
/// For example, snapping between a white/yellow beat should put us on either the yellow or white, depending on which one we're closer too. /// For example, snapping between a white/yellow beat should put us on either the yellow or white, depending on which one we're closer too.
/// If
/// </summary> /// </summary>
private void testSeekSnappingInBetweenBeat() [Test]
public void TestSeekSnappingInBetweenBeat()
{ {
reset(); reset();
@ -140,7 +127,8 @@ namespace osu.Game.Tests.Visual
/// <summary> /// <summary>
/// Tests that when seeking forward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it). /// Tests that when seeking forward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it).
/// </summary> /// </summary>
private void testSeekForwardNoSnapping() [Test]
public void TestSeekForwardNoSnapping()
{ {
reset(); reset();
@ -159,7 +147,8 @@ namespace osu.Game.Tests.Visual
/// <summary> /// <summary>
/// Tests that when seeking forward with beat snapping, all beats are snapped to and timing points are never skipped. /// Tests that when seeking forward with beat snapping, all beats are snapped to and timing points are never skipped.
/// </summary> /// </summary>
private void testSeekForwardSnappingOnBeat() [Test]
public void TestSeekForwardSnappingOnBeat()
{ {
reset(); reset();
@ -181,7 +170,8 @@ namespace osu.Game.Tests.Visual
/// Tests that when seeking forward from in-between two beats, the next beat or timing point is snapped to, and no beats are skipped. /// Tests that when seeking forward from in-between two beats, the next beat or timing point is snapped to, and no beats are skipped.
/// This will also test being extremely close to the next beat/timing point, to ensure rounding is not an issue. /// This will also test being extremely close to the next beat/timing point, to ensure rounding is not an issue.
/// </summary> /// </summary>
private void testSeekForwardSnappingFromInBetweenBeat() [Test]
public void TestSeekForwardSnappingFromInBetweenBeat()
{ {
reset(); reset();
@ -214,21 +204,20 @@ namespace osu.Game.Tests.Visual
/// <summary> /// <summary>
/// Tests that when seeking backward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it). /// Tests that when seeking backward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it).
/// </summary> /// </summary>
private void testSeekBackwardNoSnapping() [Test]
public void TestSeekBackwardNoSnapping()
{ {
reset(); reset();
AddStep("Seek(450)", () => Clock.Seek(450)); AddStep("Seek(450)", () => Clock.Seek(450));
AddStep("SeekBackward", () => Clock.SeekBackward()); AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 425", () => Clock.CurrentTime == 425); AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("SeekBackward", () => Clock.SeekBackward()); AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 375", () => Clock.CurrentTime == 375); AddAssert("Time = 350", () => Clock.CurrentTime == 350);
AddStep("SeekBackward", () => Clock.SeekBackward()); AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 325", () => Clock.CurrentTime == 325); AddAssert("Time = 150", () => Clock.CurrentTime == 150);
AddStep("SeekBackward", () => Clock.SeekBackward()); AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 125", () => Clock.CurrentTime == 125); AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 25", () => Clock.CurrentTime == 25);
AddStep("SeekBackward", () => Clock.SeekBackward()); AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 0", () => Clock.CurrentTime == 0); AddAssert("Time = 0", () => Clock.CurrentTime == 0);
} }
@ -236,7 +225,8 @@ namespace osu.Game.Tests.Visual
/// <summary> /// <summary>
/// Tests that when seeking backward with beat snapping, all beats are snapped to and timing points are never skipped. /// Tests that when seeking backward with beat snapping, all beats are snapped to and timing points are never skipped.
/// </summary> /// </summary>
private void testSeekBackwardSnappingOnBeat() [Test]
public void TestSeekBackwardSnappingOnBeat()
{ {
reset(); reset();
@ -259,7 +249,8 @@ namespace osu.Game.Tests.Visual
/// Tests that when seeking backward from in-between two beats, the previous beat or timing point is snapped to, and no beats are skipped. /// Tests that when seeking backward from in-between two beats, the previous beat or timing point is snapped to, and no beats are skipped.
/// This will also test being extremely close to the previous beat/timing point, to ensure rounding is not an issue. /// This will also test being extremely close to the previous beat/timing point, to ensure rounding is not an issue.
/// </summary> /// </summary>
private void testSeekBackwardSnappingFromInBetweenBeat() [Test]
public void TestSeekBackwardSnappingFromInBetweenBeat()
{ {
reset(); reset();
@ -280,7 +271,8 @@ namespace osu.Game.Tests.Visual
/// <summary> /// <summary>
/// Tests that there are no rounding issues when snapping to beats within a timing point with a floating-point beatlength. /// Tests that there are no rounding issues when snapping to beats within a timing point with a floating-point beatlength.
/// </summary> /// </summary>
private void testSeekingWithFloatingPointBeatLength() [Test]
public void TestSeekingWithFloatingPointBeatLength()
{ {
reset(); reset();
@ -288,7 +280,7 @@ namespace osu.Game.Tests.Visual
AddStep("Seek(0)", () => Clock.Seek(0)); AddStep("Seek(0)", () => Clock.Seek(0));
for (int i = 0; i < 20; i++) for (int i = 0; i < 9; i++)
{ {
AddStep("SeekForward, Snap", () => AddStep("SeekForward, Snap", () =>
{ {
@ -298,7 +290,7 @@ namespace osu.Game.Tests.Visual
AddAssert("Time > lastTime", () => Clock.CurrentTime > lastTime); AddAssert("Time > lastTime", () => Clock.CurrentTime > lastTime);
} }
for (int i = 0; i < 20; i++) for (int i = 0; i < 9; i++)
{ {
AddStep("SeekBackward, Snap", () => AddStep("SeekBackward, Snap", () =>
{ {
@ -316,16 +308,6 @@ namespace osu.Game.Tests.Visual
AddStep("Reset", () => Clock.Seek(0)); AddStep("Reset", () => Clock.Seek(0));
} }
private class TestHitObjectComposer : HitObjectComposer
{
public TestHitObjectComposer(Ruleset ruleset)
: base(ruleset)
{
}
protected override IReadOnlyList<ICompositionTool> CompositionTools => new ICompositionTool[0];
}
private class TimingPointVisualiser : CompositeDrawable private class TimingPointVisualiser : CompositeDrawable
{ {
private readonly double length; private readonly double length;

View File

@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -17,6 +19,12 @@ namespace osu.Game.Tests.Visual
[Description("PlaySongSelect leaderboard")] [Description("PlaySongSelect leaderboard")]
public class TestCaseLeaderboard : OsuTestCase public class TestCaseLeaderboard : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] {
typeof(Placeholder),
typeof(MessagePlaceholder),
typeof(RetrievalFailurePlaceholder),
};
private RulesetStore rulesets; private RulesetStore rulesets;
private readonly FailableLeaderboard leaderboard; private readonly FailableLeaderboard leaderboard;

View File

@ -0,0 +1,63 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
public class TestCaseLoadingAnimation : GridTestCase
{
public TestCaseLoadingAnimation()
: base(2, 2)
{
LoadingAnimation loading;
Cell(0).AddRange(new Drawable[]
{
new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both
},
loading = new LoadingAnimation()
});
loading.Show();
Cell(1).AddRange(new Drawable[]
{
new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both
},
loading = new LoadingAnimation()
});
loading.Show();
Cell(2).AddRange(new Drawable[]
{
new Box
{
Colour = Color4.Gray,
RelativeSizeAxes = Axes.Both
},
loading = new LoadingAnimation()
});
loading.Show();
Cell(3).AddRange(new Drawable[]
{
loading = new LoadingAnimation()
});
Scheduler.AddDelayed(() => loading.ToggleVisibility(), 200, true);
}
}
}

View File

@ -66,8 +66,8 @@ namespace osu.Game.Beatmaps
// General // General
public int AudioLeadIn { get; set; } public int AudioLeadIn { get; set; }
public bool Countdown { get; set; } public bool Countdown { get; set; } = true;
public float StackLeniency { get; set; } public float StackLeniency { get; set; } = 0.7f;
public bool SpecialStyle { get; set; } public bool SpecialStyle { get; set; }
public int RulesetID { get; set; } public int RulesetID { get; set; }

View File

@ -138,7 +138,7 @@ namespace osu.Game.Beatmaps
PostNotification?.Invoke(new SimpleNotification PostNotification?.Invoke(new SimpleNotification
{ {
Icon = FontAwesome.fa_superpowers, Icon = FontAwesome.fa_superpowers,
Text = "You gotta be a supporter to download for now 'yo" Text = "You gotta be an osu!supporter to download for now 'yo"
}); });
return; return;
} }

View File

@ -69,11 +69,22 @@ namespace osu.Game.Beatmaps
} }
catch catch
{ {
return new TrackVirtual(); return null;
} }
} }
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile))); protected override Waveform GetWaveform()
{
try
{
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new Waveform(trackData);
}
catch
{
return null;
}
}
protected override Storyboard GetStoryboard() protected override Storyboard GetStoryboard()
{ {

View File

@ -13,7 +13,13 @@ namespace osu.Game.Beatmaps
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; } public int ID { get; set; }
public int? OnlineBeatmapSetID { get; set; } private int? onlineBeatmapSetID;
public int? OnlineBeatmapSetID
{
get { return onlineBeatmapSetID; }
set { onlineBeatmapSetID = value > 0 ? value : null; }
}
public BeatmapMetadata Metadata { get; set; } public BeatmapMetadata Metadata { get; set; }

View File

@ -45,7 +45,7 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4"); protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4");
protected override Track GetTrack() => new TrackVirtual(); protected override Track GetTrack() => new TrackVirtual { Length = 1000 };
private class DummyRulesetInfo : RulesetInfo private class DummyRulesetInfo : RulesetInfo
{ {

View File

@ -19,7 +19,7 @@ using osu.Game.Skinning;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
public abstract class WorkingBeatmap : IDisposable public abstract partial class WorkingBeatmap : IDisposable
{ {
public readonly BeatmapInfo BeatmapInfo; public readonly BeatmapInfo BeatmapInfo;
@ -116,9 +116,6 @@ namespace osu.Game.Beatmaps
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
} }
// Post-process
rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
foreach (var obj in converted.HitObjects) foreach (var obj in converted.HitObjects)
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
@ -127,6 +124,9 @@ namespace osu.Game.Beatmaps
foreach (var obj in converted.HitObjects) foreach (var obj in converted.HitObjects)
mod.ApplyToHitObject(obj); mod.ApplyToHitObject(obj);
// Post-process
rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();
return converted; return converted;
} }
@ -145,7 +145,7 @@ namespace osu.Game.Beatmaps
private Track populateTrack() private Track populateTrack()
{ {
// we want to ensure that we always have a track, even if it's a fake one. // we want to ensure that we always have a track, even if it's a fake one.
var t = GetTrack() ?? new TrackVirtual(); var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap);
applyRateAdjustments(t); applyRateAdjustments(t);
return t; return t;
} }

View File

@ -0,0 +1,38 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
public partial class WorkingBeatmap
{
/// <summary>
/// A type of <see cref="TrackVirtual"/> which provides a valid length based on the <see cref="HitObject"/>s of an <see cref="IBeatmap"/>.
/// </summary>
protected class VirtualBeatmapTrack : TrackVirtual
{
private const double excess_length = 1000;
public VirtualBeatmapTrack(IBeatmap beatmap)
{
var lastObject = beatmap.HitObjects.LastOrDefault();
switch (lastObject)
{
case null:
Length = excess_length;
break;
case IHasEndTime endTime:
Length = endTime.EndTime + excess_length;
break;
default:
Length = lastObject.StartTime + excess_length;
break;
}
}
}
}
}

View File

@ -18,6 +18,8 @@ namespace osu.Game.Graphics.Containers
private SampleChannel samplePopIn; private SampleChannel samplePopIn;
private SampleChannel samplePopOut; private SampleChannel samplePopOut;
protected virtual bool PlaySamplesOnStateChange => true;
private PreviewTrackManager previewTrackManager; private PreviewTrackManager previewTrackManager;
protected readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All); protected readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
@ -69,12 +71,14 @@ namespace osu.Game.Graphics.Containers
{ {
case Visibility.Visible: case Visibility.Visible:
if (OverlayActivationMode != OverlayActivation.Disabled) if (OverlayActivationMode != OverlayActivation.Disabled)
samplePopIn?.Play(); {
if (PlaySamplesOnStateChange) samplePopIn?.Play();
}
else else
State = Visibility.Hidden; State = Visibility.Hidden;
break; break;
case Visibility.Hidden: case Visibility.Hidden:
samplePopOut?.Play(); if (PlaySamplesOnStateChange) samplePopOut?.Play();
break; break;
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -16,6 +17,8 @@ namespace osu.Game.Graphics.Containers
protected override SpriteText CreateSpriteText() => new OsuSpriteText(); protected override SpriteText CreateSpriteText() => new OsuSpriteText();
public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable);
public void AddIcon(FontAwesome icon, Action<SpriteText> creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); public void AddIcon(FontAwesome icon, Action<SpriteText> creationParameters = null) => AddText(((char)icon).ToString(), creationParameters);
} }
} }

View File

@ -12,14 +12,14 @@ namespace osu.Game.Graphics
{ {
public class DrawableDate : OsuSpriteText, IHasTooltip public class DrawableDate : OsuSpriteText, IHasTooltip
{ {
private readonly DateTimeOffset date; protected readonly DateTimeOffset Date;
public DrawableDate(DateTimeOffset date) public DrawableDate(DateTimeOffset date)
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Font = "Exo2.0-RegularItalic"; Font = "Exo2.0-RegularItalic";
this.date = date.ToLocalTime(); Date = date.ToLocalTime();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -38,7 +38,7 @@ namespace osu.Game.Graphics
{ {
updateTime(); updateTime();
var diffToNow = DateTimeOffset.Now.Subtract(date); var diffToNow = DateTimeOffset.Now.Subtract(Date);
double timeUntilNextUpdate = 1000; double timeUntilNextUpdate = 1000;
if (diffToNow.TotalSeconds > 60) if (diffToNow.TotalSeconds > 60)
@ -58,8 +58,10 @@ namespace osu.Game.Graphics
public override bool HandleMouseInput => true; public override bool HandleMouseInput => true;
private void updateTime() => Text = date.Humanize(); protected virtual string Format() => Date.Humanize();
public string TooltipText => date.ToString(); private void updateTime() => Text = Format();
public virtual string TooltipText => string.Format($"{Date:MMMM d, yyyy h:mm tt \"UTC\"z}");
} }
} }

View File

@ -4,12 +4,17 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using OpenTK; using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class LoadingAnimation : VisibilityContainer public class LoadingAnimation : VisibilityContainer
{ {
private readonly SpriteIcon spinner; private readonly SpriteIcon spinner;
private readonly SpriteIcon spinnerShadow;
private const float spin_duration = 600;
private const float transition_duration = 200;
public LoadingAnimation() public LoadingAnimation()
{ {
@ -20,12 +25,22 @@ namespace osu.Game.Graphics.UserInterface
Children = new Drawable[] Children = new Drawable[]
{ {
spinner = new SpriteIcon spinnerShadow = new SpriteIcon
{ {
Size = new Vector2(20),
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Icon = FontAwesome.fa_spinner RelativeSizeAxes = Axes.Both,
Position = new Vector2(1, 1),
Colour = Color4.Black,
Alpha = 0.4f,
Icon = FontAwesome.fa_circle_o_notch
},
spinner = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.fa_circle_o_notch
} }
}; };
} }
@ -34,12 +49,11 @@ namespace osu.Game.Graphics.UserInterface
{ {
base.LoadComplete(); base.LoadComplete();
spinner.Spin(2000, RotationDirection.Clockwise); spinner.Spin(spin_duration, RotationDirection.Clockwise);
spinnerShadow.Spin(spin_duration, RotationDirection.Clockwise);
} }
private const float transition_duration = 500; protected override void PopIn() => this.FadeIn(transition_duration * 2, Easing.OutQuint);
protected override void PopIn() => this.FadeIn(transition_duration * 5, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint); protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint);
} }

View File

@ -0,0 +1,376 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using osu.Game.Database;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20180628011956_RemoveNegativeSetIDs")]
partial class RemoveNegativeSetIDs
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<double>("SliderMultiplier");
b.Property<double>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("AudioLeadIn");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<bool>("Countdown");
b.Property<double>("DistanceSpacing");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("WidescreenStoryboard");
b.HasKey("ID");
b.HasIndex("BaseDifficultyID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("Hash");
b.HasIndex("MD5Hash");
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("BeatmapInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("Protected");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapSetID")
.IsUnique();
b.ToTable("BeatmapSetInfo");
});
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int>("SkinInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("SkinInfoID");
b.ToTable("SkinFileInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Creator");
b.Property<bool>("DeletePending");
b.Property<string>("Name");
b.HasKey("ID");
b.ToTable("SkinInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
.WithMany()
.HasForeignKey("BaseDifficultyID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
.WithMany("Beatmaps")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("Beatmaps")
.HasForeignKey("MetadataID");
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
.WithMany("Files")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("BeatmapSets")
.HasForeignKey("MetadataID");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Skinning.SkinInfo")
.WithMany("Files")
.HasForeignKey("SkinInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace osu.Game.Migrations
{
public partial class RemoveNegativeSetIDs : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// There was a change that baetmaps were being loaded with "-1" online IDs, which is completely incorrect.
// This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps.
migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -148,7 +148,7 @@ namespace osu.Game.Online.API
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
// before actually going online. // before actually going online.
while (State != APIState.Online) while (State > APIState.Offline && State < APIState.Online)
Thread.Sleep(500); Thread.Sleep(500);
break; break;
@ -158,7 +158,6 @@ namespace osu.Game.Online.API
if (authentication.RequestAccessToken() == null) if (authentication.RequestAccessToken() == null)
{ {
Logout(false); Logout(false);
State = APIState.Offline;
continue; continue;
} }
@ -208,6 +207,14 @@ namespace osu.Game.Online.API
{ {
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout); HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
// special cases for un-typed but useful message responses.
switch (we.Message)
{
case "Unauthorized":
statusCode = HttpStatusCode.Unauthorized;
break;
}
switch (statusCode) switch (statusCode)
{ {
case HttpStatusCode.Unauthorized: case HttpStatusCode.Unauthorized:
@ -292,6 +299,7 @@ namespace osu.Game.Online.API
password = null; password = null;
authentication.Clear(); authentication.Clear();
LocalUser.Value = createGuestUser(); LocalUser.Value = createGuestUser();
State = APIState.Offline;
} }
private static User createGuestUser() => new User private static User createGuestUser() => new User

View File

@ -30,6 +30,8 @@ namespace osu.Game.Overlays
State = Visibility.Visible; State = Visibility.Visible;
} }
protected override bool PlaySamplesOnStateChange => false;
private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v)
{ {
if (v != Visibility.Hidden) return; if (v != Visibility.Hidden) return;

View File

@ -10,6 +10,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
@ -48,11 +49,17 @@ namespace osu.Game.Overlays.Direct
set set
{ {
if (value) if (value)
{
icon.FadeTo(0.5f, transition_duration, Easing.OutQuint);
loadingAnimation.Show(); loadingAnimation.Show();
}
else else
{
icon.FadeTo(1, transition_duration, Easing.OutQuint);
loadingAnimation.Hide(); loadingAnimation.Hide();
} }
} }
}
public PlayButton(BeatmapSetInfo setInfo = null) public PlayButton(BeatmapSetInfo setInfo = null)
{ {
@ -67,7 +74,10 @@ namespace osu.Game.Overlays.Direct
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.fa_play, Icon = FontAwesome.fa_play,
}, },
loadingAnimation = new LoadingAnimation(), loadingAnimation = new LoadingAnimation
{
Size = new Vector2(15),
},
}); });
Playing.ValueChanged += playingStateChanged; Playing.ValueChanged += playingStateChanged;

View File

@ -186,7 +186,7 @@ namespace osu.Game.Overlays.KeyBinding
{ {
if (bindTarget.IsHovered) if (bindTarget.IsHovered)
{ {
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); bindTarget.UpdateKeyCombination(new KeyCombination(KeyCombination.FromInputState(state).Keys.Append(state.Mouse.ScrollDelta.Y > 0 ? InputKey.MouseWheelUp : InputKey.MouseWheelDown)));
finalise(); finalise();
return true; return true;
} }

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Graphics;
namespace osu.Game.Overlays.Profile.Components
{
public class DrawableJoinDate : DrawableDate
{
public DrawableJoinDate(DateTimeOffset date)
: base(date)
{
}
protected override string Format() => Text = Date.ToUniversalTime().Year < 2008 ? "Here since the beginning" : $"{Date:MMMM yyyy}";
public override string TooltipText => $"{Date:MMMM d, yyyy}";
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Profile.Components
{
public class GradeBadge : Container
{
private const float width = 50;
private readonly string grade;
private readonly Sprite badge;
private readonly SpriteText numberText;
public int DisplayCount
{
set => numberText.Text = value.ToString(@"#,0");
}
public GradeBadge(string grade)
{
this.grade = grade;
Width = width;
Height = 41;
Add(badge = new Sprite
{
Width = width,
Height = 26
});
Add(numberText = new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
TextSize = 14,
Font = @"Exo2.0-Bold"
});
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
badge.Texture = textures.Get($"Grades/{grade}");
}
}
}

View File

@ -16,6 +16,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Profile.Components;
using osu.Game.Overlays.Profile.Header; using osu.Game.Overlays.Profile.Header;
using osu.Game.Users; using osu.Game.Users;
@ -375,12 +376,12 @@ namespace osu.Game.Overlays.Profile
if (user.JoinDate.ToUniversalTime().Year < 2008) if (user.JoinDate.ToUniversalTime().Year < 2008)
{ {
infoTextLeft.AddText("Here since the beginning", boldItalic); infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), lightText);
} }
else else
{ {
infoTextLeft.AddText("Joined ", lightText); infoTextLeft.AddText("Joined ", lightText);
infoTextLeft.AddText(new DrawableDate(user.JoinDate), boldItalic); infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic);
} }
infoTextLeft.NewLine(); infoTextLeft.NewLine();
@ -470,43 +471,5 @@ namespace osu.Game.Overlays.Profile
infoTextRight.NewLine(); infoTextRight.NewLine();
} }
private class GradeBadge : Container
{
private const float width = 50;
private readonly string grade;
private readonly Sprite badge;
private readonly SpriteText numberText;
public int DisplayCount
{
set { numberText.Text = value.ToString(@"#,0"); }
}
public GradeBadge(string grade)
{
this.grade = grade;
Width = width;
Height = 41;
Add(badge = new Sprite
{
Width = width,
Height = 26
});
Add(numberText = new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
TextSize = 14,
Font = @"Exo2.0-Bold"
});
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
badge.Texture = textures.Get($"Grades/{grade}");
}
}
} }
} }

View File

@ -141,11 +141,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
break; break;
case RecentActivityType.UserSupportFirst: case RecentActivityType.UserSupportFirst:
message = $"{userLinkTemplate()} has become an osu! supporter - thanks for your generosity!"; message = $"{userLinkTemplate()} has become an osu!supporter - thanks for your generosity!";
break; break;
case RecentActivityType.UserSupportGift: case RecentActivityType.UserSupportGift:
message = $"{userLinkTemplate()} has received the gift of osu! supporter!"; message = $"{userLinkTemplate()} has received the gift of osu!supporter!";
break; break;
case RecentActivityType.UsernameChange: case RecentActivityType.UsernameChange:

View File

@ -45,11 +45,15 @@ namespace osu.Game.Rulesets.Judgements
public double TimeOffset { get; set; } public double TimeOffset { get; set; }
/// <summary> /// <summary>
/// Whether the <see cref="Result"/> should affect the combo portion of the score. /// Whether the <see cref="Result"/> should affect the current combo.
/// If false, the <see cref="Result"/> will be considered for the bonus portion of the score.
/// </summary> /// </summary>
public virtual bool AffectsCombo => true; public virtual bool AffectsCombo => true;
/// <summary>
/// Whether the <see cref="Result"/> should be counted as base (combo) or bonus score.
/// </summary>
public virtual bool IsBonus => !AffectsCombo;
/// <summary> /// <summary>
/// The numeric representation for the result achieved. /// The numeric representation for the result achieved.
/// </summary> /// </summary>

View File

@ -10,7 +10,7 @@ using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Objects.Legacy namespace osu.Game.Rulesets.Objects.Legacy
{ {
internal abstract class ConvertSlider : HitObject, IHasCurve internal abstract class ConvertSlider : HitObject, IHasCurve, IHasLegacyLastTickOffset
{ {
/// <summary> /// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second. /// Scoring distance with a speed-adjusted beat length of 1 second.
@ -45,5 +45,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
} }
public double LegacyLastTickOffset => 36;
} }
} }

View File

@ -1,26 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
/// A HitObject that is part of a combo and has extended information about its position relative to other combo objects.
/// </summary>
public interface IHasComboIndex : IHasCombo
{
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int IndexInCurrentCombo { get; set; }
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int ComboIndex { get; set; }
/// <summary>
/// Whether this is the last object in the current combo.
/// </summary>
bool LastInCombo { get; set; }
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
/// A type of <see cref="HitObject"/> which may require the last tick to be offset.
/// This is specific to osu!stable conversion, and should not be used elsewhere.
/// </summary>
public interface IHasLegacyLastTickOffset
{
double LegacyLastTickOffset { get; }
}
}

View File

@ -84,10 +84,11 @@ namespace osu.Game.Rulesets
{ {
try try
{ {
var instance = r.CreateInstance(); var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo), (RulesetInfo)null)).RulesetInfo;
r.Name = instance.Description; r.Name = instanceInfo.Name;
r.ShortName = instance.ShortName; r.ShortName = instanceInfo.ShortName;
r.InstantiationInfo = instanceInfo.InstantiationInfo;
r.Available = true; r.Available = true;
} }

View File

@ -261,14 +261,20 @@ namespace osu.Game.Rulesets.Scoring
break; break;
} }
baseScore += judgement.NumericResult;
rollingMaxBaseScore += judgement.MaxNumericResult;
JudgedHits++; JudgedHits++;
} }
else if (judgement.IsHit)
if (judgement.IsBonus)
{
if (judgement.IsHit)
bonusScore += judgement.NumericResult; bonusScore += judgement.NumericResult;
} }
else
{
baseScore += judgement.NumericResult;
rollingMaxBaseScore += judgement.MaxNumericResult;
}
}
/// <summary> /// <summary>
/// Removes a judgement. This should reverse everything in <see cref="OnNewJudgement(Judgement)"/>. /// Removes a judgement. This should reverse everything in <see cref="OnNewJudgement(Judgement)"/>.
@ -280,14 +286,18 @@ namespace osu.Game.Rulesets.Scoring
HighestCombo.Value = judgement.HighestComboAtJudgement; HighestCombo.Value = judgement.HighestComboAtJudgement;
if (judgement.AffectsCombo) if (judgement.AffectsCombo)
JudgedHits--;
if (judgement.IsBonus)
{
if (judgement.IsHit)
bonusScore -= judgement.NumericResult;
}
else
{ {
baseScore -= judgement.NumericResult; baseScore -= judgement.NumericResult;
rollingMaxBaseScore -= judgement.MaxNumericResult; rollingMaxBaseScore -= judgement.MaxNumericResult;
JudgedHits--;
} }
else if (judgement.IsHit)
bonusScore -= judgement.NumericResult;
} }
private void updateScore() private void updateScore()

View File

@ -52,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
if (Beatmap.Value == null) if (Beatmap.Value == null)
return; return;
if (Beatmap.Value.Track.Length == double.PositiveInfinity) return;
float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth);
adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length);
} }

View File

@ -47,8 +47,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
return; return;
} }
// Todo: This should be handled more gracefully timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
timeline.RelativeChildSize = Beatmap.Value.Track.Length == double.PositiveInfinity ? Vector2.One : new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
} }
protected void Add(Drawable visualisation) => timeline.Add(visualisation); protected void Add(Drawable visualisation) => timeline.Add(visualisation);

View File

@ -52,17 +52,20 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
Beatmap.BindTo(beatmap); Beatmap.BindTo(beatmap);
} Beatmap.BindValueChanged(b =>
protected override void LoadComplete()
{ {
base.LoadComplete(); waveform.Waveform = b.Waveform;
Beatmap.BindValueChanged(b => waveform.Waveform = b.Waveform); track = b.Track;
waveform.Waveform = Beatmap.Value.Waveform; }, true);
} }
/// <summary> /// <summary>
/// The track's time in the previous frame. /// The timeline's scroll position in the last frame.
/// </summary>
private float lastScrollPosition;
/// <summary>
/// The track time in the last frame.
/// </summary> /// </summary>
private double lastTrackTime; private double lastTrackTime;
@ -76,6 +79,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
/// </summary> /// </summary>
private bool trackWasPlaying; private bool trackWasPlaying;
private Track track;
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
@ -83,49 +88,48 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
// The extrema of track time should be positioned at the centre of the container when scrolled to the start or end // The extrema of track time should be positioned at the centre of the container when scrolled to the start or end
Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 }; Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 };
if (handlingDragInput) // This needs to happen after transforms are updated, but before the scroll position is updated in base.UpdateAfterChildren
{ if (adjustableClock.IsRunning)
// The user is dragging - the track should always follow the timeline
seekTrackToCurrent();
}
else if (adjustableClock.IsRunning)
{
// If the user hasn't provided mouse input but the track is running, always follow the track
scrollToTrackTime(); scrollToTrackTime();
} }
else
protected override void UpdateAfterChildren()
{ {
// The track isn't playing, so we want to smooth-scroll once more, and re-enable wheel scrolling base.UpdateAfterChildren();
// There are two cases we have to be wary of:
// 1) The user scrolls on this timeline: We want the track to follow us if (handlingDragInput)
seekTrackToCurrent();
else if (!adjustableClock.IsRunning)
{
// The track isn't running. There are two cases we have to be wary of:
// 1) The user flick-drags on this timeline: We want the track to follow us
// 2) The user changes the track time through some other means (scrolling in the editor or overview timeline): We want to follow the track time // 2) The user changes the track time through some other means (scrolling in the editor or overview timeline): We want to follow the track time
// The simplest way to cover both cases is by checking that inter-frame track times are identical // The simplest way to cover both cases is by checking whether the scroll position has changed and the audio hasn't been changed externally
if (adjustableClock.CurrentTime == lastTrackTime) if (Current != lastScrollPosition && adjustableClock.CurrentTime == lastTrackTime)
{
// The track hasn't been seeked externally
seekTrackToCurrent(); seekTrackToCurrent();
}
else else
{
// The track has been seeked externally
scrollToTrackTime(); scrollToTrackTime();
} }
}
lastScrollPosition = Current;
lastTrackTime = adjustableClock.CurrentTime; lastTrackTime = adjustableClock.CurrentTime;
void seekTrackToCurrent()
{
if (!(Beatmap.Value.Track is TrackVirtual))
adjustableClock.Seek(Current / Content.DrawWidth * Beatmap.Value.Track.Length);
} }
void scrollToTrackTime() private void seekTrackToCurrent()
{ {
if (!(Beatmap.Value.Track is TrackVirtual)) if (!track.IsLoaded)
ScrollTo((float)(adjustableClock.CurrentTime / Beatmap.Value.Track.Length) * Content.DrawWidth, false); return;
adjustableClock.Seek(Current / Content.DrawWidth * track.Length);
} }
private void scrollToTrackTime()
{
if (!track.IsLoaded)
return;
ScrollTo((float)(adjustableClock.CurrentTime / track.Length) * Content.DrawWidth, false);
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)

View File

@ -158,6 +158,7 @@ namespace osu.Game.Screens.Play
userAudioOffset.TriggerChange(); userAudioOffset.TriggerChange();
ScoreProcessor = RulesetContainer.CreateScoreProcessor(); ScoreProcessor = RulesetContainer.CreateScoreProcessor();
if (!ScoreProcessor.Mode.Disabled)
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
Children = new Drawable[] Children = new Drawable[]

View File

@ -145,7 +145,7 @@ namespace osu.Game.Screens.Select.Leaderboards
replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!")); replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!"));
break; break;
case PlaceholderState.NotSupporter: case PlaceholderState.NotSupporter:
replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!")); replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"));
break; break;
default: default:
replacePlaceholder(null); replacePlaceholder(null);
@ -333,23 +333,4 @@ namespace osu.Game.Screens.Select.Leaderboards
} }
} }
} }
public enum LeaderboardScope
{
Local,
Country,
Global,
Friend,
}
public enum PlaceholderState
{
Successful,
Retrieving,
NetworkFailure,
Unavailable,
NoScores,
NotLoggedIn,
NotSupporter,
}
} }

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Screens.Select.Leaderboards
{
public enum LeaderboardScope
{
Local,
Country,
Global,
Friend,
}
}

View File

@ -2,10 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
namespace osu.Game.Screens.Select.Leaderboards namespace osu.Game.Screens.Select.Leaderboards
{ {
@ -15,22 +12,13 @@ namespace osu.Game.Screens.Select.Leaderboards
public MessagePlaceholder(string message) public MessagePlaceholder(string message)
{ {
Direction = FillDirection.Horizontal; AddIcon(FontAwesome.fa_exclamation_circle, cp =>
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{ {
new SpriteIcon cp.TextSize = TEXT_SIZE;
{ cp.Padding = new MarginPadding { Right = 10 };
Icon = FontAwesome.fa_exclamation_circle, });
Size = new Vector2(26),
Margin = new MarginPadding { Right = 10 }, AddText(this.message = message);
},
new OsuSpriteText
{
Text = this.message = message,
TextSize = 22,
},
};
} }
public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message; public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message;

View File

@ -3,16 +3,27 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.Select.Leaderboards namespace osu.Game.Screens.Select.Leaderboards
{ {
public abstract class Placeholder : FillFlowContainer, IEquatable<Placeholder> public abstract class Placeholder : OsuTextFlowContainer, IEquatable<Placeholder>
{ {
protected const float TEXT_SIZE = 22;
public override bool HandleMouseInput => true;
protected Placeholder() protected Placeholder()
: base(cp => cp.TextSize = TEXT_SIZE)
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
TextAnchor = Anchor.TopCentre;
Padding = new MarginPadding(20);
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
} }
public virtual bool Equals(Placeholder other) => GetType() == other?.GetType(); public virtual bool Equals(Placeholder other) => GetType() == other?.GetType();

View File

@ -0,0 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Screens.Select.Leaderboards
{
public enum PlaceholderState
{
Successful,
Retrieving,
NetworkFailure,
Unavailable,
NoScores,
NotLoggedIn,
NotSupporter,
}
}

View File

@ -3,11 +3,9 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using OpenTK; using OpenTK;
namespace osu.Game.Screens.Select.Leaderboards namespace osu.Game.Screens.Select.Leaderboards
@ -18,22 +16,13 @@ namespace osu.Game.Screens.Select.Leaderboards
public RetrievalFailurePlaceholder() public RetrievalFailurePlaceholder()
{ {
Direction = FillDirection.Horizontal; AddArbitraryDrawable(new RetryButton
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
new RetryButton
{ {
Action = () => OnRetry?.Invoke(), Action = () => OnRetry?.Invoke(),
Margin = new MarginPadding { Right = 10 }, Padding = new MarginPadding { Right = 10 }
}, });
new OsuSpriteText
{ AddText(@"Couldn't retrieve scores!");
Anchor = Anchor.TopLeft,
Text = @"Couldn't retrieve scores!",
TextSize = 22,
},
};
} }
public class RetryButton : OsuHoverContainer public class RetryButton : OsuHoverContainer
@ -44,18 +33,16 @@ namespace osu.Game.Screens.Select.Leaderboards
public RetryButton() public RetryButton()
{ {
Height = 26; AutoSizeAxes = Axes.Both;
Width = 26;
Child = new OsuClickableContainer Child = new OsuClickableContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => Action?.Invoke(), Action = () => Action?.Invoke(),
Child = icon = new SpriteIcon Child = icon = new SpriteIcon
{ {
Icon = FontAwesome.fa_refresh, Icon = FontAwesome.fa_refresh,
Size = new Vector2(26), Size = new Vector2(TEXT_SIZE),
Shadow = true, Shadow = true,
}, },
}; };

View File

@ -1,12 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Tests.Beatmaps namespace osu.Game.Tests.Beatmaps
{ {
@ -26,21 +24,6 @@ namespace osu.Game.Tests.Beatmaps
private readonly IBeatmap beatmap; private readonly IBeatmap beatmap;
protected override IBeatmap GetBeatmap() => beatmap; protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null; protected override Texture GetBackground() => null;
protected override Track GetTrack() => null;
protected override Track GetTrack()
{
var lastObject = beatmap.HitObjects.LastOrDefault();
if (lastObject != null)
return new TestTrack(((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime) + 1000);
return new TrackVirtual();
}
private class TestTrack : TrackVirtual
{
public TestTrack(double length)
{
Length = length;
}
}
} }
} }

View File

@ -14,12 +14,12 @@
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj" /> <ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Humanizer" Version="2.2.0" /> <PackageReference Include="Humanizer" Version="2.3.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="ppy.osu.Framework" Version="2018.622.0" /> <PackageReference Include="ppy.osu.Framework" Version="2018.626.0" />
<PackageReference Include="SharpCompress" Version="0.18.1" /> <PackageReference Include="SharpCompress" Version="0.17.1" />
<PackageReference Include="NUnit" Version="3.10.1" /> <PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
</ItemGroup> </ItemGroup>

View File

@ -11,7 +11,7 @@
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj" /> <ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="DeepEqual" Version="1.6.0" /> <PackageReference Include="DeepEqual" Version="1.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.2" />
<PackageReference Include="NUnit" Version="3.10.1" /> <PackageReference Include="NUnit" Version="3.10.1" />