1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-21 20:12:57 +08:00

Merge branch 'master' into url-parsing-support

This commit is contained in:
Dan Balasescu 2018-02-01 16:13:32 +09:00 committed by GitHub
commit d332a2dddd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 281 additions and 77 deletions

@ -1 +1 @@
Subproject commit 2610a3133721b0bc4af852342aa2a179d0e66497 Subproject commit 90bf49a2df3dbad5994d922df63e4891c622dbc3

@ -1 +1 @@
Subproject commit 266965f0d795b94a126e2da302bd2c10eadd642a Subproject commit 92ec3d10b12c5e9bfc1d3b05d3db174a506efd6d

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Judgements
{
public class OsuSliderTailJudgement : OsuJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0;
}
}

View File

@ -18,14 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
{ {
private readonly Slider slider; private readonly Slider slider;
public readonly DrawableHitCircle InitialCircle;
private readonly List<Drawable> components = new List<Drawable>(); private readonly List<Drawable> components = new List<Drawable>();
private readonly Container<DrawableSliderTick> ticks; public readonly DrawableHitCircle HeadCircle;
private readonly Container<DrawableRepeatPoint> repeatPoints;
public readonly SliderBody Body; public readonly SliderBody Body;
public readonly SliderBall Ball; public readonly SliderBall Ball;
@ -34,6 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
slider = s; slider = s;
DrawableSliderTail tail;
Container<DrawableSliderTick> ticks;
Container<DrawableRepeatPoint> repeatPoints;
Children = new Drawable[] Children = new Drawable[]
{ {
Body = new SliderBody(s) Body = new SliderBody(s)
@ -51,27 +50,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true, AlwaysPresent = true,
Alpha = 0 Alpha = 0
}, },
InitialCircle = new DrawableHitCircle(new HitCircle HeadCircle = new DrawableHitCircle(s.HeadCircle),
{ tail = new DrawableSliderTail(s.TailCircle)
StartTime = s.StartTime,
Position = s.StackedPosition,
IndexInCurrentCombo = s.IndexInCurrentCombo,
Scale = s.Scale,
ComboColour = s.ComboColour,
Samples = s.Samples,
SampleControlPoint = s.SampleControlPoint,
TimePreempt = s.TimePreempt,
TimeFadein = s.TimeFadein,
HitWindow300 = s.HitWindow300,
HitWindow100 = s.HitWindow100,
HitWindow50 = s.HitWindow50
})
}; };
components.Add(Body); components.Add(Body);
components.Add(Ball); components.Add(Ball);
AddNested(InitialCircle); AddNested(HeadCircle);
AddNested(tail);
components.Add(tail);
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>()) foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{ {
@ -87,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}; };
ticks.Add(drawableTick); ticks.Add(drawableTick);
components.Add(drawableTick);
AddNested(drawableTick); AddNested(drawableTick);
} }
@ -121,27 +111,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
currentSpan = span; currentSpan = span;
//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 (!HeadCircle.IsHit)
InitialCircle.Position = slider.Curve.PositionAt(progress); HeadCircle.Position = slider.Curve.PositionAt(progress);
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(progress, span); foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(progress, span);
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in ticks.Children) t.Tracking = Ball.Tracking; foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{ {
if (!userTriggered && Time.Current >= slider.EndTime) if (!userTriggered && Time.Current >= slider.EndTime)
{ {
var judgementsCount = ticks.Children.Count + repeatPoints.Children.Count + 1; var judgementsCount = NestedHitObjects.Count;
var judgementsHit = ticks.Children.Count(t => t.Judgements.Any(j => j.IsHit)) + repeatPoints.Children.Count(t => t.Judgements.Any(j => j.IsHit)); var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
if (InitialCircle.Judgements.Any(j => j.IsHit))
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 && HeadCircle.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 && HeadCircle.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 });
@ -173,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
public Drawable ProxiedLayer => InitialCircle.ApproachCircle; public Drawable ProxiedLayer => HeadCircle.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

@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking
{
/// <summary>
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
/// </summary>
public override bool DisplayJudgement => false;
public bool Tracking { get; set; }
public DrawableSliderTail(HitCircle hitCircle)
: base(hitCircle)
{
AlwaysPresent = true;
RelativeSizeAxes = Axes.Both;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (!userTriggered && timeOffset >= 0)
AddJudgement(new OsuSliderTailJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss });
}
}
}

