Dim circles instead of fade; improved hit samples; changed jump distance to be closer to cuttingedge

This commit is contained in:
Henry Lin 2021-06-17 22:01:58 +08:00
parent f22beaeb5b
commit b7f43405fc

View File

@ -26,6 +26,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -46,12 +47,23 @@ namespace osu.Game.Rulesets.Osu.Mods
Value = null Value = null
}; };
public bool PerformFail() => true;
public bool RestartOnFail => false; public bool RestartOnFail => false;
public bool DisplayResultsOnFail => true; public bool DisplayResultsOnFail => true;
// Maximum distance to jump
private const float max_distance = 250f;
// The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle.
// The closer the hit objects draw to the border, the sharper the turn
private const byte border_distance_x = 192;
private const byte border_distance_y = 144;
public bool PerformFail()
{
return true;
}
public void ApplyToHealthProcessor(HealthProcessor healthProcessor) public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{ {
// Sudden death // Sudden death
@ -60,9 +72,6 @@ namespace osu.Game.Rulesets.Osu.Mods
&& !result.IsHit; && !result.IsHit;
} }
// Maximum distance to jump
private const float max_distance = 250f;
public override void ApplyToBeatmap(IBeatmap beatmap) public override void ApplyToBeatmap(IBeatmap beatmap)
{ {
Seed.Value ??= RNG.Next(); Seed.Value ??= RNG.Next();
@ -90,6 +99,55 @@ namespace osu.Game.Rulesets.Osu.Mods
base.ApplyToBeatmap(beatmap); base.ApplyToBeatmap(beatmap);
} }
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
{
}
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
// Decrease AR to increase preempt time
difficulty.ApproachRate *= 0.5f;
difficulty.CircleSize *= 0.75f;
}
// Background metronome
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
drawableRuleset.Overlays.Add(new TargetBeatContainer());
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject drawable, ArmedState state)
{
}
protected override void ApplyNormalVisibilityState(DrawableHitObject drawable, ArmedState state)
{
if (!(drawable is DrawableHitCircle circle)) return;
var h = (OsuHitObject)drawable.HitObject;
using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
{
drawable.ScaleTo(0.5f)
.Then().ScaleTo(1f, h.TimePreempt);
var colour = drawable.Colour;
var avgColour = colour.AverageColour.Linear;
drawable.FadeColour(new Color4(avgColour.R * 0.45f, avgColour.G * 0.45f, avgColour.B * 0.45f, avgColour.A))
.Then().Delay(h.TimeFadeIn).FadeColour(colour);
// remove approach circles
circle.ApproachCircle.Hide();
}
}
private static float map(float value, float fromLow, float fromHigh, float toLow, float toHigh)
{
return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
}
private IEnumerable<double> generateBeats(IBeatmap beatmap, IReadOnlyCollection<OsuHitObject> origHitObjects) private IEnumerable<double> generateBeats(IBeatmap beatmap, IReadOnlyCollection<OsuHitObject> origHitObjects)
{ {
var startTime = origHitObjects.First().StartTime; var startTime = origHitObjects.First().StartTime;
@ -125,35 +183,43 @@ namespace osu.Game.Rulesets.Osu.Mods
.ToList(); .ToList();
// Remove beats that are too close to the next one (e.g. due to timing point changes) // Remove beats that are too close to the next one (e.g. due to timing point changes)
for (int i = beats.Count - 2; i >= 0; i--) for (var i = beats.Count - 2; i >= 0; i--)
{ {
var beat = beats[i]; var beat = beats[i];
if (Precision.AlmostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) if (Precision.AlmostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) beats.RemoveAt(i);
{
beats.RemoveAt(i);
}
} }
return beats; return beats;
} }
private void addHitSamples(IReadOnlyList<OsuHitObject> hitObjects, IReadOnlyCollection<OsuHitObject> origHitObjects) private void addHitSamples(IEnumerable<OsuHitObject> hitObjects, IReadOnlyList<OsuHitObject> origHitObjects)
{ {
for (int i = 0; i < hitObjects.Count; i++) var lastSampleIdx = 0;
foreach (var x in hitObjects)
{ {
var x = hitObjects[i];
var samples = getSamplesAtTime(origHitObjects, x.StartTime); var samples = getSamplesAtTime(origHitObjects, x.StartTime);
if (samples == null) if (samples == null)
{ {
if (i > 0) while (lastSampleIdx < origHitObjects.Count && origHitObjects[lastSampleIdx].StartTime <= x.StartTime)
x.Samples = hitObjects[i - 1].Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); lastSampleIdx++;
lastSampleIdx--;
if (lastSampleIdx < 0 && lastSampleIdx >= origHitObjects.Count) continue;
if (lastSampleIdx < origHitObjects.Count - 1)
{
// get samples from the next hit object if it is closer in time
if (origHitObjects[lastSampleIdx + 1].StartTime - x.StartTime < x.StartTime - origHitObjects[lastSampleIdx].StartTime)
lastSampleIdx++;
}
x.Samples = origHitObjects[lastSampleIdx].Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList();
} }
else else
{
x.Samples = samples; x.Samples = samples;
}
} }
} }
@ -168,13 +234,13 @@ namespace osu.Game.Rulesets.Osu.Mods
// Then reprocess them to ensure continuity in the combo indices and add indices in current combo // Then reprocess them to ensure continuity in the combo indices and add indices in current combo
var combos = hitObjects.GroupBy(x => x.ComboIndex).ToList(); var combos = hitObjects.GroupBy(x => x.ComboIndex).ToList();
for (int i = 0; i < combos.Count; i++) for (var i = 0; i < combos.Count; i++)
{ {
var group = combos[i].ToList(); var group = combos[i].ToList();
group.First().NewCombo = true; group.First().NewCombo = true;
group.Last().LastInCombo = true; group.Last().LastInCombo = true;
for (int j = 0; j < group.Count; j++) for (var j = 0; j < group.Count; j++)
{ {
var x = group[j]; var x = group[j];
x.ComboIndex = i; x.ComboIndex = i;
@ -190,53 +256,52 @@ namespace osu.Game.Rulesets.Osu.Mods
float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max);
var direction = MathHelper.TwoPi * nextSingle(); var direction = MathHelper.TwoPi * nextSingle();
var maxComboIndex = hitObjects.Last().ComboIndex;
for (int i = 0; i < hitObjects.Count; i++) for (var i = 0; i < hitObjects.Count; i++)
{ {
var obj = hitObjects[i]; var obj = hitObjects[i];
var lastPos = i == 0
? Vector2.Divide(OsuPlayfield.BASE_SIZE, 2)
: hitObjects[i - 1].Position;
if (i == 0) var distance = map(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, 333f);
{ if (obj.NewCombo) distance *= 1.5f;
obj.Position = new Vector2(nextSingle(OsuPlayfield.BASE_SIZE.X), nextSingle(OsuPlayfield.BASE_SIZE.Y)); if (obj.Kiai) distance *= 1.2f;
} distance = Math.Min(max_distance, distance);
var relativePos = new Vector2(
distance * (float)Math.Cos(direction),
distance * (float)Math.Sin(direction)
);
relativePos = getRotatedVector(lastPos, relativePos);
direction = (float)Math.Atan2(relativePos.Y, relativePos.X);
var newPosition = Vector2.Add(lastPos, relativePos);
if (newPosition.Y < 0)
newPosition.Y = 0;
else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y)
newPosition.Y = OsuPlayfield.BASE_SIZE.Y;
if (newPosition.X < 0)
newPosition.X = 0;
else if (newPosition.X > OsuPlayfield.BASE_SIZE.X)
newPosition.X = OsuPlayfield.BASE_SIZE.X;
obj.Position = newPosition;
if (obj.LastInCombo)
direction = MathHelper.TwoPi * nextSingle();
else else
{ direction += distance / max_distance * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi);
var distance = 40f * (float)Math.Pow(1.05, obj.ComboIndex);
if (obj.NewCombo) distance *= 1.5f;
distance = Math.Min(max_distance, distance);
var relativePos = new Vector2(
distance * (float)Math.Cos(direction),
distance * (float)Math.Sin(direction)
);
relativePos = getRotatedVector(hitObjects[i - 1].Position, relativePos);
direction = (float)Math.Atan2(relativePos.Y, relativePos.X);
var newPosition = Vector2.Add(hitObjects[i - 1].Position, relativePos);
if (newPosition.Y < 0)
newPosition.Y = 0;
else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y)
newPosition.Y = OsuPlayfield.BASE_SIZE.Y;
if (newPosition.X < 0)
newPosition.X = 0;
else if (newPosition.X > OsuPlayfield.BASE_SIZE.X)
newPosition.X = OsuPlayfield.BASE_SIZE.X;
obj.Position = newPosition;
if (obj.LastInCombo)
direction = MathHelper.TwoPi * nextSingle();
else
direction += distance / max_distance * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi);
}
} }
} }
/// <summary> /// <summary>
/// Get samples (if any) for a specific point in time. /// Get samples (if any) for a specific point in time.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Samples will be returned if a hit circle or a slider node exists at that point of time. /// Samples will be returned if a hit circle or a slider node exists at that point of time.
/// </remarks> /// </remarks>
/// <param name="hitObjects">The list of hit objects in a beatmap, ordered by StartTime</param> /// <param name="hitObjects">The list of hit objects in a beatmap, ordered by StartTime</param>
/// <param name="time">The point in time to get samples for</param> /// <param name="time">The point in time to get samples for</param>
@ -260,62 +325,15 @@ namespace osu.Game.Rulesets.Osu.Mods
IList<HitSampleInfo> samples; IList<HitSampleInfo> samples;
if (sampleObj is Slider slider) if (sampleObj is Slider slider)
{
samples = slider.NodeSamples[(int)Math.Round((time - slider.StartTime) % slider.SpanDuration)]; samples = slider.NodeSamples[(int)Math.Round((time - slider.StartTime) % slider.SpanDuration)];
}
else else
{
samples = sampleObj.Samples; samples = sampleObj.Samples;
}
return samples; return samples;
} }
protected override void ApplyIncreasedVisibilityState(DrawableHitObject drawable, ArmedState state)
{
}
protected override void ApplyNormalVisibilityState(DrawableHitObject drawable, ArmedState state)
{
if (drawable is DrawableSpinner)
return;
var h = (OsuHitObject)drawable.HitObject;
// apply grow and fade effect
if (!(drawable is DrawableHitCircle circle)) return;
using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
{
// todo: this doesn't feel quite right yet
drawable.ScaleTo(0.4f)
.Then().ScaleTo(1.6f, h.TimePreempt * 2);
drawable.FadeTo(0.5f)
.Then().Delay(h.TimePreempt * 2 / 3).FadeTo(1f);
// remove approach circles
circle.ApproachCircle.Hide();
}
}
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
{
}
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
// Decrease AR to increase preempt time
difficulty.ApproachRate *= 0.5f;
difficulty.CircleSize *= 0.75f;
}
// The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle.
// The closer the hit objects draw to the border, the sharper the turn
private const byte border_distance_x = 192;
private const byte border_distance_y = 144;
/// <summary> /// <summary>
/// Determines the position of the current hit object relative to the previous one. /// Determines the position of the current hit object relative to the previous one.
/// </summary> /// </summary>
/// <returns>The position of the current hit object relative to the previous one</returns> /// <returns>The position of the current hit object relative to the previous one</returns>
private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev)
@ -356,16 +374,19 @@ namespace osu.Game.Rulesets.Osu.Mods
return rotateVectorTowardsVector( return rotateVectorTowardsVector(
posRelativeToPrev, posRelativeToPrev,
Vector2.Subtract(playfieldMiddle, prevPosChanged), Vector2.Subtract(playfieldMiddle, prevPosChanged),
relativeRotationDistance * 0.75f Math.Min(1, relativeRotationDistance * 0.75f)
); );
} }
/// <summary> /// <summary>
/// Rotates vector "initial" towards vector "destination" /// Rotates vector "initial" towards vector "destination"
/// </summary> /// </summary>
/// <param name="initial">Vector to rotate to "destination"</param> /// <param name="initial">Vector to rotate to "destination"</param>
/// <param name="destination">Vector "initial" should be rotated to</param> /// <param name="destination">Vector "initial" should be rotated to</param>
/// <param name="relativeDistance">The angle the vector should be rotated relative to the difference between the angles of the the two vectors.</param> /// <param name="relativeDistance">
/// The angle the vector should be rotated relative to the difference between the angles of
/// the the two vectors.
/// </param>
/// <returns>Resulting vector</returns> /// <returns>Resulting vector</returns>
private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance)
{ {
@ -374,15 +395,9 @@ namespace osu.Game.Rulesets.Osu.Mods
var diff = destAngleRad - initialAngleRad; var diff = destAngleRad - initialAngleRad;
while (diff < -Math.PI) while (diff < -Math.PI) diff += 2 * Math.PI;
{
diff += 2 * Math.PI;
}
while (diff > Math.PI) while (diff > Math.PI) diff -= 2 * Math.PI;
{
diff -= 2 * Math.PI;
}
var finalAngleRad = initialAngleRad + relativeDistance * diff; var finalAngleRad = initialAngleRad + relativeDistance * diff;
@ -392,13 +407,6 @@ namespace osu.Game.Rulesets.Osu.Mods
); );
} }
// Background metronome
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
drawableRuleset.Overlays.Add(new TargetBeatContainer());
}
public class TargetBeatContainer : BeatSyncedContainer public class TargetBeatContainer : BeatSyncedContainer
{ {
private PausableSkinnableSound sample; private PausableSkinnableSound sample;
@ -408,15 +416,6 @@ namespace osu.Game.Rulesets.Osu.Mods
Divisor = 1; Divisor = 1;
} }
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")) // todo: use another sample?
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{ {
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
@ -425,6 +424,15 @@ namespace osu.Game.Rulesets.Osu.Mods
sample?.Play(); sample?.Play();
} }
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")) // todo: use another sample
};
}
} }
} }
} }