mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 18:23:04 +08:00
Merge branch 'master' into introduce-session-statics
This commit is contained in:
commit
d01c30fd52
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
@ -37,9 +38,21 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public int ComboOffset { get; set; }
|
public int ComboOffset { get; set; }
|
||||||
|
|
||||||
public int IndexInCurrentCombo { get; set; }
|
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
|
||||||
|
|
||||||
public int ComboIndex { get; set; }
|
public int IndexInCurrentCombo
|
||||||
|
{
|
||||||
|
get => IndexInCurrentComboBindable.Value;
|
||||||
|
set => IndexInCurrentComboBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
|
||||||
|
|
||||||
|
public int ComboIndex
|
||||||
|
{
|
||||||
|
get => ComboIndexBindable.Value;
|
||||||
|
set => ComboIndexBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Difference between the distance to the next object
|
/// Difference between the distance to the next object
|
||||||
@ -48,10 +61,16 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float DistanceToHyperDash { get; set; }
|
public float DistanceToHyperDash { get; set; }
|
||||||
|
|
||||||
|
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The next fruit starts a new combo. Used for explodey.
|
/// The next fruit starts a new combo. Used for explodey.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool LastInCombo { get; set; }
|
public virtual bool LastInCombo
|
||||||
|
{
|
||||||
|
get => LastInComboBindable.Value;
|
||||||
|
set => LastInComboBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public float Scale { get; set; } = 1;
|
public float Scale { get; set; } = 1;
|
||||||
|
|
||||||
|
26
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs
Normal file
26
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneHitCircleComboChange : TestSceneHitCircle
|
||||||
|
{
|
||||||
|
private readonly Bindable<int> comboIndex = new Bindable<int>();
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
|
||||||
|
{
|
||||||
|
circle.ComboIndexBindable.BindTo(comboIndex);
|
||||||
|
circle.IndexInCurrentComboBindable.BindTo(comboIndex);
|
||||||
|
return base.CreateDrawableHitCircle(circle, auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -297,11 +297,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
|
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
|
||||||
|
|
||||||
var drawable = new DrawableSlider(slider)
|
var drawable = CreateDrawableSlider(slider);
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Depth = depthIndex++
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
|
||||||
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
||||||
@ -311,6 +307,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual DrawableSlider CreateDrawableSlider(Slider slider) => new DrawableSlider(slider)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Depth = depthIndex++
|
||||||
|
};
|
||||||
|
|
||||||
private float judgementOffsetDirection = 1;
|
private float judgementOffsetDirection = 1;
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
28
osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs
Normal file
28
osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneSliderComboChange : TestSceneSlider
|
||||||
|
{
|
||||||
|
private readonly Bindable<int> comboIndex = new Bindable<int>();
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawableSlider CreateDrawableSlider(Slider slider)
|
||||||
|
{
|
||||||
|
slider.ComboIndexBindable.BindTo(comboIndex);
|
||||||
|
slider.IndexInCurrentComboBindable.BindTo(comboIndex);
|
||||||
|
|
||||||
|
return base.CreateDrawableSlider(slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
Normal file
96
osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneSpinnerRotation : TestSceneOsuPlayer
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private AudioManager audioManager { get; set; }
|
||||||
|
|
||||||
|
private TrackVirtualManual track;
|
||||||
|
|
||||||
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
track = (TrackVirtualManual)working.Track;
|
||||||
|
return working;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableSpinner drawableSpinner;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||||
|
AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpinnerRewindingRotation()
|
||||||
|
{
|
||||||
|
addSeekStep(5000);
|
||||||
|
AddAssert("is rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100));
|
||||||
|
|
||||||
|
addSeekStep(0);
|
||||||
|
AddAssert("is rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpinnerMiddleRewindingRotation()
|
||||||
|
{
|
||||||
|
double estimatedRotation = 0;
|
||||||
|
|
||||||
|
addSeekStep(5000);
|
||||||
|
AddStep("retrieve rotation", () => estimatedRotation = drawableSpinner.Disc.RotationAbsolute);
|
||||||
|
|
||||||
|
addSeekStep(2500);
|
||||||
|
addSeekStep(5000);
|
||||||
|
AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSeekStep(double time)
|
||||||
|
{
|
||||||
|
AddStep($"seek to {time}", () => track.Seek(time));
|
||||||
|
|
||||||
|
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
EndTime = 5000,
|
||||||
|
},
|
||||||
|
// placeholder object to avoid hitting the results screen
|
||||||
|
new HitObject
|
||||||
|
{
|
||||||
|
StartTime = 99999,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
|
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()),
|
||||||
ApproachCircle = new ApproachCircle
|
ApproachCircle = new ApproachCircle
|
||||||
{
|
{
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
private readonly NumberPiece number;
|
private readonly NumberPiece number;
|
||||||
private readonly GlowPiece glow;
|
private readonly GlowPiece glow;
|
||||||
|
|
||||||
public MainCirclePiece(int index)
|
public MainCirclePiece()
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
@ -31,10 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
{
|
{
|
||||||
glow = new GlowPiece(),
|
glow = new GlowPiece(),
|
||||||
circle = new CirclePiece(),
|
circle = new CirclePiece(),
|
||||||
number = new NumberPiece
|
number = new NumberPiece(),
|
||||||
{
|
|
||||||
Text = (index + 1).ToString(),
|
|
||||||
},
|
|
||||||
ring = new RingPiece(),
|
ring = new RingPiece(),
|
||||||
flash = new FlashPiece(),
|
flash = new FlashPiece(),
|
||||||
explode = new ExplodePiece(),
|
explode = new ExplodePiece(),
|
||||||
@ -42,12 +39,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly IBindable<ArmedState> state = new Bindable<ArmedState>();
|
private readonly IBindable<ArmedState> state = new Bindable<ArmedState>();
|
||||||
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableObject)
|
private void load(DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
|
OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject;
|
||||||
|
|
||||||
state.BindTo(drawableObject.State);
|
state.BindTo(drawableObject.State);
|
||||||
state.BindValueChanged(updateState, true);
|
state.BindValueChanged(updateState, true);
|
||||||
|
|
||||||
@ -58,6 +57,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
glow.Colour = colour.NewValue;
|
glow.Colour = colour.NewValue;
|
||||||
circle.Colour = colour.NewValue;
|
circle.Colour = colour.NewValue;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable);
|
||||||
|
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState(ValueChangedEvent<ArmedState> state)
|
private void updateState(ValueChangedEvent<ArmedState> state)
|
||||||
|
@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
lastAngle -= 360;
|
lastAngle -= 360;
|
||||||
|
|
||||||
currentRotation += thisAngle - lastAngle;
|
currentRotation += thisAngle - lastAngle;
|
||||||
RotationAbsolute += Math.Abs(thisAngle - lastAngle);
|
RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastAngle = thisAngle;
|
lastAngle = thisAngle;
|
||||||
|
@ -58,13 +58,37 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
public virtual bool NewCombo { get; set; }
|
public virtual bool NewCombo { get; set; }
|
||||||
|
|
||||||
public int ComboOffset { get; set; }
|
public readonly Bindable<int> ComboOffsetBindable = new Bindable<int>();
|
||||||
|
|
||||||
public virtual int IndexInCurrentCombo { get; set; }
|
public int ComboOffset
|
||||||
|
{
|
||||||
|
get => ComboOffsetBindable.Value;
|
||||||
|
set => ComboOffsetBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual int ComboIndex { get; set; }
|
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
|
||||||
|
|
||||||
public bool LastInCombo { get; set; }
|
public virtual int IndexInCurrentCombo
|
||||||
|
{
|
||||||
|
get => IndexInCurrentComboBindable.Value;
|
||||||
|
set => IndexInCurrentComboBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
|
||||||
|
|
||||||
|
public virtual int ComboIndex
|
||||||
|
{
|
||||||
|
get => ComboIndexBindable.Value;
|
||||||
|
set => ComboIndexBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
|
public bool LastInCombo
|
||||||
|
{
|
||||||
|
get => LastInComboBindable.Value;
|
||||||
|
set => LastInComboBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
|
@ -33,28 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
|
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
|
||||||
|
|
||||||
public override int ComboIndex
|
|
||||||
{
|
|
||||||
get => base.ComboIndex;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
base.ComboIndex = value;
|
|
||||||
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
|
|
||||||
n.ComboIndex = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int IndexInCurrentCombo
|
|
||||||
{
|
|
||||||
get => base.IndexInCurrentCombo;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
base.IndexInCurrentCombo = value;
|
|
||||||
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
|
|
||||||
n.IndexInCurrentCombo = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly Bindable<SliderPath> PathBindable = new Bindable<SliderPath>();
|
public readonly Bindable<SliderPath> PathBindable = new Bindable<SliderPath>();
|
||||||
|
|
||||||
public SliderPath Path
|
public SliderPath Path
|
||||||
@ -192,8 +170,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
Position = Position,
|
Position = Position,
|
||||||
Samples = getNodeSamples(0),
|
Samples = getNodeSamples(0),
|
||||||
SampleControlPoint = SampleControlPoint,
|
SampleControlPoint = SampleControlPoint,
|
||||||
IndexInCurrentCombo = IndexInCurrentCombo,
|
|
||||||
ComboIndex = ComboIndex,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -205,8 +181,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
Position = EndPosition,
|
Position = EndPosition,
|
||||||
IndexInCurrentCombo = IndexInCurrentCombo,
|
|
||||||
ComboIndex = ComboIndex,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -25,13 +24,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly IBindable<ArmedState> state = new Bindable<ArmedState>();
|
private readonly IBindable<ArmedState> state = new Bindable<ArmedState>();
|
||||||
|
|
||||||
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
||||||
{
|
{
|
||||||
|
OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject;
|
||||||
|
|
||||||
Sprite hitCircleSprite;
|
Sprite hitCircleSprite;
|
||||||
|
SkinnableSpriteText hitCircleText;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -42,14 +44,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
|
hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
|
||||||
{
|
{
|
||||||
Font = OsuFont.Numeric.With(size: 40),
|
Font = OsuFont.Numeric.With(size: 40),
|
||||||
UseFullGlyphHeight = false,
|
UseFullGlyphHeight = false,
|
||||||
}, confineMode: ConfineMode.NoScaling)
|
}, confineMode: ConfineMode.NoScaling),
|
||||||
{
|
|
||||||
Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString()
|
|
||||||
},
|
|
||||||
new Sprite
|
new Sprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("hitcircleoverlay"),
|
Texture = skin.GetTexture("hitcircleoverlay"),
|
||||||
@ -63,6 +62,9 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
|
|
||||||
accentColour.BindTo(drawableObject.AccentColour);
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true);
|
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true);
|
||||||
|
|
||||||
|
indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable);
|
||||||
|
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState(ValueChangedEvent<ArmedState> state)
|
private void updateState(ValueChangedEvent<ArmedState> state)
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -115,6 +116,32 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
assertPosition(4, 1f);
|
assertPosition(4, 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderMultiplierDoesNotAffectRelativeBeatLength()
|
||||||
|
{
|
||||||
|
var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range });
|
||||||
|
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
|
||||||
|
|
||||||
|
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||||
|
AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 5000);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
assertPosition(i, i / 5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderMultiplierAffectsNonRelativeBeatLength()
|
||||||
|
{
|
||||||
|
var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range });
|
||||||
|
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
|
||||||
|
|
||||||
|
createTest(beatmap);
|
||||||
|
AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 2000);
|
||||||
|
|
||||||
|
assertPosition(0, 0);
|
||||||
|
assertPosition(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}",
|
private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}",
|
||||||
() => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
|
() => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
|
||||||
|
|
||||||
@ -193,6 +220,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
||||||
|
|
||||||
|
public new Bindable<double> TimeRange => base.TimeRange;
|
||||||
|
|
||||||
public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
|
202
osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs
Normal file
202
osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
using IntroSequence = osu.Game.Configuration.IntroSequence;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Menus
|
||||||
|
{
|
||||||
|
public class TestSceneScreenNavigation : ManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private const float click_padding = 25;
|
||||||
|
|
||||||
|
private GameHost host;
|
||||||
|
private TestOsuGame osuGame;
|
||||||
|
|
||||||
|
private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, osuGame.LayoutRectangle.Bottom - click_padding));
|
||||||
|
|
||||||
|
private Vector2 optionsButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, click_padding));
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host)
|
||||||
|
{
|
||||||
|
this.host = host;
|
||||||
|
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("Create new game instance", () =>
|
||||||
|
{
|
||||||
|
if (osuGame != null)
|
||||||
|
{
|
||||||
|
Remove(osuGame);
|
||||||
|
osuGame.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
osuGame = new TestOsuGame(LocalStorage, API);
|
||||||
|
osuGame.SetHost(host);
|
||||||
|
|
||||||
|
// todo: this can be removed once we can run audio trakcs without a device present
|
||||||
|
// see https://github.com/ppy/osu/issues/1302
|
||||||
|
osuGame.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
|
||||||
|
|
||||||
|
Add(osuGame);
|
||||||
|
});
|
||||||
|
AddUntilStep("Wait for load", () => osuGame.IsLoaded);
|
||||||
|
AddUntilStep("Wait for intro", () => osuGame.ScreenStack.CurrentScreen is IntroScreen);
|
||||||
|
confirmAtMainMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExitSongSelectWithEscape()
|
||||||
|
{
|
||||||
|
TestSongSelect songSelect = null;
|
||||||
|
|
||||||
|
pushAndConfirm(() => songSelect = new TestSongSelect());
|
||||||
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddStep("Press escape", () => pressAndRelease(Key.Escape));
|
||||||
|
AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||||
|
exitViaEscapeAndConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExitSongSelectWithClick()
|
||||||
|
{
|
||||||
|
TestSongSelect songSelect = null;
|
||||||
|
|
||||||
|
pushAndConfirm(() => songSelect = new TestSongSelect());
|
||||||
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
||||||
|
|
||||||
|
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
|
||||||
|
AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == osuGame.BackButton));
|
||||||
|
|
||||||
|
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||||
|
exitViaBackButtonAndConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExitMultiWithEscape()
|
||||||
|
{
|
||||||
|
pushAndConfirm(() => new Screens.Multi.Multiplayer());
|
||||||
|
exitViaEscapeAndConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExitMultiWithBackButton()
|
||||||
|
{
|
||||||
|
pushAndConfirm(() => new Screens.Multi.Multiplayer());
|
||||||
|
exitViaBackButtonAndConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOpenOptionsAndExitWithEscape()
|
||||||
|
{
|
||||||
|
AddUntilStep("Wait for options to load", () => osuGame.Settings.IsLoaded);
|
||||||
|
AddStep("Enter menu", () => pressAndRelease(Key.Enter));
|
||||||
|
AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition));
|
||||||
|
AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Options overlay was opened", () => osuGame.Settings.State.Value == Visibility.Visible);
|
||||||
|
AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape));
|
||||||
|
AddAssert("Options overlay was closed", () => osuGame.Settings.State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushAndConfirm(Func<Screen> newScreen)
|
||||||
|
{
|
||||||
|
Screen screen = null;
|
||||||
|
AddStep("Push new screen", () => osuGame.ScreenStack.Push(screen = newScreen()));
|
||||||
|
AddUntilStep("Wait for new screen", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exitViaEscapeAndConfirm()
|
||||||
|
{
|
||||||
|
AddStep("Press escape", () => pressAndRelease(Key.Escape));
|
||||||
|
confirmAtMainMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exitViaBackButtonAndConfirm()
|
||||||
|
{
|
||||||
|
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
||||||
|
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
|
||||||
|
confirmAtMainMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
|
||||||
|
|
||||||
|
private void pressAndRelease(Key key)
|
||||||
|
{
|
||||||
|
InputManager.PressKey(key);
|
||||||
|
InputManager.ReleaseKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestOsuGame : OsuGame
|
||||||
|
{
|
||||||
|
public new ScreenStack ScreenStack => base.ScreenStack;
|
||||||
|
|
||||||
|
public new BackButton BackButton => base.BackButton;
|
||||||
|
|
||||||
|
public new SettingsPanel Settings => base.Settings;
|
||||||
|
|
||||||
|
public new OsuConfigManager LocalConfig => base.LocalConfig;
|
||||||
|
|
||||||
|
protected override Loader CreateLoader() => new TestLoader();
|
||||||
|
|
||||||
|
public TestOsuGame(Storage storage, IAPIProvider api)
|
||||||
|
{
|
||||||
|
Storage = storage;
|
||||||
|
API = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
API.Login("Rhythm Champion", "osu!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSongSelect : PlaySongSelect
|
||||||
|
{
|
||||||
|
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestLoader : Loader
|
||||||
|
{
|
||||||
|
protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler();
|
||||||
|
|
||||||
|
private class TestShaderPrecompiler : ShaderPrecompiler
|
||||||
|
{
|
||||||
|
protected override bool AllLoaded => true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public TestSceneBackButton()
|
public TestSceneBackButton()
|
||||||
{
|
{
|
||||||
BackButton button;
|
BackButton button;
|
||||||
|
BackButton.Receptor receptor = new BackButton.Receptor();
|
||||||
|
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
@ -31,12 +32,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
receptor,
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.SlateGray
|
Colour = Color4.SlateGray
|
||||||
},
|
},
|
||||||
button = new BackButton
|
button = new BackButton(receptor)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -45,25 +44,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public virtual void PostProcess()
|
public virtual void PostProcess()
|
||||||
{
|
{
|
||||||
void updateNestedCombo(HitObject obj, int comboIndex, int indexInCurrentCombo)
|
|
||||||
{
|
|
||||||
if (obj is IHasComboInformation objectComboInfo)
|
|
||||||
{
|
|
||||||
objectComboInfo.ComboIndex = comboIndex;
|
|
||||||
objectComboInfo.IndexInCurrentCombo = indexInCurrentCombo;
|
|
||||||
foreach (var nestedObject in obj.NestedHitObjects)
|
|
||||||
updateNestedCombo(nestedObject, comboIndex, indexInCurrentCombo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var hitObject in Beatmap.HitObjects)
|
|
||||||
{
|
|
||||||
if (hitObject is IHasComboInformation objectComboInfo)
|
|
||||||
{
|
|
||||||
foreach (var nested in hitObject.NestedHitObjects)
|
|
||||||
updateNestedCombo(nested, objectComboInfo.ComboIndex, objectComboInfo.IndexInCurrentCombo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,16 @@ using osu.Game.Input.Bindings;
|
|||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class BackButton : VisibilityContainer, IKeyBindingHandler<GlobalAction>
|
public class BackButton : VisibilityContainer
|
||||||
{
|
{
|
||||||
public Action Action;
|
public Action Action;
|
||||||
|
|
||||||
private readonly TwoLayerButton button;
|
private readonly TwoLayerButton button;
|
||||||
|
|
||||||
public BackButton()
|
public BackButton(Receptor receptor)
|
||||||
{
|
{
|
||||||
|
receptor.OnBackPressed = () => Action?.Invoke();
|
||||||
|
|
||||||
Size = TwoLayerButton.SIZE_EXTENDED;
|
Size = TwoLayerButton.SIZE_EXTENDED;
|
||||||
|
|
||||||
Child = button = new TwoLayerButton
|
Child = button = new TwoLayerButton
|
||||||
@ -37,19 +39,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
button.HoverColour = colours.PinkDark;
|
button.HoverColour = colours.PinkDark;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
|
||||||
{
|
|
||||||
if (action == GlobalAction.Back)
|
|
||||||
{
|
|
||||||
Action?.Invoke();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnReleased(GlobalAction action) => action == GlobalAction.Back;
|
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
button.MoveToX(0, 400, Easing.OutQuint);
|
button.MoveToX(0, 400, Easing.OutQuint);
|
||||||
@ -61,5 +50,24 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
button.MoveToX(-TwoLayerButton.SIZE_EXTENDED.X / 2, 400, Easing.OutQuint);
|
button.MoveToX(-TwoLayerButton.SIZE_EXTENDED.X / 2, 400, Easing.OutQuint);
|
||||||
button.FadeOut(400, Easing.OutQuint);
|
button.FadeOut(400, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Receptor : Drawable, IKeyBindingHandler<GlobalAction>
|
||||||
|
{
|
||||||
|
public Action OnBackPressed;
|
||||||
|
|
||||||
|
public bool OnPressed(GlobalAction action)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case GlobalAction.Back:
|
||||||
|
OnBackPressed?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnReleased(GlobalAction action) => action == GlobalAction.Back;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,10 @@ namespace osu.Game.Online.API
|
|||||||
public void Perform(IAPIProvider api)
|
public void Perform(IAPIProvider api)
|
||||||
{
|
{
|
||||||
if (!(api is APIAccess apiAccess))
|
if (!(api is APIAccess apiAccess))
|
||||||
throw new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests.");
|
{
|
||||||
|
Fail(new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
API = apiAccess;
|
API = apiAccess;
|
||||||
|
|
||||||
|
@ -81,10 +81,14 @@ namespace osu.Game
|
|||||||
|
|
||||||
public readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>();
|
public readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>();
|
||||||
|
|
||||||
private OsuScreenStack screenStack;
|
protected OsuScreenStack ScreenStack;
|
||||||
|
|
||||||
|
protected BackButton BackButton;
|
||||||
|
|
||||||
|
protected SettingsPanel Settings;
|
||||||
|
|
||||||
private VolumeOverlay volume;
|
private VolumeOverlay volume;
|
||||||
private OsuLogo osuLogo;
|
private OsuLogo osuLogo;
|
||||||
private BackButton backButton;
|
|
||||||
|
|
||||||
private MainMenu menuScreen;
|
private MainMenu menuScreen;
|
||||||
|
|
||||||
@ -96,8 +100,6 @@ namespace osu.Game
|
|||||||
|
|
||||||
private readonly string[] args;
|
private readonly string[] args;
|
||||||
|
|
||||||
private SettingsPanel settings;
|
|
||||||
|
|
||||||
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
||||||
|
|
||||||
private readonly List<OverlayContainer> toolbarElements = new List<OverlayContainer>();
|
private readonly List<OverlayContainer> toolbarElements = new List<OverlayContainer>();
|
||||||
@ -318,6 +320,8 @@ namespace osu.Game
|
|||||||
}, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true);
|
}, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual Loader CreateLoader() => new Loader();
|
||||||
|
|
||||||
#region Beatmap progression
|
#region Beatmap progression
|
||||||
|
|
||||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
|
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
|
||||||
@ -356,7 +360,7 @@ namespace osu.Game
|
|||||||
performFromMainMenuTask?.Cancel();
|
performFromMainMenuTask?.Cancel();
|
||||||
|
|
||||||
// if the current screen does not allow screen changing, give the user an option to try again later.
|
// if the current screen does not allow screen changing, give the user an option to try again later.
|
||||||
if (!bypassScreenAllowChecks && (screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false)
|
if (!bypassScreenAllowChecks && (ScreenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false)
|
||||||
{
|
{
|
||||||
notifications.Post(new SimpleNotification
|
notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
@ -374,7 +378,7 @@ namespace osu.Game
|
|||||||
CloseAllOverlays(false);
|
CloseAllOverlays(false);
|
||||||
|
|
||||||
// we may already be at the target screen type.
|
// we may already be at the target screen type.
|
||||||
if (targetScreen != null && screenStack.CurrentScreen?.GetType() == targetScreen)
|
if (targetScreen != null && ScreenStack.CurrentScreen?.GetType() == targetScreen)
|
||||||
{
|
{
|
||||||
action();
|
action();
|
||||||
return;
|
return;
|
||||||
@ -421,6 +425,7 @@ namespace osu.Game
|
|||||||
ScoreManager.PresentImport = items => PresentScore(items.First());
|
ScoreManager.PresentImport = items => PresentScore(items.First());
|
||||||
|
|
||||||
Container logoContainer;
|
Container logoContainer;
|
||||||
|
BackButton.Receptor receptor;
|
||||||
|
|
||||||
dependencies.CacheAs(idleTracker = new GameIdleTracker(6000));
|
dependencies.CacheAs(idleTracker = new GameIdleTracker(6000));
|
||||||
|
|
||||||
@ -437,15 +442,16 @@ namespace osu.Game
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
|
receptor = new BackButton.Receptor(),
|
||||||
backButton = new BackButton
|
ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
|
||||||
|
BackButton = new BackButton(receptor)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
if ((screenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true)
|
if ((ScreenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true)
|
||||||
screenStack.Exit();
|
ScreenStack.Exit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
|
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
@ -458,18 +464,15 @@ namespace osu.Game
|
|||||||
idleTracker
|
idleTracker
|
||||||
});
|
});
|
||||||
|
|
||||||
screenStack.ScreenPushed += screenPushed;
|
ScreenStack.ScreenPushed += screenPushed;
|
||||||
screenStack.ScreenExited += screenExited;
|
ScreenStack.ScreenExited += screenExited;
|
||||||
|
|
||||||
loadComponentSingleFile(osuLogo, logo =>
|
loadComponentSingleFile(osuLogo, logo =>
|
||||||
{
|
{
|
||||||
logoContainer.Add(logo);
|
logoContainer.Add(logo);
|
||||||
|
|
||||||
// Loader has to be created after the logo has finished loading as Loader performs logo transformations on entering.
|
// Loader has to be created after the logo has finished loading as Loader performs logo transformations on entering.
|
||||||
screenStack.Push(new Loader
|
ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both));
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
loadComponentSingleFile(Toolbar = new Toolbar
|
loadComponentSingleFile(Toolbar = new Toolbar
|
||||||
@ -504,7 +507,7 @@ namespace osu.Game
|
|||||||
loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
||||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true);
|
loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true);
|
||||||
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
|
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
|
||||||
@ -535,7 +538,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
Add(externalLinkOpener = new ExternalLinkOpener());
|
Add(externalLinkOpener = new ExternalLinkOpener());
|
||||||
|
|
||||||
var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications };
|
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications };
|
||||||
overlays.AddRange(singleDisplaySideOverlays);
|
overlays.AddRange(singleDisplaySideOverlays);
|
||||||
|
|
||||||
foreach (var overlay in singleDisplaySideOverlays)
|
foreach (var overlay in singleDisplaySideOverlays)
|
||||||
@ -588,7 +591,7 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
float offset = 0;
|
float offset = 0;
|
||||||
|
|
||||||
if (settings.State.Value == Visibility.Visible)
|
if (Settings.State.Value == Visibility.Visible)
|
||||||
offset += ToolbarButton.WIDTH / 2;
|
offset += ToolbarButton.WIDTH / 2;
|
||||||
if (notifications.State.Value == Visibility.Visible)
|
if (notifications.State.Value == Visibility.Visible)
|
||||||
offset -= ToolbarButton.WIDTH / 2;
|
offset -= ToolbarButton.WIDTH / 2;
|
||||||
@ -596,7 +599,7 @@ namespace osu.Game
|
|||||||
screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint);
|
screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.State.ValueChanged += _ => updateScreenOffset();
|
Settings.State.ValueChanged += _ => updateScreenOffset();
|
||||||
notifications.State.ValueChanged += _ => updateScreenOffset();
|
notifications.State.ValueChanged += _ => updateScreenOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,7 +744,7 @@ namespace osu.Game
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.ToggleSettings:
|
case GlobalAction.ToggleSettings:
|
||||||
settings.ToggleVisibility();
|
Settings.ToggleVisibility();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.ToggleDirect:
|
case GlobalAction.ToggleDirect:
|
||||||
@ -788,13 +791,13 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected override bool OnExiting()
|
protected override bool OnExiting()
|
||||||
{
|
{
|
||||||
if (screenStack.CurrentScreen is Loader)
|
if (ScreenStack.CurrentScreen is Loader)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (introScreen == null)
|
if (introScreen == null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!introScreen.DidLoadMenu || !(screenStack.CurrentScreen is IntroScreen))
|
if (!introScreen.DidLoadMenu || !(ScreenStack.CurrentScreen is IntroScreen))
|
||||||
{
|
{
|
||||||
Scheduler.Add(introScreen.MakeCurrent);
|
Scheduler.Add(introScreen.MakeCurrent);
|
||||||
return true;
|
return true;
|
||||||
@ -822,7 +825,7 @@ namespace osu.Game
|
|||||||
screenContainer.Padding = new MarginPadding { Top = ToolbarOffset };
|
screenContainer.Padding = new MarginPadding { Top = ToolbarOffset };
|
||||||
overlayContent.Padding = new MarginPadding { Top = ToolbarOffset };
|
overlayContent.Padding = new MarginPadding { Top = ToolbarOffset };
|
||||||
|
|
||||||
MenuCursorContainer.CanShowCursor = (screenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ScreenChanged(IScreen current, IScreen newScreen)
|
protected virtual void ScreenChanged(IScreen current, IScreen newScreen)
|
||||||
@ -848,9 +851,9 @@ namespace osu.Game
|
|||||||
Toolbar.Show();
|
Toolbar.Show();
|
||||||
|
|
||||||
if (newOsuScreen.AllowBackButton)
|
if (newOsuScreen.AllowBackButton)
|
||||||
backButton.Show();
|
BackButton.Show();
|
||||||
else
|
else
|
||||||
backButton.Hide();
|
BackButton.Hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected RulesetConfigCache RulesetConfigCache;
|
protected RulesetConfigCache RulesetConfigCache;
|
||||||
|
|
||||||
protected APIAccess API;
|
protected IAPIProvider API;
|
||||||
|
|
||||||
protected MenuCursorContainer MenuCursorContainer;
|
protected MenuCursorContainer MenuCursorContainer;
|
||||||
|
|
||||||
@ -73,6 +73,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
|
protected Storage Storage { get; set; }
|
||||||
|
|
||||||
private Bindable<WorkingBeatmap> beatmap; // cached via load() method
|
private Bindable<WorkingBeatmap> beatmap; // cached via load() method
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
@ -123,7 +125,7 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
Resources.AddStore(new DllResourceStore(@"osu.Game.Resources.dll"));
|
Resources.AddStore(new DllResourceStore(@"osu.Game.Resources.dll"));
|
||||||
|
|
||||||
dependencies.Cache(contextFactory = new DatabaseContextFactory(Host.Storage));
|
dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage));
|
||||||
|
|
||||||
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
||||||
largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore()));
|
largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore()));
|
||||||
@ -158,21 +160,21 @@ namespace osu.Game
|
|||||||
|
|
||||||
runMigrations();
|
runMigrations();
|
||||||
|
|
||||||
dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio, new NamespacedResourceStore<byte[]>(Resources, "Skins/Legacy")));
|
dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore<byte[]>(Resources, "Skins/Legacy")));
|
||||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||||
|
|
||||||
API = new APIAccess(LocalConfig);
|
if (API == null) API = new APIAccess(LocalConfig);
|
||||||
|
|
||||||
dependencies.CacheAs<IAPIProvider>(API);
|
dependencies.CacheAs(API);
|
||||||
|
|
||||||
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
|
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
|
||||||
|
|
||||||
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
|
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
|
||||||
dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage));
|
dependencies.Cache(FileStore = new FileStore(contextFactory, Storage));
|
||||||
|
|
||||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Host.Storage, API, contextFactory, Host));
|
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host));
|
||||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap));
|
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap));
|
||||||
|
|
||||||
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
|
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
|
||||||
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
|
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
|
||||||
@ -207,7 +209,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
FileStore.Cleanup();
|
FileStore.Cleanup();
|
||||||
|
|
||||||
AddInternal(API);
|
if (API is APIAccess apiAcces)
|
||||||
|
AddInternal(apiAcces);
|
||||||
AddInternal(RulesetConfigCache);
|
AddInternal(RulesetConfigCache);
|
||||||
|
|
||||||
GlobalActionContainer globalBinding;
|
GlobalActionContainer globalBinding;
|
||||||
@ -267,9 +270,13 @@ namespace osu.Game
|
|||||||
|
|
||||||
public override void SetHost(GameHost host)
|
public override void SetHost(GameHost host)
|
||||||
{
|
{
|
||||||
if (LocalConfig == null)
|
|
||||||
LocalConfig = new OsuConfigManager(host.Storage);
|
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
|
|
||||||
|
if (Storage == null)
|
||||||
|
Storage = host.Storage;
|
||||||
|
|
||||||
|
if (LocalConfig == null)
|
||||||
|
LocalConfig = new OsuConfigManager(Storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Overlays
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
beatmap.BindValueChanged(beatmapChanged, true);
|
beatmap.BindValueChanged(beatmapChanged, true);
|
||||||
mods.BindValueChanged(_ => updateAudioAdjustments(), true);
|
mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,12 +213,12 @@ namespace osu.Game.Overlays
|
|||||||
current = beatmap.NewValue;
|
current = beatmap.NewValue;
|
||||||
TrackChanged?.Invoke(current, direction);
|
TrackChanged?.Invoke(current, direction);
|
||||||
|
|
||||||
updateAudioAdjustments();
|
ResetTrackAdjustments();
|
||||||
|
|
||||||
queuedDirection = null;
|
queuedDirection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAudioAdjustments()
|
public void ResetTrackAdjustments()
|
||||||
{
|
{
|
||||||
var track = current?.Track;
|
var track = current?.Track;
|
||||||
if (track == null)
|
if (track == null)
|
||||||
|
@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public JudgementResult Result { get; private set; }
|
public JudgementResult Result { get; private set; }
|
||||||
|
|
||||||
|
private Bindable<int> comboIndexBindable;
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
protected override bool RequiresChildrenUpdate => true;
|
protected override bool RequiresChildrenUpdate => true;
|
||||||
@ -122,6 +124,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (HitObject is IHasComboInformation combo)
|
||||||
|
{
|
||||||
|
comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy();
|
||||||
|
comboIndexBindable.BindValueChanged(_ => updateAccentColour());
|
||||||
|
}
|
||||||
|
|
||||||
updateState(ArmedState.Idle, true);
|
updateState(ArmedState.Idle, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,12 +253,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.SkinChanged(skin, allowFallback);
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
|
||||||
if (HitObject is IHasComboInformation combo)
|
updateAccentColour();
|
||||||
{
|
|
||||||
var comboColours = skin.GetConfig<GlobalSkinConfiguration, List<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value;
|
|
||||||
|
|
||||||
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplySkin(skin, allowFallback);
|
ApplySkin(skin, allowFallback);
|
||||||
|
|
||||||
@ -257,6 +261,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
updateState(State.Value, true);
|
updateState(State.Value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateAccentColour()
|
||||||
|
{
|
||||||
|
if (HitObject is IHasComboInformation combo)
|
||||||
|
{
|
||||||
|
var comboColours = CurrentSkin.GetConfig<GlobalSkinConfiguration, List<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value;
|
||||||
|
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when a change is made to the skin.
|
/// Called when a change is made to the skin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -316,8 +329,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
get => lifetimeStart ?? (HitObject.StartTime - InitialLifetimeOffset);
|
get => lifetimeStart ?? (HitObject.StartTime - InitialLifetimeOffset);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.LifetimeStart = value;
|
|
||||||
lifetimeStart = value;
|
lifetimeStart = value;
|
||||||
|
base.LifetimeStart = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -82,6 +83,15 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
|
|
||||||
CreateNestedHitObjects();
|
CreateNestedHitObjects();
|
||||||
|
|
||||||
|
if (this is IHasComboInformation hasCombo)
|
||||||
|
{
|
||||||
|
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
|
||||||
|
{
|
||||||
|
n.ComboIndexBindable.BindTo(hasCombo.ComboIndexBindable);
|
||||||
|
n.IndexInCurrentComboBindable.BindTo(hasCombo.IndexInCurrentComboBindable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||||
|
|
||||||
foreach (var h in nestedHitObjects)
|
foreach (var h in nestedHitObjects)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Types
|
namespace osu.Game.Rulesets.Objects.Types
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -8,16 +10,22 @@ namespace osu.Game.Rulesets.Objects.Types
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasComboInformation : IHasCombo
|
public interface IHasComboInformation : IHasCombo
|
||||||
{
|
{
|
||||||
|
Bindable<int> IndexInCurrentComboBindable { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The offset of this hitobject in the current combo.
|
/// The offset of this hitobject in the current combo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int IndexInCurrentCombo { get; set; }
|
int IndexInCurrentCombo { get; set; }
|
||||||
|
|
||||||
|
Bindable<int> ComboIndexBindable { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The offset of this combo in relation to the beatmap.
|
/// The offset of this combo in relation to the beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int ComboIndex { get; set; }
|
int ComboIndex { get; set; }
|
||||||
|
|
||||||
|
Bindable<bool> LastInComboBindable { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this is the last object in the current combo.
|
/// Whether this is the last object in the current combo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -131,7 +131,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
if (duration > maxDuration)
|
if (duration > maxDuration)
|
||||||
{
|
{
|
||||||
maxDuration = duration;
|
maxDuration = duration;
|
||||||
baseBeatLength = timingPoints[i].BeatLength;
|
// The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths
|
||||||
|
// the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here
|
||||||
|
baseBeatLength = timingPoints[i].BeatLength / Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
|
|
||||||
if (!initialStateCache.IsValid)
|
if (!initialStateCache.IsValid)
|
||||||
{
|
{
|
||||||
|
foreach (var cached in hitObjectInitialStateCache.Values)
|
||||||
|
cached.Invalidate();
|
||||||
|
|
||||||
switch (direction.Value)
|
switch (direction.Value)
|
||||||
{
|
{
|
||||||
case ScrollingDirection.Up:
|
case ScrollingDirection.Up:
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -45,7 +46,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
text = new OsuSpriteText
|
text = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = "hold for menu",
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft
|
Origin = Anchor.CentreLeft
|
||||||
@ -60,9 +60,23 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
|
private Bindable<int> activationDelay;
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
activationDelay = config.GetBindable<int>(OsuSetting.UIHoldActivationDelay);
|
||||||
|
activationDelay.BindValueChanged(v =>
|
||||||
|
{
|
||||||
|
text.Text = v.NewValue > 0
|
||||||
|
? "hold for menu"
|
||||||
|
: "press for menu";
|
||||||
|
}, true);
|
||||||
|
|
||||||
text.FadeInFromZero(500, Easing.OutQuint).Delay(1500).FadeOut(500, Easing.OutQuint);
|
text.FadeInFromZero(500, Easing.OutQuint).Delay(1500).FadeOut(500, Easing.OutQuint);
|
||||||
|
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,6 +490,7 @@ namespace osu.Game.Screens.Select
|
|||||||
BeatmapDetails.Leaderboard.RefreshScores();
|
BeatmapDetails.Leaderboard.RefreshScores();
|
||||||
|
|
||||||
Beatmap.Value.Track.Looping = true;
|
Beatmap.Value.Track.Looping = true;
|
||||||
|
music?.ResetTrackAdjustments();
|
||||||
|
|
||||||
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
|
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
|
||||||
{
|
{
|
||||||
|
@ -12,13 +12,17 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class SkinReloadableDrawable : CompositeDrawable
|
public abstract class SkinReloadableDrawable : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current skin source.
|
||||||
|
/// </summary>
|
||||||
|
protected ISkinSource CurrentSkin { get; private set; }
|
||||||
|
|
||||||
private readonly Func<ISkinSource, bool> allowFallback;
|
private readonly Func<ISkinSource, bool> allowFallback;
|
||||||
private ISkinSource skin;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
|
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(skin);
|
private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="SkinReloadableDrawable"/>
|
/// Create a new <see cref="SkinReloadableDrawable"/>
|
||||||
@ -32,19 +36,19 @@ namespace osu.Game.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource source)
|
private void load(ISkinSource source)
|
||||||
{
|
{
|
||||||
skin = source;
|
CurrentSkin = source;
|
||||||
skin.SourceChanged += onChange;
|
CurrentSkin.SourceChanged += onChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onChange() =>
|
private void onChange() =>
|
||||||
// schedule required to avoid calls after disposed.
|
// schedule required to avoid calls after disposed.
|
||||||
// note that this has the side-effect of components only performing a skin change when they are alive.
|
// note that this has the side-effect of components only performing a skin change when they are alive.
|
||||||
Scheduler.AddOnce(() => SkinChanged(skin, allowDefaultFallback));
|
Scheduler.AddOnce(() => SkinChanged(CurrentSkin, allowDefaultFallback));
|
||||||
|
|
||||||
protected override void LoadAsyncComplete()
|
protected override void LoadAsyncComplete()
|
||||||
{
|
{
|
||||||
base.LoadAsyncComplete();
|
base.LoadAsyncComplete();
|
||||||
SkinChanged(skin, allowDefaultFallback);
|
SkinChanged(CurrentSkin, allowDefaultFallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -60,8 +64,8 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
if (skin != null)
|
if (CurrentSkin != null)
|
||||||
skin.SourceChanged -= onChange;
|
CurrentSkin.SourceChanged -= onChange;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Humanizer" Version="2.7.2" />
|
<PackageReference Include="Humanizer" Version="2.7.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||||
|
@ -113,7 +113,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Humanizer" Version="2.7.2" />
|
<PackageReference Include="Humanizer" Version="2.7.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||||
|
Loading…
Reference in New Issue
Block a user