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

Merge pull request #1824 from Shawdooow/approach-rate

Add support for variable approach rate
This commit is contained in:
Dean Herbert 2018-01-12 18:29:40 +09:00 committed by GitHub
commit 57d02f9b64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 41 additions and 49 deletions

@ -1 +1 @@
Subproject commit 65947291229541de3eb1aff0e703f6968b07f976 Subproject commit a6090d3f6f03eaf9a3f643cf1ef3db96384c62ff

View File

@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Beatmaps namespace osu.Game.Rulesets.Osu.Beatmaps
{ {
@ -37,7 +36,6 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
private void applyStacking(Beatmap<OsuHitObject> beatmap) private void applyStacking(Beatmap<OsuHitObject> beatmap)
{ {
const int stack_distance = 3; const int stack_distance = 3;
float stackThreshold = DrawableOsuHitObject.TIME_PREEMPT * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
// Reset stacking // Reset stacking
for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++) for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++)
@ -58,6 +56,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
continue; continue;
double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
float stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
if (objectN.StartTime - endTime > stackThreshold) if (objectN.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the next object. //We are no longer within stacking range of the next object.
@ -100,6 +99,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectI = beatmap.HitObjects[i]; OsuHitObject objectI = beatmap.HitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue; if (objectI.StackHeight != 0 || objectI is Spinner) continue;
float stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
/* If this object is a hitcircle, then we enter this "special" case. /* If this object is a hitcircle, then we enter this "special" case.
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
* Any other case is handled by the "is Slider" code below this. * Any other case is handled by the "is Slider" code below this.

View File

@ -16,17 +16,15 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; public override string Description => @"Play with no approach circles and fading notes for a slight score advantage.";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
private const double fade_in_duration_multiplier = 0.4; private const float fade_in_duration_multiplier = 0.4f;
private const double fade_out_duration_multiplier = 0.3; private const double fade_out_duration_multiplier = 0.3;
private float preEmpt => DrawableOsuHitObject.TIME_PREEMPT;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables) public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{ {
foreach (var d in drawables.OfType<DrawableOsuHitObject>()) foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{ {
d.ApplyCustomUpdateState += ApplyHiddenState; d.ApplyCustomUpdateState += ApplyHiddenState;
d.FadeInDuration = preEmpt * fade_in_duration_multiplier; d.HitObject.TimeFadein = d.HitObject.TimePreempt * fade_in_duration_multiplier;
} }
} }
@ -35,8 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods
if (!(drawable is DrawableOsuHitObject d)) if (!(drawable is DrawableOsuHitObject d))
return; return;
var fadeOutStartTime = d.HitObject.StartTime - preEmpt + d.FadeInDuration; var fadeOutStartTime = d.HitObject.StartTime - d.HitObject.TimePreempt + d.HitObject.TimeFadein;
var fadeOutDuration = preEmpt * fade_out_duration_multiplier; var fadeOutDuration = d.HitObject.TimePreempt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out) // new duration from completed fade in to end (before fading out)
var longFadeDuration = ((d.HitObject as IHasEndTime)?.EndTime ?? d.HitObject.StartTime) - fadeOutStartTime; var longFadeDuration = ((d.HitObject as IHasEndTime)?.EndTime ?? d.HitObject.StartTime) - fadeOutStartTime;

View File

@ -96,12 +96,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
using (fp.BeginAbsoluteSequence(fadeInTime)) using (fp.BeginAbsoluteSequence(fadeInTime))
{ {
fp.FadeIn(DrawableOsuHitObject.TIME_FADEIN); fp.FadeIn(currHitObject.TimeFadein);
fp.ScaleTo(1, DrawableOsuHitObject.TIME_FADEIN, Easing.Out); fp.ScaleTo(1, currHitObject.TimeFadein, Easing.Out);
fp.MoveTo(pointEndPosition, DrawableOsuHitObject.TIME_FADEIN, Easing.Out); fp.MoveTo(pointEndPosition, currHitObject.TimeFadein, Easing.Out);
fp.Delay(fadeOutTime - fadeInTime).FadeOut(DrawableOsuHitObject.TIME_FADEIN); fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadein);
} }
fp.Expire(true); fp.Expire(true);

View File

@ -88,8 +88,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdatePreemptState(); base.UpdatePreemptState();
ApproachCircle.FadeIn(Math.Min(FadeInDuration * 2, TIME_PREEMPT)); ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadein * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT); ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt);
} }
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)
@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
switch (state) switch (state)
{ {
case ArmedState.Idle: case ArmedState.Idle:
this.Delay(TIME_PREEMPT).FadeOut(500); this.Delay(HitObject.TimePreempt).FadeOut(500);
Expire(true); Expire(true);

View File

@ -10,15 +10,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject> public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
{ {
public const float TIME_PREEMPT = 600; public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt;
public const float TIME_FADEIN = 400;
/// <summary>
/// The number of milliseconds used to fade in.
/// </summary>
public virtual double FadeInDuration { get; set; } = TIME_FADEIN;
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - TIME_PREEMPT;
protected DrawableOsuHitObject(OsuHitObject hitObject) protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject) : base(hitObject)
@ -29,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected sealed override void UpdateState(ArmedState state) protected sealed override void UpdateState(ArmedState state)
{ {
double transformTime = HitObject.StartTime - TIME_PREEMPT; double transformTime = HitObject.StartTime - HitObject.TimePreempt;
base.ApplyTransformsAt(transformTime, true); base.ApplyTransformsAt(transformTime, true);
base.ClearTransformsAfter(transformTime, true); base.ClearTransformsAfter(transformTime, true);
@ -38,12 +30,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
UpdatePreemptState(); UpdatePreemptState();
using (BeginDelayedSequence(TIME_PREEMPT + (Judgements.FirstOrDefault()?.TimeOffset ?? 0), true)) using (BeginDelayedSequence(HitObject.TimePreempt + (Judgements.FirstOrDefault()?.TimeOffset ?? 0), true))
UpdateCurrentState(state); UpdateCurrentState(state);
} }
} }
protected virtual void UpdatePreemptState() => this.FadeIn(FadeInDuration); protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein);
protected virtual void UpdateCurrentState(ArmedState state) protected virtual void UpdateCurrentState(ArmedState state)
{ {

View File

@ -58,7 +58,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Scale = s.Scale, Scale = s.Scale,
ComboColour = s.ComboColour, ComboColour = s.ComboColour,
Samples = s.Samples, Samples = s.Samples,
SampleControlPoint = s.SampleControlPoint SampleControlPoint = s.SampleControlPoint,
TimePreempt = s.TimePreempt,
TimeFadein = s.TimeFadein
}) })
}; };
@ -71,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>()) foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{ {
var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration;
var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2); var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2);
var fadeOutTime = repeatStartTime + repeatDuration; var fadeOutTime = repeatStartTime + repeatDuration;
var drawableTick = new DrawableSliderTick(tick) var drawableTick = new DrawableSliderTick(tick)
@ -88,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>()) foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
{ {
var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration; var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration;
var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2); var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2);
var fadeOutTime = repeatStartTime + repeatDuration; var fadeOutTime = repeatStartTime + repeatDuration;
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
@ -106,12 +108,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private int currentRepeat; private int currentRepeat;
public bool Tracking; public bool Tracking;
public override double FadeInDuration
{
get { return base.FadeInDuration; }
set { InitialCircle.FadeInDuration = base.FadeInDuration = value; }
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();

View File

@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && Disc.Tracking) if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(FadeInDuration); spmCounter.FadeIn(HitObject.TimeFadein);
base.Update(); base.Update();
} }
@ -191,14 +191,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.UpdatePreemptState(); base.UpdatePreemptState();
circleContainer.ScaleTo(Spinner.Scale * 0.3f); circleContainer.ScaleTo(Spinner.Scale * 0.3f);
circleContainer.ScaleTo(Spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint);
Disc.RotateTo(-720); Disc.RotateTo(-720);
symbol.RotateTo(-720); symbol.RotateTo(-720);
mainContainer mainContainer
.ScaleTo(0) .ScaleTo(0)
.ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, Easing.OutQuint) .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint)
.Then() .Then()
.ScaleTo(1, 500, Easing.OutQuint); .ScaleTo(1, 500, Easing.OutQuint);
} }

View File

@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public void UpdateProgress(double progress, int repeat) public void UpdateProgress(double progress, int repeat)
{ {
double start = 0; double start = 0;
double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - DrawableOsuHitObject.TIME_PREEMPT)) / DrawableOsuHitObject.TIME_FADEIN, 0, 1) : 1; double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1;
if (repeat >= slider.RepeatCount - 1) if (repeat >= slider.RepeatCount - 1)
{ {

View File

@ -20,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Objects
private const double hit_window_100 = 80; private const double hit_window_100 = 80;
private const double hit_window_300 = 30; private const double hit_window_300 = 30;
public float TimePreempt = 600;
public float TimeFadein = 400;
public Vector2 Position { get; set; } public Vector2 Position { get; set; }
public float X => Position.X; public float X => Position.X;
public float Y => Position.Y; public float Y => Position.Y;
@ -72,6 +75,9 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
} }
} }

View File

@ -5,7 +5,6 @@ using OpenTK;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -133,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Replays
// Do some nice easing for cursor movements // Do some nice easing for cursor movements
if (Frames.Count > 0) if (Frames.Count > 0)
{ {
moveToHitObject(h.StartTime, startPosition, h.Radius, easing); moveToHitObject(h, startPosition, easing);
} }
// Add frames to click the hitobject // Add frames to click the hitobject
@ -191,12 +190,12 @@ namespace osu.Game.Rulesets.Osu.Replays
} }
} }
private void moveToHitObject(double targetTime, Vector2 targetPos, double hitObjectRadius, Easing easing) private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing)
{ {
ReplayFrame lastFrame = Frames[Frames.Count - 1]; ReplayFrame lastFrame = Frames[Frames.Count - 1];
// Wait until Auto could "see and react" to the next note. // Wait until Auto could "see and react" to the next note.
double waitTime = targetTime - Math.Max(0.0, DrawableOsuHitObject.TIME_PREEMPT - reactionTime); double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
if (waitTime > lastFrame.Time) if (waitTime > lastFrame.Time)
{ {
lastFrame = new ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState); lastFrame = new ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState);
@ -205,17 +204,17 @@ namespace osu.Game.Rulesets.Osu.Replays
Vector2 lastPosition = lastFrame.Position; Vector2 lastPosition = lastFrame.Position;
double timeDifference = ApplyModsToTime(targetTime - lastFrame.Time); double timeDifference = ApplyModsToTime(h.StartTime - lastFrame.Time);
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up. // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
if (timeDifference > 0 && // Sanity checks if (timeDifference > 0 && // Sanity checks
((lastPosition - targetPos).Length > hitObjectRadius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
{ {
// Perform eased movement // Perform eased movement
for (double time = lastFrame.Time + FrameDelay; time < targetTime; time += FrameDelay) for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay)
{ {
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, targetTime, easing); Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing);
AddFrameToReplay(new ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState)); AddFrameToReplay(new ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState));
} }