Merge pull request #1782 from Aergwyn/standard-hd-support

Implement osu! hidden
This commit is contained in:
Dean Herbert 2017-12-30 15:31:05 +09:00 committed by GitHub
commit 845c1946db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 324 additions and 129 deletions

View File

@ -11,8 +11,11 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Osu.Mods
{
@ -23,13 +26,86 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModEasy : ModEasy
{
}
public class OsuModHidden : ModHidden
public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects
{
public override string Description => @"Play with no approach circles and fading notes for a slight score advantage.";
public override double ScoreMultiplier => 1.06;
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
private float preEmpt => DrawableOsuHitObject.TIME_PREEMPT;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{
d.ApplyCustomUpdateState += ApplyHiddenState;
d.FadeInDuration = preEmpt * fade_in_duration_multiplier;
}
}
protected void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
{
if (!(drawable is DrawableOsuHitObject d))
return;
var fadeOutStartTime = d.HitObject.StartTime - preEmpt + d.FadeInDuration;
var fadeOutDuration = preEmpt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out)
var longFadeDuration = ((d.HitObject as IHasEndTime)?.EndTime ?? d.HitObject.StartTime) - fadeOutStartTime;
switch (drawable)
{
case DrawableHitCircle circle:
// we don't want to see the approach circle
circle.ApproachCircle.Hide();
// fade out immediately after fade in.
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
circle.FadeOut(fadeOutDuration);
break;
case DrawableSlider slider:
using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
{
slider.Body.FadeOut(longFadeDuration, Easing.Out);
// delay a bit less to let the sliderball fade out peacefully instead of having a hard cut
using (slider.BeginDelayedSequence(longFadeDuration - fadeOutDuration, true))
slider.Ball.FadeOut(fadeOutDuration);
}
break;
case DrawableSpinner spinner:
// hide elements we don't care about.
spinner.Disc.Hide();
spinner.Ticks.Hide();
spinner.Background.Hide();
using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true))
{
spinner.FadeOut(fadeOutDuration);
// speed up the end sequence accordingly
switch (state)
{
case ArmedState.Hit:
spinner.ScaleTo(spinner.Scale * 1.2f, fadeOutDuration * 2, Easing.Out);
break;
case ArmedState.Miss:
spinner.ScaleTo(spinner.Scale * 0.8f, fadeOutDuration * 2, Easing.In);
break;
}
spinner.Expire();
}
break;
}
}
}
public class OsuModHardRock : ModHardRock, IApplicableToHitObject<OsuHitObject>
@ -51,11 +127,6 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve
}
public void ApplyToHitObjects(RulesetContainer<OsuHitObject> rulesetContainer)
{
}
}
public class OsuModSuddenDeath : ModSuddenDeath
@ -96,7 +167,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModPerfect : ModPerfect
{
}
public class OsuModSpunOut : Mod

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -48,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
number = new NumberPiece
{
Text = h is Spinner ? "S" : (HitObject.ComboIndex + 1).ToString(),
Text = (HitObject.ComboIndex + 1).ToString(),
},
ring = new RingPiece(),
flash = new FlashPiece(),
@ -88,25 +87,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdatePreemptState();
ApproachCircle.FadeIn(Math.Min(TIME_FADEIN * 2, TIME_PREEMPT));
ApproachCircle.FadeIn(Math.Min(FadeInDuration * 2, TIME_PREEMPT));
ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT);
}
protected override void UpdateCurrentState(ArmedState state)
{
double duration = ((HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime) - HitObject.StartTime;
glow.Delay(duration).FadeOut(400);
glow.FadeOut(400);
switch (state)
{
case ArmedState.Idle:
this.Delay(duration + TIME_PREEMPT).FadeOut(TIME_FADEOUT);
this.Delay(TIME_PREEMPT).FadeOut(500);
Expire(true);
// override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
LifetimeEnd = HitObject.StartTime + HitObject.HitWindowFor(HitResult.Miss);
break;
case ArmedState.Miss:
ApproachCircle.FadeOut(50);
this.FadeOut(TIME_FADEOUT / 5);
this.FadeOut(100);
Expire();
break;
case ArmedState.Hit:

View File

@ -12,7 +12,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public const float TIME_PREEMPT = 600;
public const float TIME_FADEIN = 400;
public const float TIME_FADEOUT = 500;
/// <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)
: base(hitObject)
@ -37,10 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
protected virtual void UpdatePreemptState()
{
this.FadeIn(TIME_FADEIN);
}
protected virtual void UpdatePreemptState() => this.FadeIn(FadeInDuration);
protected virtual void UpdateCurrentState(ArmedState state)
{

View File

@ -17,23 +17,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
private readonly Slider slider;
private readonly DrawableHitCircle initialCircle;
public readonly DrawableHitCircle InitialCircle;
private readonly List<ISliderProgress> components = new List<ISliderProgress>();
private readonly Container<DrawableSliderTick> ticks;
private readonly Container<DrawableRepeatPoint> repeatPoints;
private readonly SliderBody body;
private readonly SliderBall ball;
public readonly SliderBody Body;
public readonly SliderBall Ball;
public DrawableSlider(Slider s) : base(s)
public DrawableSlider(Slider s)
: base(s)
{
slider = s;
Children = new Drawable[]
{
body = new SliderBody(s)
Body = new SliderBody(s)
{
AccentColour = AccentColour,
Position = s.StackedPosition,
@ -41,16 +42,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
ticks = new Container<DrawableSliderTick>(),
repeatPoints = new Container<DrawableRepeatPoint>(),
ball = new SliderBall(s)
Ball = new SliderBall(s)
{
Scale = new Vector2(s.Scale),
AccentColour = AccentColour,
AlwaysPresent = true,
Alpha = 0
},
initialCircle = new DrawableHitCircle(new HitCircle
InitialCircle = new DrawableHitCircle(new HitCircle
{
//todo: avoid creating this temporary HitCircle.
StartTime = s.StartTime,
Position = s.StackedPosition,
ComboIndex = s.ComboIndex,
@ -61,16 +61,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
})
};
components.Add(body);
components.Add(ball);
components.Add(Body);
components.Add(Ball);
AddNested(initialCircle);
AddNested(InitialCircle);
var repeatDuration = s.Curve.Distance / s.Velocity;
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{
var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration;
var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2);
var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2);
var fadeOutTime = repeatStartTime + repeatDuration;
var drawableTick = new DrawableSliderTick(tick)
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
{
var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration;
var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2);
var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2);
var fadeOutTime = repeatStartTime + repeatDuration;
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
@ -105,11 +105,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private int currentRepeat;
public bool Tracking;
public override double FadeInDuration
{
get { return base.FadeInDuration; }
set { InitialCircle.FadeInDuration = base.FadeInDuration = value; }
}
protected override void Update()
{
base.Update();
Tracking = ball.Tracking;
Tracking = Ball.Tracking;
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
@ -120,11 +126,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
currentRepeat = repeat;
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!initialCircle.Judgements.Any(j => j.IsHit))
initialCircle.Position = slider.Curve.PositionAt(progress);
if (!InitialCircle.Judgements.Any(j => j.IsHit))
InitialCircle.Position = slider.Curve.PositionAt(progress);
foreach (var c in components) c.UpdateProgress(progress, repeat);
foreach (var t in ticks.Children) t.Tracking = ball.Tracking;
foreach (var t in ticks.Children) t.Tracking = Ball.Tracking;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
@ -133,13 +139,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
var judgementsCount = ticks.Children.Count + repeatPoints.Children.Count + 1;
var judgementsHit = ticks.Children.Count(t => t.Judgements.Any(j => j.IsHit)) + repeatPoints.Children.Count(t => t.Judgements.Any(j => j.IsHit));
if (initialCircle.Judgements.Any(j => j.IsHit))
if (InitialCircle.Judgements.Any(j => j.IsHit))
judgementsHit++;
var hitFraction = (double)judgementsHit / judgementsCount;
if (hitFraction == 1 && initialCircle.Judgements.Any(j => j.Result == HitResult.Great))
if (hitFraction == 1 && InitialCircle.Judgements.Any(j => j.Result == HitResult.Great))
AddJudgement(new OsuJudgement { Result = HitResult.Great });
else if (hitFraction >= 0.5 && initialCircle.Judgements.Any(j => j.Result >= HitResult.Good))
else if (hitFraction >= 0.5 && InitialCircle.Judgements.Any(j => j.Result >= HitResult.Good))
AddJudgement(new OsuJudgement { Result = HitResult.Good });
else if (hitFraction > 0)
AddJudgement(new OsuJudgement { Result = HitResult.Meh });
@ -150,21 +156,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateCurrentState(ArmedState state)
{
ball.FadeIn();
Ball.FadeIn();
using (BeginDelayedSequence(slider.Duration, true))
{
body.FadeOut(160);
ball.FadeOut(160);
Body.FadeOut(160);
Ball.FadeOut(160);
this.FadeOut(800)
.Expire();
}
}
public Drawable ProxiedLayer => initialCircle.ApproachCircle;
public Drawable ProxiedLayer => InitialCircle.ApproachCircle;
public override Vector2 SelectionPoint => ToScreenSpace(body.Position);
public override Quad SelectionQuad => body.PathDrawQuad;
public override Vector2 SelectionPoint => ToScreenSpace(Body.Position);
public override Quad SelectionQuad => Body.PathDrawQuad;
}
}

View File

@ -20,13 +20,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
private readonly Spinner spinner;
private readonly SpinnerDisc disc;
private readonly SpinnerTicks ticks;
public readonly SpinnerDisc Disc;
public readonly SpinnerTicks Ticks;
private readonly SpinnerSpmCounter spmCounter;
private readonly Container mainContainer;
private readonly SpinnerBackground background;
public readonly SpinnerBackground Background;
private readonly Container circleContainer;
private readonly CirclePiece circle;
private readonly GlowPiece glow;
@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
background = new SpinnerBackground
Background = new SpinnerBackground
{
Alpha = 0.6f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
disc = new SpinnerDisc(spinner)
Disc = new SpinnerDisc(spinner)
{
Scale = Vector2.Zero,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
circleContainer.CreateProxy(),
ticks = new SpinnerTicks
Ticks = new SpinnerTicks
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -114,22 +114,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
};
}
public float Progress => MathHelper.Clamp(disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1);
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1);
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (Time.Current < HitObject.StartTime) return;
if (Progress >= 1 && !disc.Complete)
if (Progress >= 1 && !Disc.Complete)
{
disc.Complete = true;
Disc.Complete = true;
const float duration = 200;
disc.FadeAccent(completeColour, duration);
Disc.FadeAccent(completeColour, duration);
background.FadeAccent(completeColour, duration);
background.FadeOut(duration);
Background.FadeAccent(completeColour, duration);
Background.FadeOut(duration);
circle.FadeColour(completeColour, duration);
glow.FadeColour(completeColour, duration);
@ -153,20 +153,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
normalColour = baseColour;
background.AccentColour = normalColour;
Background.AccentColour = normalColour;
completeColour = colours.YellowLight.Opacity(0.75f);
disc.AccentColour = fillColour;
Disc.AccentColour = fillColour;
circle.Colour = colours.BlueDark;
glow.Colour = colours.BlueDark;
}
protected override void Update()
{
disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && disc.Tracking)
spmCounter.FadeIn(TIME_FADEIN);
Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(FadeInDuration);
base.Update();
}
@ -175,14 +175,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateAfterChildren();
circle.Rotation = disc.Rotation;
ticks.Rotation = disc.Rotation;
spmCounter.SetRotation(disc.RotationAbsolute);
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
spmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
symbol.RotateTo(disc.Rotation / 2, 500, Easing.OutQuint);
symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint);
}
protected override void UpdatePreemptState()
@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circleContainer.ScaleTo(spinner.Scale * 0.3f);
circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint);
disc.RotateTo(-720);
Disc.RotateTo(-720);
symbol.RotateTo(-720);
mainContainer

