diff --git a/COMPILING.md b/COMPILING.md new file mode 100644 index 0000000000..bfcbf6bc2c --- /dev/null +++ b/COMPILING.md @@ -0,0 +1,36 @@ +# Linux +### 1. Requirements: +Mono >= 5.4.0 (>= 5.8.0 recommended) +Please check [here](http://www.mono-project.com/download/) for stable or [here](http://www.mono-project.com/download/alpha/) for an alpha release. +NuGet >= 4.4.0 +msbuild +git + +### 2. Cloning project +Clone the entire repository with submodules using +``` +git clone https://github.com/ppy/osu --recursive +``` +Then restore NuGet packages from the repository +``` +nuget restore +``` +### 3. Compiling +Simply run `msbuild` where `osu.sln` is located, this will create all binaries in `osu/osu.Desktop/bin/Debug`. +### 4. Optimizing +If you want additional performance you can change build type to Release with +``` +msbuild -p:Configuration=Release +``` +Additionally, mono provides an AOT utility which attempts to precompile binaries. You can utilize that by running +``` +mono --aot ./osu\!.exe +``` +### 5. Troubleshooting +You may run into trouble with NuGet versioning, as the one in packaging system is almost always out of date. Simply run +``` +nuget +sudo nuget update -self +``` +**Warning** NuGet creates few config files when it's run for the first time. +Do not run NuGet as root on the first run or you might run into very peculiar issues. diff --git a/osu-framework b/osu-framework index 2438afea86..0c48da1d49 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 2438afea86d0ce1f91b1b2f01bca1cf8afdd4659 +Subproject commit 0c48da1d49f470d56aeab9b15651ce0a4f5ac261 diff --git a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs index 71349285b3..7b1f80f439 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs @@ -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 drawables) + { + foreach (var d in drawables.OfType()) + { + 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 @@ -51,11 +127,6 @@ namespace osu.Game.Rulesets.Osu.Mods slider.ControlPoints = newControlPoints; slider.Curve?.Calculate(); // Recalculate the slider curve } - - public void ApplyToHitObjects(RulesetContainer rulesetContainer) - { - - } } public class OsuModSuddenDeath : ModSuddenDeath @@ -96,7 +167,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModPerfect : ModPerfect { - } public class OsuModSpunOut : Mod diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a973d2b580..7bdb6d04b6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -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 @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly NumberPiece number; private readonly GlowPiece glow; - public DrawableHitCircle(OsuHitObject h) : base(h) + public DrawableHitCircle(HitCircle h) : base(h) { Origin = Anchor.Centre; @@ -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: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 3e0c23a1ab..f5f0300ae1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -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; + + /// + /// The number of milliseconds used to fade in. + /// + 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) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 41d5ef1b02..022bedf1fd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -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 components = new List(); private readonly Container ticks; private readonly Container 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(), repeatPoints = new Container(), - 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()) { 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()) { 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; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 054a2067ec..30c9fb13d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -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 diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index ca75a61738..9f54ce3fa3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -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; diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs index ef0bffa14e..cdce19ad21 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs @@ -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 RequiredTypes => new[] + { + typeof(HitCircle), + typeof(OsuModHidden), + typeof(DrawableHitCircle) + }; + private readonly Container content; protected override Container 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(HitCircle 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); + } + } } } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs index 7ce9c35bd5..5b6b357351 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs @@ -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 RequiredTypes => new[] { typeof(DrawableSlider) }; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Slider), + typeof(HitCircle), + typeof(OsuModHidden), + typeof(DrawableSlider), + typeof(DrawableHitCircle), + typeof(DrawableSliderTick), + typeof(DrawableRepeatPoint) + }; private readonly Container content; protected override Container 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 { 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 { 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); } } + } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs index 76cc70effd..c4ee56455a 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs @@ -1,11 +1,15 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // 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 RequiredTypes => new[] +{ + typeof(Spinner), + typeof(OsuModHidden), + typeof(DrawableSpinner) + }; + private readonly Container content; protected override Container 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); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 7c9cbd63fc..f37b87e533 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -35,16 +35,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override DrawableHitObject 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; } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 77b7c3add2..35c2e9234d 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -204,13 +204,13 @@ namespace osu.Game.Overlays.Mods { iconsContainer.AddRange(new[] { - backgroundIcon = new ModIcon(Mods[1]) + backgroundIcon = new PassThroughTooltipModIcon(Mods[1]) { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, Position = new Vector2(1.5f), }, - foregroundIcon = new ModIcon(Mods[0]) + foregroundIcon = new PassThroughTooltipModIcon(Mods[0]) { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, @@ -220,7 +220,7 @@ namespace osu.Game.Overlays.Mods } else { - iconsContainer.Add(foregroundIcon = new ModIcon(Mod) + iconsContainer.Add(foregroundIcon = new PassThroughTooltipModIcon(Mod) { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -259,5 +259,14 @@ namespace osu.Game.Overlays.Mods Mod = mod; } + + private class PassThroughTooltipModIcon : ModIcon + { + public override string TooltipText => null; + + public PassThroughTooltipModIcon(Mod mod) : base(mod) + { + } + } } } diff --git a/osu.Game/Overlays/Profile/SupporterIcon.cs b/osu.Game/Overlays/Profile/SupporterIcon.cs index 570d5a13bb..b5cd94e54b 100644 --- a/osu.Game/Overlays/Profile/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/SupporterIcon.cs @@ -1,20 +1,23 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; namespace osu.Game.Overlays.Profile { - public class SupporterIcon : CircularContainer + public class SupporterIcon : CircularContainer, IHasTooltip { private readonly Box background; + public string TooltipText => "osu!supporter"; + public SupporterIcon() { Masking = true; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs new file mode 100644 index 0000000000..1024d5686d --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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 +{ + /// + /// An interface for s that can be applied to s. + /// + public interface IApplicableToDrawableHitObjects + { + /// + /// Applies this to a list of s. + /// + /// The list of s to apply to. + void ApplyToDrawableHitObjects(IEnumerable drawables); + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index c118cd5bee..af038909d2 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -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); } + /// + /// Bind to apply a custom state which can override the default implementation. + /// + public event Action ApplyCustomUpdateState; + protected abstract void UpdateState(ArmedState state); } } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 5ca3d9521b..90f63aeab6 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI private readonly ModType type; - public string TooltipText { get; } + public virtual string TooltipText { get; } public ModIcon(Mod mod) { diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index fe7c0c05ed..40a37c689b 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -260,6 +260,9 @@ namespace osu.Game.Rulesets.UI } Playfield.PostProcess(); + + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(Playfield.HitObjects.Objects); } protected override void Update() diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index 489ab79fa4..03e9462636 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select HeaderText = @"You have no beatmaps!"; BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps?"; - Icon = FontAwesome.fa_trash_o; + Icon = FontAwesome.fa_plane; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 22191c98ab..6cb12430c3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -310,6 +310,7 @@ +