View File

@ -12,14 +12,14 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableSliderTick : DrawableOsuHitObject public class DrawableSliderTick : DrawableOsuHitObject, IRequireTracking
{ {
private readonly SliderTick sliderTick; private readonly SliderTick sliderTick;
public double FadeInTime; public double FadeInTime;
public double FadeOutTime; public double FadeOutTime;
public bool Tracking; public bool Tracking { get; set; }
public override bool DisplayJudgement => false; public override bool DisplayJudgement => false;

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public interface IRequireTracking
{
/// <summary>
/// Whether the <see cref="DrawableSlider"/> is currently being tracked by the user.
/// </summary>
bool Tracking { get; set; }
}
}

View File

@ -80,6 +80,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public double Velocity; public double Velocity;
public double TickDistance; public double TickDistance;
public HitCircle HeadCircle;
public HitCircle TailCircle;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@ -97,10 +100,37 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects();
createSliderEnds();
createTicks(); createTicks();
createRepeatPoints(); createRepeatPoints();
} }
private void createSliderEnds()
{
HeadCircle = new HitCircle
{
StartTime = StartTime,
Position = StackedPosition,
IndexInCurrentCombo = IndexInCurrentCombo,
ComboColour = ComboColour,
Samples = Samples,
SampleControlPoint = SampleControlPoint
};
TailCircle = new HitCircle
{
StartTime = EndTime,
Position = StackedEndPosition,
IndexInCurrentCombo = IndexInCurrentCombo,
ComboColour = ComboColour,
Samples = Samples,
SampleControlPoint = SampleControlPoint
};
AddNested(HeadCircle);
AddNested(TailCircle);
}
private void createTicks() private void createTicks()
{ {
if (TickDistance == 0) return; if (TickDistance == 0) return;

View File

@ -16,6 +16,9 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using System.Linq; using System.Linq;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
@ -142,7 +145,34 @@ namespace osu.Game.Rulesets.Osu.Tests
foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>()) foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable }); mod.ApplyToDrawableHitObjects(new[] { drawable });
drawable.OnJudgement += onJudgement;
Add(drawable); Add(drawable);
} }
private float judgementOffsetDirection = 1;
private void onJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
var osuObject = judgedObject as DrawableOsuHitObject;
if (osuObject == null)
return;
OsuSpriteText text;
Add(text = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = judgement.IsHit ? "Hit!" : "Miss!",
Colour = judgement.IsHit ? Color4.Green : Color4.Red,
TextSize = 30,
Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45)
});
text.Delay(150)
.Then().FadeOut(200)
.Then().Expire();
judgementOffsetDirection *= -1;
}
} }
} }

View File

@ -55,6 +55,7 @@
<Compile Include="Edit\OsuEditPlayfield.cs" /> <Compile Include="Edit\OsuEditPlayfield.cs" />
<Compile Include="Edit\OsuEditRulesetContainer.cs" /> <Compile Include="Edit\OsuEditRulesetContainer.cs" />
<Compile Include="Edit\OsuHitObjectComposer.cs" /> <Compile Include="Edit\OsuHitObjectComposer.cs" />
<Compile Include="Judgements\OsuSliderTailJudgement.cs" />
<Compile Include="Mods\OsuModAutopilot.cs" /> <Compile Include="Mods\OsuModAutopilot.cs" />
<Compile Include="Mods\OsuModAutoplay.cs" /> <Compile Include="Mods\OsuModAutoplay.cs" />
<Compile Include="Mods\OsuModDaycore.cs" /> <Compile Include="Mods\OsuModDaycore.cs" />
@ -75,6 +76,8 @@
<Compile Include="Objects\Drawables\Connections\FollowPointRenderer.cs" /> <Compile Include="Objects\Drawables\Connections\FollowPointRenderer.cs" />
<Compile Include="Judgements\OsuJudgement.cs" /> <Compile Include="Judgements\OsuJudgement.cs" />
<Compile Include="Objects\Drawables\DrawableRepeatPoint.cs" /> <Compile Include="Objects\Drawables\DrawableRepeatPoint.cs" />
<Compile Include="Objects\Drawables\DrawableSliderTail.cs" />
<Compile Include="Objects\Drawables\IRequireTracking.cs" />
<Compile Include="Objects\Drawables\ITrackSnaking.cs" /> <Compile Include="Objects\Drawables\ITrackSnaking.cs" />
<Compile Include="Objects\Drawables\Pieces\ApproachCircle.cs" /> <Compile Include="Objects\Drawables\Pieces\ApproachCircle.cs" />
<Compile Include="Objects\Drawables\Pieces\SpinnerBackground.cs" /> <Compile Include="Objects\Drawables\Pieces\SpinnerBackground.cs" />