View File

@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private const float idle_alpha = 0.2f;
private const float tracking_alpha = 0.4f;
public override bool IsPresent => true; // handle input when hidden
public SpinnerDisc(Spinner s)
{
spinner = s;

View File

@ -11,60 +11,106 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using osu.Game.Rulesets.Osu.Mods;
using OpenTK.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using System.Collections.Generic;
using System;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseHitCircle : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HitCircle),
typeof(OsuModHidden),
typeof(DrawableHitCircle)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private bool auto;
private bool hidden;
private int depthIndex;
private int circleSize;
private float circleScale = 1;
public TestCaseHitCircle()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => addSingle());
AddStep("Stream", addStream);
AddStep("Single", () => testSingle());
AddStep("Stream", testStream);
AddToggleStep("Auto", v => auto = v);
AddToggleStep("Hidden", v => hidden = v);
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s);
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s);
}
private void addSingle(double timeOffset = 0, Vector2? positionOffset = null)
private void testSingle(double timeOffset = 0, Vector2? positionOffset = null)
{
positionOffset = positionOffset ?? Vector2.Zero;
var circle = new HitCircle
{
StartTime = Time.Current + 1000 + timeOffset,
Position = positionOffset.Value
Position = positionOffset.Value,
ComboColour = Color4.LightSeaGreen
};
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 });
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new DrawableHitCircle(circle)
var drawable = new TestDrawableHitCircle(circle, auto)
{
Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++
};
if (auto)
drawable.State.Value = ArmedState.Hit;
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable);
}
private void addStream()
private void testStream()
{
Vector2 pos = Vector2.Zero;
for (int i = 0; i <= 1000; i += 100)
{
addSingle(i, pos);
testSingle(i, pos);
pos += new Vector2(10);
}
}
private class TestDrawableHitCircle : DrawableHitCircle
{
private readonly bool auto;
public TestDrawableHitCircle(OsuHitObject h, bool auto) : base(h)
{
this.auto = auto;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (auto && !userTriggered && timeOffset > 0)
{
// pretend we really hit it
AddJudgement(new OsuJudgement
{
Result = HitObject.ScoreResultForOffset(timeOffset)
});
}
base.CheckForJudgements(userTriggered, timeOffset);
}
}
}
}

