mirror of
https://github.com/ppy/osu.git
synced 2025-02-13 12:43:16 +08:00
Merge pull request #2635 from ekrctb/catch-droplet-fix
Use FastRandom for catch droplets and bananas positions
This commit is contained in:
commit
f9e2aabbdb
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
@ -17,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
||||
|
||||
[TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")]
|
||||
[TestCase("spinner")]
|
||||
[TestCase("spinner-and-circles")]
|
||||
public new void Test(string name)
|
||||
{
|
||||
base.Test(name);
|
||||
@ -27,22 +30,15 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
if (hitObject is JuiceStream stream)
|
||||
{
|
||||
foreach (var nested in stream.NestedHitObjects)
|
||||
{
|
||||
yield return new ConvertValue
|
||||
{
|
||||
StartTime = nested.StartTime,
|
||||
Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH
|
||||
};
|
||||
}
|
||||
yield return new ConvertValue((CatchHitObject)nested);
|
||||
}
|
||||
else if (hitObject is BananaShower shower)
|
||||
{
|
||||
foreach (var nested in shower.NestedHitObjects)
|
||||
yield return new ConvertValue((CatchHitObject)nested);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new ConvertValue
|
||||
{
|
||||
StartTime = hitObject.StartTime,
|
||||
Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH
|
||||
};
|
||||
}
|
||||
yield return new ConvertValue((CatchHitObject)hitObject);
|
||||
}
|
||||
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
@ -55,8 +51,31 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
/// </summary>
|
||||
private const float conversion_lenience = 2;
|
||||
|
||||
public double StartTime;
|
||||
public float Position;
|
||||
[JsonIgnore]
|
||||
public readonly CatchHitObject HitObject;
|
||||
|
||||
public ConvertValue(CatchHitObject hitObject)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
startTime = 0;
|
||||
position = 0;
|
||||
}
|
||||
|
||||
private double startTime;
|
||||
|
||||
public double StartTime
|
||||
{
|
||||
get => HitObject?.StartTime ?? startTime;
|
||||
set => startTime = value;
|
||||
}
|
||||
|
||||
private float position;
|
||||
|
||||
public float Position
|
||||
{
|
||||
get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position;
|
||||
set => position = value;
|
||||
}
|
||||
|
||||
public bool Equals(ConvertValue other)
|
||||
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
|
||||
|
@ -27,22 +27,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
var comboData = obj as IHasCombo;
|
||||
var endTime = obj as IHasEndTime;
|
||||
|
||||
if (positionData == null)
|
||||
{
|
||||
if (endTime != null)
|
||||
{
|
||||
yield return new BananaShower
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
Duration = endTime.Duration,
|
||||
NewCombo = comboData?.NewCombo ?? false
|
||||
};
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (curveData != null)
|
||||
{
|
||||
yield return new JuiceStream
|
||||
@ -54,20 +38,30 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
Distance = curveData.Distance,
|
||||
RepeatSamples = curveData.RepeatSamples,
|
||||
RepeatCount = curveData.RepeatCount,
|
||||
X = positionData.X / CatchPlayfield.BASE_WIDTH,
|
||||
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
|
||||
NewCombo = comboData?.NewCombo ?? false
|
||||
};
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new Fruit
|
||||
else if (endTime != null)
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
X = positionData.X / CatchPlayfield.BASE_WIDTH
|
||||
};
|
||||
yield return new BananaShower
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
Duration = endTime.Duration,
|
||||
NewCombo = comboData?.NewCombo ?? false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new Fruit
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override Beatmap<CatchHitObject> CreateBeatmap() => new CatchBeatmap();
|
||||
|
@ -9,6 +9,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using OpenTK;
|
||||
using osu.Game.Rulesets.Catch.MathUtils;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
@ -21,13 +22,54 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
public override void PostProcess()
|
||||
{
|
||||
applyPositionOffsets();
|
||||
|
||||
initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
|
||||
|
||||
base.PostProcess();
|
||||
|
||||
int index = 0;
|
||||
foreach (var obj in Beatmap.HitObjects.OfType<CatchHitObject>())
|
||||
{
|
||||
obj.IndexInBeatmap = index++;
|
||||
if (obj.LastInCombo && obj.NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
|
||||
lastNested.LastInCombo = true;
|
||||
}
|
||||
}
|
||||
|
||||
public const int RNG_SEED = 1337;
|
||||
|
||||
private void applyPositionOffsets()
|
||||
{
|
||||
var rng = new FastRandom(RNG_SEED);
|
||||
// todo: HardRock displacement should be applied here
|
||||
|
||||
foreach (var obj in Beatmap.HitObjects)
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
case BananaShower bananaShower:
|
||||
foreach (var nested in bananaShower.NestedHitObjects)
|
||||
{
|
||||
((BananaShower.Banana)nested).X = (float)rng.NextDouble();
|
||||
rng.Next(); // osu!stable retrieved a random banana type
|
||||
rng.Next(); // osu!stable retrieved a random banana rotation
|
||||
rng.Next(); // osu!stable retrieved a random banana colour
|
||||
}
|
||||
break;
|
||||
case JuiceStream juiceStream:
|
||||
foreach (var nested in juiceStream.NestedHitObjects)
|
||||
{
|
||||
var hitObject = (CatchHitObject)nested;
|
||||
if (hitObject is TinyDroplet)
|
||||
hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH;
|
||||
else if (hitObject is Droplet)
|
||||
rng.Next(); // osu!stable retrieved a random droplet rotation
|
||||
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initialiseHyperDash(List<CatchHitObject> objects)
|
||||
|
91
osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
Normal file
91
osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.MathUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// A PRNG specified in http://heliosphan.org/fastrandom.html.
|
||||
/// </summary>
|
||||
public class FastRandom
|
||||
{
|
||||
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
|
||||
private const uint int_mask = 0x7FFFFFFF;
|
||||
private const uint y = 842502087;
|
||||
private const uint z = 3579807591;
|
||||
private const uint w = 273326509;
|
||||
private uint _x, _y = y, _z = z, _w = w;
|
||||
|
||||
public FastRandom(int seed)
|
||||
{
|
||||
_x = (uint)seed;
|
||||
}
|
||||
|
||||
public FastRandom()
|
||||
: this(Environment.TickCount)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random unsigned integer within the range [<see cref="uint.MinValue"/>, <see cref="uint.MaxValue"/>).
|
||||
/// </summary>
|
||||
/// <returns>The random value.</returns>
|
||||
public uint NextUInt()
|
||||
{
|
||||
uint t = _x ^ _x << 11;
|
||||
_x = _y;
|
||||
_y = _z;
|
||||
_z = _w;
|
||||
return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random integer value within the range [0, <see cref="int.MaxValue"/>).
|
||||
/// </summary>
|
||||
/// <returns>The random value.</returns>
|
||||
public int Next() => (int)(int_mask & NextUInt());
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random integer value within the range [0, <paramref name="upperBound"/>).
|
||||
/// </summary>
|
||||
/// <param name="upperBound">The upper bound.</param>
|
||||
/// <returns>The random value.</returns>
|
||||
public int Next(int upperBound) => (int)(NextDouble() * upperBound);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random integer value within the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
|
||||
/// </summary>
|
||||
/// <param name="lowerBound">The lower bound of the range.</param>
|
||||
/// <param name="upperBound">The upper bound of the range.</param>
|
||||
/// <returns>The random value.</returns>
|
||||
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random double value within the range [0, 1).
|
||||
/// </summary>
|
||||
/// <returns>The random value.</returns>
|
||||
public double NextDouble() => int_to_real * Next();
|
||||
|
||||
private uint bitBuffer;
|
||||
private int bitIndex = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls.
|
||||
/// </summary>
|
||||
/// <returns>The random value.</returns>
|
||||
public bool NextBool()
|
||||
{
|
||||
if (bitIndex == 32)
|
||||
{
|
||||
bitBuffer = NextUInt();
|
||||
bitIndex = 1;
|
||||
|
||||
return (bitBuffer & 1) == 1;
|
||||
}
|
||||
|
||||
bitIndex++;
|
||||
return ((bitBuffer >>= 1) & 1) == 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
@ -31,8 +30,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
AddNested(new Banana
|
||||
{
|
||||
Samples = Samples,
|
||||
StartTime = i,
|
||||
X = RNG.NextSingle()
|
||||
StartTime = i
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
protected override void CreateNestedHitObjects()
|
||||
{
|
||||
base.CreateNestedHitObjects();
|
||||
|
||||
createTicks();
|
||||
}
|
||||
|
||||
@ -124,9 +123,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
|
||||
});
|
||||
}
|
||||
|
||||
if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
|
||||
lastNested.LastInCombo = LastInCombo;
|
||||
}
|
||||
|
||||
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,65 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 2589,
|
||||
"Objects": [{
|
||||
"StartTime": 2589,
|
||||
"Position": 256
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 2915,
|
||||
"Objects": [{
|
||||
"StartTime": 2915,
|
||||
"Position": 65
|
||||
},
|
||||
{
|
||||
"StartTime": 2916,
|
||||
"Position": 482
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 3078,
|
||||
"Objects": [{
|
||||
"StartTime": 3078,
|
||||
"Position": 164
|
||||
},
|
||||
{
|
||||
"StartTime": 3079,
|
||||
"Position": 315
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 3241,
|
||||
"Objects": [{
|
||||
"StartTime": 3241,
|
||||
"Position": 145
|
||||
},
|
||||
{
|
||||
"StartTime": 3242,
|
||||
"Position": 159
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 3404,
|
||||
"Objects": [{
|
||||
"StartTime": 3404,
|
||||
"Position": 310
|
||||
},
|
||||
{
|
||||
"StartTime": 3405,
|
||||
"Position": 441
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 5197,
|
||||
"Objects": [{
|
||||
"StartTime": 5197,
|
||||
"Position": 256
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.7
|
||||
Mode: 2
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:5
|
||||
CircleSize:2
|
||||
OverallDifficulty:5
|
||||
ApproachRate:8
|
||||
SliderMultiplier:1.4
|
||||
SliderTickRate:4
|
||||
|
||||
[TimingPoints]
|
||||
2589,326.086956521739,4,2,1,70,1,0
|
||||
|
||||
[HitObjects]
|
||||
256,192,2589,5,0,0:0:0:0:
|
||||
256,192,2915,12,0,2916,0:0:0:0:
|
||||
256,192,3078,12,0,3079,0:0:0:0:
|
||||
256,192,3241,12,0,3242,0:0:0:0:
|
||||
256,192,3404,12,0,3405,0:0:0:0:
|
||||
256,192,5197,5,0,0:0:0:0:
|
@ -0,0 +1,74 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 18500,
|
||||
"Objects": [{
|
||||
"StartTime": 18500,
|
||||
"Position": 65
|
||||
},
|
||||
{
|
||||
"StartTime": 18559,
|
||||
"Position": 482
|
||||
},
|
||||
{
|
||||
"StartTime": 18618,
|
||||
"Position": 164
|
||||
},
|
||||
{
|
||||
"StartTime": 18678,
|
||||
"Position": 315
|
||||
},
|
||||
{
|
||||
"StartTime": 18737,
|
||||
"Position": 145
|
||||
},
|
||||
{
|
||||
"StartTime": 18796,
|
||||
"Position": 159
|
||||
},
|
||||
{
|
||||
"StartTime": 18856,
|
||||
"Position": 310
|
||||
},
|
||||
{
|
||||
"StartTime": 18915,
|
||||
"Position": 441
|
||||
},
|
||||
{
|
||||
"StartTime": 18975,
|
||||
"Position": 428
|
||||
},
|
||||
{
|
||||
"StartTime": 19034,
|
||||
"Position": 243
|
||||
},
|
||||
{
|
||||
"StartTime": 19093,
|
||||
"Position": 422
|
||||
},
|
||||
{
|
||||
"StartTime": 19153,
|
||||
"Position": 481
|
||||
},
|
||||
{
|
||||
"StartTime": 19212,
|
||||
"Position": 104
|
||||
},
|
||||
{
|
||||
"StartTime": 19271,
|
||||
"Position": 473
|
||||
},
|
||||
{
|
||||
"StartTime": 19331,
|
||||
"Position": 135
|
||||
},
|
||||
{
|
||||
"StartTime": 19390,
|
||||
"Position": 360
|
||||
},
|
||||
{
|
||||
"StartTime": 19450,
|
||||
"Position": 123
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
Mode: 2
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
13426,-100,4,3,1,45,0,0
|
||||
14884,-100,4,2,1,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
256,192,18500,12,0,19450,0:0:0:0:
|
@ -116,9 +116,6 @@ namespace osu.Game.Beatmaps
|
||||
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
|
||||
}
|
||||
|
||||
// Post-process
|
||||
rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();
|
||||
|
||||
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
|
||||
foreach (var obj in converted.HitObjects)
|
||||
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
|
||||
@ -127,6 +124,9 @@ namespace osu.Game.Beatmaps
|
||||
foreach (var obj in converted.HitObjects)
|
||||
mod.ApplyToHitObject(obj);
|
||||
|
||||
// Post-process
|
||||
rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user