View File

@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Taiko.Replays
{ {
bool hitButton = true; bool hitButton = true;
Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(-100000, ReplayButtonState.None));
Frames.Add(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, ReplayButtonState.None));
for (int i = 0; i < Beatmap.HitObjects.Count; i++) for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{ {
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
break; break;
} }
Frames.Add(new ReplayFrame(j, null, null, button)); Frames.Add(new TaikoReplayFrame(j, button));
d = (d + 1) % 4; d = (d + 1) % 4;
if (++count == req) if (++count == req)
break; break;
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
{ {
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>()) foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{ {
Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2)); Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2));
hitButton = !hitButton; hitButton = !hitButton;
} }
} }
@ -107,18 +107,18 @@ namespace osu.Game.Rulesets.Taiko.Replays
button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2; button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2;
} }
Frames.Add(new ReplayFrame(h.StartTime, null, null, button)); Frames.Add(new TaikoReplayFrame(h.StartTime, button));
} }
else else
throw new InvalidOperationException("Unknown hit object type."); throw new InvalidOperationException("Unknown hit object type.");
Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY, ReplayButtonState.None));
if (i < Beatmap.HitObjects.Count - 1) if (i < Beatmap.HitObjects.Count - 1)
{ {
double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000; double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
if (waitTime > endTime) if (waitTime > endTime)
Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(waitTime, ReplayButtonState.None));
} }
hitButton = !hitButton; hitButton = !hitButton;

View File

@ -0,0 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Taiko.Replays
{
public class TaikoReplayFrame : ReplayFrame
{
public override bool IsImportant => MouseLeft || MouseRight;
public TaikoReplayFrame(double time, ReplayButtonState buttons)
: base(time, null, null, buttons)
{
}
}
}

View File

@ -95,6 +95,7 @@
<Compile Include="Replays\TaikoAutoGenerator.cs" /> <Compile Include="Replays\TaikoAutoGenerator.cs" />
<Compile Include="Objects\TaikoHitObject.cs" /> <Compile Include="Objects\TaikoHitObject.cs" />
<Compile Include="Objects\TaikoHitObjectDifficulty.cs" /> <Compile Include="Objects\TaikoHitObjectDifficulty.cs" />
<Compile Include="Replays\TaikoReplayFrame.cs" />
<Compile Include="TaikoDifficultyCalculator.cs" /> <Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scoring\TaikoScoreProcessor.cs" /> <Compile Include="Scoring\TaikoScoreProcessor.cs" />

View File

@ -8,6 +8,8 @@ using OpenTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -35,9 +37,9 @@ namespace osu.Game.Tests.Visual
new SelectionLayer(playfield) new SelectionLayer(playfield)
}; };
playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f })); var hitCircle1 = new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f };
playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f })); var hitCircle2 = new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f };
playfield.Add(new DrawableSlider(new Slider var slider = new Slider
{ {
ControlPoints = new List<Vector2> ControlPoints = new List<Vector2>
{ {
@ -48,8 +50,16 @@ namespace osu.Game.Tests.Visual
Position = new Vector2(128, 256), Position = new Vector2(128, 256),
Velocity = 1, Velocity = 1,
TickDistance = 100, TickDistance = 100,
Scale = 0.5f Scale = 0.5f,
})); };
hitCircle1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
hitCircle2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
playfield.Add(new DrawableHitCircle(hitCircle1));
playfield.Add(new DrawableHitCircle(hitCircle2));
playfield.Add(new DrawableSlider(slider));
} }
} }
} }