View File

@ -13,37 +13,52 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using osu.Game.Rulesets.Osu.Mods;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseSlider : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableSlider) };
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Slider),
typeof(HitCircle),
typeof(OsuModHidden),
typeof(DrawableSlider),
typeof(DrawableHitCircle),
typeof(DrawableSliderTick),
typeof(DrawableRepeatPoint)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private bool hidden;
private int repeats;
private int depthIndex;
private int circleSize;
private float circleScale = 1;
private double speedMultiplier = 2;
private double sliderMultiplier = 2;
private int depthIndex;
public TestCaseSlider()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => addSingle());
AddStep("Repeated (1)", () => addRepeated(1));
AddStep("Repeated (2)", () => addRepeated(2));
AddStep("Repeated (3)", () => addRepeated(3));
AddStep("Repeated (4)", () => addRepeated(4));
AddStep("Stream", addStream);
AddSliderStep("SpeedMultiplier", 0.01, 10, 2, s => speedMultiplier = s);
AddSliderStep("SliderMultiplier", 0.01, 10, 2, s => sliderMultiplier = s);
AddStep("Single", () => testSingle());
AddStep("Stream", testStream);
AddStep("Repeated", () => testRepeated(repeats));
AddToggleStep("Hidden", v => hidden = v);
AddSliderStep("Repeats", 1, 10, 1, s => repeats = s);
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s);
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s);
AddSliderStep("SpeedMultiplier", 0.1, 10, 2, s => speedMultiplier = s);
AddSliderStep("SliderMultiplier", 0.1, 10, 2, s => sliderMultiplier = s);
}
private void addSingle(double timeOffset = 0, Vector2? positionOffset = null)
private void testSingle(double timeOffset = 0, Vector2? positionOffset = null)
{
positionOffset = positionOffset ?? Vector2.Zero;
@ -51,32 +66,19 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000 + timeOffset,
Position = new Vector2(-200, 0) + positionOffset.Value,
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0) + positionOffset.Value,
new Vector2(400, 0) + positionOffset.Value,
},
Distance = 400,
Distance = 400
};
var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
var difficulty = new BeatmapDifficulty
{
SliderMultiplier = (float)sliderMultiplier,
CircleSize = 0
};
slider.ApplyDefaults(cpi, difficulty);
Add(new DrawableSlider(slider)
{
Anchor = Anchor.Centre,
Depth = depthIndex++
});
addSlider(slider);
}
private void addRepeated(int repeats)
private void testRepeated(int repeats)
{
// The first run through the slider is considered a repeat
repeats++;
@ -89,6 +91,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0),
@ -99,32 +102,45 @@ namespace osu.Game.Rulesets.Osu.Tests
RepeatSamples = repeatSamples
};
addSlider(slider);
}
private void testStream()
{
Vector2 pos = Vector2.Zero;
for (int i = 0; i <= 1000; i += 100)
{
testSingle(i, pos);
pos += new Vector2(10);
}
}
private void addSlider(Slider slider)
{
var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
var difficulty = new BeatmapDifficulty
{
SliderMultiplier = (float)sliderMultiplier,
CircleSize = 0
CircleSize = circleSize
};
slider.ApplyDefaults(cpi, difficulty);
Add(new DrawableSlider(slider)
var drawable = new DrawableSlider(slider)
{
Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++
});
}
};
private void addStream()
{
Vector2 pos = Vector2.Zero;
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
for (int i = 0; i <= 1000; i += 100)
{
addSingle(i, pos);
pos += new Vector2(10);
}
Add(drawable);
}
}
}

