1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 06:33:20 +08:00

Fix catch hardrock mod not using the correct RNG (#5561)

Fix catch hardrock mod not using the correct RNG

Co-authored-by: Dean Herbert <pe@ppy.sh>
This commit is contained in:
Dean Herbert 2019-08-05 11:30:29 +02:00 committed by GitHub
commit 6d4d6a6532
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 695 additions and 120 deletions

View File

@ -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,7 +23,10 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("spinner")]
[TestCase("spinner-and-circles")]
[TestCase("slider")]
public 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)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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