View File

@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
public class IconButton : OsuClickableContainer public class IconButton : OsuClickableContainer
{ {
private const float button_size = 30; public const float BUTTON_SIZE = 30;
private Color4? flashColour; private Color4? flashColour;
/// <summary> /// <summary>
@ -106,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Size = new Vector2(button_size), Size = new Vector2(BUTTON_SIZE),
CornerRadius = 5, CornerRadius = 5,
Masking = true, Masking = true,
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters

View File

@ -7,6 +7,7 @@ using osu.Framework.Threading;
using OpenTK; using OpenTK;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume namespace osu.Game.Graphics.UserInterface.Volume
@ -14,6 +15,7 @@ namespace osu.Game.Graphics.UserInterface.Volume
public class VolumeControl : OverlayContainer public class VolumeControl : OverlayContainer
{ {
private readonly VolumeMeter volumeMeterMaster; private readonly VolumeMeter volumeMeterMaster;
private readonly IconButton muteIcon;
protected override bool BlockPassThroughMouse => false; protected override bool BlockPassThroughMouse => false;
@ -34,6 +36,17 @@ namespace osu.Game.Graphics.UserInterface.Volume
Spacing = new Vector2(15, 0), Spacing = new Vector2(15, 0),
Children = new Drawable[] Children = new Drawable[]
{ {
new Container
{
Size = new Vector2(IconButton.BUTTON_SIZE),
Child = muteIcon = new IconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_volume_up,
Action = () => Adjust(GlobalAction.ToggleMute),
}
},
volumeMeterMaster = new VolumeMeter("Master"), volumeMeterMaster = new VolumeMeter("Master"),
volumeMeterEffect = new VolumeMeter("Effects"), volumeMeterEffect = new VolumeMeter("Effects"),
volumeMeterMusic = new VolumeMeter("Music") volumeMeterMusic = new VolumeMeter("Music")
@ -46,18 +59,10 @@ namespace osu.Game.Graphics.UserInterface.Volume
{ {
base.LoadComplete(); base.LoadComplete();
volumeMeterMaster.Bindable.ValueChanged += volumeChanged; volumeMeterMaster.Bindable.ValueChanged += _ => settingChanged();
volumeMeterEffect.Bindable.ValueChanged += volumeChanged; volumeMeterEffect.Bindable.ValueChanged += _ => settingChanged();
volumeMeterMusic.Bindable.ValueChanged += volumeChanged; volumeMeterMusic.Bindable.ValueChanged += _ => settingChanged();
} muted.ValueChanged += _ => settingChanged();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
volumeMeterMaster.Bindable.ValueChanged -= volumeChanged;
volumeMeterEffect.Bindable.ValueChanged -= volumeChanged;
volumeMeterMusic.Bindable.ValueChanged -= volumeChanged;
} }
public bool Adjust(GlobalAction action) public bool Adjust(GlobalAction action)
@ -76,23 +81,45 @@ namespace osu.Game.Graphics.UserInterface.Volume
else else
volumeMeterMaster.Increase(); volumeMeterMaster.Increase();
return true; return true;
case GlobalAction.ToggleMute:
Show();
muted.Toggle();
return true;
} }
return false; return false;
} }
private void volumeChanged(double newVolume) private void settingChanged()
{ {
Show(); Show();
schedulePopOut(); schedulePopOut();
} }
private readonly BindableDouble muteAdjustment = new BindableDouble();
private readonly BindableBool muted = new BindableBool();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
{ {
volumeMeterMaster.Bindable.BindTo(audio.Volume); volumeMeterMaster.Bindable.BindTo(audio.Volume);
volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterEffect.Bindable.BindTo(audio.VolumeSample);
volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack);
muted.ValueChanged += mute =>
{
if (mute)
{
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
muteIcon.Icon = FontAwesome.fa_volume_off;
}
else
{
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
muteIcon.Icon = FontAwesome.fa_volume_up;
}
};
} }
private ScheduledDelegate popOutDelegate; private ScheduledDelegate popOutDelegate;