View File

@ -1,11 +1,15 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
@ -15,30 +19,47 @@ namespace osu.Game.Rulesets.Osu.Tests
[Ignore("getting CI working")]
public class TestCaseSpinner : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Spinner),
typeof(OsuModHidden),
typeof(DrawableSpinner)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private bool hidden;
private int depthIndex;
private int circleSize;
private float circleScale = 1;
public TestCaseSpinner()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", addSingle);
AddStep("Single", testSingle);
AddToggleStep("Hidden", v => hidden = v);
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s);
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s);
}
private void addSingle()
private void testSingle()
{
var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 };
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 });
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new DrawableSpinner(spinner)
{
Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++
};
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable);
}
}

View File

@ -35,16 +35,13 @@ namespace osu.Game.Rulesets.Osu.UI
protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h)
{
var circle = h as HitCircle;
if (circle != null)
if (h is HitCircle circle)
return new DrawableHitCircle(circle);
var slider = h as Slider;
if (slider != null)
if (h is Slider slider)
return new DrawableSlider(slider);
var spinner = h as Spinner;
if (spinner != null)
if (h is Spinner spinner)
return new DrawableSpinner(spinner);
return null;
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// An interface for <see cref="Mod"/>s that can be applied to <see cref="DrawableHitObject"/>s.
/// </summary>
public interface IApplicableToDrawableHitObjects
{
/// <summary>
/// Applies this <see cref="IApplicableToDrawableHitObjects"/> to a list of <see cref="DrawableHitObject"/>s.
/// </summary>
/// <param name="drawable">The list of <see cref="DrawableHitObject"/>s to apply to.</param>
void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables);
}
}

