mirror of
https://github.com/ppy/osu.git
synced 2026-05-15 17:33:52 +08:00
Compare commits
226 Commits
2019.730.0
...
2019.809.0
@@ -1,2 +0,0 @@
|
||||
language: csharp
|
||||
solution: osu.sln
|
||||
@@ -34,7 +34,7 @@ If you are not interested in developing the game, you can still consume our [bin
|
||||
| ------------- | ------------- |
|
||||
|
||||
- **Linux** users are recommended to self-compile until we have official deployment in place.
|
||||
- **iOS** users can join the [TestFlight beta program](https://t.co/PasE1zrHhw) (note that due to high demand this is regularly full).
|
||||
- **iOS** users can join the [TestFlight beta program](https://testflight.apple.com/join/2tLcjWlF) (note that due to high demand this is regularly full).
|
||||
- **Android** users can self-compile, and expect a public beta soon.
|
||||
|
||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cake" Version="0.30.0" />
|
||||
<PackageReference Include="Cake.CoreCLR" Version="0.30.0" />
|
||||
<PackageReference Include="Cake" Version="0.34.1" />
|
||||
<PackageReference Include="Cake.CoreCLR" Version="0.34.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+2
-2
@@ -62,7 +62,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.730.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.731.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.809.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="System.IO.Packaging" Version="4.5.0" />
|
||||
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@@ -22,10 +23,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase("spinner")]
|
||||
[TestCase("spinner-and-circles")]
|
||||
[TestCase("slider")]
|
||||
public new void Test(string name)
|
||||
{
|
||||
base.Test(name);
|
||||
}
|
||||
[TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
|
||||
[TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
|
||||
[TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
|
||||
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.Catch.MathUtils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
base.PostProcess();
|
||||
|
||||
applyPositionOffsets();
|
||||
ApplyPositionOffsets(Beatmap);
|
||||
|
||||
initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
|
||||
|
||||
@@ -40,19 +41,29 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private void applyPositionOffsets()
|
||||
public static void ApplyPositionOffsets(IBeatmap beatmap, params Mod[] mods)
|
||||
{
|
||||
var rng = new FastRandom(RNG_SEED);
|
||||
// todo: HardRock displacement should be applied here
|
||||
|
||||
foreach (var obj in Beatmap.HitObjects)
|
||||
bool shouldApplyHardRockOffset = mods.Any(m => m is ModHardRock);
|
||||
float? lastPosition = null;
|
||||
double lastStartTime = 0;
|
||||
|
||||
foreach (var obj in beatmap.HitObjects.OfType<CatchHitObject>())
|
||||
{
|
||||
obj.XOffset = 0;
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case Fruit fruit:
|
||||
if (shouldApplyHardRockOffset)
|
||||
applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng);
|
||||
break;
|
||||
|
||||
case BananaShower bananaShower:
|
||||
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
|
||||
{
|
||||
banana.X = (float)rng.NextDouble();
|
||||
banana.XOffset = (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
|
||||
@@ -63,12 +74,13 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
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)
|
||||
var catchObject = (CatchHitObject)nested;
|
||||
catchObject.XOffset = 0;
|
||||
|
||||
if (catchObject is TinyDroplet)
|
||||
catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
|
||||
else if (catchObject is Droplet)
|
||||
rng.Next(); // osu!stable retrieved a random droplet rotation
|
||||
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -76,6 +88,105 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
|
||||
{
|
||||
if (hitObject is JuiceStream stream)
|
||||
{
|
||||
lastPosition = stream.EndX;
|
||||
lastStartTime = stream.EndTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(hitObject is Fruit))
|
||||
return;
|
||||
|
||||
float offsetPosition = hitObject.X;
|
||||
double startTime = hitObject.StartTime;
|
||||
|
||||
if (lastPosition == null)
|
||||
{
|
||||
lastPosition = offsetPosition;
|
||||
lastStartTime = startTime;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float positionDiff = offsetPosition - lastPosition.Value;
|
||||
double timeDiff = startTime - lastStartTime;
|
||||
|
||||
if (timeDiff > 1000)
|
||||
{
|
||||
lastPosition = offsetPosition;
|
||||
lastStartTime = startTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (positionDiff == 0)
|
||||
{
|
||||
applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
|
||||
hitObject.XOffset = offsetPosition - hitObject.X;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
|
||||
applyOffset(ref offsetPosition, positionDiff);
|
||||
|
||||
hitObject.XOffset = offsetPosition - hitObject.X;
|
||||
|
||||
lastPosition = offsetPosition;
|
||||
lastStartTime = startTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield.
|
||||
/// </summary>
|
||||
/// <param name="position">The position which the offset should be applied to.</param>
|
||||
/// <param name="maxOffset">The maximum offset, cannot exceed 20px.</param>
|
||||
/// <param name="rng">The random number generator.</param>
|
||||
private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng)
|
||||
{
|
||||
bool right = rng.NextBool();
|
||||
float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
|
||||
|
||||
if (right)
|
||||
{
|
||||
// Clamp to the right bound
|
||||
if (position + rand <= 1)
|
||||
position += rand;
|
||||
else
|
||||
position -= rand;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clamp to the left bound
|
||||
if (position - rand >= 0)
|
||||
position -= rand;
|
||||
else
|
||||
position += rand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield.
|
||||
/// </summary>
|
||||
/// <param name="position">The position which the offset should be applied to.</param>
|
||||
/// <param name="amount">The amount to offset by.</param>
|
||||
private static void applyOffset(ref float position, float amount)
|
||||
{
|
||||
if (amount > 0)
|
||||
{
|
||||
// Clamp to the right bound
|
||||
if (position + amount < 1)
|
||||
position += amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clamp to the left bound
|
||||
if (position + amount > 0)
|
||||
position += amount;
|
||||
}
|
||||
}
|
||||
|
||||
private void initialiseHyperDash(List<CatchHitObject> objects)
|
||||
{
|
||||
List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
@@ -108,7 +107,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
case ModType.Fun:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new ModWindUp<CatchHitObject>(), new ModWindDown<CatchHitObject>())
|
||||
new MultiMod(new ModWindUp(), new ModWindDown())
|
||||
};
|
||||
|
||||
default:
|
||||
|
||||
@@ -61,6 +61,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
|
||||
/// <returns>The random value.</returns>
|
||||
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
|
||||
|
||||
/// <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(double lowerBound, double upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random double value within the range [0, 1).
|
||||
/// </summary>
|
||||
|
||||
@@ -1,121 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModHardRock : ModHardRock, IApplicableToHitObject
|
||||
public class CatchModHardRock : ModHardRock, IApplicableToBeatmap
|
||||
{
|
||||
public override double ScoreMultiplier => 1.12;
|
||||
public override bool Ranked => true;
|
||||
|
||||
private float? lastPosition;
|
||||
private double lastStartTime;
|
||||
|
||||
public void ApplyToHitObject(HitObject hitObject)
|
||||
{
|
||||
if (hitObject is JuiceStream stream)
|
||||
{
|
||||
lastPosition = stream.EndX;
|
||||
lastStartTime = stream.EndTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(hitObject is Fruit))
|
||||
return;
|
||||
|
||||
var catchObject = (CatchHitObject)hitObject;
|
||||
|
||||
float position = catchObject.X;
|
||||
double startTime = hitObject.StartTime;
|
||||
|
||||
if (lastPosition == null)
|
||||
{
|
||||
lastPosition = position;
|
||||
lastStartTime = startTime;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float positionDiff = position - lastPosition.Value;
|
||||
double timeDiff = startTime - lastStartTime;
|
||||
|
||||
if (timeDiff > 1000)
|
||||
{
|
||||
lastPosition = position;
|
||||
lastStartTime = startTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (positionDiff == 0)
|
||||
{
|
||||
applyRandomOffset(ref position, timeDiff / 4d);
|
||||
catchObject.X = position;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
|
||||
applyOffset(ref position, positionDiff);
|
||||
|
||||
catchObject.X = position;
|
||||
|
||||
lastPosition = position;
|
||||
lastStartTime = startTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield.
|
||||
/// </summary>
|
||||
/// <param name="position">The position which the offset should be applied to.</param>
|
||||
/// <param name="maxOffset">The maximum offset, cannot exceed 20px.</param>
|
||||
private void applyRandomOffset(ref float position, double maxOffset)
|
||||
{
|
||||
bool right = RNG.NextBool();
|
||||
float rand = Math.Min(20, (float)RNG.NextDouble(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
|
||||
|
||||
if (right)
|
||||
{
|
||||
// Clamp to the right bound
|
||||
if (position + rand <= 1)
|
||||
position += rand;
|
||||
else
|
||||
position -= rand;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clamp to the left bound
|
||||
if (position - rand >= 0)
|
||||
position -= rand;
|
||||
else
|
||||
position += rand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield.
|
||||
/// </summary>
|
||||
/// <param name="position">The position which the offset should be applied to.</param>
|
||||
/// <param name="amount">The amount to offset by.</param>
|
||||
private void applyOffset(ref float position, float amount)
|
||||
{
|
||||
if (amount > 0)
|
||||
{
|
||||
// Clamp to the right bound
|
||||
if (position + amount < 1)
|
||||
position += amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clamp to the left bound
|
||||
if (position + amount > 0)
|
||||
position += amount;
|
||||
}
|
||||
}
|
||||
public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
@@ -12,7 +13,18 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public const double OBJECT_RADIUS = 44;
|
||||
|
||||
public float X { get; set; }
|
||||
private float x;
|
||||
|
||||
public float X
|
||||
{
|
||||
get => x + XOffset;
|
||||
set => x = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||
/// </summary>
|
||||
internal float XOffset { get; set; }
|
||||
|
||||
public double TimePreempt = 1000;
|
||||
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 369,
|
||||
"Objects": [{
|
||||
"StartTime": 369,
|
||||
"Position": 177
|
||||
},
|
||||
{
|
||||
"StartTime": 450,
|
||||
"Position": 216.539276
|
||||
},
|
||||
{
|
||||
"StartTime": 532,
|
||||
"Position": 256.5667
|
||||
},
|
||||
{
|
||||
"StartTime": 614,
|
||||
"Position": 296.594116
|
||||
},
|
||||
{
|
||||
"StartTime": 696,
|
||||
"Position": 336.621521
|
||||
},
|
||||
{
|
||||
"StartTime": 778,
|
||||
"Position": 376.99762
|
||||
},
|
||||
{
|
||||
"StartTime": 860,
|
||||
"Position": 337.318878
|
||||
},
|
||||
{
|
||||
"StartTime": 942,
|
||||
"Position": 297.291443
|
||||
},
|
||||
{
|
||||
"StartTime": 1024,
|
||||
"Position": 257.264038
|
||||
},
|
||||
{
|
||||
"StartTime": 1106,
|
||||
"Position": 217.2366
|
||||
},
|
||||
{
|
||||
"StartTime": 1188,
|
||||
"Position": 177
|
||||
},
|
||||
{
|
||||
"StartTime": 1270,
|
||||
"Position": 216.818192
|
||||
},
|
||||
{
|
||||
"StartTime": 1352,
|
||||
"Position": 256.8456
|
||||
},
|
||||
{
|
||||
"StartTime": 1434,
|
||||
"Position": 296.873047
|
||||
},
|
||||
{
|
||||
"StartTime": 1516,
|
||||
"Position": 336.900452
|
||||
},
|
||||
{
|
||||
"StartTime": 1598,
|
||||
"Position": 376.99762
|
||||
},
|
||||
{
|
||||
"StartTime": 1680,
|
||||
"Position": 337.039948
|
||||
},
|
||||
{
|
||||
"StartTime": 1762,
|
||||
"Position": 297.0125
|
||||
},
|
||||
{
|
||||
"StartTime": 1844,
|
||||
"Position": 256.9851
|
||||
},
|
||||
{
|
||||
"StartTime": 1926,
|
||||
"Position": 216.957672
|
||||
},
|
||||
{
|
||||
"StartTime": 2008,
|
||||
"Position": 177
|
||||
},
|
||||
{
|
||||
"StartTime": 2090,
|
||||
"Position": 217.097137
|
||||
},
|
||||
{
|
||||
"StartTime": 2172,
|
||||
"Position": 257.124573
|
||||
},
|
||||
{
|
||||
"StartTime": 2254,
|
||||
"Position": 297.152
|
||||
},
|
||||
{
|
||||
"StartTime": 2336,
|
||||
"Position": 337.179443
|
||||
},
|
||||
{
|
||||
"StartTime": 2418,
|
||||
"Position": 376.99762
|
||||
},
|
||||
{
|
||||
"StartTime": 2500,
|
||||
"Position": 336.760956
|
||||
},
|
||||
{
|
||||
"StartTime": 2582,
|
||||
"Position": 296.733643
|
||||
},
|
||||
{
|
||||
"StartTime": 2664,
|
||||
"Position": 256.7062
|
||||
},
|
||||
{
|
||||
"StartTime": 2746,
|
||||
"Position": 216.678772
|
||||
},
|
||||
{
|
||||
"StartTime": 2828,
|
||||
"Position": 177
|
||||
},
|
||||
{
|
||||
"StartTime": 2909,
|
||||
"Position": 216.887909
|
||||
},
|
||||
{
|
||||
"StartTime": 2991,
|
||||
"Position": 256.915344
|
||||
},
|
||||
{
|
||||
"StartTime": 3073,
|
||||
"Position": 296.942749
|
||||
},
|
||||
{
|
||||
"StartTime": 3155,
|
||||
"Position": 336.970184
|
||||
},
|
||||
{
|
||||
"StartTime": 3237,
|
||||
"Position": 376.99762
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.4
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:4
|
||||
|
||||
[TimingPoints]
|
||||
369,327.868852459016,4,2,2,32,1,0
|
||||
|
||||
[HitObjects]
|
||||
177,191,369,6,0,L|382:192,7,200
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 369,
|
||||
"Objects": [{
|
||||
"StartTime": 369,
|
||||
"Position": 65
|
||||
},
|
||||
{
|
||||
"StartTime": 450,
|
||||
"Position": 482
|
||||
},
|
||||
{
|
||||
"StartTime": 532,
|
||||
"Position": 164
|
||||
},
|
||||
{
|
||||
"StartTime": 614,
|
||||
"Position": 315
|
||||
},
|
||||
{
|
||||
"StartTime": 696,
|
||||
"Position": 145
|
||||
},
|
||||
{
|
||||
"StartTime": 778,
|
||||
"Position": 159
|
||||
},
|
||||
{
|
||||
"StartTime": 860,
|
||||
"Position": 310
|
||||
},
|
||||
{
|
||||
"StartTime": 942,
|
||||
"Position": 441
|
||||
},
|
||||
{
|
||||
"StartTime": 1024,
|
||||
"Position": 428
|
||||
},
|
||||
{
|
||||
"StartTime": 1106,
|
||||
"Position": 243
|
||||
},
|
||||
{
|
||||
"StartTime": 1188,
|
||||
"Position": 422
|
||||
},
|
||||
{
|
||||
"StartTime": 1270,
|
||||
"Position": 481
|
||||
},
|
||||
{
|
||||
"StartTime": 1352,
|
||||
"Position": 104
|
||||
},
|
||||
{
|
||||
"StartTime": 1434,
|
||||
"Position": 473
|
||||
},
|
||||
{
|
||||
"StartTime": 1516,
|
||||
"Position": 135
|
||||
},
|
||||
{
|
||||
"StartTime": 1598,
|
||||
"Position": 360
|
||||
},
|
||||
{
|
||||
"StartTime": 1680,
|
||||
"Position": 123
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.4
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:4
|
||||
|
||||
[TimingPoints]
|
||||
369,327.868852459016,4,2,2,32,1,0
|
||||
|
||||
[HitObjects]
|
||||
256,192,369,12,0,1680,0:0:0:0:
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 369,
|
||||
"Objects": [{
|
||||
"StartTime": 369,
|
||||
"Position": 258
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 450,
|
||||
"Objects": [{
|
||||
"StartTime": 450,
|
||||
"Position": 254
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 532,
|
||||
"Objects": [{
|
||||
"StartTime": 532,
|
||||
"Position": 241
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 614,
|
||||
"Objects": [{
|
||||
"StartTime": 614,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 696,
|
||||
"Objects": [{
|
||||
"StartTime": 696,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 778,
|
||||
"Objects": [{
|
||||
"StartTime": 778,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 860,
|
||||
"Objects": [{
|
||||
"StartTime": 860,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 942,
|
||||
"Objects": [{
|
||||
"StartTime": 942,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1024,
|
||||
"Objects": [{
|
||||
"StartTime": 1024,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1106,
|
||||
"Objects": [{
|
||||
"StartTime": 1106,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1188,
|
||||
"Objects": [{
|
||||
"StartTime": 1188,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1270,
|
||||
"Objects": [{
|
||||
"StartTime": 1270,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1352,
|
||||
"Objects": [{
|
||||
"StartTime": 1352,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1434,
|
||||
"Objects": [{
|
||||
"StartTime": 1434,
|
||||
"Position": 258
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1516,
|
||||
"Objects": [{
|
||||
"StartTime": 1516,
|
||||
"Position": 253
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1598,
|
||||
"Objects": [{
|
||||
"StartTime": 1598,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1680,
|
||||
"Objects": [{
|
||||
"StartTime": 1680,
|
||||
"Position": 260
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1762,
|
||||
"Objects": [{
|
||||
"StartTime": 1762,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1844,
|
||||
"Objects": [{
|
||||
"StartTime": 1844,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 1926,
|
||||
"Objects": [{
|
||||
"StartTime": 1926,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2008,
|
||||
"Objects": [{
|
||||
"StartTime": 2008,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2090,
|
||||
"Objects": [{
|
||||
"StartTime": 2090,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2172,
|
||||
"Objects": [{
|
||||
"StartTime": 2172,
|
||||
"Position": 243
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2254,
|
||||
"Objects": [{
|
||||
"StartTime": 2254,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2336,
|
||||
"Objects": [{
|
||||
"StartTime": 2336,
|
||||
"Position": 278
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2418,
|
||||
"Objects": [{
|
||||
"StartTime": 2418,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2500,
|
||||
"Objects": [{
|
||||
"StartTime": 2500,
|
||||
"Position": 258
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2582,
|
||||
"Objects": [{
|
||||
"StartTime": 2582,
|
||||
"Position": 256
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2664,
|
||||
"Objects": [{
|
||||
"StartTime": 2664,
|
||||
"Position": 242
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2746,
|
||||
"Objects": [{
|
||||
"StartTime": 2746,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2828,
|
||||
"Objects": [{
|
||||
"StartTime": 2828,
|
||||
"Position": 238
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2909,
|
||||
"Objects": [{
|
||||
"StartTime": 2909,
|
||||
"Position": 271
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2991,
|
||||
"Objects": [{
|
||||
"StartTime": 2991,
|
||||
"Position": 254
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.4
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
369,327.868852459016,4,2,2,32,1,0
|
||||
|
||||
[HitObjects]
|
||||
258,189,369,1,0,0:0:0:0:
|
||||
258,189,450,1,0,0:0:0:0:
|
||||
258,189,532,1,0,0:0:0:0:
|
||||
258,189,614,1,0,0:0:0:0:
|
||||
258,189,696,1,0,0:0:0:0:
|
||||
258,189,778,1,0,0:0:0:0:
|
||||
258,189,860,1,0,0:0:0:0:
|
||||
258,189,942,1,0,0:0:0:0:
|
||||
258,189,1024,1,0,0:0:0:0:
|
||||
258,189,1106,1,0,0:0:0:0:
|
||||
258,189,1188,1,0,0:0:0:0:
|
||||
258,189,1270,1,0,0:0:0:0:
|
||||
258,189,1352,1,0,0:0:0:0:
|
||||
258,189,1434,1,0,0:0:0:0:
|
||||
258,189,1516,1,0,0:0:0:0:
|
||||
258,189,1598,1,0,0:0:0:0:
|
||||
258,189,1680,1,0,0:0:0:0:
|
||||
258,189,1762,1,0,0:0:0:0:
|
||||
258,189,1844,1,0,0:0:0:0:
|
||||
258,189,1926,1,0,0:0:0:0:
|
||||
258,189,2008,1,0,0:0:0:0:
|
||||
258,189,2090,1,0,0:0:0:0:
|
||||
258,189,2172,1,0,0:0:0:0:
|
||||
258,189,2254,1,0,0:0:0:0:
|
||||
258,189,2336,1,0,0:0:0:0:
|
||||
258,189,2418,1,0,0:0:0:0:
|
||||
258,189,2500,1,0,0:0:0:0:
|
||||
258,189,2582,1,0,0:0:0:0:
|
||||
258,189,2664,1,0,0:0:0:0:
|
||||
258,189,2746,1,0,0:0:0:0:
|
||||
258,189,2828,1,0,0:0:0:0:
|
||||
258,189,2909,1,0,0:0:0:0:
|
||||
258,189,2991,1,0,0:0:0:0:
|
||||
@@ -20,10 +20,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
|
||||
[TestCase("basic")]
|
||||
public new void Test(string name)
|
||||
{
|
||||
base.Test(name);
|
||||
}
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
{
|
||||
@@ -35,11 +32,37 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
};
|
||||
}
|
||||
|
||||
protected override ManiaConvertMapping CreateConvertMapping() => new ManiaConvertMapping(Converter);
|
||||
private readonly Dictionary<HitObject, RngSnapshot> rngSnapshots = new Dictionary<HitObject, RngSnapshot>();
|
||||
|
||||
protected override void OnConversionGenerated(HitObject original, IEnumerable<HitObject> result, IBeatmapConverter beatmapConverter)
|
||||
{
|
||||
base.OnConversionGenerated(original, result, beatmapConverter);
|
||||
|
||||
rngSnapshots[original] = new RngSnapshot(beatmapConverter);
|
||||
}
|
||||
|
||||
protected override ManiaConvertMapping CreateConvertMapping(HitObject source) => new ManiaConvertMapping(rngSnapshots[source]);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
}
|
||||
|
||||
public class RngSnapshot
|
||||
{
|
||||
public readonly uint RandomW;
|
||||
public readonly uint RandomX;
|
||||
public readonly uint RandomY;
|
||||
public readonly uint RandomZ;
|
||||
|
||||
public RngSnapshot(IBeatmapConverter converter)
|
||||
{
|
||||
var maniaConverter = (ManiaBeatmapConverter)converter;
|
||||
RandomW = maniaConverter.Random.W;
|
||||
RandomX = maniaConverter.Random.X;
|
||||
RandomY = maniaConverter.Random.Y;
|
||||
RandomZ = maniaConverter.Random.Z;
|
||||
}
|
||||
}
|
||||
|
||||
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
|
||||
{
|
||||
public uint RandomW;
|
||||
@@ -51,13 +74,12 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
}
|
||||
|
||||
public ManiaConvertMapping(IBeatmapConverter converter)
|
||||
public ManiaConvertMapping(RngSnapshot snapshot)
|
||||
{
|
||||
var maniaConverter = (ManiaBeatmapConverter)converter;
|
||||
RandomW = maniaConverter.Random.W;
|
||||
RandomX = maniaConverter.Random.X;
|
||||
RandomY = maniaConverter.Random.Y;
|
||||
RandomZ = maniaConverter.Random.Z;
|
||||
RandomW = snapshot.RandomW;
|
||||
RandomX = snapshot.RandomX;
|
||||
RandomY = snapshot.RandomY;
|
||||
RandomZ = snapshot.RandomZ;
|
||||
}
|
||||
|
||||
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <summary>
|
||||
/// Keep the same as last row.
|
||||
/// </summary>
|
||||
ForceStack = 1 << 0,
|
||||
ForceStack = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Keep different from last row.
|
||||
|
||||
@@ -13,7 +13,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
@@ -154,7 +153,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
case ModType.Fun:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new ModWindUp<ManiaHitObject>(), new ModWindDown<ManiaHitObject>())
|
||||
new MultiMod(new ModWindUp(), new ModWindDown())
|
||||
};
|
||||
|
||||
default:
|
||||
|
||||
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModMirror : Mod, IApplicableToBeatmap<ManiaHitObject>
|
||||
public class ManiaModMirror : Mod, IApplicableToBeatmap
|
||||
{
|
||||
public override string Name => "Mirror";
|
||||
public override string Acronym => "MR";
|
||||
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToBeatmap(Beatmap<ManiaHitObject> beatmap)
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModRandom : Mod, IApplicableToBeatmap<ManiaHitObject>
|
||||
public class ManiaModRandom : Mod, IApplicableToBeatmap
|
||||
{
|
||||
public override string Name => "Random";
|
||||
public override string Acronym => "RD";
|
||||
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override string Description => @"Shuffle around the keys!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public void ApplyToBeatmap(Beatmap<ManiaHitObject> beatmap)
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList();
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
|
||||
private Cached subtractionCache = new Cached();
|
||||
private readonly Cached subtractionCache = new Cached();
|
||||
|
||||
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
@@ -21,10 +20,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestCase("basic")]
|
||||
[TestCase("colinear-perfect-curve")]
|
||||
[TestCase("slider-ticks")]
|
||||
public new void Test(string name)
|
||||
{
|
||||
base.Test(name);
|
||||
}
|
||||
[TestCase("repeat-slider")]
|
||||
[TestCase("uneven-repeat-slider")]
|
||||
[TestCase("old-stacking")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
{
|
||||
@@ -32,22 +31,22 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
case Slider slider:
|
||||
foreach (var nested in slider.NestedHitObjects)
|
||||
yield return createConvertValue(nested);
|
||||
yield return createConvertValue((OsuHitObject)nested);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
yield return createConvertValue(hitObject);
|
||||
yield return createConvertValue((OsuHitObject)hitObject);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ConvertValue createConvertValue(HitObject obj) => new ConvertValue
|
||||
ConvertValue createConvertValue(OsuHitObject 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,
|
||||
X = obj.StackedPosition.X,
|
||||
Y = obj.StackedPosition.Y
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
|
||||
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
@@ -208,17 +209,22 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
|
||||
break;
|
||||
|
||||
// The start position of the hitobject, or the position at the end of the path if the hitobject is a slider
|
||||
Vector2 position2 = currHitObject is Slider currSlider
|
||||
? currSlider.Position + currSlider.Path.PositionAt(1)
|
||||
: currHitObject.Position;
|
||||
|
||||
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
|
||||
{
|
||||
currHitObject.StackHeight++;
|
||||
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
|
||||
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
|
||||
}
|
||||
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance)
|
||||
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
|
||||
{
|
||||
//Case for sliders - bump notes down and right, rather than up and left.
|
||||
sliderStack++;
|
||||
beatmap.HitObjects[j].StackHeight -= sliderStack;
|
||||
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
|
||||
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
var spanProgress = slider.ProgressAt(completionProgress);
|
||||
|
||||
double start = 0;
|
||||
double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
|
||||
double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1;
|
||||
|
||||
if (span >= slider.SpanCount() - 1)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
|
||||
TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
|
||||
TimeFadeIn = 400; // as per osu-stable
|
||||
|
||||
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
private Cached<Vector2> endPositionCache;
|
||||
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
|
||||
|
||||
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ using osu.Game.Overlays.Settings;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
@@ -136,7 +135,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModWiggle(),
|
||||
new OsuModSpinIn(),
|
||||
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
|
||||
new MultiMod(new ModWindUp<OsuHitObject>(), new ModWindDown<OsuHitObject>()),
|
||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 32165,
|
||||
"Objects": [{
|
||||
"StartTime": 32165,
|
||||
"EndTime": 32165,
|
||||
"X": 32,
|
||||
"Y": 320
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 32517,
|
||||
"Objects": [{
|
||||
"StartTime": 32517,
|
||||
"EndTime": 32517,
|
||||
"X": 246.396057,
|
||||
"Y": 182.396057
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 32605,
|
||||
"Objects": [{
|
||||
"StartTime": 32605,
|
||||
"EndTime": 32605,
|
||||
"X": 249.597382,
|
||||
"Y": 185.597382
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 32693,
|
||||
"Objects": [{
|
||||
"StartTime": 32693,
|
||||
"EndTime": 32693,
|
||||
"X": 252.798691,
|
||||
"Y": 188.798691
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 32781,
|
||||
"Objects": [{
|
||||
"StartTime": 32781,
|
||||
"EndTime": 32781,
|
||||
"X": 256,
|
||||
"Y": 192
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 33248,
|
||||
"Objects": [{
|
||||
"StartTime": 33248,
|
||||
"EndTime": 33248,
|
||||
"X": 39.3960648,
|
||||
"Y": 76.3960648
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 33307,
|
||||
"Objects": [{
|
||||
"StartTime": 33307,
|
||||
"EndTime": 33307,
|
||||
"X": 42.5973778,
|
||||
"Y": 79.597374
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 33383,
|
||||
"Objects": [{
|
||||
"StartTime": 33383,
|
||||
"EndTime": 33383,
|
||||
"X": 45.798687,
|
||||
"Y": 82.79869
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 33459,
|
||||
"Objects": [{
|
||||
"StartTime": 33459,
|
||||
"EndTime": 33459,
|
||||
"X": 49,
|
||||
"Y": 86
|
||||
},
|
||||
{
|
||||
"StartTime": 33635,
|
||||
"EndTime": 33635,
|
||||
"X": 123.847847,
|
||||
"Y": 85.7988
|
||||
},
|
||||
{
|
||||
"StartTime": 33811,
|
||||
"EndTime": 33811,
|
||||
"X": 198.6957,
|
||||
"Y": 85.5975952
|
||||
},
|
||||
{
|
||||
"StartTime": 33988,
|
||||
"EndTime": 33988,
|
||||
"X": 273.9688,
|
||||
"Y": 85.39525
|
||||
},
|
||||
{
|
||||
"StartTime": 34164,
|
||||
"EndTime": 34164,
|
||||
"X": 348.816681,
|
||||
"Y": 85.19404
|
||||
},
|
||||
{
|
||||
"StartTime": 34246,
|
||||
"EndTime": 34246,
|
||||
"X": 398.998718,
|
||||
"Y": 85.05914
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 34341,
|
||||
"Objects": [{
|
||||
"StartTime": 34341,
|
||||
"EndTime": 34341,
|
||||
"X": 401.201324,
|
||||
"Y": 88.20131
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 34400,
|
||||
"Objects": [{
|
||||
"StartTime": 34400,
|
||||
"EndTime": 34400,
|
||||
"X": 404.402618,
|
||||
"Y": 91.402626
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 34459,
|
||||
"Objects": [{
|
||||
"StartTime": 34459,
|
||||
"EndTime": 34459,
|
||||
"X": 407.603943,
|
||||
"Y": 94.6039352
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 34989,
|
||||
"Objects": [{
|
||||
"StartTime": 34989,
|
||||
"EndTime": 34989,
|
||||
"X": 163,
|
||||
"Y": 138
|
||||
},
|
||||
{
|
||||
"StartTime": 35018,
|
||||
"EndTime": 35018,
|
||||
"X": 188,
|
||||
"Y": 138
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 35106,
|
||||
"Objects": [{
|
||||
"StartTime": 35106,
|
||||
"EndTime": 35106,
|
||||
"X": 163,
|
||||
"Y": 138
|
||||
},
|
||||
{
|
||||
"StartTime": 35135,
|
||||
"EndTime": 35135,
|
||||
"X": 188,
|
||||
"Y": 138
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 35224,
|
||||
"Objects": [{
|
||||
"StartTime": 35224,
|
||||
"EndTime": 35224,
|
||||
"X": 163,
|
||||
"Y": 138
|
||||
},
|
||||
{
|
||||
"StartTime": 35253,
|
||||
"EndTime": 35253,
|
||||
"X": 188,
|
||||
"Y": 138
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 35695,
|
||||
"Objects": [{
|
||||
"StartTime": 35695,
|
||||
"EndTime": 35695,
|
||||
"X": 166,
|
||||
"Y": 76
|
||||
},
|
||||
{
|
||||
"StartTime": 35871,
|
||||
"EndTime": 35871,
|
||||
"X": 240.99855,
|
||||
"Y": 75.53417
|
||||
},
|
||||
{
|
||||
"StartTime": 36011,
|
||||
"EndTime": 36011,
|
||||
"X": 315.9971,
|
||||
"Y": 75.0683441
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 36106,
|
||||
"Objects": [{
|
||||
"StartTime": 36106,
|
||||
"EndTime": 36106,
|
||||
"X": 315,
|
||||
"Y": 75
|
||||
},
|
||||
{
|
||||
"StartTime": 36282,
|
||||
"EndTime": 36282,
|
||||
"X": 240.001526,
|
||||
"Y": 75.47769
|
||||
},
|
||||
{
|
||||
"StartTime": 36422,
|
||||
"EndTime": 36422,
|
||||
"X": 165.003052,
|
||||
"Y": 75.95539
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 36518,
|
||||
"Objects": [{
|
||||
"StartTime": 36518,
|
||||
"EndTime": 36518,
|
||||
"X": 166,
|
||||
"Y": 76
|
||||
},
|
||||
{
|
||||
"StartTime": 36694,
|
||||
"EndTime": 36694,
|
||||
"X": 240.99855,
|
||||
"Y": 75.53417
|
||||
},
|
||||
{
|
||||
"StartTime": 36834,
|
||||
"EndTime": 36834,
|
||||
"X": 315.9971,
|
||||
"Y": 75.0683441
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 36929,
|
||||
"Objects": [{
|
||||
"StartTime": 36929,
|
||||
"EndTime": 36929,
|
||||
"X": 315,
|
||||
"Y": 75
|
||||
},
|
||||
{
|
||||
"StartTime": 37105,
|
||||
"EndTime": 37105,
|
||||
"X": 240.001526,
|
||||
"Y": 75.47769
|
||||
},
|
||||
{
|
||||
"StartTime": 37245,
|
||||
"EndTime": 37245,
|
||||
"X": 165.003052,
|
||||
"Y": 75.95539
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
osu file format v3
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:3
|
||||
CircleSize:5
|
||||
OverallDifficulty:8
|
||||
ApproachRate:8
|
||||
SliderMultiplier:1.5
|
||||
SliderTickRate:2
|
||||
|
||||
[TimingPoints]
|
||||
48,352.941176470588,4,1,1,100,1,0
|
||||
|
||||
[HitObjects]
|
||||
// Hit circles
|
||||
32,320,32165,5,2,0:0:0:0:
|
||||
256,192,32517,5,0,0:0:0:0:
|
||||
256,192,32605,1,0,0:0:0:0:
|
||||
256,192,32693,1,0,0:0:0:0:
|
||||
256,192,32781,1,0,0:0:0:0:
|
||||
|
||||
// Hit circles on slider endpoints
|
||||
49,86,33248,1,0,0:0:0:0:
|
||||
49,86,33307,1,0,0:0:0:0:
|
||||
49,86,33383,1,0,0:0:0:0:
|
||||
49,86,33459,2,0,L|421:85,1,350
|
||||
398,85,34341,1,0,0:0:0:0:
|
||||
398,85,34400,1,0,0:0:0:0:
|
||||
398,85,34459,1,0,0:0:0:0:
|
||||
|
||||
// Sliders
|
||||
163,138,34989,2,0,L|196:138,1,25
|
||||
163,138,35106,2,0,L|196:138,1,25
|
||||
163,138,35224,2,0,L|196:138,1,25
|
||||
|
||||
// Reversed sliders
|
||||
166,76,35695,2,0,L|327:75,1,150
|
||||
315,75,36106,2,0,L|158:76,1,150
|
||||
166,76,36518,2,0,L|327:75,1,150
|
||||
315,75,36929,2,0,L|158:76,1,150
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 369,
|
||||
"Objects": [{
|
||||
"StartTime": 369,
|
||||
"EndTime": 369,
|
||||
"X": 177,
|
||||
"Y": 191
|
||||
},
|
||||
{
|
||||
"StartTime": 450,
|
||||
"EndTime": 450,
|
||||
"X": 216.539276,
|
||||
"Y": 191.192871
|
||||
},
|
||||
{
|
||||
"StartTime": 532,
|
||||
"EndTime": 532,
|
||||
"X": 256.5667,
|
||||
"Y": 191.388138
|
||||
},
|
||||
{
|
||||
"StartTime": 614,
|
||||
"EndTime": 614,
|
||||
"X": 296.594116,
|
||||
"Y": 191.583389
|
||||
},
|
||||
{
|
||||
"StartTime": 696,
|
||||
"EndTime": 696,
|
||||
"X": 336.621521,
|
||||
"Y": 191.778641
|
||||
},
|
||||
{
|
||||
"StartTime": 778,
|
||||
"EndTime": 778,
|
||||
"X": 376.648926,
|
||||
"Y": 191.9739
|
||||
},
|
||||
{
|
||||
"StartTime": 860,
|
||||
"EndTime": 860,
|
||||
"X": 337.318878,
|
||||
"Y": 191.782043
|
||||
},
|
||||
{
|
||||
"StartTime": 942,
|
||||
"EndTime": 942,
|
||||
"X": 297.291443,
|
||||
"Y": 191.586792
|
||||
},
|
||||
{
|
||||
"StartTime": 1024,
|
||||
"EndTime": 1024,
|
||||
"X": 257.264038,
|
||||
"Y": 191.391541
|
||||
},
|
||||
{
|
||||
"StartTime": 1106,
|
||||
"EndTime": 1106,
|
||||
"X": 217.2366,
|
||||
"Y": 191.196274
|
||||
},
|
||||
{
|
||||
"StartTime": 1188,
|
||||
"EndTime": 1188,
|
||||
"X": 177.209213,
|
||||
"Y": 191.001022
|
||||
},
|
||||
{
|
||||
"StartTime": 1270,
|
||||
"EndTime": 1270,
|
||||
"X": 216.818192,
|
||||
"Y": 191.194229
|
||||
},
|
||||
{
|
||||
"StartTime": 1352,
|
||||
"EndTime": 1352,
|
||||
"X": 256.8456,
|
||||
"Y": 191.3895
|
||||
},
|
||||
{
|
||||
"StartTime": 1434,
|
||||
"EndTime": 1434,
|
||||
"X": 296.873047,
|
||||
"Y": 191.584747
|
||||
},
|
||||
{
|
||||
"StartTime": 1516,
|
||||
"EndTime": 1516,
|
||||
"X": 336.900452,
|
||||
"Y": 191.78
|
||||
},
|
||||
{
|
||||
"StartTime": 1598,
|
||||
"EndTime": 1598,
|
||||
"X": 376.927917,
|
||||
"Y": 191.975266
|
||||
},
|
||||
{
|
||||
"StartTime": 1680,
|
||||
"EndTime": 1680,
|
||||
"X": 337.039948,
|
||||
"Y": 191.780685
|
||||
},
|
||||
{
|
||||
"StartTime": 1762,
|
||||
"EndTime": 1762,
|
||||
"X": 297.0125,
|
||||
"Y": 191.585434
|
||||
},
|
||||
{
|
||||
"StartTime": 1844,
|
||||
"EndTime": 1844,
|
||||
"X": 256.9851,
|
||||
"Y": 191.390167
|
||||
},
|
||||
{
|
||||
"StartTime": 1926,
|
||||
"EndTime": 1926,
|
||||
"X": 216.957672,
|
||||
"Y": 191.194916
|
||||
},
|
||||
{
|
||||
"StartTime": 2008,
|
||||
"EndTime": 2008,
|
||||
"X": 177.069717,
|
||||
"Y": 191.000336
|
||||
},
|
||||
{
|
||||
"StartTime": 2090,
|
||||
"EndTime": 2090,
|
||||
"X": 217.097137,
|
||||
"Y": 191.1956
|
||||
},
|
||||
{
|
||||
"StartTime": 2172,
|
||||
"EndTime": 2172,
|
||||
"X": 257.124573,
|
||||
"Y": 191.390854
|
||||
},
|
||||
{
|
||||
"StartTime": 2254,
|
||||
"EndTime": 2254,
|
||||
"X": 297.152,
|
||||
"Y": 191.5861
|
||||
},
|
||||
{
|
||||
"StartTime": 2336,
|
||||
"EndTime": 2336,
|
||||
"X": 337.179443,
|
||||
"Y": 191.781372
|
||||
},
|
||||
{
|
||||
"StartTime": 2418,
|
||||
"EndTime": 2418,
|
||||
"X": 376.7884,
|
||||
"Y": 191.974579
|
||||
},
|
||||
{
|
||||
"StartTime": 2500,
|
||||
"EndTime": 2500,
|
||||
"X": 336.760956,
|
||||
"Y": 191.779327
|
||||
},
|
||||
{
|
||||
"StartTime": 2582,
|
||||
"EndTime": 2582,
|
||||
"X": 296.733643,
|
||||
"Y": 191.584076
|
||||
},
|
||||
{
|
||||
"StartTime": 2664,
|
||||
"EndTime": 2664,
|
||||
"X": 256.7062,
|
||||
"Y": 191.388809
|
||||
},
|
||||
{
|
||||
"StartTime": 2746,
|
||||
"EndTime": 2746,
|
||||
"X": 216.678772,
|
||||
"Y": 191.193558
|
||||
},
|
||||
{
|
||||
"StartTime": 2828,
|
||||
"EndTime": 2828,
|
||||
"X": 177.348663,
|
||||
"Y": 191.0017
|
||||
},
|
||||
{
|
||||
"StartTime": 2909,
|
||||
"EndTime": 2909,
|
||||
"X": 216.887909,
|
||||
"Y": 191.19458
|
||||
},
|
||||
{
|
||||
"StartTime": 2991,
|
||||
"EndTime": 2991,
|
||||
"X": 256.915344,
|
||||
"Y": 191.389832
|
||||
},
|
||||
{
|
||||
"StartTime": 3073,
|
||||
"EndTime": 3073,
|
||||
"X": 296.942749,
|
||||
"Y": 191.585083
|
||||
},
|
||||
{
|
||||
"StartTime": 3155,
|
||||
"EndTime": 3155,
|
||||
"X": 336.970184,
|
||||
"Y": 191.78035
|
||||
},
|
||||
{
|
||||
"StartTime": 3201,
|
||||
"EndTime": 3201,
|
||||
"X": 376.99762,
|
||||
"Y": 191.9756
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.4
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:4
|
||||
|
||||
[TimingPoints]
|
||||
369,327.868852459016,4,2,2,32,1,0
|
||||
|
||||
[HitObjects]
|
||||
177,191,369,6,0,L|382:192,7,200
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 369,
|
||||
"Objects": [{
|
||||
"StartTime": 369,
|
||||
"EndTime": 369,
|
||||
"X": 127,
|
||||
"Y": 194
|
||||
},
|
||||
{
|
||||
"StartTime": 450,
|
||||
"EndTime": 450,
|
||||
"X": 166.53389,
|
||||
"Y": 193.8691
|
||||
},
|
||||
{
|
||||
"StartTime": 532,
|
||||
"EndTime": 532,
|
||||
"X": 206.555847,
|
||||
"Y": 193.736572
|
||||
},
|
||||
{
|
||||
"StartTime": 614,
|
||||
"EndTime": 614,
|
||||
"X": 246.57782,
|
||||
"Y": 193.60405
|
||||
},
|
||||
{
|
||||
"StartTime": 696,
|
||||
"EndTime": 696,
|
||||
"X": 286.5998,
|
||||
"Y": 193.471527
|
||||
},
|
||||
{
|
||||
"StartTime": 778,
|
||||
"EndTime": 778,
|
||||
"X": 326.621765,
|
||||
"Y": 193.339
|
||||
},
|
||||
{
|
||||
"StartTime": 860,
|
||||
"EndTime": 860,
|
||||
"X": 366.6437,
|
||||
"Y": 193.206482
|
||||
},
|
||||
{
|
||||
"StartTime": 942,
|
||||
"EndTime": 942,
|
||||
"X": 406.66568,
|
||||
"Y": 193.073959
|
||||
},
|
||||
{
|
||||
"StartTime": 970,
|
||||
"EndTime": 970,
|
||||
"X": 420.331726,
|
||||
"Y": 193.0287
|
||||
},
|
||||
{
|
||||
"StartTime": 997,
|
||||
"EndTime": 997,
|
||||
"X": 407.153748,
|
||||
"Y": 193.072342
|
||||
},
|
||||
{
|
||||
"StartTime": 1079,
|
||||
"EndTime": 1079,
|
||||
"X": 367.131775,
|
||||
"Y": 193.204865
|
||||
},
|
||||
{
|
||||
"StartTime": 1161,
|
||||
"EndTime": 1161,
|
||||
"X": 327.1098,
|
||||
"Y": 193.337387
|
||||
},
|
||||
{
|
||||
"StartTime": 1243,
|
||||
"EndTime": 1243,
|
||||
"X": 287.08783,
|
||||
"Y": 193.46991
|
||||
},
|
||||
{
|
||||
"StartTime": 1325,
|
||||
"EndTime": 1325,
|
||||
"X": 247.0659,
|
||||
"Y": 193.602432
|
||||
},
|
||||
{
|
||||
"StartTime": 1407,
|
||||
"EndTime": 1407,
|
||||
"X": 207.043915,
|
||||
"Y": 193.734955
|
||||
},
|
||||
{
|
||||
"StartTime": 1489,
|
||||
"EndTime": 1489,
|
||||
"X": 167.021988,
|
||||
"Y": 193.867477
|
||||
},
|
||||
{
|
||||
"StartTime": 1571,
|
||||
"EndTime": 1571,
|
||||
"X": 127,
|
||||
"Y": 194
|
||||
},
|
||||
{
|
||||
"StartTime": 1653,
|
||||
"EndTime": 1653,
|
||||
"X": 167.021988,
|
||||
"Y": 193.867477
|
||||
},
|
||||
{
|
||||
"StartTime": 1735,
|
||||
"EndTime": 1735,
|
||||
"X": 207.043976,
|
||||
"Y": 193.734955
|
||||
},
|
||||
{
|
||||
"StartTime": 1817,
|
||||
"EndTime": 1817,
|
||||
"X": 247.065887,
|
||||
"Y": 193.602432
|
||||
},
|
||||
{
|
||||
"StartTime": 1899,
|
||||
"EndTime": 1899,
|
||||
"X": 287.08783,
|
||||
"Y": 193.46991
|
||||
},
|
||||
{
|
||||
"StartTime": 1981,
|
||||
"EndTime": 1981,
|
||||
"X": 327.1098,
|
||||
"Y": 193.337387
|
||||
},
|
||||
{
|
||||
"StartTime": 2062,
|
||||
"EndTime": 2062,
|
||||
"X": 366.643738,
|
||||
"Y": 193.206482
|
||||
},
|
||||
{
|
||||
"StartTime": 2144,
|
||||
"EndTime": 2144,
|
||||
"X": 406.665649,
|
||||
"Y": 193.073959
|
||||
},
|
||||
{
|
||||
"StartTime": 2172,
|
||||
"EndTime": 2172,
|
||||
"X": 420.331726,
|
||||
"Y": 193.0287
|
||||
},
|
||||
{
|
||||
"StartTime": 2199,
|
||||
"EndTime": 2199,
|
||||
"X": 407.153748,
|
||||
"Y": 193.072342
|
||||
},
|
||||
{
|
||||
"StartTime": 2281,
|
||||
"EndTime": 2281,
|
||||
"X": 367.1318,
|
||||
"Y": 193.204865
|
||||
},
|
||||
{
|
||||
"StartTime": 2363,
|
||||
"EndTime": 2363,
|
||||
"X": 327.1098,
|
||||
"Y": 193.337387
|
||||
},
|
||||
{
|
||||
"StartTime": 2445,
|
||||
"EndTime": 2445,
|
||||
"X": 287.08783,
|
||||
"Y": 193.46991
|
||||
},
|
||||
{
|
||||
"StartTime": 2527,
|
||||
"EndTime": 2527,
|
||||
"X": 247.065887,
|
||||
"Y": 193.602432
|
||||
},
|
||||
{
|
||||
"StartTime": 2609,
|
||||
"EndTime": 2609,
|
||||
"X": 207.043976,
|
||||
"Y": 193.734955
|
||||
},
|
||||
{
|
||||
"StartTime": 2691,
|
||||
"EndTime": 2691,
|
||||
"X": 167.021988,
|
||||
"Y": 193.867477
|
||||
},
|
||||
{
|
||||
"StartTime": 2773,
|
||||
"EndTime": 2773,
|
||||
"X": 127,
|
||||
"Y": 194
|
||||
},
|
||||
{
|
||||
"StartTime": 2855,
|
||||
"EndTime": 2855,
|
||||
"X": 167.021988,
|
||||
"Y": 193.867477
|
||||
},
|
||||
{
|
||||
"StartTime": 2937,
|
||||
"EndTime": 2937,
|
||||
"X": 207.043976,
|
||||
"Y": 193.734955
|
||||
},
|
||||
{
|
||||
"StartTime": 3019,
|
||||
"EndTime": 3019,
|
||||
"X": 247.065948,
|
||||
"Y": 193.602432
|
||||
},
|
||||
{
|
||||
"StartTime": 3101,
|
||||
"EndTime": 3101,
|
||||
"X": 287.087952,
|
||||
"Y": 193.46991
|
||||
},
|
||||
{
|
||||
"StartTime": 3183,
|
||||
"EndTime": 3183,
|
||||
"X": 327.109772,
|
||||
"Y": 193.337387
|
||||
},
|
||||
{
|
||||
"StartTime": 3265,
|
||||
"EndTime": 3265,
|
||||
"X": 367.131775,
|
||||
"Y": 193.204865
|
||||
},
|
||||
{
|
||||
"StartTime": 3347,
|
||||
"EndTime": 3347,
|
||||
"X": 407.153748,
|
||||
"Y": 193.072342
|
||||
},
|
||||
{
|
||||
"StartTime": 3374,
|
||||
"EndTime": 3374,
|
||||
"X": 420.331726,
|
||||
"Y": 193.0287
|
||||
},
|
||||
{
|
||||
"StartTime": 3401,
|
||||
"EndTime": 3401,
|
||||
"X": 407.153748,
|
||||
"Y": 193.072342
|
||||
},
|
||||
{
|
||||
"StartTime": 3483,
|
||||
"EndTime": 3483,
|
||||
"X": 367.131775,
|
||||
"Y": 193.204865
|
||||
},
|
||||
{
|
||||
"StartTime": 3565,
|
||||
"EndTime": 3565,
|
||||
"X": 327.109772,
|
||||
"Y": 193.337387
|
||||
},
|
||||
{
|
||||
"StartTime": 3647,
|
||||
"EndTime": 3647,
|
||||
"X": 287.087952,
|
||||
"Y": 193.46991
|
||||
},
|
||||
{
|
||||
"StartTime": 3729,
|
||||
"EndTime": 3729,
|
||||
"X": 247.065948,
|
||||
"Y": 193.602432
|
||||
},
|
||||
{
|
||||
"StartTime": 3811,
|
||||
"EndTime": 3811,
|
||||
"X": 207.043976,
|
||||
"Y": 193.734955
|
||||
},
|
||||
{
|
||||
"StartTime": 3893,
|
||||
"EndTime": 3893,
|
||||
"X": 167.021988,
|
||||
"Y": 193.867477
|
||||
},
|
||||
{
|
||||
"StartTime": 3975,
|
||||
"EndTime": 3975,
|
||||
"X": 127,
|
||||
"Y": 194
|
||||
},
|
||||
{
|
||||
"StartTime": 4057,
|
||||
"EndTime": 4057,
|
||||
"X": 167.021988,
|
||||
"Y": 193.867477
|
||||
},
|
||||
{
|
||||
"StartTime": 4139,
|
||||
"EndTime": 4139,
|
||||
"X": 207.043976,
|
||||
"Y": 193.734955
|
||||
},
|
||||
{
|
||||
"StartTime": 4221,
|
||||
"EndTime": 4221,
|
||||
"X": 247.065948,
|
||||
"Y": 193.602432
|
||||
},
|
||||
{
|
||||
"StartTime": 4303,
|
||||
"EndTime": 4303,
|
||||
"X": 287.087952,
|
||||
"Y": 193.46991
|
||||
},
|
||||
{
|
||||
"StartTime": 4385,
|
||||
"EndTime": 4385,
|
||||
"X": 327.109772,
|
||||
"Y": 193.337387
|
||||
},
|
||||
{
|
||||
"StartTime": 4467,
|
||||
"EndTime": 4467,
|
||||
"X": 367.131775,
|
||||
"Y": 193.204865
|
||||
},
|
||||
{
|
||||
"StartTime": 4540,
|
||||
"EndTime": 4540,
|
||||
"X": 420.331726,
|
||||
"Y": 193.0287
|
||||
},
|
||||
{
|
||||
"StartTime": 4549,
|
||||
"EndTime": 4549,
|
||||
"X": 407.153748,
|
||||
"Y": 193.072342
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.4
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:4
|
||||
|
||||
[TimingPoints]
|
||||
369,327.868852459016,4,2,2,32,1,0
|
||||
|
||||
[HitObjects]
|
||||
// A slider with an un-even amount of ticks
|
||||
127,194,369,6,0,L|429:193,7,293.333333333333
|
||||
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
{
|
||||
public class OsuCursor : SkinReloadableDrawable
|
||||
{
|
||||
private const float size = 28;
|
||||
|
||||
private bool cursorExpand;
|
||||
|
||||
private Bindable<double> cursorScale;
|
||||
@@ -30,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
public OsuCursor()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(28);
|
||||
|
||||
Size = new Vector2(size);
|
||||
}
|
||||
|
||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||
@@ -46,62 +49,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
BorderThickness = Size.X / 6,
|
||||
BorderColour = Color4.White,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Pink.Opacity(0.5f),
|
||||
Radius = 5,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
BorderThickness = Size.X / 3,
|
||||
BorderColour = Color4.White.Opacity(0.5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(0.1f),
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.White,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}, confineMode: ConfineMode.NoScaling)
|
||||
Child = scaleTarget = new SkinnableDrawable("Play/osu/cursor", _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
@@ -144,5 +92,76 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
}
|
||||
|
||||
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
|
||||
|
||||
private class DefaultCursor : CompositeDrawable
|
||||
{
|
||||
public DefaultCursor()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
BorderThickness = size / 6,
|
||||
BorderColour = Color4.White,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Pink.Opacity(0.5f),
|
||||
Radius = 5,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
BorderThickness = size / 3,
|
||||
BorderColour = Color4.White.Opacity(0.5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(0.1f),
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.White,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,15 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
private const float playfield_size_adjust = 0.8f;
|
||||
|
||||
public OsuPlayfieldAdjustmentContainer()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
// Calculated from osu!stable as 512 (default gamefield size) / 640 (default window size)
|
||||
Size = new Vector2(0.8f);
|
||||
Size = new Vector2(playfield_size_adjust);
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
@@ -41,7 +43,19 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// The following calculation results in a constant of 1.6 when OsuPlayfieldAdjustmentContainer
|
||||
// is consuming the full game_size. This matches the osu-stable "magic ratio".
|
||||
//
|
||||
// game_size = DrawSizePreservingFillContainer.TargetSize = new Vector2(1024, 768)
|
||||
//
|
||||
// Parent is a 4:3 aspect enforced, using height as the constricting dimension
|
||||
// Parent.ChildSize.X = min(game_size.X, game_size.Y * (4 / 3)) * playfield_size_adjust
|
||||
// Parent.ChildSize.X = 819.2
|
||||
//
|
||||
// Scale = 819.2 / 512
|
||||
// Scale = 1.6
|
||||
Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X);
|
||||
// Size = 0.625
|
||||
Size = Vector2.Divide(Vector2.One, Scale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
[NonParallelizable]
|
||||
[TestCase("basic")]
|
||||
[TestCase("slider-generating-drumroll")]
|
||||
public new void Test(string name)
|
||||
{
|
||||
base.Test(name);
|
||||
}
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
@@ -107,7 +106,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
case ModType.Fun:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new ModWindUp<TaikoHitObject>(), new ModWindDown<TaikoHitObject>())
|
||||
new MultiMod(new ModWindUp(), new ModWindDown())
|
||||
};
|
||||
|
||||
default:
|
||||
|
||||
@@ -482,5 +482,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInvalidEventStillPasses()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
|
||||
using (var badResStream = TestResources.OpenResource("invalid-events.osu"))
|
||||
using (var badStream = new StreamReader(badResStream))
|
||||
{
|
||||
Assert.DoesNotThrow(() => decoder.Decode(badStream));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
[TestFixture]
|
||||
public class SliderEventGenerationTest
|
||||
{
|
||||
private const double start_time = 0;
|
||||
private const double span_duration = 1000;
|
||||
|
||||
[Test]
|
||||
public void TestSingleSpan()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
|
||||
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
|
||||
|
||||
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tail));
|
||||
Assert.That(events[3].Time, Is.EqualTo(span_duration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRepeat()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
|
||||
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
|
||||
|
||||
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Repeat));
|
||||
Assert.That(events[2].Time, Is.EqualTo(span_duration));
|
||||
|
||||
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[3].Time, Is.EqualTo(span_duration + span_duration / 2));
|
||||
|
||||
Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tail));
|
||||
Assert.That(events[5].Time, Is.EqualTo(2 * span_duration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonEvenTicks()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
|
||||
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[1].Time, Is.EqualTo(300));
|
||||
|
||||
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[2].Time, Is.EqualTo(600));
|
||||
|
||||
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[3].Time, Is.EqualTo(900));
|
||||
|
||||
Assert.That(events[4].Type, Is.EqualTo(SliderEventType.Repeat));
|
||||
Assert.That(events[4].Time, Is.EqualTo(span_duration));
|
||||
|
||||
Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[5].Time, Is.EqualTo(1100));
|
||||
|
||||
Assert.That(events[6].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[6].Time, Is.EqualTo(1400));
|
||||
|
||||
Assert.That(events[7].Type, Is.EqualTo(SliderEventType.Tick));
|
||||
Assert.That(events[7].Time, Is.EqualTo(1700));
|
||||
|
||||
Assert.That(events[9].Type, Is.EqualTo(SliderEventType.Tail));
|
||||
Assert.That(events[9].Time, Is.EqualTo(2 * span_duration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLegacyLastTickOffset()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
|
||||
|
||||
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
|
||||
Assert.That(events[2].Time, Is.EqualTo(900));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMinimumTickDistance()
|
||||
{
|
||||
const double velocity = 5;
|
||||
const double min_distance = velocity * 10;
|
||||
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
int tickIndex = -1;
|
||||
|
||||
while (++tickIndex < events.Length)
|
||||
{
|
||||
if (events[tickIndex].Type != SliderEventType.Tick)
|
||||
continue;
|
||||
|
||||
Assert.That(events[tickIndex].Time, Is.LessThan(span_duration - min_distance).Or.GreaterThan(span_duration + min_distance));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
osu file format v14
|
||||
|
||||
[Events]
|
||||
bad,event,this,should,fail
|
||||
//Background and Video events
|
||||
0,0,"machinetop_background.jpg",0,0
|
||||
//Break Periods
|
||||
2,122474,140135
|
||||
//Storyboard Layer 0 (Background)
|
||||
this,is,also,bad
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
@@ -1,8 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
@@ -11,78 +14,172 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[TestFixture]
|
||||
public class TestSceneBreakOverlay : OsuTestScene
|
||||
{
|
||||
private readonly BreakOverlay breakOverlay;
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(BreakOverlay),
|
||||
};
|
||||
|
||||
private readonly TestBreakOverlay breakOverlay;
|
||||
|
||||
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 5000,
|
||||
},
|
||||
new BreakPeriod
|
||||
{
|
||||
StartTime = 6000,
|
||||
EndTime = 13500,
|
||||
},
|
||||
};
|
||||
|
||||
public TestSceneBreakOverlay()
|
||||
{
|
||||
Child = breakOverlay = new BreakOverlay(true);
|
||||
|
||||
AddStep("2s break", () => startBreak(2000));
|
||||
AddStep("5s break", () => startBreak(5000));
|
||||
AddStep("10s break", () => startBreak(10000));
|
||||
AddStep("15s break", () => startBreak(15000));
|
||||
AddStep("2s, 2s", startMultipleBreaks);
|
||||
AddStep("0.5s, 0.7s, 1s, 2s", startAnotherMultipleBreaks);
|
||||
Add(breakOverlay = new TestBreakOverlay(true));
|
||||
}
|
||||
|
||||
private void startBreak(double duration)
|
||||
[Test]
|
||||
public void TestShowBreaks()
|
||||
{
|
||||
breakOverlay.Breaks = new List<BreakPeriod>
|
||||
setClock(false);
|
||||
|
||||
addShowBreakStep(2);
|
||||
addShowBreakStep(5);
|
||||
addShowBreakStep(15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoEffectsBreak()
|
||||
{
|
||||
var shortBreak = new BreakPeriod { EndTime = 500 };
|
||||
|
||||
setClock(true);
|
||||
loadBreaksStep("short break", new[] { shortBreak });
|
||||
|
||||
addBreakSeeks(shortBreak, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleBreaks()
|
||||
{
|
||||
setClock(true);
|
||||
loadBreaksStep("multiple breaks", testBreaks);
|
||||
|
||||
foreach (var b in testBreaks)
|
||||
addBreakSeeks(b, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRewindBreaks()
|
||||
{
|
||||
setClock(true);
|
||||
loadBreaksStep("multiple breaks", testBreaks);
|
||||
|
||||
foreach (var b in testBreaks.Reverse())
|
||||
addBreakSeeks(b, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkipBreaks()
|
||||
{
|
||||
setClock(true);
|
||||
loadBreaksStep("multiple breaks", testBreaks);
|
||||
|
||||
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
|
||||
AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
|
||||
|
||||
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
|
||||
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
|
||||
seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false);
|
||||
}
|
||||
|
||||
private void addShowBreakStep(double seconds)
|
||||
{
|
||||
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod
|
||||
{
|
||||
StartTime = Clock.CurrentTime,
|
||||
EndTime = Clock.CurrentTime + duration,
|
||||
EndTime = Clock.CurrentTime + seconds * 1000,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void startMultipleBreaks()
|
||||
private void setClock(bool useManual)
|
||||
{
|
||||
double currentTime = Clock.CurrentTime;
|
||||
|
||||
breakOverlay.Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod
|
||||
{
|
||||
StartTime = currentTime,
|
||||
EndTime = currentTime + 2000,
|
||||
},
|
||||
new BreakPeriod
|
||||
{
|
||||
StartTime = currentTime + 4000,
|
||||
EndTime = currentTime + 6000,
|
||||
}
|
||||
};
|
||||
AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
|
||||
}
|
||||
|
||||
private void startAnotherMultipleBreaks()
|
||||
private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks)
|
||||
{
|
||||
double currentTime = Clock.CurrentTime;
|
||||
AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
|
||||
seekAndAssertBreak("seek back to 0", 0, false);
|
||||
}
|
||||
|
||||
breakOverlay.Breaks = new List<BreakPeriod>
|
||||
private void addBreakSeeks(BreakPeriod b, bool isReversed)
|
||||
{
|
||||
if (isReversed)
|
||||
{
|
||||
new BreakPeriod // Duration is less than 650 - too short to appear
|
||||
{
|
||||
StartTime = currentTime,
|
||||
EndTime = currentTime + 500,
|
||||
},
|
||||
new BreakPeriod
|
||||
{
|
||||
StartTime = currentTime + 1500,
|
||||
EndTime = currentTime + 2200,
|
||||
},
|
||||
new BreakPeriod
|
||||
{
|
||||
StartTime = currentTime + 3200,
|
||||
EndTime = currentTime + 4200,
|
||||
},
|
||||
new BreakPeriod
|
||||
{
|
||||
StartTime = currentTime + 5200,
|
||||
EndTime = currentTime + 7200,
|
||||
}
|
||||
};
|
||||
seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
|
||||
seekAndAssertBreak("seek to break end", b.EndTime, false);
|
||||
seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
|
||||
seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
|
||||
}
|
||||
else
|
||||
{
|
||||
seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
|
||||
seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
|
||||
seekAndAssertBreak("seek to break end", b.EndTime, false);
|
||||
seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
|
||||
{
|
||||
AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
|
||||
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
|
||||
{
|
||||
breakOverlay.ProgressTime();
|
||||
return breakOverlay.IsBreakTime.Value == shouldBeBreak;
|
||||
});
|
||||
}
|
||||
|
||||
private class TestBreakOverlay : BreakOverlay
|
||||
{
|
||||
private readonly FramedClock framedManualClock;
|
||||
private readonly ManualClock manualClock;
|
||||
private IFrameBasedClock originalClock;
|
||||
|
||||
public new int CurrentBreakIndex => base.CurrentBreakIndex;
|
||||
|
||||
public double ManualClockTime
|
||||
{
|
||||
get => manualClock.CurrentTime;
|
||||
set => manualClock.CurrentTime = value;
|
||||
}
|
||||
|
||||
public TestBreakOverlay(bool letterboxing)
|
||||
: base(letterboxing)
|
||||
{
|
||||
framedManualClock = new FramedClock(manualClock = new ManualClock());
|
||||
ProcessCustomClock = false;
|
||||
}
|
||||
|
||||
public void ProgressTime()
|
||||
{
|
||||
framedManualClock.ProcessFrame();
|
||||
Update();
|
||||
}
|
||||
|
||||
public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
originalClock = Clock;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Users;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
@@ -23,18 +25,25 @@ namespace osu.Game.Tests.Visual.Online
|
||||
public TestSceneProfileRulesetSelector()
|
||||
{
|
||||
ProfileRulesetSelector selector;
|
||||
Bindable<User> user = new Bindable<User>();
|
||||
|
||||
Child = selector = new ProfileRulesetSelector
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
User = { BindTarget = user }
|
||||
};
|
||||
|
||||
AddStep("set osu! as default", () => selector.SetDefaultRuleset(new OsuRuleset().RulesetInfo));
|
||||
AddStep("set mania as default", () => selector.SetDefaultRuleset(new ManiaRuleset().RulesetInfo));
|
||||
AddStep("set taiko as default", () => selector.SetDefaultRuleset(new TaikoRuleset().RulesetInfo));
|
||||
AddStep("set catch as default", () => selector.SetDefaultRuleset(new CatchRuleset().RulesetInfo));
|
||||
AddStep("select default ruleset", selector.SelectDefaultRuleset);
|
||||
|
||||
AddStep("User with osu as default", () => user.Value = new User { PlayMode = "osu" });
|
||||
AddStep("User with mania as default", () => user.Value = new User { PlayMode = "mania" });
|
||||
AddStep("User with taiko as default", () => user.Value = new User { PlayMode = "taiko" });
|
||||
AddStep("User with catch as default", () => user.Value = new User { PlayMode = "fruits" });
|
||||
AddStep("null user", () => user.Value = null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,28 +69,22 @@ namespace osu.Game.Tests.Visual.Online
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("null user", () => graph.User.Value = null);
|
||||
AddStep("null user", () => graph.Statistics.Value = null);
|
||||
AddStep("rank only", () =>
|
||||
{
|
||||
graph.User.Value = new User
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 123456 },
|
||||
PP = 12345,
|
||||
}
|
||||
Ranks = new UserStatistics.UserRanks { Global = 123456 },
|
||||
PP = 12345,
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("with rank history", () =>
|
||||
{
|
||||
graph.User.Value = new User
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 89000 },
|
||||
PP = 12345,
|
||||
},
|
||||
Ranks = new UserStatistics.UserRanks { Global = 89000 },
|
||||
PP = 12345,
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
Data = data,
|
||||
@@ -100,13 +94,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
AddStep("with zero values", () =>
|
||||
{
|
||||
graph.User.Value = new User
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 89000 },
|
||||
PP = 12345,
|
||||
},
|
||||
Ranks = new UserStatistics.UserRanks { Global = 89000 },
|
||||
PP = 12345,
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
Data = dataWithZeros,
|
||||
@@ -116,13 +107,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
AddStep("small amount of data", () =>
|
||||
{
|
||||
graph.User.Value = new User
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 12000 },
|
||||
PP = 12345,
|
||||
},
|
||||
Ranks = new UserStatistics.UserRanks { Global = 12000 },
|
||||
PP = 12345,
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
Data = smallData,
|
||||
@@ -132,13 +120,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
AddStep("graph with edges", () =>
|
||||
{
|
||||
graph.User.Value = new User
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 12000 },
|
||||
PP = 12345,
|
||||
},
|
||||
Ranks = new UserStatistics.UserRanks { Global = 12000 },
|
||||
PP = 12345,
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
Data = edgyData,
|
||||
|
||||
@@ -50,12 +50,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Current = 727,
|
||||
Progress = 69,
|
||||
}
|
||||
},
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
Mode = @"osu",
|
||||
Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
|
||||
},
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
Mode = @"osu",
|
||||
Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
|
||||
},
|
||||
},
|
||||
Badges = new[]
|
||||
{
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Users;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneUserRequest : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private readonly Bindable<User> user = new Bindable<User>();
|
||||
private GetUserRequest request;
|
||||
private readonly DimmedLoadingLayer loading;
|
||||
|
||||
public TestSceneUserRequest()
|
||||
{
|
||||
Add(new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserTestContainer
|
||||
{
|
||||
User = { BindTarget = user }
|
||||
},
|
||||
loading = new DimmedLoadingLayer
|
||||
{
|
||||
Alpha = 0
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddStep(@"local user", () => getUser());
|
||||
AddStep(@"local user with taiko ruleset", () => getUser(ruleset: new TaikoRuleset().RulesetInfo));
|
||||
AddStep(@"cookiezi", () => getUser(124493));
|
||||
AddStep(@"cookiezi with mania ruleset", () => getUser(124493, new ManiaRuleset().RulesetInfo));
|
||||
}
|
||||
|
||||
private void getUser(long? userId = null, RulesetInfo ruleset = null)
|
||||
{
|
||||
loading.Show();
|
||||
|
||||
request?.Cancel();
|
||||
request = new GetUserRequest(userId, ruleset);
|
||||
request.Success += user =>
|
||||
{
|
||||
this.user.Value = user;
|
||||
loading.Hide();
|
||||
};
|
||||
api.Queue(request);
|
||||
}
|
||||
|
||||
private class UserTestContainer : FillFlowContainer
|
||||
{
|
||||
public readonly Bindable<User> User = new Bindable<User>();
|
||||
|
||||
public UserTestContainer()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Direction = FillDirection.Vertical;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
User.BindValueChanged(onUserUpdate, true);
|
||||
}
|
||||
|
||||
private void onUserUpdate(ValueChangedEvent<User> user)
|
||||
{
|
||||
Clear();
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new SpriteText
|
||||
{
|
||||
Text = $@"Username: {user.NewValue?.Username}"
|
||||
},
|
||||
new SpriteText
|
||||
{
|
||||
Text = $@"RankedScore: {user.NewValue?.Statistics.RankedScore}"
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace osu.Game.Tournament.Screens.Ladder
|
||||
};
|
||||
}
|
||||
|
||||
private Cached layout = new Cached();
|
||||
private readonly Cached layout = new Cached();
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.IO.File;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@@ -290,8 +289,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
string[] split = line.Split(',');
|
||||
|
||||
EventType type;
|
||||
|
||||
if (!Enum.TryParse(split[0], out type))
|
||||
throw new InvalidDataException($@"Unknown event type {split[0]}");
|
||||
throw new InvalidDataException($@"Unknown event type: {split[0]}");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
@@ -319,90 +319,79 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
private void handleTimingPoint(string line)
|
||||
{
|
||||
try
|
||||
string[] split = line.Split(',');
|
||||
|
||||
double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim()));
|
||||
double beatLength = Parsing.ParseDouble(split[1].Trim());
|
||||
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
|
||||
|
||||
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
|
||||
if (split.Length >= 3)
|
||||
timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]);
|
||||
|
||||
LegacySampleBank sampleSet = defaultSampleBank;
|
||||
if (split.Length >= 4)
|
||||
sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]);
|
||||
|
||||
int customSampleBank = 0;
|
||||
if (split.Length >= 5)
|
||||
customSampleBank = Parsing.ParseInt(split[4]);
|
||||
|
||||
int sampleVolume = defaultSampleVolume;
|
||||
if (split.Length >= 6)
|
||||
sampleVolume = Parsing.ParseInt(split[5]);
|
||||
|
||||
bool timingChange = true;
|
||||
if (split.Length >= 7)
|
||||
timingChange = split[6][0] == '1';
|
||||
|
||||
bool kiaiMode = false;
|
||||
bool omitFirstBarSignature = false;
|
||||
|
||||
if (split.Length >= 8)
|
||||
{
|
||||
string[] split = line.Split(',');
|
||||
|
||||
double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim()));
|
||||
double beatLength = Parsing.ParseDouble(split[1].Trim());
|
||||
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
|
||||
|
||||
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
|
||||
if (split.Length >= 3)
|
||||
timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]);
|
||||
|
||||
LegacySampleBank sampleSet = defaultSampleBank;
|
||||
if (split.Length >= 4)
|
||||
sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]);
|
||||
|
||||
int customSampleBank = 0;
|
||||
if (split.Length >= 5)
|
||||
customSampleBank = Parsing.ParseInt(split[4]);
|
||||
|
||||
int sampleVolume = defaultSampleVolume;
|
||||
if (split.Length >= 6)
|
||||
sampleVolume = Parsing.ParseInt(split[5]);
|
||||
|
||||
bool timingChange = true;
|
||||
if (split.Length >= 7)
|
||||
timingChange = split[6][0] == '1';
|
||||
|
||||
bool kiaiMode = false;
|
||||
bool omitFirstBarSignature = false;
|
||||
|
||||
if (split.Length >= 8)
|
||||
{
|
||||
EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]);
|
||||
kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai);
|
||||
omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine);
|
||||
}
|
||||
|
||||
string stringSampleSet = sampleSet.ToString().ToLowerInvariant();
|
||||
if (stringSampleSet == @"none")
|
||||
stringSampleSet = @"normal";
|
||||
|
||||
if (timingChange)
|
||||
{
|
||||
var controlPoint = CreateTimingControlPoint();
|
||||
controlPoint.Time = time;
|
||||
controlPoint.BeatLength = beatLength;
|
||||
controlPoint.TimeSignature = timeSignature;
|
||||
|
||||
handleTimingControlPoint(controlPoint);
|
||||
}
|
||||
|
||||
handleDifficultyControlPoint(new DifficultyControlPoint
|
||||
{
|
||||
Time = time,
|
||||
SpeedMultiplier = speedMultiplier,
|
||||
AutoGenerated = timingChange
|
||||
});
|
||||
|
||||
handleEffectControlPoint(new EffectControlPoint
|
||||
{
|
||||
Time = time,
|
||||
KiaiMode = kiaiMode,
|
||||
OmitFirstBarLine = omitFirstBarSignature,
|
||||
AutoGenerated = timingChange
|
||||
});
|
||||
|
||||
handleSampleControlPoint(new LegacySampleControlPoint
|
||||
{
|
||||
Time = time,
|
||||
SampleBank = stringSampleSet,
|
||||
SampleVolume = sampleVolume,
|
||||
CustomSampleBank = customSampleBank,
|
||||
AutoGenerated = timingChange
|
||||
});
|
||||
EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]);
|
||||
kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai);
|
||||
omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine);
|
||||
}
|
||||
catch (FormatException)
|
||||
|
||||
string stringSampleSet = sampleSet.ToString().ToLowerInvariant();
|
||||
if (stringSampleSet == @"none")
|
||||
stringSampleSet = @"normal";
|
||||
|
||||
if (timingChange)
|
||||
{
|
||||
Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important);
|
||||
var controlPoint = CreateTimingControlPoint();
|
||||
controlPoint.Time = time;
|
||||
controlPoint.BeatLength = beatLength;
|
||||
controlPoint.TimeSignature = timeSignature;
|
||||
|
||||
handleTimingControlPoint(controlPoint);
|
||||
}
|
||||
catch (OverflowException)
|
||||
|
||||
handleDifficultyControlPoint(new DifficultyControlPoint
|
||||
{
|
||||
Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important);
|
||||
}
|
||||
Time = time,
|
||||
SpeedMultiplier = speedMultiplier,
|
||||
AutoGenerated = timingChange
|
||||
});
|
||||
|
||||
handleEffectControlPoint(new EffectControlPoint
|
||||
{
|
||||
Time = time,
|
||||
KiaiMode = kiaiMode,
|
||||
OmitFirstBarLine = omitFirstBarSignature,
|
||||
AutoGenerated = timingChange
|
||||
});
|
||||
|
||||
handleSampleControlPoint(new LegacySampleControlPoint
|
||||
{
|
||||
Time = time,
|
||||
SampleBank = stringSampleSet,
|
||||
SampleVolume = sampleVolume,
|
||||
CustomSampleBank = customSampleBank,
|
||||
AutoGenerated = timingChange
|
||||
});
|
||||
}
|
||||
|
||||
private void handleTimingControlPoint(TimingControlPoint newPoint)
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $"Failed to process line \"{line}\" into {output}");
|
||||
Logger.Log($"Failed to process line \"{line}\" into {output}: {e.Message}", LoggingTarget.Runtime, LogLevel.Important);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
storyboardSprite = null;
|
||||
|
||||
EventType type;
|
||||
|
||||
if (!Enum.TryParse(split[0], out type))
|
||||
throw new InvalidDataException($@"Unknown event type {split[0]}");
|
||||
throw new InvalidDataException($@"Unknown event type: {split[0]}");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace osu.Game.Beatmaps.Legacy
|
||||
public enum LegacyMods
|
||||
{
|
||||
None = 0,
|
||||
NoFail = 1 << 0,
|
||||
NoFail = 1,
|
||||
Easy = 1 << 1,
|
||||
TouchDevice = 1 << 2,
|
||||
Hidden = 1 << 3,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Beatmaps.Timing
|
||||
{
|
||||
public class BreakPeriod
|
||||
@@ -35,6 +37,6 @@ namespace osu.Game.Beatmaps.Timing
|
||||
/// </summary>
|
||||
/// <param name="time">The time to check in milliseconds.</param>
|
||||
/// <returns>Whether the time falls within this <see cref="BreakPeriod"/>.</returns>
|
||||
public bool Contains(double time) => time >= StartTime && time <= EndTime;
|
||||
public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,14 @@ namespace osu.Game.Beatmaps
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IBeatmapConverter"/> to convert a <see cref="IBeatmap"/> for a specified <see cref="Ruleset"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> to be converted.</param>
|
||||
/// <param name="ruleset">The <see cref="Ruleset"/> for which <paramref name="beatmap"/> should be converted.</param>
|
||||
/// <returns>The applicable <see cref="IBeatmapConverter"/>.</returns>
|
||||
protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a playable <see cref="IBeatmap"/> from <see cref="Beatmap"/> using the applicable converters for a specific <see cref="RulesetInfo"/>.
|
||||
/// <para>
|
||||
@@ -104,7 +112,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
IBeatmapConverter converter = rulesetInstance.CreateBeatmapConverter(Beatmap);
|
||||
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
|
||||
|
||||
// Check if the beatmap can be converted
|
||||
if (!converter.CanConvert)
|
||||
@@ -141,6 +149,9 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
processor?.PostProcess();
|
||||
|
||||
foreach (var mod in mods.OfType<IApplicableToBeatmap>())
|
||||
mod.ApplyToBeatmap(converted);
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,21 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model type.</typeparam>
|
||||
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
||||
public abstract class ArchiveModelManager<TModel, TFileModel> : ArchiveModelManager, ICanAcceptFiles, IModelManager<TModel>
|
||||
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>
|
||||
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
||||
where TFileModel : INamedFileInfo, new()
|
||||
{
|
||||
private const int import_queue_request_concurrency = 1;
|
||||
|
||||
/// <summary>
|
||||
/// A singleton scheduler shared by all <see cref="ArchiveModelManager{TModel,TFileModel}"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly.
|
||||
/// It is mainly being used as a queue mechanism for large imports.
|
||||
/// </remarks>
|
||||
private static readonly ThreadedTaskScheduler import_scheduler = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager<TModel, TFileModel>));
|
||||
|
||||
/// <summary>
|
||||
/// Set an endpoint for notifications to be posted to.
|
||||
/// </summary>
|
||||
@@ -336,7 +347,7 @@ namespace osu.Game.Database
|
||||
|
||||
flushEvents(true);
|
||||
return item;
|
||||
}, cancellationToken, TaskCreationOptions.HideScheduler, IMPORT_SCHEDULER).Unwrap();
|
||||
}, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap();
|
||||
|
||||
/// <summary>
|
||||
/// Perform an update of the specified item.
|
||||
@@ -646,18 +657,4 @@ namespace osu.Game.Database
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public abstract class ArchiveModelManager
|
||||
{
|
||||
private const int import_queue_request_concurrency = 1;
|
||||
|
||||
/// <summary>
|
||||
/// A singleton scheduler shared by all <see cref="ArchiveModelManager{TModel,TFileModel}"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly.
|
||||
/// It is mainly being used as a queue mechanism for large imports.
|
||||
/// </remarks>
|
||||
protected static readonly ThreadedTaskScheduler IMPORT_SCHEDULER = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
private readonly List<TriangleParticle> parts = new List<TriangleParticle>();
|
||||
private Vector2 size;
|
||||
|
||||
private TriangleBatch<TexturedVertex2D> vertexBatch;
|
||||
private QuadBatch<TexturedVertex2D> vertexBatch;
|
||||
|
||||
public TrianglesDrawNode(Triangles source)
|
||||
: base(source)
|
||||
@@ -217,7 +217,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount))
|
||||
{
|
||||
vertexBatch?.Dispose();
|
||||
vertexBatch = new TriangleBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||
vertexBatch = new QuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||
}
|
||||
|
||||
shader.Bind();
|
||||
|
||||
@@ -92,7 +92,8 @@ namespace osu.Game.Graphics
|
||||
|
||||
using (var image = await host.TakeScreenshotAsync())
|
||||
{
|
||||
Interlocked.Decrement(ref screenShotTasks);
|
||||
if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false)
|
||||
cursorVisibility.Value = true;
|
||||
|
||||
var fileName = getFileName();
|
||||
if (fileName == null) return;
|
||||
@@ -125,14 +126,6 @@ namespace osu.Game.Graphics
|
||||
}
|
||||
});
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (cursorVisibility.Value == false && Interlocked.CompareExchange(ref screenShotTasks, 0, 0) == 0)
|
||||
cursorVisibility.Value = true;
|
||||
}
|
||||
|
||||
private string getFileName()
|
||||
{
|
||||
var dt = DateTime.Now;
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
Child = button = new TwoLayerButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Text = @"back",
|
||||
Icon = OsuIcon.LeftCircle,
|
||||
Action = () => Action?.Invoke()
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[Flags]
|
||||
public enum BarDirection
|
||||
{
|
||||
LeftToRight = 1 << 0,
|
||||
LeftToRight = 1,
|
||||
RightToLeft = 1 << 1,
|
||||
TopToBottom = 1 << 2,
|
||||
BottomToTop = 1 << 3,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class DimmedLoadingLayer : VisibilityContainer
|
||||
{
|
||||
private const float transition_duration = 250;
|
||||
|
||||
private readonly LoadingAnimation loading;
|
||||
|
||||
public DimmedLoadingLayer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black.Opacity(0.5f),
|
||||
},
|
||||
loading = new LoadingAnimation(),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.FadeIn(transition_duration, Easing.OutQuint);
|
||||
loading.Show();
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.FadeOut(transition_duration, Easing.OutQuint);
|
||||
loading.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
return base.Invalidate(invalidation, source, shallPropagate);
|
||||
}
|
||||
|
||||
private Cached pathCached = new Cached();
|
||||
private readonly Cached pathCached = new Cached();
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
|
||||
@@ -81,7 +81,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Colour = Color4.White,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
}
|
||||
},
|
||||
new HoverClickSounds()
|
||||
};
|
||||
|
||||
Current.ValueChanged += selected =>
|
||||
|
||||
@@ -15,6 +15,7 @@ using System;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@@ -28,7 +29,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private const int transform_time = 600;
|
||||
private const int pulse_length = 250;
|
||||
|
||||
private const float shear = 0.1f;
|
||||
private const float shear_width = 5f;
|
||||
|
||||
private static readonly Vector2 shear = new Vector2(shear_width / Footer.HEIGHT, 0);
|
||||
|
||||
public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50);
|
||||
public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50);
|
||||
@@ -56,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
|
||||
c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
|
||||
|
||||
X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear * 0.5f : 0;
|
||||
X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
|
||||
|
||||
Remove(c1);
|
||||
Remove(c2);
|
||||
@@ -70,6 +73,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public TwoLayerButton()
|
||||
{
|
||||
Size = SIZE_RETRACTED;
|
||||
Shear = shear;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@@ -82,7 +86,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Shear = new Vector2(shear, 0),
|
||||
Masking = true,
|
||||
MaskingSmoothness = 2,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
@@ -105,6 +108,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -shear,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -119,7 +123,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Shear = new Vector2(shear, 0),
|
||||
Masking = true,
|
||||
MaskingSmoothness = 2,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
@@ -144,6 +147,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Shear = -shear,
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -188,7 +192,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
var flash = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Shear = new Vector2(shear, 0),
|
||||
Colour = Color4.White.Opacity(0.5f),
|
||||
};
|
||||
Add(flash);
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
Favourite,
|
||||
RankedAndApproved,
|
||||
Loved,
|
||||
Unranked,
|
||||
Graveyard
|
||||
}
|
||||
|
||||
@@ -2,18 +2,21 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserRequest : APIRequest<User>
|
||||
{
|
||||
private readonly long? userId;
|
||||
private readonly RulesetInfo ruleset;
|
||||
|
||||
public GetUserRequest(long? userId = null)
|
||||
public GetUserRequest(long? userId = null, RulesetInfo ruleset = null)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.ruleset = ruleset;
|
||||
}
|
||||
|
||||
protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me";
|
||||
protected override string Target => userId.HasValue ? $@"users/{userId}/{ruleset?.ShortName}" : $@"me/{ruleset?.ShortName}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
@@ -10,12 +12,24 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly ScoreType type;
|
||||
private readonly RulesetInfo ruleset;
|
||||
|
||||
public GetUserScoresRequest(long userId, ScoreType type, int page = 0, int itemsPerPage = 5)
|
||||
public GetUserScoresRequest(long userId, ScoreType type, int page = 0, int itemsPerPage = 5, RulesetInfo ruleset = null)
|
||||
: base(page, itemsPerPage)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.type = type;
|
||||
this.ruleset = ruleset;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
|
||||
if (ruleset != null)
|
||||
req.AddParameter("mode", ruleset.ShortName);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLowerInvariant()}";
|
||||
|
||||
@@ -27,24 +27,25 @@ namespace osu.Game.Online.API.Requests
|
||||
}
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)searchCategory}&sort={sortCriteria.ToString().ToLowerInvariant()}_{directionString}";
|
||||
protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={searchCategory.ToString().ToLowerInvariant()}&sort={sortCriteria.ToString().ToLowerInvariant()}_{directionString}";
|
||||
}
|
||||
|
||||
public enum BeatmapSearchCategory
|
||||
{
|
||||
Any = 7,
|
||||
Any,
|
||||
|
||||
[Description("Ranked & Approved")]
|
||||
RankedApproved = 0,
|
||||
Qualified = 3,
|
||||
Loved = 8,
|
||||
Favourites = 2,
|
||||
[Description("Has Leaderboard")]
|
||||
Leaderboard,
|
||||
Ranked,
|
||||
Qualified,
|
||||
Loved,
|
||||
Favourites,
|
||||
|
||||
[Description("Pending & WIP")]
|
||||
PendingWIP = 4,
|
||||
Graveyard = 5,
|
||||
Pending,
|
||||
Graveyard,
|
||||
|
||||
[Description("My Maps")]
|
||||
MyMaps = 6,
|
||||
Mine,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +213,27 @@ namespace osu.Game.Online.Chat
|
||||
PostMessage(content, true);
|
||||
break;
|
||||
|
||||
case "join":
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
target.AddNewMessages(new ErrorMessage("Usage: /join [channel]"));
|
||||
break;
|
||||
}
|
||||
|
||||
var channel = availableChannels.Where(c => c.Name == content || c.Name == $"#{content}").FirstOrDefault();
|
||||
|
||||
if (channel == null)
|
||||
{
|
||||
target.AddNewMessages(new ErrorMessage($"Channel '{content}' not found."));
|
||||
break;
|
||||
}
|
||||
|
||||
JoinChannel(channel);
|
||||
CurrentChannel.Value = channel;
|
||||
break;
|
||||
|
||||
case "help":
|
||||
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]"));
|
||||
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel]"));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace osu.Game.Online.Chat
|
||||
public ErrorMessage(string message)
|
||||
: base(message)
|
||||
{
|
||||
Sender.Colour = @"ff0000";
|
||||
// todo: this should likely be styled differently in the future.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,22 +48,24 @@ namespace osu.Game.Online
|
||||
attachDownload(manager.GetExistingDownload(modelInfo.NewValue));
|
||||
}, true);
|
||||
|
||||
manager.DownloadBegan += download =>
|
||||
{
|
||||
if (download.Model.Equals(Model.Value))
|
||||
attachDownload(download);
|
||||
};
|
||||
|
||||
manager.DownloadFailed += download =>
|
||||
{
|
||||
if (download.Model.Equals(Model.Value))
|
||||
attachDownload(null);
|
||||
};
|
||||
|
||||
manager.DownloadBegan += downloadBegan;
|
||||
manager.DownloadFailed += downloadFailed;
|
||||
manager.ItemAdded += itemAdded;
|
||||
manager.ItemRemoved += itemRemoved;
|
||||
}
|
||||
|
||||
private void downloadBegan(ArchiveDownloadRequest<TModel> request)
|
||||
{
|
||||
if (request.Model.Equals(Model.Value))
|
||||
attachDownload(request);
|
||||
}
|
||||
|
||||
private void downloadFailed(ArchiveDownloadRequest<TModel> request)
|
||||
{
|
||||
if (request.Model.Equals(Model.Value))
|
||||
attachDownload(null);
|
||||
}
|
||||
|
||||
private ArchiveDownloadRequest<TModel> attachedRequest;
|
||||
|
||||
private void attachDownload(ArchiveDownloadRequest<TModel> request)
|
||||
@@ -126,8 +128,10 @@ namespace osu.Game.Online
|
||||
|
||||
if (manager != null)
|
||||
{
|
||||
manager.DownloadBegan -= attachDownload;
|
||||
manager.DownloadBegan -= downloadBegan;
|
||||
manager.DownloadFailed -= downloadFailed;
|
||||
manager.ItemAdded -= itemAdded;
|
||||
manager.ItemRemoved -= itemRemoved;
|
||||
}
|
||||
|
||||
State.UnbindAll();
|
||||
|
||||
+3
-1
@@ -307,7 +307,9 @@ namespace osu.Game
|
||||
if (nextBeatmap?.Track != null)
|
||||
nextBeatmap.Track.Completed += currentTrackCompleted;
|
||||
|
||||
beatmap.OldValue?.Dispose();
|
||||
using (var oldBeatmap = beatmap.OldValue)
|
||||
if (oldBeatmap?.Track != null)
|
||||
oldBeatmap.Track.Completed -= currentTrackCompleted;
|
||||
|
||||
nextBeatmap?.LoadBeatmapAsync();
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
|
||||
Scores = null;
|
||||
|
||||
if (beatmap?.OnlineBeatmapID.HasValue != true)
|
||||
if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap.Status <= BeatmapSetOnlineStatus.Pending)
|
||||
return;
|
||||
|
||||
loadingAnimation.Show();
|
||||
|
||||
@@ -256,6 +256,9 @@ namespace osu.Game.Overlays
|
||||
loadedChannels.Add(loaded);
|
||||
LoadComponentAsync(loaded, l =>
|
||||
{
|
||||
if (currentChannel.Value != e.NewValue)
|
||||
return;
|
||||
|
||||
loading.Hide();
|
||||
|
||||
currentChannelContainer.Clear(false);
|
||||
@@ -381,7 +384,18 @@ namespace osu.Game.Overlays
|
||||
foreach (Channel channel in channels)
|
||||
{
|
||||
ChannelTabControl.RemoveChannel(channel);
|
||||
loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel));
|
||||
|
||||
var loaded = loadedChannels.Find(c => c.Channel == channel);
|
||||
|
||||
if (loaded != null)
|
||||
{
|
||||
loadedChannels.Remove(loaded);
|
||||
|
||||
// Because the container is only cleared in the async load callback of a new channel, it is forcefully cleared
|
||||
// to ensure that the previous channel doesn't get updated after it's disposed
|
||||
currentChannelContainer.Remove(loaded);
|
||||
loaded.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Direct
|
||||
|
||||
protected override Color4 BackgroundColour => OsuColour.FromHex(@"384552");
|
||||
protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked;
|
||||
protected override BeatmapSearchCategory DefaultCategory => BeatmapSearchCategory.Leaderboard;
|
||||
|
||||
protected override Drawable CreateSupplementaryControls() => rulesetSelector = new DirectRulesetSelector();
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
foregroundIcon.Highlighted = Selected;
|
||||
foregroundIcon.Highlighted.Value = Selected;
|
||||
|
||||
SelectionChanged?.Invoke(SelectedMod);
|
||||
return true;
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@@ -16,6 +18,8 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
private Color4 accentColour = Color4.White;
|
||||
|
||||
public readonly Bindable<User> User = new Bindable<User>();
|
||||
|
||||
public ProfileRulesetSelector()
|
||||
{
|
||||
TabContainer.Masking = false;
|
||||
@@ -32,24 +36,17 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
((ProfileRulesetTabItem)tabItem).AccentColour = accentColour;
|
||||
}
|
||||
|
||||
public void SetDefaultRuleset(RulesetInfo ruleset)
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
// Todo: This method shouldn't exist, but bindables don't provide the concept of observing a change to the default value
|
||||
foreach (TabItem<RulesetInfo> tabItem in TabContainer)
|
||||
((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID;
|
||||
base.LoadComplete();
|
||||
|
||||
User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true);
|
||||
}
|
||||
|
||||
public void SelectDefaultRuleset()
|
||||
public void SetDefaultRuleset(RulesetInfo ruleset)
|
||||
{
|
||||
// Todo: This method shouldn't exist, but bindables don't provide the concept of observing a change to the default value
|
||||
foreach (TabItem<RulesetInfo> tabItem in TabContainer)
|
||||
{
|
||||
if (((ProfileRulesetTabItem)tabItem).IsDefault)
|
||||
{
|
||||
Current.Value = ((ProfileRulesetTabItem)tabItem).Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID;
|
||||
}
|
||||
|
||||
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value)
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
|
||||
private KeyValuePair<int, int>[] ranks;
|
||||
private int dayIndex;
|
||||
public Bindable<User> User = new Bindable<User>();
|
||||
public readonly Bindable<UserStatistics> Statistics = new Bindable<UserStatistics>();
|
||||
|
||||
public RankGraph()
|
||||
{
|
||||
@@ -56,8 +56,6 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
};
|
||||
|
||||
graph.OnBallMove += i => dayIndex = i;
|
||||
|
||||
User.ValueChanged += userChanged;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -66,18 +64,25 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
graph.LineColour = colours.Yellow;
|
||||
}
|
||||
|
||||
private void userChanged(ValueChangedEvent<User> e)
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue), true);
|
||||
}
|
||||
|
||||
private void updateStatistics(UserStatistics statistics)
|
||||
{
|
||||
placeholder.FadeIn(fade_duration, Easing.Out);
|
||||
|
||||
if (e.NewValue?.Statistics?.Ranks.Global == null)
|
||||
if (statistics?.Ranks.Global == null)
|
||||
{
|
||||
graph.FadeOut(fade_duration, Easing.Out);
|
||||
ranks = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int[] userRanks = e.NewValue.RankHistory?.Data ?? new[] { e.NewValue.Statistics.Ranks.Global.Value };
|
||||
int[] userRanks = statistics.RankHistory?.Data ?? new[] { statistics.Ranks.Global.Value };
|
||||
ranks = userRanks.Select((x, index) => new KeyValuePair<int, int>(index, x)).Where(x => x.Value != 0).ToArray();
|
||||
|
||||
if (ranks.Length > 1)
|
||||
@@ -191,7 +196,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
}
|
||||
}
|
||||
|
||||
public string TooltipText => User.Value?.Statistics?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}";
|
||||
public string TooltipText => Statistics.Value?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}";
|
||||
|
||||
public ITooltip GetCustomTooltip() => new RankGraphTooltip();
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
background.Colour = colours.Pink;
|
||||
iconContainer.Colour = colours.GreySeafoam;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-";
|
||||
detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-";
|
||||
|
||||
rankGraph.User.Value = user;
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
}
|
||||
|
||||
private class ScoreRankInfo : CompositeDrawable
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections
|
||||
{
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User, "Favourite Beatmaps"),
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User, "Ranked & Approved Beatmaps"),
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Loved, User, "Loved Beatmaps"),
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"),
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps"),
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace osu.Game.Overlays.SearchableList
|
||||
|
||||
protected abstract Color4 BackgroundColour { get; }
|
||||
protected abstract T DefaultTab { get; }
|
||||
protected abstract U DefaultCategory { get; }
|
||||
protected virtual Drawable CreateSupplementaryControls() => null;
|
||||
|
||||
/// <summary>
|
||||
@@ -109,6 +110,9 @@ namespace osu.Game.Overlays.SearchableList
|
||||
|
||||
Tabs.Current.Value = DefaultTab;
|
||||
Tabs.Current.TriggerChange();
|
||||
|
||||
DisplayStyleControl.Dropdown.Current.Value = DefaultCategory;
|
||||
DisplayStyleControl.Dropdown.Current.TriggerChange();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace osu.Game.Overlays.Settings
|
||||
Content.Anchor = Anchor.CentreLeft;
|
||||
Content.Origin = Anchor.CentreLeft;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
ScrollbarVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ namespace osu.Game.Overlays
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 };
|
||||
ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
|
||||
Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
|
||||
}
|
||||
|
||||
protected class SettingsSectionsContainer : SectionsContainer<SettingsSection>
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace osu.Game.Overlays.Social
|
||||
{
|
||||
protected override Color4 BackgroundColour => OsuColour.FromHex(@"47253a");
|
||||
protected override SocialSortCriteria DefaultTab => SocialSortCriteria.Rank;
|
||||
protected override SortDirection DefaultCategory => SortDirection.Ascending;
|
||||
|
||||
public FilterControl()
|
||||
{
|
||||
|
||||
@@ -2,21 +2,18 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a <see cref="Mod"/> that applies changes to a <see cref="Beatmap"/>
|
||||
/// after conversion and post-processing has completed.
|
||||
/// Interface for a <see cref="Mod"/> that applies changes to a <see cref="Beatmap"/> after conversion and post-processing has completed.
|
||||
/// </summary>
|
||||
public interface IApplicableToBeatmap<TObject> : IApplicableMod
|
||||
where TObject : HitObject
|
||||
public interface IApplicableToBeatmap : IApplicableMod
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies this <see cref="IApplicableToBeatmap{TObject}"/> to a <see cref="Beatmap{TObject}"/>.
|
||||
/// Applies this <see cref="IApplicableToBeatmap"/> to an <see cref="IBeatmap"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The <see cref="Beatmap{TObject}"/> to apply to.</param>
|
||||
void ApplyToBeatmap(Beatmap<TObject> beatmap);
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> to apply to.</param>
|
||||
void ApplyToBeatmap(IBeatmap beatmap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,12 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
shader.Unbind();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
quadBatch?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,27 +13,21 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModTimeRamp : Mod
|
||||
public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap
|
||||
{
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) };
|
||||
|
||||
protected abstract double FinalRateAdjustment { get; }
|
||||
}
|
||||
|
||||
public abstract class ModTimeRamp<T> : ModTimeRamp, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap<T>
|
||||
where T : HitObject
|
||||
{
|
||||
private double finalRateTime;
|
||||
|
||||
private double beginRampTime;
|
||||
|
||||
private IAdjustableClock clock;
|
||||
|
||||
/// <summary>
|
||||
/// The point in the beatmap at which the final ramping rate should be reached.
|
||||
/// </summary>
|
||||
private const double final_rate_progress = 0.75f;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) };
|
||||
|
||||
protected abstract double FinalRateAdjustment { get; }
|
||||
|
||||
private double finalRateTime;
|
||||
private double beginRampTime;
|
||||
private IAdjustableClock clock;
|
||||
|
||||
public virtual void ApplyToClock(IAdjustableClock clock)
|
||||
{
|
||||
this.clock = clock;
|
||||
@@ -44,7 +38,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
applyAdjustment(1);
|
||||
}
|
||||
|
||||
public virtual void ApplyToBeatmap(Beatmap<T> beatmap)
|
||||
public virtual void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
HitObject lastObject = beatmap.HitObjects.LastOrDefault();
|
||||
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public class ModWindDown<T> : ModTimeRamp<T>
|
||||
where T : HitObject
|
||||
public class ModWindDown : ModTimeRamp
|
||||
{
|
||||
public override string Name => "Wind Down";
|
||||
public override string Acronym => "WD";
|
||||
@@ -19,6 +17,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
protected override double FinalRateAdjustment => -0.25;
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp<T>)).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public class ModWindUp<T> : ModTimeRamp<T>
|
||||
where T : HitObject
|
||||
public class ModWindUp : ModTimeRamp
|
||||
{
|
||||
public override string Name => "Wind Up";
|
||||
public override string Acronym => "WU";
|
||||
@@ -19,6 +17,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
protected override double FinalRateAdjustment => 0.5;
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown<T>)).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Audio;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.MathUtils;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
@@ -41,208 +40,192 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
[CanBeNull]
|
||||
public override HitObject Parse(string text)
|
||||
{
|
||||
try
|
||||
string[] split = text.Split(',');
|
||||
|
||||
Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));
|
||||
|
||||
double startTime = Parsing.ParseDouble(split[2]) + Offset;
|
||||
|
||||
ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]);
|
||||
|
||||
int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4;
|
||||
type &= ~ConvertHitObjectType.ComboOffset;
|
||||
|
||||
bool combo = type.HasFlag(ConvertHitObjectType.NewCombo);
|
||||
type &= ~ConvertHitObjectType.NewCombo;
|
||||
|
||||
var soundType = (LegacySoundType)Parsing.ParseInt(split[4]);
|
||||
var bankInfo = new SampleBankInfo();
|
||||
|
||||
HitObject result = null;
|
||||
|
||||
if (type.HasFlag(ConvertHitObjectType.Circle))
|
||||
{
|
||||
string[] split = text.Split(',');
|
||||
result = CreateHit(pos, combo, comboOffset);
|
||||
|
||||
Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));
|
||||
if (split.Length > 5)
|
||||
readCustomSampleBanks(split[5], bankInfo);
|
||||
}
|
||||
else if (type.HasFlag(ConvertHitObjectType.Slider))
|
||||
{
|
||||
PathType pathType = PathType.Catmull;
|
||||
double? length = null;
|
||||
|
||||
double startTime = Parsing.ParseDouble(split[2]) + Offset;
|
||||
string[] pointSplit = split[5].Split('|');
|
||||
|
||||
ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]);
|
||||
int pointCount = 1;
|
||||
foreach (var t in pointSplit)
|
||||
if (t.Length > 1)
|
||||
pointCount++;
|
||||
|
||||
int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4;
|
||||
type &= ~ConvertHitObjectType.ComboOffset;
|
||||
var points = new Vector2[pointCount];
|
||||
|
||||
bool combo = type.HasFlag(ConvertHitObjectType.NewCombo);
|
||||
type &= ~ConvertHitObjectType.NewCombo;
|
||||
int pointIndex = 1;
|
||||
|
||||
var soundType = (LegacySoundType)Parsing.ParseInt(split[4]);
|
||||
var bankInfo = new SampleBankInfo();
|
||||
|
||||
HitObject result = null;
|
||||
|
||||
if (type.HasFlag(ConvertHitObjectType.Circle))
|
||||
foreach (string t in pointSplit)
|
||||
{
|
||||
result = CreateHit(pos, combo, comboOffset);
|
||||
|
||||
if (split.Length > 5)
|
||||
readCustomSampleBanks(split[5], bankInfo);
|
||||
}
|
||||
else if (type.HasFlag(ConvertHitObjectType.Slider))
|
||||
{
|
||||
PathType pathType = PathType.Catmull;
|
||||
double? length = null;
|
||||
|
||||
string[] pointSplit = split[5].Split('|');
|
||||
|
||||
int pointCount = 1;
|
||||
foreach (var t in pointSplit)
|
||||
if (t.Length > 1)
|
||||
pointCount++;
|
||||
|
||||
var points = new Vector2[pointCount];
|
||||
|
||||
int pointIndex = 1;
|
||||
|
||||
foreach (string t in pointSplit)
|
||||
if (t.Length == 1)
|
||||
{
|
||||
if (t.Length == 1)
|
||||
switch (t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case @"C":
|
||||
pathType = PathType.Catmull;
|
||||
break;
|
||||
|
||||
case @"B":
|
||||
pathType = PathType.Bezier;
|
||||
break;
|
||||
|
||||
case @"L":
|
||||
pathType = PathType.Linear;
|
||||
break;
|
||||
|
||||
case @"P":
|
||||
pathType = PathType.PerfectCurve;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] temp = t.Split(':');
|
||||
points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos;
|
||||
}
|
||||
|
||||
// osu-stable special-cased colinear perfect curves to a CurveType.Linear
|
||||
bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y));
|
||||
|
||||
if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points))
|
||||
pathType = PathType.Linear;
|
||||
|
||||
int repeatCount = Parsing.ParseInt(split[6]);
|
||||
|
||||
if (repeatCount > 9000)
|
||||
throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
|
||||
|
||||
// osu-stable treated the first span of the slider as a repeat, but no repeats are happening
|
||||
repeatCount = Math.Max(0, repeatCount - 1);
|
||||
|
||||
if (split.Length > 7)
|
||||
{
|
||||
length = Math.Max(0, Parsing.ParseDouble(split[7]));
|
||||
if (length == 0)
|
||||
length = null;
|
||||
}
|
||||
|
||||
if (split.Length > 10)
|
||||
readCustomSampleBanks(split[10], bankInfo);
|
||||
|
||||
// One node for each repeat + the start and end nodes
|
||||
int nodes = repeatCount + 2;
|
||||
|
||||
// Populate node sample bank infos with the default hit object sample bank
|
||||
var nodeBankInfos = new List<SampleBankInfo>();
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeBankInfos.Add(bankInfo.Clone());
|
||||
|
||||
// Read any per-node sample banks
|
||||
if (split.Length > 9 && split[9].Length > 0)
|
||||
{
|
||||
string[] sets = split[9].Split('|');
|
||||
|
||||
for (int i = 0; i < nodes; i++)
|
||||
{
|
||||
if (i >= sets.Length)
|
||||
case @"C":
|
||||
pathType = PathType.Catmull;
|
||||
break;
|
||||
|
||||
SampleBankInfo info = nodeBankInfos[i];
|
||||
readCustomSampleBanks(sets[i], info);
|
||||
}
|
||||
}
|
||||
|
||||
// Populate node sound types with the default hit object sound type
|
||||
var nodeSoundTypes = new List<LegacySoundType>();
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeSoundTypes.Add(soundType);
|
||||
|
||||
// Read any per-node sound types
|
||||
if (split.Length > 8 && split[8].Length > 0)
|
||||
{
|
||||
string[] adds = split[8].Split('|');
|
||||
|
||||
for (int i = 0; i < nodes; i++)
|
||||
{
|
||||
if (i >= adds.Length)
|
||||
case @"B":
|
||||
pathType = PathType.Bezier;
|
||||
break;
|
||||
|
||||
int sound;
|
||||
int.TryParse(adds[i], out sound);
|
||||
nodeSoundTypes[i] = (LegacySoundType)sound;
|
||||
case @"L":
|
||||
pathType = PathType.Linear;
|
||||
break;
|
||||
|
||||
case @"P":
|
||||
pathType = PathType.PerfectCurve;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate the final per-node samples
|
||||
var nodeSamples = new List<List<HitSampleInfo>>(nodes);
|
||||
string[] temp = t.Split(':');
|
||||
points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos;
|
||||
}
|
||||
|
||||
// osu-stable special-cased colinear perfect curves to a CurveType.Linear
|
||||
bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y));
|
||||
|
||||
if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points))
|
||||
pathType = PathType.Linear;
|
||||
|
||||
int repeatCount = Parsing.ParseInt(split[6]);
|
||||
|
||||
if (repeatCount > 9000)
|
||||
throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
|
||||
|
||||
// osu-stable treated the first span of the slider as a repeat, but no repeats are happening
|
||||
repeatCount = Math.Max(0, repeatCount - 1);
|
||||
|
||||
if (split.Length > 7)
|
||||
{
|
||||
length = Math.Max(0, Parsing.ParseDouble(split[7]));
|
||||
if (length == 0)
|
||||
length = null;
|
||||
}
|
||||
|
||||
if (split.Length > 10)
|
||||
readCustomSampleBanks(split[10], bankInfo);
|
||||
|
||||
// One node for each repeat + the start and end nodes
|
||||
int nodes = repeatCount + 2;
|
||||
|
||||
// Populate node sample bank infos with the default hit object sample bank
|
||||
var nodeBankInfos = new List<SampleBankInfo>();
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeBankInfos.Add(bankInfo.Clone());
|
||||
|
||||
// Read any per-node sample banks
|
||||
if (split.Length > 9 && split[9].Length > 0)
|
||||
{
|
||||
string[] sets = split[9].Split('|');
|
||||
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
|
||||
|
||||
result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples);
|
||||
|
||||
// The samples are played when the slider ends, which is the last node
|
||||
result.Samples = nodeSamples[nodeSamples.Count - 1];
|
||||
}
|
||||
else if (type.HasFlag(ConvertHitObjectType.Spinner))
|
||||
{
|
||||
double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset);
|
||||
|
||||
result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime);
|
||||
|
||||
if (split.Length > 6)
|
||||
readCustomSampleBanks(split[6], bankInfo);
|
||||
}
|
||||
else if (type.HasFlag(ConvertHitObjectType.Hold))
|
||||
{
|
||||
// Note: Hold is generated by BMS converts
|
||||
|
||||
double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2]));
|
||||
|
||||
if (split.Length > 5 && !string.IsNullOrEmpty(split[5]))
|
||||
{
|
||||
string[] ss = split[5].Split(':');
|
||||
endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0]));
|
||||
readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
|
||||
if (i >= sets.Length)
|
||||
break;
|
||||
|
||||
SampleBankInfo info = nodeBankInfos[i];
|
||||
readCustomSampleBanks(sets[i], info);
|
||||
}
|
||||
|
||||
result = CreateHold(pos, combo, comboOffset, endTime + Offset);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
// Populate node sound types with the default hit object sound type
|
||||
var nodeSoundTypes = new List<LegacySoundType>();
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeSoundTypes.Add(soundType);
|
||||
|
||||
// Read any per-node sound types
|
||||
if (split.Length > 8 && split[8].Length > 0)
|
||||
{
|
||||
Logger.Log($"Unknown hit object type: {type}. Skipped.", level: LogLevel.Error);
|
||||
return null;
|
||||
string[] adds = split[8].Split('|');
|
||||
|
||||
for (int i = 0; i < nodes; i++)
|
||||
{
|
||||
if (i >= adds.Length)
|
||||
break;
|
||||
|
||||
int sound;
|
||||
int.TryParse(adds[i], out sound);
|
||||
nodeSoundTypes[i] = (LegacySoundType)sound;
|
||||
}
|
||||
}
|
||||
|
||||
result.StartTime = startTime;
|
||||
// Generate the final per-node samples
|
||||
var nodeSamples = new List<List<HitSampleInfo>>(nodes);
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
|
||||
|
||||
if (result.Samples.Count == 0)
|
||||
result.Samples = convertSoundType(soundType, bankInfo);
|
||||
result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples);
|
||||
|
||||
FirstObject = false;
|
||||
|
||||
return result;
|
||||
// The samples are played when the slider ends, which is the last node
|
||||
result.Samples = nodeSamples[nodeSamples.Count - 1];
|
||||
}
|
||||
catch (FormatException)
|
||||
else if (type.HasFlag(ConvertHitObjectType.Spinner))
|
||||
{
|
||||
Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important);
|
||||
double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset);
|
||||
|
||||
result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime);
|
||||
|
||||
if (split.Length > 6)
|
||||
readCustomSampleBanks(split[6], bankInfo);
|
||||
}
|
||||
catch (OverflowException)
|
||||
else if (type.HasFlag(ConvertHitObjectType.Hold))
|
||||
{
|
||||
Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important);
|
||||
// Note: Hold is generated by BMS converts
|
||||
|
||||
double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2]));
|
||||
|
||||
if (split.Length > 5 && !string.IsNullOrEmpty(split[5]))
|
||||
{
|
||||
string[] ss = split[5].Split(':');
|
||||
endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0]));
|
||||
readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
|
||||
}
|
||||
|
||||
result = CreateHold(pos, combo, comboOffset, endTime + Offset);
|
||||
}
|
||||
|
||||
return null;
|
||||
if (result == null)
|
||||
throw new InvalidDataException($"Unknown hit object type: {split[3]}");
|
||||
|
||||
result.StartTime = startTime;
|
||||
|
||||
if (result.Samples.Count == 0)
|
||||
result.Samples = convertSoundType(soundType, bankInfo);
|
||||
|
||||
FirstObject = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void readCustomSampleBanks(string str, SampleBankInfo bankInfo)
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
[Flags]
|
||||
internal enum ConvertHitObjectType
|
||||
{
|
||||
Circle = 1 << 0,
|
||||
Circle = 1,
|
||||
Slider = 1 << 1,
|
||||
NewCombo = 1 << 2,
|
||||
Spinner = 1 << 3,
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects
|
||||
{
|
||||
public static class SliderEventGenerator
|
||||
{
|
||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset)
|
||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
||||
double? legacyLastTickOffset)
|
||||
{
|
||||
// A very lenient maximum length of a slider for ticks to be generated.
|
||||
// This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage.
|
||||
@@ -36,24 +38,17 @@ namespace osu.Game.Rulesets.Objects
|
||||
var spanStartTime = startTime + span * spanDuration;
|
||||
var reversed = span % 2 == 1;
|
||||
|
||||
for (var d = tickDistance; d <= length; d += tickDistance)
|
||||
var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd);
|
||||
|
||||
if (reversed)
|
||||
{
|
||||
if (d >= length - minDistanceFromEnd)
|
||||
break;
|
||||
|
||||
var pathProgress = d / length;
|
||||
var timeProgress = reversed ? 1 - pathProgress : pathProgress;
|
||||
|
||||
yield return new SliderEventDescriptor
|
||||
{
|
||||
Type = SliderEventType.Tick,
|
||||
SpanIndex = span,
|
||||
SpanStartTime = spanStartTime,
|
||||
Time = spanStartTime + timeProgress * spanDuration,
|
||||
PathProgress = pathProgress,
|
||||
};
|
||||
// For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets
|
||||
ticks = ticks.Reverse();
|
||||
}
|
||||
|
||||
foreach (var e in ticks)
|
||||
yield return e;
|
||||
|
||||
if (span < spanCount - 1)
|
||||
{
|
||||
yield return new SliderEventDescriptor
|
||||
@@ -103,6 +98,40 @@ namespace osu.Game.Rulesets.Objects
|
||||
PathProgress = spanCount % 2,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the ticks for a span of the slider.
|
||||
/// </summary>
|
||||
/// <param name="spanIndex">The span index.</param>
|
||||
/// <param name="spanStartTime">The start time of the span.</param>
|
||||
/// <param name="spanDuration">The duration of the span.</param>
|
||||
/// <param name="reversed">Whether the span is reversed.</param>
|
||||
/// <param name="length">The length of the path.</param>
|
||||
/// <param name="tickDistance">The distance between each tick.</param>
|
||||
/// <param name="minDistanceFromEnd">The distance from the end of the path at which ticks are not allowed to be added.</param>
|
||||
/// <returns>A <see cref="SliderEventDescriptor"/> for each tick. If <paramref name="reversed"/> is true, the ticks will be returned in reverse-StartTime order.</returns>
|
||||
private static IEnumerable<SliderEventDescriptor> generateTicks(int spanIndex, double spanStartTime, double spanDuration, bool reversed, double length, double tickDistance,
|
||||
double minDistanceFromEnd)
|
||||
{
|
||||
for (var d = tickDistance; d <= length; d += tickDistance)
|
||||
{
|
||||
if (d >= length - minDistanceFromEnd)
|
||||
break;
|
||||
|
||||
// Always generate ticks from the start of the path rather than the span to ensure that ticks in repeat spans are positioned identically to those in non-repeat spans
|
||||
var pathProgress = d / length;
|
||||
var timeProgress = reversed ? 1 - pathProgress : pathProgress;
|
||||
|
||||
yield return new SliderEventDescriptor
|
||||
{
|
||||
Type = SliderEventType.Tick,
|
||||
SpanIndex = spanIndex,
|
||||
SpanStartTime = spanStartTime,
|
||||
Time = spanStartTime + timeProgress * spanDuration,
|
||||
PathProgress = pathProgress,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -217,9 +217,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private double baseScore;
|
||||
private double bonusScore;
|
||||
|
||||
protected ScoreProcessor()
|
||||
{
|
||||
}
|
||||
private double scoreMultiplier = 1;
|
||||
|
||||
public ScoreProcessor(DrawableRuleset<TObject> drawableRuleset)
|
||||
{
|
||||
@@ -239,6 +237,15 @@ namespace osu.Game.Rulesets.Scoring
|
||||
}
|
||||
|
||||
Mode.ValueChanged += _ => updateScore();
|
||||
Mods.ValueChanged += mods =>
|
||||
{
|
||||
scoreMultiplier = 1;
|
||||
|
||||
foreach (var m in mods.NewValue)
|
||||
scoreMultiplier *= m.ScoreMultiplier;
|
||||
|
||||
updateScore();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -306,6 +313,9 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <summary>
|
||||
/// Applies the score change of a <see cref="JudgementResult"/> to this <see cref="ScoreProcessor"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Any changes applied via this method can be reverted via <see cref="RevertResult"/>.
|
||||
/// </remarks>
|
||||
/// <param name="result">The <see cref="JudgementResult"/> to apply.</param>
|
||||
protected virtual void ApplyResult(JudgementResult result)
|
||||
{
|
||||
@@ -350,7 +360,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts the score change of a <see cref="JudgementResult"/> that was applied to this <see cref="ScoreProcessor"/>.
|
||||
/// Reverts the score change of a <see cref="JudgementResult"/> that was applied to this <see cref="ScoreProcessor"/> via <see cref="ApplyResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="result">The judgement scoring result.</param>
|
||||
protected virtual void RevertResult(JudgementResult result)
|
||||
@@ -388,7 +398,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (rollingMaxBaseScore != 0)
|
||||
Accuracy.Value = baseScore / rollingMaxBaseScore;
|
||||
|
||||
TotalScore.Value = getScore(Mode.Value);
|
||||
TotalScore.Value = getScore(Mode.Value) * scoreMultiplier;
|
||||
}
|
||||
|
||||
private double getScore(ScoringMode mode)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user