1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 04:07:25 +08:00

Merge branch 'master' into hitobject-constructor-safety

This commit is contained in:
Dean Herbert 2017-12-30 15:32:36 +09:00 committed by GitHub
commit 7f54778fed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 364 additions and 130 deletions

36
COMPILING.md Normal file
View File

@ -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.

View File

@ -11,8 +11,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK; 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 namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -23,13 +26,86 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModEasy : ModEasy 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 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 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> public class OsuModHardRock : ModHardRock, IApplicableToHitObject<OsuHitObject>
@ -51,11 +127,6 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.ControlPoints = newControlPoints; slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve slider.Curve?.Calculate(); // Recalculate the slider curve
} }
public void ApplyToHitObjects(RulesetContainer<OsuHitObject> rulesetContainer)
{
}
} }
public class OsuModSuddenDeath : ModSuddenDeath public class OsuModSuddenDeath : ModSuddenDeath
@ -96,7 +167,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModPerfect : ModPerfect public class OsuModPerfect : ModPerfect
{ {
} }
public class OsuModSpunOut : Mod public class OsuModSpunOut : Mod

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -88,25 +87,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdatePreemptState(); 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); ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT);
} }
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)
{ {
double duration = ((HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime) - HitObject.StartTime; glow.FadeOut(400);
glow.Delay(duration).FadeOut(400);
switch (state) switch (state)
{ {
case ArmedState.Idle: case ArmedState.Idle:
this.Delay(duration + TIME_PREEMPT).FadeOut(TIME_FADEOUT); this.Delay(TIME_PREEMPT).FadeOut(500);
Expire(true); 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; break;
case ArmedState.Miss: case ArmedState.Miss:
ApproachCircle.FadeOut(50); ApproachCircle.FadeOut(50);
this.FadeOut(TIME_FADEOUT / 5); this.FadeOut(100);
Expire(); Expire();
break; break;
case ArmedState.Hit: 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_PREEMPT = 600;
public const float TIME_FADEIN = 400; 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) protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject) : base(hitObject)
@ -37,10 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
protected virtual void UpdatePreemptState() protected virtual void UpdatePreemptState() => this.FadeIn(FadeInDuration);
{
this.FadeIn(TIME_FADEIN);
}
protected virtual void UpdateCurrentState(ArmedState state) 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 Slider slider;
private readonly DrawableHitCircle initialCircle; public readonly DrawableHitCircle InitialCircle;
private readonly List<ISliderProgress> components = new List<ISliderProgress>(); private readonly List<ISliderProgress> components = new List<ISliderProgress>();
private readonly Container<DrawableSliderTick> ticks; private readonly Container<DrawableSliderTick> ticks;
private readonly Container<DrawableRepeatPoint> repeatPoints; private readonly Container<DrawableRepeatPoint> repeatPoints;
private readonly SliderBody body; public readonly SliderBody Body;
private readonly SliderBall ball; public readonly SliderBall Ball;
public DrawableSlider(Slider s) : base(s) public DrawableSlider(Slider s)
: base(s)
{ {
slider = s; slider = s;
Children = new Drawable[] Children = new Drawable[]
{ {
body = new SliderBody(s) Body = new SliderBody(s)
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Position = s.StackedPosition, Position = s.StackedPosition,
@ -41,16 +42,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, },
ticks = new Container<DrawableSliderTick>(), ticks = new Container<DrawableSliderTick>(),
repeatPoints = new Container<DrawableRepeatPoint>(), repeatPoints = new Container<DrawableRepeatPoint>(),
ball = new SliderBall(s) Ball = new SliderBall(s)
{ {
Scale = new Vector2(s.Scale), Scale = new Vector2(s.Scale),
AccentColour = AccentColour, AccentColour = AccentColour,
AlwaysPresent = true, AlwaysPresent = true,
Alpha = 0 Alpha = 0
}, },
initialCircle = new DrawableHitCircle(new HitCircle InitialCircle = new DrawableHitCircle(new HitCircle
{ {
//todo: avoid creating this temporary HitCircle.
StartTime = s.StartTime, StartTime = s.StartTime,
Position = s.StackedPosition, Position = s.StackedPosition,
ComboIndex = s.ComboIndex, ComboIndex = s.ComboIndex,
@ -61,16 +61,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}) })
}; };
components.Add(body); components.Add(Body);
components.Add(ball); components.Add(Ball);
AddNested(initialCircle); AddNested(InitialCircle);
var repeatDuration = s.Curve.Distance / s.Velocity; var repeatDuration = s.Curve.Distance / s.Velocity;
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 ? TIME_FADEIN : TIME_FADEIN / 2); var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2);
var fadeOutTime = repeatStartTime + repeatDuration; var fadeOutTime = repeatStartTime + repeatDuration;
var drawableTick = new DrawableSliderTick(tick) var drawableTick = new DrawableSliderTick(tick)
@ -87,7 +87,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 ? TIME_FADEIN : TIME_FADEIN / 2); var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2);
var fadeOutTime = repeatStartTime + repeatDuration; var fadeOutTime = repeatStartTime + repeatDuration;
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
@ -105,11 +105,17 @@ 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();
Tracking = ball.Tracking; Tracking = Ball.Tracking;
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); 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; currentRepeat = repeat;
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!initialCircle.Judgements.Any(j => j.IsHit)) if (!InitialCircle.Judgements.Any(j => j.IsHit))
initialCircle.Position = slider.Curve.PositionAt(progress); InitialCircle.Position = slider.Curve.PositionAt(progress);
foreach (var c in components) c.UpdateProgress(progress, repeat); 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) 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 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)); 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++; judgementsHit++;
var hitFraction = (double)judgementsHit / judgementsCount; 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 }); 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 }); AddJudgement(new OsuJudgement { Result = HitResult.Good });
else if (hitFraction > 0) else if (hitFraction > 0)
AddJudgement(new OsuJudgement { Result = HitResult.Meh }); AddJudgement(new OsuJudgement { Result = HitResult.Meh });
@ -150,21 +156,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)
{ {
ball.FadeIn(); Ball.FadeIn();
using (BeginDelayedSequence(slider.Duration, true)) using (BeginDelayedSequence(slider.Duration, true))
{ {
body.FadeOut(160); Body.FadeOut(160);
ball.FadeOut(160); Ball.FadeOut(160);
this.FadeOut(800) this.FadeOut(800)
.Expire(); .Expire();
} }
} }
public Drawable ProxiedLayer => initialCircle.ApproachCircle; public Drawable ProxiedLayer => InitialCircle.ApproachCircle;
public override Vector2 SelectionPoint => ToScreenSpace(body.Position); public override Vector2 SelectionPoint => ToScreenSpace(Body.Position);
public override Quad SelectionQuad => body.PathDrawQuad; 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 Spinner spinner;
private readonly SpinnerDisc disc; public readonly SpinnerDisc Disc;
private readonly SpinnerTicks ticks; public readonly SpinnerTicks Ticks;
private readonly SpinnerSpmCounter spmCounter; private readonly SpinnerSpmCounter spmCounter;
private readonly Container mainContainer; private readonly Container mainContainer;
private readonly SpinnerBackground background; public readonly SpinnerBackground Background;
private readonly Container circleContainer; private readonly Container circleContainer;
private readonly CirclePiece circle; private readonly CirclePiece circle;
private readonly GlowPiece glow; private readonly GlowPiece glow;
@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
{ {
background = new SpinnerBackground Background = new SpinnerBackground
{ {
Alpha = 0.6f, Alpha = 0.6f,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
disc = new SpinnerDisc(spinner) Disc = new SpinnerDisc(spinner)
{ {
Scale = Vector2.Zero, Scale = Vector2.Zero,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
circleContainer.CreateProxy(), circleContainer.CreateProxy(),
ticks = new SpinnerTicks Ticks = new SpinnerTicks
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = 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) protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{ {
if (Time.Current < HitObject.StartTime) return; 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; const float duration = 200;
disc.FadeAccent(completeColour, duration); Disc.FadeAccent(completeColour, duration);
background.FadeAccent(completeColour, duration); Background.FadeAccent(completeColour, duration);
background.FadeOut(duration); Background.FadeOut(duration);
circle.FadeColour(completeColour, duration); circle.FadeColour(completeColour, duration);
glow.FadeColour(completeColour, duration); glow.FadeColour(completeColour, duration);
@ -153,20 +153,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
normalColour = baseColour; normalColour = baseColour;
background.AccentColour = normalColour; Background.AccentColour = normalColour;
completeColour = colours.YellowLight.Opacity(0.75f); completeColour = colours.YellowLight.Opacity(0.75f);
disc.AccentColour = fillColour; Disc.AccentColour = fillColour;
circle.Colour = colours.BlueDark; circle.Colour = colours.BlueDark;
glow.Colour = colours.BlueDark; glow.Colour = colours.BlueDark;
} }
protected override void Update() protected override void Update()
{ {
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(TIME_FADEIN); spmCounter.FadeIn(FadeInDuration);
base.Update(); base.Update();
} }
@ -175,14 +175,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
circle.Rotation = disc.Rotation; circle.Rotation = Disc.Rotation;
ticks.Rotation = disc.Rotation; Ticks.Rotation = Disc.Rotation;
spmCounter.SetRotation(disc.RotationAbsolute); spmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; 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() 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 * 0.3f);
circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint);
disc.RotateTo(-720); Disc.RotateTo(-720);
symbol.RotateTo(-720); symbol.RotateTo(-720);
mainContainer 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 idle_alpha = 0.2f;
private const float tracking_alpha = 0.4f; private const float tracking_alpha = 0.4f;
public override bool IsPresent => true; // handle input when hidden
public SpinnerDisc(Spinner s) public SpinnerDisc(Spinner s)
{ {
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.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; 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 namespace osu.Game.Rulesets.Osu.Tests
{ {
[Ignore("getting CI working")] [Ignore("getting CI working")]
public class TestCaseHitCircle : OsuTestCase public class TestCaseHitCircle : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HitCircle),
typeof(OsuModHidden),
typeof(DrawableHitCircle)
};
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private bool auto; private bool auto;
private bool hidden;
private int depthIndex; private int depthIndex;
private int circleSize;
private float circleScale = 1;
public TestCaseHitCircle() public TestCaseHitCircle()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => addSingle()); AddStep("Single", () => testSingle());
AddStep("Stream", addStream); AddStep("Stream", testStream);
AddToggleStep("Auto", v => auto = v); 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; positionOffset = positionOffset ?? Vector2.Zero;
var circle = new HitCircle var circle = new HitCircle
{ {
StartTime = Time.Current + 1000 + timeOffset, 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, Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++ Depth = depthIndex++
}; };
if (auto) if (auto)
drawable.State.Value = ArmedState.Hit; drawable.State.Value = ArmedState.Hit;
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable); Add(drawable);
} }
private void addStream() private void testStream()
{ {
Vector2 pos = Vector2.Zero; Vector2 pos = Vector2.Zero;
for (int i = 0; i <= 1000; i += 100) for (int i = 0; i <= 1000; i += 100)
{ {
addSingle(i, pos); testSingle(i, pos);
pos += new Vector2(10); 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.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Osu.Mods;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[Ignore("getting CI working")] [Ignore("getting CI working")]
public class TestCaseSlider : OsuTestCase 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; private readonly Container content;
protected override Container<Drawable> Content => 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 speedMultiplier = 2;
private double sliderMultiplier = 2; private double sliderMultiplier = 2;
private int depthIndex;
public TestCaseSlider() public TestCaseSlider()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => addSingle()); AddStep("Single", () => testSingle());
AddStep("Repeated (1)", () => addRepeated(1)); AddStep("Stream", testStream);
AddStep("Repeated (2)", () => addRepeated(2)); AddStep("Repeated", () => testRepeated(repeats));
AddStep("Repeated (3)", () => addRepeated(3)); AddToggleStep("Hidden", v => hidden = v);
AddStep("Repeated (4)", () => addRepeated(4)); AddSliderStep("Repeats", 1, 10, 1, s => repeats = s);
AddStep("Stream", addStream); AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s);
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s);
AddSliderStep("SpeedMultiplier", 0.01, 10, 2, s => speedMultiplier = s); AddSliderStep("SpeedMultiplier", 0.1, 10, 2, s => speedMultiplier = s);
AddSliderStep("SliderMultiplier", 0.01, 10, 2, s => sliderMultiplier = 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; positionOffset = positionOffset ?? Vector2.Zero;
@ -51,32 +66,19 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + 1000 + timeOffset, StartTime = Time.Current + 1000 + timeOffset,
Position = new Vector2(-200, 0) + positionOffset.Value, Position = new Vector2(-200, 0) + positionOffset.Value,
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2> ControlPoints = new List<Vector2>
{ {
new Vector2(-200, 0) + positionOffset.Value, new Vector2(-200, 0) + positionOffset.Value,
new Vector2(400, 0) + positionOffset.Value, new Vector2(400, 0) + positionOffset.Value,
}, },
Distance = 400, Distance = 400
}; };
var cpi = new ControlPointInfo(); addSlider(slider);
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++
});
} }
private void addRepeated(int repeats) private void testRepeated(int repeats)
{ {
// The first run through the slider is considered a repeat // The first run through the slider is considered a repeat
repeats++; repeats++;
@ -89,6 +91,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0), Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2> ControlPoints = new List<Vector2>
{ {
new Vector2(-200, 0), new Vector2(-200, 0),
@ -99,32 +102,45 @@ namespace osu.Game.Rulesets.Osu.Tests
RepeatSamples = repeatSamples 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(); var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
var difficulty = new BeatmapDifficulty var difficulty = new BeatmapDifficulty
{ {
SliderMultiplier = (float)sliderMultiplier, SliderMultiplier = (float)sliderMultiplier,
CircleSize = 0 CircleSize = circleSize
}; };
slider.ApplyDefaults(cpi, difficulty); slider.ApplyDefaults(cpi, difficulty);
Add(new DrawableSlider(slider)
var drawable = new DrawableSlider(slider)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++ Depth = depthIndex++
}); };
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable);
}
} }
private void addStream()
{
Vector2 pos = Vector2.Zero;
for (int i = 0; i <= 1000; i += 100)
{
addSingle(i, pos);
pos += new Vector2(10);
}
}
}
} }