View File

@ -70,11 +70,8 @@ namespace osu.Game.Graphics.UserInterface.Volume
public double Volume public double Volume
{ {
get { return Bindable.Value; } get => Bindable.Value;
private set private set => Bindable.Value = value;
{
Bindable.Value = value;
}
} }
public void Increase() public void Increase()

View File

@ -29,10 +29,11 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
new KeyBinding(new[] { InputKey.Up }, GlobalAction.IncreaseVolume), new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume),
new KeyBinding(new[] { InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume), new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume),
new KeyBinding(new[] { InputKey.Down }, GlobalAction.DecreaseVolume), new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
new KeyBinding(new[] { InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
}; };
public IEnumerable<KeyBinding> InGameKeyBindings => new[] public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@ -63,6 +64,8 @@ namespace osu.Game.Input.Bindings
IncreaseVolume, IncreaseVolume,
[Description("Decrease Volume")] [Description("Decrease Volume")]
DecreaseVolume, DecreaseVolume,
[Description("Toggle mute")]
ToggleMute,
// In-Game Keybindings // In-Game Keybindings
[Description("Skip Cutscene")] [Description("Skip Cutscene")]

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Infrastructure;
using osu.Game.Database;
using osu.Game.Input.Bindings;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20180131154205_AddMuteBinding")]
public partial class AddMuteBinding : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}");
migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}");
}
}
}

View File

@ -240,13 +240,13 @@ namespace osu.Game.Rulesets.UI
foreach (var mod in Mods.OfType<IApplicableToDifficulty>()) foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty); mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
// Post-process the beatmap
processor.PostProcess(Beatmap);
// Apply defaults // Apply defaults
foreach (var h in Beatmap.HitObjects) foreach (var h in Beatmap.HitObjects)
h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
// Post-process the beatmap
processor.PostProcess(Beatmap);
KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager = CreateInputManager();
KeyBindingInputManager.RelativeSizeAxes = Axes.Both; KeyBindingInputManager.RelativeSizeAxes = Axes.Both;

View File

@ -136,9 +136,20 @@ namespace osu.Game.Rulesets.UI
int loops = 0; int loops = 0;
while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame)
{
if (!base.UpdateSubTree()) if (!base.UpdateSubTree())
return false; return false;
if (isAttached)
{
// When handling replay input, we need to consider the possibility of fast-forwarding, which may cause the clock to be updated
// to a point very far into the future, then playing a frame at that time. In such a case, lifetime MUST be updated before
// input is handled. This is why base.Update is not called from the derived Update when handling replay input, and is instead
// called manually at the correct time here.
base.Update();
}
}
return true; return true;
} }
@ -173,8 +184,11 @@ namespace osu.Game.Rulesets.UI
// to ensure that the its time is valid for our children before input is processed // to ensure that the its time is valid for our children before input is processed
Clock.ProcessFrame(); Clock.ProcessFrame();
// Process input if (!isAttached)
base.Update(); {
// For non-replay input handling, this provides equivalent input ordering as if Update was not overridden
base.Update();
}
} }
#endregion #endregion

View File

@ -282,6 +282,7 @@
<Compile Include="Migrations\20180125143340_Settings.Designer.cs"> <Compile Include="Migrations\20180125143340_Settings.Designer.cs">
<DependentUpon>20180125143340_Settings.cs</DependentUpon> <DependentUpon>20180125143340_Settings.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Migrations\20180131154205_AddMuteBinding.cs" />
<Compile Include="Overlays\Profile\SupporterIcon.cs" /> <Compile Include="Overlays\Profile\SupporterIcon.cs" />
<Compile Include="Online\API\Requests\GetFriendsRequest.cs" /> <Compile Include="Online\API\Requests\GetFriendsRequest.cs" />
<Compile Include="Overlays\Settings\DangerousSettingsButton.cs" /> <Compile Include="Overlays\Settings\DangerousSettingsButton.cs" />