View File

@ -122,6 +122,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
UpdateState(state);
// apply any custom state overrides
ApplyCustomUpdateState?.Invoke(this, state);
if (State == ArmedState.Hit)
PlaySamples();
};
@ -243,9 +246,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
h.ApplyCustomUpdateState += (d, s) => ApplyCustomUpdateState?.Invoke(d, s);
nestedHitObjects.Add(h);
}
/// <summary>
/// Bind to apply a custom state which can override the default implementation.
/// </summary>
public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState;
protected abstract void UpdateState(ArmedState state);
}
}

View File

@ -260,6 +260,9 @@ namespace osu.Game.Rulesets.UI
}
Playfield.PostProcess();
foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(Playfield.HitObjects.Objects);
}
protected override void Update()

View File

@ -310,6 +310,7 @@
<Compile Include="Overlays\Profile\Sections\Ranks\DrawableTotalScore.cs" />
<Compile Include="Overlays\Profile\Sections\Ranks\ScoreModsContainer.cs" />
<Compile Include="Overlays\Settings\Sections\Maintenance\DeleteAllBeatmapsDialog.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" />
<Compile Include="Screens\Select\ImportFromStablePopup.cs" />
<Compile Include="Overlays\Settings\SettingsButton.cs" />
<Compile Include="Rulesets\Edit\Layers\Selection\OriginHandle.cs" />