View File

@ -1,11 +1,15 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using OpenTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -15,30 +19,47 @@ namespace osu.Game.Rulesets.Osu.Tests
[Ignore("getting CI working")] [Ignore("getting CI working")]
public class TestCaseSpinner : OsuTestCase public class TestCaseSpinner : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Spinner),
typeof(OsuModHidden),
typeof(DrawableSpinner)
};
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private bool hidden;
private int depthIndex; private int depthIndex;
private int circleSize;
private float circleScale = 1;
public TestCaseSpinner() public TestCaseSpinner()
{ {
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); 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 }; 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) var drawable = new DrawableSpinner(spinner)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++ Depth = depthIndex++
}; };
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable); Add(drawable);
} }
} }

View File

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

View File

@ -1,20 +1,23 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK; using OpenTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Overlays.Profile namespace osu.Game.Overlays.Profile
{ {
public class SupporterIcon : CircularContainer public class SupporterIcon : CircularContainer, IHasTooltip
{ {
private readonly Box background; private readonly Box background;
public string TooltipText => "osu!supporter";
public SupporterIcon() public SupporterIcon()
{ {
Masking = true; Masking = true;

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); UpdateState(state);
// apply any custom state overrides
ApplyCustomUpdateState?.Invoke(this, state);
if (State == ArmedState.Hit) if (State == ArmedState.Hit)
PlaySamples(); PlaySamples();
}; };
@ -243,9 +246,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j); h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
h.ApplyCustomUpdateState += (d, s) => ApplyCustomUpdateState?.Invoke(d, s);
nestedHitObjects.Add(h); 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); protected abstract void UpdateState(ArmedState state);
} }
} }

View File

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

View File

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