1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 20:32:55 +08:00

Merge branch 'master' into perf-calculator-remove-working-beatmap

This commit is contained in:
Dean Herbert 2020-10-07 17:43:17 +09:00
commit f1a3b6d0ba
152 changed files with 2730 additions and 703 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1001.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1004.0" />
</ItemGroup>
</Project>

View File

@ -9,7 +9,7 @@ using osu.Framework.Android;
namespace osu.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
public class OsuGameActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuGameAndroid();

View File

@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (!result.Type.AffectsCombo() || !result.HasResult)
return;
if (result.Type == HitResult.Miss)
if (!result.IsHit)
{
updateCombo(0, null);
return;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneHoldNote : ManiaHitObjectTestScene
{
public TestSceneHoldNote()
[Test]
public void TestHoldNote()
{
AddToggleStep("toggle hitting", v =>
{

View File

@ -28,8 +28,15 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestFixture]
public class TestSceneNotes : OsuTestScene
{
[BackgroundDependencyLoader]
private void load()
[Test]
public void TestVariousNotes()
{
DrawableNote note1 = null;
DrawableNote note2 = null;
DrawableHoldNote holdNote1 = null;
DrawableHoldNote holdNote2 = null;
AddStep("create notes", () =>
{
Child = new FillFlowContainer
{
@ -41,12 +48,13 @@ namespace osu.Game.Rulesets.Mania.Tests
Spacing = new Vector2(20),
Children = new[]
{
createNoteDisplay(ScrollingDirection.Down, 1, out var note1),
createNoteDisplay(ScrollingDirection.Up, 2, out var note2),
createHoldNoteDisplay(ScrollingDirection.Down, 1, out var holdNote1),
createHoldNoteDisplay(ScrollingDirection.Up, 2, out var holdNote2),
createNoteDisplay(ScrollingDirection.Down, 1, out note1),
createNoteDisplay(ScrollingDirection.Up, 2, out note2),
createHoldNoteDisplay(ScrollingDirection.Down, 1, out holdNote1),
createHoldNoteDisplay(ScrollingDirection.Up, 2, out holdNote2),
}
};
});
AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2));
AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0));

View File

@ -2,10 +2,40 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements
{
public class ManiaJudgement : Judgement
{
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
case HitResult.LargeTickHit:
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
case HitResult.LargeTickMiss:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.1;
case HitResult.Meh:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
case HitResult.Ok:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.3;
case HitResult.Good:
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
case HitResult.Great:
return DEFAULT_MAX_HEALTH_INCREASE * 0.8;
case HitResult.Perfect:
return DEFAULT_MAX_HEALTH_INCREASE;
default:
return base.HealthIncreaseFor(result);
}
}
}
}

View File

@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Mania
new SettingsEnumDropdown<ManiaScrollingDirection>
{
LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
},
new SettingsSlider<double, TimeSlider>
{
LabelText = "Scroll speed",
Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
KeyboardStep = 5
},
};

View File

@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
endHold();
}
if (Tail.Result.Type == HitResult.Miss)
if (Tail.Judged && !Tail.IsHit)
HasBroken = true;
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss);
ApplyResult(r => r.Type = r.Judgement.MinResult);
return;
}

View File

@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@ -136,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// <summary>
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
/// </summary>
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult);
}
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject

View File

@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss);
ApplyResult(r => r.Type = r.Judgement.MinResult);
return;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
private int depthIndex;
public TestSceneHitCircle()
[Test]
public void TestVariousHitCircles()
{
AddStep("Miss Big Single", () => SetContents(() => testSingle(2)));
AddStep("Miss Medium Single", () => SetContents(() => testSingle(5)));

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
},
PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss
PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit
});
}
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
Autoplay = false,
Beatmap = beatmap,
PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss
PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit
});
}

View File

@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
private int depthIndex;
public TestSceneSlider()
[Test]
public void TestVariousSliders()
{
AddStep("Big Single", () => SetContents(() => testSimpleBig()));
AddStep("Medium Single", () => SetContents(() => testSimpleMedium()));

View File

@ -314,11 +314,11 @@ namespace osu.Game.Rulesets.Osu.Tests
private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult);
private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss;
private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.SmallTickHit && !judgementResults.First().IsHit;
private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit;
private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.SmallTickHit;
private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.IgnoreMiss;
private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.SmallTickMiss;
private ScoreAccessibleReplayPlayer currentPlayer;

View File

@ -1,7 +1,13 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Utils;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@ -10,40 +16,219 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuSelectionHandler : SelectionHandler
{
public override bool HandleMovement(MoveSelectionEvent moveEvent)
protected override void OnSelectionChanged()
{
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
base.OnSelectionChanged();
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
{
if (h is Spinner)
{
// Spinners don't support position adjustments
continue;
bool canOperate = SelectedHitObjects.Count() > 1 || SelectedHitObjects.Any(s => s is Slider);
SelectionBox.CanRotate = canOperate;
SelectionBox.CanScaleX = canOperate;
SelectionBox.CanScaleY = canOperate;
}
// Stacking is not considered
minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta));
maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta));
protected override void OnOperationEnded()
{
base.OnOperationEnded();
referenceOrigin = null;
}
if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight)
return false;
public override bool HandleMovement(MoveSelectionEvent moveEvent) =>
moveSelection(moveEvent.InstantDelta);
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
/// <summary>
/// During a transform, the initial origin is stored so it can be used throughout the operation.
/// </summary>
private Vector2? referenceOrigin;
public override bool HandleFlip(Direction direction)
{
if (h is Spinner)
var hitObjects = selectedMovableObjects;
var selectedObjectsQuad = getSurroundingQuad(hitObjects);
var centre = selectedObjectsQuad.Centre;
foreach (var h in hitObjects)
{
// Spinners don't support position adjustments
continue;
var pos = h.Position;
switch (direction)
{
case Direction.Horizontal:
pos.X = centre.X - (pos.X - centre.X);
break;
case Direction.Vertical:
pos.Y = centre.Y - (pos.Y - centre.Y);
break;
}
h.Position += moveEvent.InstantDelta;
h.Position = pos;
if (h is Slider slider)
{
foreach (var point in slider.Path.ControlPoints)
{
point.Position.Value = new Vector2(
(direction == Direction.Horizontal ? -1 : 1) * point.Position.Value.X,
(direction == Direction.Vertical ? -1 : 1) * point.Position.Value.Y
);
}
}
}
return true;
}
public override bool HandleScale(Vector2 scale, Anchor reference)
{
adjustScaleFromAnchor(ref scale, reference);
var hitObjects = selectedMovableObjects;
// for the time being, allow resizing of slider paths only if the slider is
// the only hit object selected. with a group selection, it's likely the user
// is not looking to change the duration of the slider but expand the whole pattern.
if (hitObjects.Length == 1 && hitObjects.First() is Slider slider)
{
Quad quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / quad.Width, 1 + scale.Y / quad.Height);
foreach (var point in slider.Path.ControlPoints)
point.Position.Value *= pathRelativeDeltaScale;
}
else
{
// move the selection before scaling if dragging from top or left anchors.
if ((reference & Anchor.x0) > 0 && !moveSelection(new Vector2(-scale.X, 0))) return false;
if ((reference & Anchor.y0) > 0 && !moveSelection(new Vector2(0, -scale.Y))) return false;
Quad quad = getSurroundingQuad(hitObjects);
foreach (var h in hitObjects)
{
h.Position = new Vector2(
quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X),
quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y)
);
}
}
return true;
}
private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference)
{
// cancel out scale in axes we don't care about (based on which drag handle was used).
if ((reference & Anchor.x1) > 0) scale.X = 0;
if ((reference & Anchor.y1) > 0) scale.Y = 0;
// reverse the scale direction if dragging from top or left.
if ((reference & Anchor.x0) > 0) scale.X = -scale.X;
if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y;
}
public override bool HandleRotation(float delta)
{
var hitObjects = selectedMovableObjects;
Quad quad = getSurroundingQuad(hitObjects);
referenceOrigin ??= quad.Centre;
foreach (var h in hitObjects)
{
h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta);
if (h is IHasPath path)
{
foreach (var point in path.Path.ControlPoints)
point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta);
}
}
// this isn't always the case but let's be lenient for now.
return true;
}
private bool moveSelection(Vector2 delta)
{
var hitObjects = selectedMovableObjects;
Quad quad = getSurroundingQuad(hitObjects);
if (quad.TopLeft.X + delta.X < 0 ||
quad.TopLeft.Y + delta.Y < 0 ||
quad.BottomRight.X + delta.X > DrawWidth ||
quad.BottomRight.Y + delta.Y > DrawHeight)
return false;
foreach (var h in hitObjects)
h.Position += delta;
return true;
}
/// <summary>
/// Returns a gamefield-space quad surrounding the provided hit objects.
/// </summary>
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
getSurroundingQuad(hitObjects.SelectMany(h => new[] { h.Position, h.EndPosition }));
/// <summary>
/// Returns a gamefield-space quad surrounding the provided points.
/// </summary>
/// <param name="points">The points to calculate a quad for.</param>
private Quad getSurroundingQuad(IEnumerable<Vector2> points)
{
if (!SelectedHitObjects.Any())
return new Quad();
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
foreach (var p in points)
{
minPosition = Vector2.ComponentMin(minPosition, p);
maxPosition = Vector2.ComponentMax(maxPosition, p);
}
Vector2 size = maxPosition - minPosition;
return new Quad(minPosition.X, minPosition.Y, size.X, size.Y);
}
/// <summary>
/// All osu! hitobjects which can be moved/rotated/scaled.
/// </summary>
private OsuHitObject[] selectedMovableObjects => SelectedHitObjects
.OfType<OsuHitObject>()
.Where(h => !(h is Spinner))
.ToArray();
/// <summary>
/// Rotate a point around an arbitrary origin.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="origin">The centre origin to rotate around.</param>
/// <param name="angle">The angle to rotate (in degrees).</param>
private static Vector2 rotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle)
{
angle = -angle;
point.X -= origin.X;
point.Y -= origin.Y;
Vector2 ret;
ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle));
ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle));
ret.X += origin.X;
ret.Y += origin.Y;
return ret;
}
}
}

View File

@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Mods
base.ApplyToDrawableHitObjects(drawables);
}
private double lastSliderHeadFadeOutStartTime;
private double lastSliderHeadFadeOutDuration;
protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
{
if (!(drawable is DrawableOsuHitObject d))
@ -54,7 +57,35 @@ namespace osu.Game.Rulesets.Osu.Mods
switch (drawable)
{
case DrawableSliderTail sliderTail:
// use stored values from head circle to achieve same fade sequence.
fadeOutDuration = lastSliderHeadFadeOutDuration;
fadeOutStartTime = lastSliderHeadFadeOutStartTime;
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
sliderTail.FadeOut(fadeOutDuration);
break;
case DrawableSliderRepeat sliderRepeat:
// use stored values from head circle to achieve same fade sequence.
fadeOutDuration = lastSliderHeadFadeOutDuration;
fadeOutStartTime = lastSliderHeadFadeOutStartTime;
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
// only apply to circle piece reverse arrow is not affected by hidden.
sliderRepeat.CirclePiece.FadeOut(fadeOutDuration);
break;
case DrawableHitCircle circle:
if (circle is DrawableSliderHead)
{
lastSliderHeadFadeOutDuration = fadeOutDuration;
lastSliderHeadFadeOutStartTime = fadeOutStartTime;
}
// we don't want to see the approach circle
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
circle.ApproachCircle.Hide();

View File

@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss);
ApplyResult(r => r.Type = r.Judgement.MinResult);
return;
}
@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var circleResult = (OsuHitCircleJudgementResult)r;
// Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss.
if (result != HitResult.Miss)
if (result.IsHit())
{
var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position);
circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2);

View File

@ -8,7 +8,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// <summary>
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
/// </summary>
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult);
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
}

View File

@ -8,7 +8,6 @@ using osu.Game.Configuration;
using osuTK;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK.Graphics;
@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (JudgedObject != null)
{
lightingColour = JudgedObject.AccentColour.GetBoundCopy();
lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true);
lightingColour.BindValueChanged(colour => Lighting.Colour = Result.IsHit ? colour.NewValue : Color4.Transparent, true);
}
else
{

View File

@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@ -52,6 +51,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
@ -63,7 +63,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Alpha = 0
},
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
};
}
@ -250,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
// rather than doing it this way, we should probably attach the sample to the tail circle.
// this can only be done after we stop using LegacyLastTick.
if (TailCircle.Result.Type != HitResult.Miss)
if (TailCircle.IsHit)
base.PlaySamples();
}

View File

@ -6,9 +6,11 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -22,6 +24,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly Drawable scaleContainer;
public readonly Drawable CirclePiece;
public override bool DisplayResult => false;
public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider)
@ -34,7 +38,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre;
InternalChild = scaleContainer = new ReverseArrowPiece();
InternalChild = scaleContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
// no default for this; only visible in legacy skins.
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()),
arrow = new ReverseArrowPiece(),
}
};
}
private readonly IBindable<float> scaleBindable = new BindableFloat();
@ -85,6 +100,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private bool hasRotation;
private readonly ReverseArrowPiece arrow;
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
// When the repeat is hit, the arrow should fade out on spot rather than following the slider
@ -114,18 +131,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
while (Math.Abs(aimRotation - Rotation) > 180)
aimRotation += aimRotation < Rotation ? 360 : -360;
while (Math.Abs(aimRotation - arrow.Rotation) > 180)
aimRotation += aimRotation < arrow.Rotation ? 360 : -360;
if (!hasRotation)
{
Rotation = aimRotation;
arrow.Rotation = aimRotation;
hasRotation = true;
}
else
{
// If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly).
Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint);
arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint);
}
}
}

View File

@ -1,15 +1,20 @@
// 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.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking
{
private readonly Slider slider;
private readonly SliderTailCircle tailCircle;
/// <summary>
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
@ -18,28 +23,73 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public bool Tracking { get; set; }
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new BindableFloat();
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
: base(hitCircle)
private readonly SkinnableDrawable circlePiece;
private readonly Container scaleContainer;
public DrawableSliderTail(Slider slider, SliderTailCircle tailCircle)
: base(tailCircle)
{
this.slider = slider;
this.tailCircle = tailCircle;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
AlwaysPresent = true;
InternalChildren = new Drawable[]
{
scaleContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new Drawable[]
{
// no default for this; only visible in legacy skins.
circlePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty())
}
},
};
}
positionBindable.BindTo(hitCircle.PositionBindable);
pathVersion.BindTo(slider.Path.Version);
[BackgroundDependencyLoader]
private void load()
{
scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
scaleBindable.BindTo(HitObject.ScaleBindable);
}
positionBindable.BindValueChanged(_ => updatePosition());
pathVersion.BindValueChanged(_ => updatePosition(), true);
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
// TODO: This has no drawable content. Support for skins should be added.
circlePiece.FadeInFromZero(HitObject.TimeFadeIn);
}
protected override void UpdateStateTransforms(ArmedState state)
{
base.UpdateStateTransforms(state);
Debug.Assert(HitObject.HitWindows != null);
switch (state)
{
case ArmedState.Idle:
this.Delay(HitObject.TimePreempt).FadeOut(500);
Expire(true);
break;
case ArmedState.Miss:
this.FadeOut(100);
break;
case ArmedState.Hit:
// todo: temporary / arbitrary
this.Delay(800).FadeOut();
break;
}
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
@ -48,6 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
private void updatePosition() => Position = HitObject.Position - slider.Position;
public void UpdateSnakingPosition(Vector2 start, Vector2 end) =>
Position = tailCircle.RepeatIndex % 2 == 0 ? end : start;
}
}

View File

@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return;
// Trigger a miss result for remaining ticks to avoid infinite gameplay.
foreach (var tick in ticks.Where(t => !t.IsHit))
foreach (var tick in ticks.Where(t => !t.Result.HasResult))
tick.TriggerResult(false);
ApplyResult(r =>
@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
else if (Progress > .75)
r.Type = HitResult.Meh;
else if (Time.Current >= Spinner.EndTime)
r.Type = HitResult.Miss;
r.Type = r.Judgement.MinResult;
});
}
@ -268,7 +268,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
while (wholeSpins != spins)
{
var tick = ticks.FirstOrDefault(t => !t.IsHit);
var tick = ticks.FirstOrDefault(t => !t.Result.HasResult);
// tick may be null if we've hit the spin limit.
if (tick != null)

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Origin = Anchor.Centre;
Masking = true;
BorderThickness = 10;
BorderThickness = 9; // roughly matches slider borders and makes stacked circles distinctly visible from each other.
BorderColour = Color4.White;
Child = new Box

View File

@ -176,6 +176,7 @@ namespace osu.Game.Rulesets.Osu.Objects
// if this is to change, we should revisit this.
AddNested(TailCircle = new SliderTailCircle(this)
{
RepeatIndex = e.SpanIndex,
StartTime = e.Time,
Position = EndPosition,
StackHeight = StackHeight
@ -183,10 +184,9 @@ namespace osu.Game.Rulesets.Osu.Objects
break;
case SliderEventType.Repeat:
AddNested(new SliderRepeat
AddNested(new SliderRepeat(this)
{
RepeatIndex = e.SpanIndex,
SpanDuration = SpanDuration,
StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration,
Position = Position + Path.PositionAt(e.PathProgress),
StackHeight = StackHeight,

View File

@ -1,9 +0,0 @@
// 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.
namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderCircle : HitCircle
{
}
}

View File

@ -0,0 +1,50 @@
// 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.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
/// <summary>
/// A hit circle which is at the end of a slider path (either repeat or final tail).
/// </summary>
public abstract class SliderEndCircle : HitCircle
{
private readonly Slider slider;
protected SliderEndCircle(Slider slider)
{
this.slider = slider;
}
public int RepeatIndex { get; set; }
public double SpanDuration => slider.SpanDuration;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
if (RepeatIndex > 0)
{
// Repeat points after the first span should appear behind the still-visible one.
TimeFadeIn = 0;
// The next end circle should appear exactly after the previous circle (on the same end) is hit.
TimePreempt = SpanDuration * 2;
}
else
{
// taken from osu-stable
const float first_end_circle_preempt_adjust = 2 / 3f;
// The first end circle should fade in with the slider.
TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt * first_end_circle_preempt_adjust;
}
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -1,35 +1,19 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderRepeat : OsuHitObject
public class SliderRepeat : SliderEndCircle
{
public int RepeatIndex { get; set; }
public double SpanDuration { get; set; }
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
public SliderRepeat(Slider slider)
: base(slider)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
// Out preempt should be one span early to give the user ample warning.
TimePreempt += SpanDuration;
// We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders
// we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time.
if (RepeatIndex > 0)
TimePreempt = Math.Min(SpanDuration * 2, TimePreempt);
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public override Judgement CreateJudgement() => new SliderRepeatJudgement();
public class SliderRepeatJudgement : OsuJudgement

View File

@ -1,7 +1,6 @@
// 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.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
@ -13,23 +12,18 @@ namespace osu.Game.Rulesets.Osu.Objects
/// Note that this should not be used for timing correctness.
/// See <see cref="SliderEventType.LegacyLastTick"/> usage in <see cref="Slider"/> for more information.
/// </summary>
public class SliderTailCircle : SliderCircle
public class SliderTailCircle : SliderEndCircle
{
private readonly IBindable<int> pathVersion = new Bindable<int>();
public SliderTailCircle(Slider slider)
: base(slider)
{
pathVersion.BindTo(slider.Path.Version);
pathVersion.BindValueChanged(_ => Position = slider.EndPosition);
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public override Judgement CreateJudgement() => new SliderTailJudgement();
public class SliderTailJudgement : OsuJudgement
{
public override HitResult MaxResult => HitResult.IgnoreHit;
public override HitResult MaxResult => HitResult.SmallTickHit;
}
}
}

View File

@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu
ReverseArrow,
HitCircleText,
SliderHeadHitCircle,
SliderTailHitCircle,
SliderFollowCircle,
SliderBall,
SliderBody,

View File

@ -1,9 +1,12 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Skinning;
@ -15,6 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
private bool disjointTrail;
private double lastTrailTime;
private IBindable<float> cursorSize;
public LegacyCursorTrail()
{
@ -22,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
private void load(ISkinSource skin, OsuConfigManager config)
{
Texture = skin.GetTexture("cursortrail");
disjointTrail = skin.GetTexture("cursormiddle") == null;
@ -32,12 +36,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
// stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
Texture.ScaleAdjust *= 1.6f;
}
cursorSize = config.GetBindable<float>(OsuSetting.GameplayCursorSize).GetBoundCopy();
}
protected override double FadeDuration => disjointTrail ? 150 : 500;
protected override bool InterpolateMovements => !disjointTrail;
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (!disjointTrail)

View File

@ -21,10 +21,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
public class LegacyMainCirclePiece : CompositeDrawable
{
private readonly string priorityLookup;
private readonly bool hasNumber;
public LegacyMainCirclePiece(string priorityLookup = null)
public LegacyMainCirclePiece(string priorityLookup = null, bool hasNumber = true)
{
this.priorityLookup = priorityLookup;
this.hasNumber = hasNumber;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
}
@ -47,6 +49,23 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject;
bool allowFallback = false;
// attempt lookup using priority specification
Texture baseTexture = getTextureWithFallback(string.Empty);
// if the base texture was not found without a fallback, switch on fallback mode and re-perform the lookup.
if (baseTexture == null)
{
allowFallback = true;
baseTexture = getTextureWithFallback(string.Empty);
}
// at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it.
// the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist.
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin).
Texture overlayTexture = getTextureWithFallback("overlay");
InternalChildren = new Drawable[]
{
circleSprites = new Container<Sprite>
@ -58,19 +77,23 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
hitCircleSprite = new Sprite
{
Texture = getTextureWithFallback(string.Empty),
Texture = baseTexture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
hitCircleOverlay = new Sprite
{
Texture = getTextureWithFallback("overlay"),
Texture = overlayTexture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}
},
hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
};
if (hasNumber)
{
AddInternal(hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
@ -78,8 +101,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
});
}
bool overlayAboveNumber = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true;
@ -95,8 +118,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
Texture tex = null;
if (!string.IsNullOrEmpty(priorityLookup))
{
tex = skin.GetTexture($"{priorityLookup}{name}");
if (!allowFallback)
return tex;
}
return tex ?? skin.GetTexture($"hitcircle{name}");
}
}
@ -107,6 +135,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
state.BindValueChanged(updateState, true);
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
if (hasNumber)
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
}
@ -120,6 +149,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
circleSprites.FadeOut(legacy_fade_duration, Easing.Out);
circleSprites.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
if (hasNumber)
{
var legacyVersion = skin.GetConfig<LegacySetting, decimal>(LegacySetting.Version)?.Value;
if (legacyVersion >= 2.0m)
@ -131,6 +162,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
hitCircleText.FadeOut(legacy_fade_duration, Easing.Out);
hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
}
}
break;
}

View File

@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
case OsuSkinComponents.SliderTailHitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece("sliderendcircle", false);
return null;
case OsuSkinComponents.SliderHeadHitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece("sliderstartcircle");

View File

@ -119,6 +119,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
/// </summary>
protected virtual bool InterpolateMovements => true;
protected virtual float IntervalMultiplier => 1.0f;
private Vector2? lastPosition;
private readonly InputResampler resampler = new InputResampler();
@ -147,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
float distance = diff.Length;
Vector2 direction = diff / distance;
float interval = partSize.X / 2.5f;
float interval = partSize.X / 2.5f * IntervalMultiplier;
for (float d = interval; d < distance; d += interval)
{

View File

@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Osu.UI
new SettingsCheckbox
{
LabelText = "Snaking in sliders",
Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
},
new SettingsCheckbox
{
LabelText = "Snaking out sliders",
Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
},
new SettingsCheckbox
{
LabelText = "Cursor trail",
Bindable = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
},
};
}

View File

@ -3,7 +3,6 @@
using System;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@ -30,8 +29,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
private readonly Random rng = new Random(1337);
[BackgroundDependencyLoader]
private void load()
[Test]
public void TestVariousHits()
{
AddStep("Hit", () => addHitJudgement(false));
AddStep("Strong hit", () => addStrongHitJudgement(false));

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{
switch (result)
{
case HitResult.Great:
case HitResult.SmallTickHit:
return 0.15;
default:

View File

@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!(obj is DrawableDrumRollTick))
return;
if (result.Type > HitResult.Miss)
if (result.IsHit)
rollingHits++;
else
rollingHits--;
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok);
}
else
ApplyResult(r => r.Type = HitResult.Miss);
ApplyResult(r => r.Type = r.Judgement.MinResult);
}
protected override void UpdateStateTransforms(ArmedState state)

View File

@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss);
ApplyResult(r => r.Type = r.Judgement.MinResult);
return;
}
@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return;
if (!validActionPressed)
ApplyResult(r => r.Type = HitResult.Miss);
ApplyResult(r => r.Type = r.Judgement.MinResult);
else
ApplyResult(r => r.Type = result);
}

View File

@ -211,9 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
tick.TriggerResult(false);
}
var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss;
ApplyResult(r => r.Type = hitResult);
ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult);
}
}

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
private double hpMultiplier;
/// <summary>
/// HP multiplier for a <see cref="HitResult.Miss"/>.
/// HP multiplier for a <see cref="HitResult"/> that does not satisfy <see cref="HitResultExtensions.IsHit"/>.
/// </summary>
private double hpMissMultiplier;
@ -45,6 +45,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring
}
protected override double GetHealthIncreaseFor(JudgementResult result)
=> base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier);
=> base.GetHealthIncreaseFor(result) * (result.IsHit ? hpMultiplier : hpMissMultiplier);
}
}

View File

@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
if (r?.Type.AffectsCombo() == false)
return;
passing = r == null || r.Type > HitResult.Miss;
passing = r == null || r.IsHit;
foreach (var sprite in InternalChildren.OfType<ScrollerSprite>())
sprite.Passing = passing;

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
Alpha = 0.15f;
Masking = true;
if (result == HitResult.Miss)
if (!result.IsHit())
return;
bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim;

View File

@ -163,7 +163,6 @@ namespace osu.Game.Rulesets.Taiko.UI
target = centreHit;
back = centre;
if (gameplayClock?.IsSeeking != true)
drumSample.Centre?.Play();
}
else if (action == RimAction)
@ -171,7 +170,6 @@ namespace osu.Game.Rulesets.Taiko.UI
target = rimHit;
back = rim;
if (gameplayClock?.IsSeeking != true)
drumSample.Rim?.Play();
}

View File

@ -12,6 +12,14 @@ namespace osu.Game.Tests.Editing
[TestFixture]
public class EditorChangeHandlerTest
{
private int stateChangedFired;
[SetUp]
public void SetUp()
{
stateChangedFired = 0;
}
[Test]
public void TestSaveRestoreState()
{
@ -23,6 +31,8 @@ namespace osu.Game.Tests.Editing
addArbitraryChange(beatmap);
handler.SaveState();
Assert.That(stateChangedFired, Is.EqualTo(1));
Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False);
@ -30,6 +40,8 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.True);
Assert.That(stateChangedFired, Is.EqualTo(2));
}
[Test]
@ -45,6 +57,7 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False);
Assert.That(stateChangedFired, Is.EqualTo(1));
string hash = handler.CurrentStateHash;
@ -52,6 +65,7 @@ namespace osu.Game.Tests.Editing
handler.SaveState();
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
Assert.That(stateChangedFired, Is.EqualTo(1));
handler.RestoreState(-1);
@ -60,6 +74,7 @@ namespace osu.Game.Tests.Editing
// we should only be able to restore once even though we saved twice.
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.True);
Assert.That(stateChangedFired, Is.EqualTo(2));
}
[Test]
@ -71,6 +86,8 @@ namespace osu.Game.Tests.Editing
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
{
Assert.That(stateChangedFired, Is.EqualTo(i));
addArbitraryChange(beatmap);
handler.SaveState();
}
@ -114,7 +131,10 @@ namespace osu.Game.Tests.Editing
{
var beatmap = new EditorBeatmap(new Beatmap());
return (new EditorChangeHandler(beatmap), beatmap);
var changeHandler = new EditorChangeHandler(beatmap);
changeHandler.OnStateChange += () => stateChangedFired++;
return (changeHandler, beatmap);
}
private void addArbitraryChange(EditorBeatmap beatmap)

View File

@ -0,0 +1,39 @@
// 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.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Timing;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class GameplayClockTest
{
[TestCase(0)]
[TestCase(1)]
public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate)
{
var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate });
var gameplayClock = new TestGameplayClock(framedClock);
gameplayClock.MutableNonGameplayAdjustments.Add(new BindableDouble());
Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0));
}
private class TestGameplayClock : GameplayClock
{
public List<Bindable<double>> MutableNonGameplayAdjustments { get; } = new List<Bindable<double>>();
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => MutableNonGameplayAdjustments;
public TestGameplayClock(IFrameBasedClock underlyingClock)
: base(underlyingClock)
{
}
}
}
}

View File

@ -0,0 +1,69 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneComposeSelectBox : OsuTestScene
{
private Container selectionArea;
public TestSceneComposeSelectBox()
{
SelectionBox selectionBox = null;
AddStep("create box", () =>
Child = selectionArea = new Container
{
Size = new Vector2(400),
Position = -new Vector2(150),
Anchor = Anchor.Centre,
Children = new Drawable[]
{
selectionBox = new SelectionBox
{
CanRotate = true,
CanScaleX = true,
CanScaleY = true,
OnRotation = handleRotation,
OnScale = handleScale
}
}
});
AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state);
AddToggleStep("toggle x", state => selectionBox.CanScaleX = state);
AddToggleStep("toggle y", state => selectionBox.CanScaleY = state);
}
private void handleScale(Vector2 amount, Anchor reference)
{
if ((reference & Anchor.y1) == 0)
{
int directionY = (reference & Anchor.y0) > 0 ? -1 : 1;
if (directionY < 0)
selectionArea.Y += amount.Y;
selectionArea.Height += directionY * amount.Y;
}
if ((reference & Anchor.x1) == 0)
{
int directionX = (reference & Anchor.x0) > 0 ? -1 : 1;
if (directionX < 0)
selectionArea.X += amount.X;
selectionArea.Width += directionX * amount.X;
}
}
private void handleRotation(float angle)
{
// kinda silly and wrong, but just showing that the drag handles work.
selectionArea.Rotation += angle;
}
}
}

View File

@ -5,6 +5,8 @@ using System;
using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing
protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true;
[Resolved]
private BeatmapManager beatmapManager { get; set; }
public override void SetUpSteps()
{
AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null));
@ -38,6 +43,15 @@ namespace osu.Game.Tests.Visual.Editing
{
AddStep("save beatmap", () => Editor.Save());
AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0);
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false);
}
[Test]
public void TestExitWithoutSave()
{
AddStep("exit without save", () => Editor.Exit());
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
}
[Test]
@ -55,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
bool success = setup.ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"));
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"));
File.Delete(temp);
Directory.Delete(extractedFolder, true);

View File

@ -19,12 +19,14 @@ namespace osu.Game.Tests.Visual.Editing
public void TestSlidingSampleStopsOnSeek()
{
DrawableSlider slider = null;
DrawableSample[] samples = null;
DrawableSample[] loopingSamples = null;
DrawableSample[] onceOffSamples = null;
AddStep("get first slider", () =>
{
slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
samples = slider.ChildrenOfType<DrawableSample>().ToArray();
onceOffSamples = slider.ChildrenOfType<DrawableSample>().Where(s => !s.Looping).ToArray();
loopingSamples = slider.ChildrenOfType<DrawableSample>().Where(s => s.Looping).ToArray();
});
AddStep("start playback", () => EditorClock.Start());
@ -34,14 +36,15 @@ namespace osu.Game.Tests.Visual.Editing
if (!slider.Tracking.Value)
return false;
if (!samples.Any(s => s.Playing))
if (!loopingSamples.Any(s => s.Playing))
return false;
EditorClock.Seek(20000);
return true;
});
AddAssert("slider samples are not playing", () => samples.Length == 5 && samples.All(s => s.Played && !s.Playing));
AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.Played || s.Playing));
AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.Played && !s.Playing));
}
}
}

View File

@ -0,0 +1,68 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneGameplaySamplePlayback : PlayerTestScene
{
[Test]
public void TestAllSamplesStopDuringSeek()
{
DrawableSlider slider = null;
DrawableSample[] samples = null;
ISamplePlaybackDisabler gameplayClock = null;
AddStep("get variables", () =>
{
gameplayClock = Player.ChildrenOfType<FrameStabilityContainer>().First().GameplayClock;
slider = Player.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
samples = slider.ChildrenOfType<DrawableSample>().ToArray();
});
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
return false;
if (!samples.Any(s => s.Playing))
return false;
Player.ChildrenOfType<GameplayClockContainer>().First().Seek(40000);
return true;
});
AddAssert("sample playback disabled", () => gameplayClock.SamplePlaybackDisabled.Value);
// because we are in frame stable context, it's quite likely that not all samples are "played" at this point.
// the important thing is that at least one started, and that sample has since stopped.
AddAssert("all looping samples stopped immediately", () => allStopped(allLoopingSounds));
AddUntilStep("all samples stopped eventually", () => allStopped(allSounds));
AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value);
AddUntilStep("seek finished, sample playback enabled", () => !gameplayClock.SamplePlaybackDisabled.Value);
AddUntilStep("any sample is playing", () => Player.ChildrenOfType<PausableSkinnableSound>().Any(s => s.IsPlaying));
}
private IEnumerable<PausableSkinnableSound> allSounds => Player.ChildrenOfType<PausableSkinnableSound>();
private IEnumerable<PausableSkinnableSound> allLoopingSounds => allSounds.Where(sound => sound.Looping);
private bool allStopped(IEnumerable<PausableSkinnableSound> sounds) => sounds.All(sound => !sound.IsPlaying);
protected override bool Autoplay => true;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
}

View File

@ -0,0 +1,46 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneLabelledSliderBar : OsuTestScene
{
[TestCase(false)]
[TestCase(true)]
public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription);
private void createSliderBar(bool hasDescription = false)
{
AddStep("create component", () =>
{
LabelledSliderBar<double> component;
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledSliderBar<double>
{
Current = new BindableDouble(5)
{
MinValue = 0,
MaxValue = 10,
Precision = 1,
}
}
};
component.Label = "a sample component";
component.Description = hasDescription ? "this text describes the component" : string.Empty;
});
}
}
}

View File

@ -10,34 +10,34 @@ namespace osu.Game.Tournament.Components
{
public class DateTextBox : SettingsTextBox
{
public new Bindable<DateTimeOffset> Bindable
public new Bindable<DateTimeOffset> Current
{
get => bindable;
get => current;
set
{
bindable = value.GetBoundCopy();
bindable.BindValueChanged(dto =>
base.Bindable.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true);
current = value.GetBoundCopy();
current.BindValueChanged(dto =>
base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true);
}
}
// hold a reference to the provided bindable so we don't have to in every settings section.
private Bindable<DateTimeOffset> bindable = new Bindable<DateTimeOffset>();
private Bindable<DateTimeOffset> current = new Bindable<DateTimeOffset>();
public DateTextBox()
{
base.Bindable = new Bindable<string>();
base.Current = new Bindable<string>();
((OsuTextBox)Control).OnCommit += (sender, newText) =>
{
try
{
bindable.Value = DateTimeOffset.Parse(sender.Text);
current.Value = DateTimeOffset.Parse(sender.Text);
}
catch
{
// reset textbox content to its last valid state on a parse failure.
bindable.TriggerChange();
current.TriggerChange();
}
};
}

View File

@ -63,25 +63,25 @@ namespace osu.Game.Tournament.Screens.Editors
{
LabelText = "Name",
Width = 0.33f,
Bindable = Model.Name
Current = Model.Name
},
new SettingsTextBox
{
LabelText = "Description",
Width = 0.33f,
Bindable = Model.Description
Current = Model.Description
},
new DateTextBox
{
LabelText = "Start Time",
Width = 0.33f,
Bindable = Model.StartDate
Current = Model.StartDate
},
new SettingsSlider<int>
{
LabelText = "Best of",
Width = 0.33f,
Bindable = Model.BestOf
Current = Model.BestOf
},
new SettingsButton
{
@ -186,14 +186,14 @@ namespace osu.Game.Tournament.Screens.Editors
LabelText = "Beatmap ID",
RelativeSizeAxes = Axes.None,
Width = 200,
Bindable = beatmapId,
Current = beatmapId,
},
new SettingsTextBox
{
LabelText = "Mods",
RelativeSizeAxes = Axes.None,
Width = 200,
Bindable = mods,
Current = mods,
},
drawableContainer = new Container
{

View File

@ -74,13 +74,13 @@ namespace osu.Game.Tournament.Screens.Editors
{
LabelText = "Mod",
Width = 0.33f,
Bindable = Model.Mod
Current = Model.Mod
},
new SettingsSlider<int>
{
LabelText = "Seed",
Width = 0.33f,
Bindable = Model.Seed
Current = Model.Seed
},
new SettingsButton
{
@ -187,21 +187,21 @@ namespace osu.Game.Tournament.Screens.Editors
LabelText = "Beatmap ID",
RelativeSizeAxes = Axes.None,
Width = 200,
Bindable = beatmapId,
Current = beatmapId,
},
new SettingsSlider<int>
{
LabelText = "Seed",
RelativeSizeAxes = Axes.None,
Width = 200,
Bindable = beatmap.Seed
Current = beatmap.Seed
},
new SettingsTextBox
{
LabelText = "Score",
RelativeSizeAxes = Axes.None,
Width = 200,
Bindable = score,
Current = score,
},
drawableContainer = new Container
{

View File

@ -102,31 +102,31 @@ namespace osu.Game.Tournament.Screens.Editors
{
LabelText = "Name",
Width = 0.2f,
Bindable = Model.FullName
Current = Model.FullName
},
new SettingsTextBox
{
LabelText = "Acronym",
Width = 0.2f,
Bindable = Model.Acronym
Current = Model.Acronym
},
new SettingsTextBox
{
LabelText = "Flag",
Width = 0.2f,
Bindable = Model.FlagName
Current = Model.FlagName
},
new SettingsTextBox
{
LabelText = "Seed",
Width = 0.2f,
Bindable = Model.Seed
Current = Model.Seed
},
new SettingsSlider<int>
{
LabelText = "Last Year Placement",
Width = 0.33f,
Bindable = Model.LastYearPlacing
Current = Model.LastYearPlacing
},
new SettingsButton
{
@ -247,7 +247,7 @@ namespace osu.Game.Tournament.Screens.Editors
LabelText = "User ID",
RelativeSizeAxes = Axes.None,
Width = 200,
Bindable = userId,
Current = userId,
},
drawableContainer = new Container
{

View File

@ -113,13 +113,13 @@ namespace osu.Game.Tournament.Screens.Gameplay
new SettingsSlider<int>
{
LabelText = "Chroma width",
Bindable = LadderInfo.ChromaKeyWidth,
Current = LadderInfo.ChromaKeyWidth,
KeyboardStep = 1,
},
new SettingsSlider<int>
{
LabelText = "Players per team",
Bindable = LadderInfo.PlayersPerTeam,
Current = LadderInfo.PlayersPerTeam,
KeyboardStep = 1,
}
}

View File

@ -51,15 +51,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
editorInfo.Selected.ValueChanged += selection =>
{
roundDropdown.Bindable = selection.NewValue?.Round;
roundDropdown.Current = selection.NewValue?.Round;
losersCheckbox.Current = selection.NewValue?.Losers;
dateTimeBox.Bindable = selection.NewValue?.Date;
dateTimeBox.Current = selection.NewValue?.Date;
team1Dropdown.Bindable = selection.NewValue?.Team1;
team2Dropdown.Bindable = selection.NewValue?.Team2;
team1Dropdown.Current = selection.NewValue?.Team1;
team2Dropdown.Current = selection.NewValue?.Team2;
};
roundDropdown.Bindable.ValueChanged += round =>
roundDropdown.Current.ValueChanged += round =>
{
if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value)
{
@ -88,7 +88,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
{
public SettingsRoundDropdown(BindableList<TournamentRound> rounds)
{
Bindable = new Bindable<TournamentRound>();
Current = new Bindable<TournamentRound>();
foreach (var r in rounds.Prepend(new TournamentRound()))
add(r);

View File

@ -61,7 +61,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
new SettingsTeamDropdown(LadderInfo.Teams)
{
LabelText = "Show specific team",
Bindable = currentTeam,
Current = currentTeam,
}
}
}

View File

@ -114,6 +114,25 @@ namespace osu.Game.Beatmaps
return computeDifficulty(key, beatmapInfo, rulesetInfo);
}
/// <summary>
/// Retrieves the <see cref="DifficultyRating"/> that describes a star rating.
/// </summary>
/// <remarks>
/// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties
/// </remarks>
/// <param name="starRating">The star rating.</param>
/// <returns>The <see cref="DifficultyRating"/> that best describes <paramref name="starRating"/>.</returns>
public static DifficultyRating GetDifficultyRating(double starRating)
{
if (starRating < 2.0) return DifficultyRating.Easy;
if (starRating < 2.7) return DifficultyRating.Normal;
if (starRating < 4.0) return DifficultyRating.Hard;
if (starRating < 5.3) return DifficultyRating.Insane;
if (starRating < 6.5) return DifficultyRating.Expert;
return DifficultyRating.ExpertPlus;
}
private CancellationTokenSource trackedUpdateCancellationSource;
private readonly List<CancellationTokenSource> linkedCancellationSources = new List<CancellationTokenSource>();
@ -307,5 +326,7 @@ namespace osu.Game.Beatmaps
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
}
public DifficultyRating DifficultyRating => BeatmapDifficultyManager.GetDifficultyRating(Stars);
}
}

View File

@ -135,21 +135,7 @@ namespace osu.Game.Beatmaps
public List<ScoreInfo> Scores { get; set; }
[JsonIgnore]
public DifficultyRating DifficultyRating
{
get
{
var rating = StarDifficulty;
if (rating < 2.0) return DifficultyRating.Easy;
if (rating < 2.7) return DifficultyRating.Normal;
if (rating < 4.0) return DifficultyRating.Hard;
if (rating < 5.3) return DifficultyRating.Insane;
if (rating < 6.5) return DifficultyRating.Expert;
return DifficultyRating.ExpertPlus;
}
}
public DifficultyRating DifficultyRating => BeatmapDifficultyManager.GetDifficultyRating(StarDifficulty);
public string[] SearchableTerms => new[]
{

View File

@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps
/// </summary>
public readonly WorkingBeatmap DefaultBeatmap;
public override string[] HandledExtensions => new[] { ".osz" };
public override IEnumerable<string> HandledExtensions => new[] { ".osz" };
protected override string[] HashableFileTypes => new[] { ".osu" };

View File

@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
@ -18,6 +20,8 @@ namespace osu.Game.Beatmaps.ControlPoints
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
public virtual Color4 GetRepresentingColour(OsuColour colours) => colours.Yellow;
/// <summary>
/// Determines whether this <see cref="ControlPoint"/> results in a meaningful change when placed alongside another.
/// </summary>

View File

@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
@ -23,6 +25,8 @@ namespace osu.Game.Beatmaps.ControlPoints
MaxValue = 10
};
public override Color4 GetRepresentingColour(OsuColour colours) => colours.GreenDark;
/// <summary>
/// The speed multiplier at this control point.
/// </summary>

View File

@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
@ -18,6 +20,8 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary>
public readonly BindableBool OmitFirstBarLineBindable = new BindableBool();
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple;
/// <summary>
/// Whether the first bar line of this control point is ignored.
/// </summary>

View File

@ -3,6 +3,8 @@
using osu.Framework.Bindables;
using osu.Game.Audio;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
@ -16,6 +18,8 @@ namespace osu.Game.Beatmaps.ControlPoints
SampleVolumeBindable = { Disabled = true }
};
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink;
/// <summary>
/// The default sample bank at this control point.
/// </summary>

View File

@ -3,6 +3,8 @@
using osu.Framework.Bindables;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
@ -18,6 +20,8 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary>
private const double default_beat_length = 60000.0 / 60.0;
public override Color4 GetRepresentingColour(OsuColour colours) => colours.YellowDark;
public static readonly TimingControlPoint DEFAULT = new TimingControlPoint
{
BeatLengthBindable =

View File

@ -2,7 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Threading;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -14,6 +18,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osuTK;
using osuTK.Graphics;
@ -21,9 +26,6 @@ namespace osu.Game.Beatmaps.Drawables
{
public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip
{
private readonly BeatmapInfo beatmap;
private readonly RulesetInfo ruleset;
private readonly Container iconContainer;
/// <summary>
@ -35,23 +37,49 @@ namespace osu.Game.Beatmaps.Drawables
set => iconContainer.Size = value;
}
public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true)
[NotNull]
private readonly BeatmapInfo beatmap;
[CanBeNull]
private readonly RulesetInfo ruleset;
[CanBeNull]
private readonly IReadOnlyList<Mod> mods;
private readonly bool shouldShowTooltip;
private readonly IBindable<StarDifficulty> difficultyBindable = new Bindable<StarDifficulty>();
private Drawable background;
/// <summary>
/// Creates a new <see cref="DifficultyIcon"/> with a given <see cref="RulesetInfo"/> and <see cref="Mod"/> combination.
/// </summary>
/// <param name="beatmap">The beatmap to show the difficulty of.</param>
/// <param name="ruleset">The ruleset to show the difficulty with.</param>
/// <param name="mods">The mods to show the difficulty with.</param>
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
public DifficultyIcon([NotNull] BeatmapInfo beatmap, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true)
: this(beatmap, shouldShowTooltip)
{
this.ruleset = ruleset ?? beatmap.Ruleset;
this.mods = mods ?? Array.Empty<Mod>();
}
/// <summary>
/// Creates a new <see cref="DifficultyIcon"/> that follows the currently-selected ruleset and mods.
/// </summary>
/// <param name="beatmap">The beatmap to show the difficulty of.</param>
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true)
{
this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap));
this.ruleset = ruleset ?? beatmap.Ruleset;
if (shouldShowTooltip)
TooltipContent = beatmap;
this.shouldShowTooltip = shouldShowTooltip;
AutoSizeAxes = Axes.Both;
InternalChild = iconContainer = new Container { Size = new Vector2(20f) };
}
public ITooltip GetCustomTooltip() => new DifficultyIconTooltip();
public object TooltipContent { get; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@ -70,10 +98,10 @@ namespace osu.Game.Beatmaps.Drawables
Type = EdgeEffectType.Shadow,
Radius = 5,
},
Child = new Box
Child = background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.ForDifficultyRating(beatmap.DifficultyRating),
Colour = colours.ForDifficultyRating(beatmap.DifficultyRating) // Default value that will be re-populated once difficulty calculation completes
},
},
new ConstrainedIconContainer
@ -82,16 +110,73 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
Icon = ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
}
Icon = (ruleset ?? beatmap.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
},
new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0),
};
difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating));
}
public ITooltip GetCustomTooltip() => new DifficultyIconTooltip();
public object TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null;
private class DifficultyRetriever : Component
{
public readonly Bindable<StarDifficulty> StarDifficulty = new Bindable<StarDifficulty>();
private readonly BeatmapInfo beatmap;
private readonly RulesetInfo ruleset;
private readonly IReadOnlyList<Mod> mods;
private CancellationTokenSource difficultyCancellation;
[Resolved]
private BeatmapDifficultyManager difficultyManager { get; set; }
public DifficultyRetriever(BeatmapInfo beatmap, RulesetInfo ruleset, IReadOnlyList<Mod> mods)
{
this.beatmap = beatmap;
this.ruleset = ruleset;
this.mods = mods;
}
private IBindable<StarDifficulty> localStarDifficulty;
[BackgroundDependencyLoader]
private void load()
{
difficultyCancellation = new CancellationTokenSource();
localStarDifficulty = ruleset != null
? difficultyManager.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token)
: difficultyManager.GetBindableDifficulty(beatmap, difficultyCancellation.Token);
localStarDifficulty.BindValueChanged(difficulty => StarDifficulty.Value = difficulty.NewValue);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
difficultyCancellation?.Cancel();
}
}
private class DifficultyIconTooltipContent
{
public readonly BeatmapInfo Beatmap;
public readonly IBindable<StarDifficulty> Difficulty;
public DifficultyIconTooltipContent(BeatmapInfo beatmap, IBindable<StarDifficulty> difficulty)
{
Beatmap = beatmap;
Difficulty = difficulty;
}
}
private class DifficultyIconTooltip : VisibilityContainer, ITooltip
{
private readonly OsuSpriteText difficultyName, starRating;
private readonly Box background;
private readonly FillFlowContainer difficultyFlow;
public DifficultyIconTooltip()
@ -159,14 +244,22 @@ namespace osu.Game.Beatmaps.Drawables
background.Colour = colours.Gray3;
}
private readonly IBindable<StarDifficulty> starDifficulty = new Bindable<StarDifficulty>();
public bool SetContent(object content)
{
if (!(content is BeatmapInfo beatmap))
if (!(content is DifficultyIconTooltipContent iconContent))
return false;
difficultyName.Text = beatmap.Version;
starRating.Text = $"{beatmap.StarDifficulty:0.##}";
difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating, true);
difficultyName.Text = iconContent.Beatmap.Version;
starDifficulty.UnbindAll();
starDifficulty.BindTo(iconContent.Difficulty);
starDifficulty.BindValueChanged(difficulty =>
{
starRating.Text = $"{difficulty.NewValue.Stars:0.##}";
difficultyFlow.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating, true);
}, true);
return true;
}

View File

@ -20,7 +20,7 @@ namespace osu.Game.Beatmaps.Drawables
public class GroupedDifficultyIcon : DifficultyIcon
{
public GroupedDifficultyIcon(List<BeatmapInfo> beatmaps, RulesetInfo ruleset, Color4 counterColour)
: base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, false)
: base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, null, false)
{
AddInternal(new OsuSpriteText
{

View File

@ -57,7 +57,7 @@ namespace osu.Game.Configuration
yield return new SettingsSlider<float>
{
LabelText = attr.Label,
Bindable = bNumber,
Current = bNumber,
KeyboardStep = 0.1f,
};
@ -67,7 +67,7 @@ namespace osu.Game.Configuration
yield return new SettingsSlider<double>
{
LabelText = attr.Label,
Bindable = bNumber,
Current = bNumber,
KeyboardStep = 0.1f,
};
@ -77,7 +77,7 @@ namespace osu.Game.Configuration
yield return new SettingsSlider<int>
{
LabelText = attr.Label,
Bindable = bNumber
Current = bNumber
};
break;
@ -86,7 +86,7 @@ namespace osu.Game.Configuration
yield return new SettingsCheckbox
{
LabelText = attr.Label,
Bindable = bBool
Current = bBool
};
break;
@ -95,7 +95,7 @@ namespace osu.Game.Configuration
yield return new SettingsTextBox
{
LabelText = attr.Label,
Bindable = bString
Current = bString
};
break;
@ -105,7 +105,7 @@ namespace osu.Game.Configuration
var dropdown = (Drawable)Activator.CreateInstance(dropdownType);
dropdownType.GetProperty(nameof(SettingsDropdown<object>.LabelText))?.SetValue(dropdown, attr.Label);
dropdownType.GetProperty(nameof(SettingsDropdown<object>.Bindable))?.SetValue(dropdown, bindable);
dropdownType.GetProperty(nameof(SettingsDropdown<object>.Current))?.SetValue(dropdown, bindable);
yield return dropdown;

View File

@ -70,7 +70,7 @@ namespace osu.Game.Database
private readonly Bindable<WeakReference<TModel>> itemRemoved = new Bindable<WeakReference<TModel>>();
public virtual string[] HandledExtensions => new[] { ".zip" };
public virtual IEnumerable<string> HandledExtensions => new[] { ".zip" };
public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop;

View File

@ -1,6 +1,7 @@
// 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.Collections.Generic;
using System.Threading.Tasks;
namespace osu.Game.Database
@ -19,6 +20,6 @@ namespace osu.Game.Database
/// <summary>
/// An array of accepted file extensions (in the standard format of ".abc").
/// </summary>
string[] HandledExtensions { get; }
IEnumerable<string> HandledExtensions { get; }
}
}

View File

@ -0,0 +1,24 @@
// 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 osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class LabelledSliderBar<TNumber> : LabelledComponent<SettingsSlider<TNumber>, TNumber>
where TNumber : struct, IEquatable<TNumber>, IComparable<TNumber>, IConvertible
{
public LabelledSliderBar()
: base(true)
{
}
protected override SettingsSlider<TNumber> CreateComponent() => new SettingsSlider<TNumber>
{
TransferValueOnCommit = true,
RelativeSizeAxes = Axes.X,
};
}
}

View File

@ -232,9 +232,9 @@ namespace osu.Game
dependencies.Cache(new SessionStatics());
dependencies.Cache(new OsuColour());
fileImporters.Add(BeatmapManager);
fileImporters.Add(ScoreManager);
fileImporters.Add(SkinManager);
RegisterImportHandler(BeatmapManager);
RegisterImportHandler(ScoreManager);
RegisterImportHandler(SkinManager);
// tracks play so loud our samples can't keep up.
// this adds a global reduction of track volume for the time being.
@ -343,6 +343,18 @@ namespace osu.Game
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
/// <summary>
/// Register a global handler for file imports. Most recently registered will have precedence.
/// </summary>
/// <param name="handler">The handler to register.</param>
public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Insert(0, handler);
/// <summary>
/// Unregister a global handler for file imports.
/// </summary>
/// <param name="handler">The previously registered handler.</param>
public void UnregisterImportHandler(ICanAcceptFiles handler) => fileImporters.Remove(handler);
public async Task Import(params string[] paths)
{
var extension = Path.GetExtension(paths.First())?.ToLowerInvariant();
@ -354,7 +366,7 @@ namespace osu.Game
}
}
public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray();
public IEnumerable<string> HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions);
protected override void Dispose(bool isDisposing)
{

View File

@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
updateItems();
dropdown.Bindable = audio.AudioDevice;
dropdown.Current = audio.AudioDevice;
audio.OnNewDevice += onDeviceChanged;
audio.OnLostDevice += onDeviceChanged;

View File

@ -21,23 +21,23 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
new SettingsCheckbox
{
LabelText = "Interface voices",
Bindable = config.GetBindable<bool>(OsuSetting.MenuVoice)
Current = config.GetBindable<bool>(OsuSetting.MenuVoice)
},
new SettingsCheckbox
{
LabelText = "osu! music theme",
Bindable = config.GetBindable<bool>(OsuSetting.MenuMusic)
Current = config.GetBindable<bool>(OsuSetting.MenuMusic)
},
new SettingsDropdown<IntroSequence>
{
LabelText = "Intro sequence",
Bindable = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence),
Current = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence),
Items = Enum.GetValues(typeof(IntroSequence)).Cast<IntroSequence>()
},
new SettingsDropdown<BackgroundSource>
{
LabelText = "Background source",
Bindable = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
Current = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
Items = Enum.GetValues(typeof(BackgroundSource)).Cast<BackgroundSource>()
}
};

View File

@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
new SettingsSlider<double, OffsetSlider>
{
LabelText = "Audio offset",
Bindable = config.GetBindable<double>(OsuSetting.AudioOffset),
Current = config.GetBindable<double>(OsuSetting.AudioOffset),
KeyboardStep = 1f
},
new SettingsButton

View File

@ -20,28 +20,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
new SettingsSlider<double>
{
LabelText = "Master",
Bindable = audio.Volume,
Current = audio.Volume,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<double>
{
LabelText = "Master (window inactive)",
Bindable = config.GetBindable<double>(OsuSetting.VolumeInactive),
Current = config.GetBindable<double>(OsuSetting.VolumeInactive),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<double>
{
LabelText = "Effect",
Bindable = audio.VolumeSample,
Current = audio.VolumeSample,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<double>
{
LabelText = "Music",
Bindable = audio.VolumeTrack,
Current = audio.VolumeTrack,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},

View File

@ -19,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
new SettingsCheckbox
{
LabelText = "Show log overlay",
Bindable = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
},
new SettingsCheckbox
{
LabelText = "Bypass front-to-back render pass",
Bindable = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
Current = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
}
};
}

View File

@ -21,62 +21,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
new SettingsSlider<double>
{
LabelText = "Background dim",
Bindable = config.GetBindable<double>(OsuSetting.DimLevel),
Current = config.GetBindable<double>(OsuSetting.DimLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<double>
{
LabelText = "Background blur",
Bindable = config.GetBindable<double>(OsuSetting.BlurLevel),
Current = config.GetBindable<double>(OsuSetting.BlurLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsCheckbox
{
LabelText = "Lighten playfield during breaks",
Bindable = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
},
new SettingsCheckbox
{
LabelText = "Show score overlay",
Bindable = config.GetBindable<bool>(OsuSetting.ShowInterface)
Current = config.GetBindable<bool>(OsuSetting.ShowInterface)
},
new SettingsCheckbox
{
LabelText = "Show difficulty graph on progress bar",
Bindable = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
},
new SettingsCheckbox
{
LabelText = "Show health display even when you can't fail",
Bindable = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" }
},
new SettingsCheckbox
{
LabelText = "Fade playfield to red when health is low",
Bindable = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
},
new SettingsCheckbox
{
LabelText = "Always show key overlay",
Bindable = config.GetBindable<bool>(OsuSetting.KeyOverlay)
Current = config.GetBindable<bool>(OsuSetting.KeyOverlay)
},
new SettingsCheckbox
{
LabelText = "Positional hitsounds",
Bindable = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
},
new SettingsEnumDropdown<ScoreMeterType>
{
LabelText = "Score meter type",
Bindable = config.GetBindable<ScoreMeterType>(OsuSetting.ScoreMeter)
Current = config.GetBindable<ScoreMeterType>(OsuSetting.ScoreMeter)
},
new SettingsEnumDropdown<ScoringMode>
{
LabelText = "Score display mode",
Bindable = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode)
Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode)
}
};
@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
Add(new SettingsCheckbox
{
LabelText = "Disable Windows key during gameplay",
Bindable = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
});
}
}

View File

@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
new SettingsCheckbox
{
LabelText = "Increase visibility of first object when visual impairment mods are enabled",
Bindable = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility),
Current = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility),
},
};
}

View File

@ -31,31 +31,31 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
new SettingsCheckbox
{
LabelText = "Right mouse drag to absolute scroll",
Bindable = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll),
Current = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll),
},
new SettingsCheckbox
{
LabelText = "Show converted beatmaps",
Bindable = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
},
new SettingsSlider<double, StarsSlider>
{
LabelText = "Display beatmaps from",
Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMinimum),
Current = config.GetBindable<double>(OsuSetting.DisplayStarsMinimum),
KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
},
new SettingsSlider<double, MaximumStarsSlider>
{
LabelText = "up to",
Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum),
Current = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum),
KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
},
new SettingsEnumDropdown<RandomSelectAlgorithm>
{
LabelText = "Random selection algorithm",
Bindable = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm),
Current = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm),
}
};
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
new SettingsCheckbox
{
LabelText = "Prefer metadata in original language",
Bindable = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode)
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode)
},
};
}

View File

@ -240,12 +240,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
new SettingsCheckbox
{
LabelText = "Remember username",
Bindable = config.GetBindable<bool>(OsuSetting.SaveUsername),
Current = config.GetBindable<bool>(OsuSetting.SaveUsername),
},
new SettingsCheckbox
{
LabelText = "Stay signed in",
Bindable = config.GetBindable<bool>(OsuSetting.SavePassword),
Current = config.GetBindable<bool>(OsuSetting.SavePassword),
},
new Container
{

View File

@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
Add(new SettingsEnumDropdown<ReleaseStream>
{
LabelText = "Release stream",
Bindable = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream),
Current = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream),
});
if (updateManager?.CanCheckForUpdate == true)

View File

@ -19,22 +19,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsCheckbox
{
LabelText = "Storyboard / Video",
Bindable = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
},
new SettingsCheckbox
{
LabelText = "Hit Lighting",
Bindable = config.GetBindable<bool>(OsuSetting.HitLighting)
Current = config.GetBindable<bool>(OsuSetting.HitLighting)
},
new SettingsEnumDropdown<ScreenshotFormat>
{
LabelText = "Screenshot format",
Bindable = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat)
Current = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat)
},
new SettingsCheckbox
{
LabelText = "Show menu cursor in screenshots",
Bindable = config.GetBindable<bool>(OsuSetting.ScreenshotCaptureMenuCursor)
Current = config.GetBindable<bool>(OsuSetting.ScreenshotCaptureMenuCursor)
}
};
}

View File

@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModeDropdown = new SettingsDropdown<WindowMode>
{
LabelText = "Screen mode",
Bindable = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
ItemSource = windowModes,
},
resolutionSettingsContainer = new Container
@ -74,14 +74,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
LabelText = "UI Scaling",
TransferValueOnCommit = true,
Bindable = osuConfig.GetBindable<float>(OsuSetting.UIScale),
Current = osuConfig.GetBindable<float>(OsuSetting.UIScale),
KeyboardStep = 0.01f,
Keywords = new[] { "scale", "letterbox" },
},
new SettingsEnumDropdown<ScalingMode>
{
LabelText = "Screen Scaling",
Bindable = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
Current = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
Keywords = new[] { "scale", "letterbox" },
},
scalingSettings = new FillFlowContainer<SettingsSlider<float>>
@ -97,28 +97,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsSlider<float>
{
LabelText = "Horizontal position",
Bindable = scalingPositionX,
Current = scalingPositionX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<float>
{
LabelText = "Vertical position",
Bindable = scalingPositionY,
Current = scalingPositionY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<float>
{
LabelText = "Horizontal scale",
Bindable = scalingSizeX,
Current = scalingSizeX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider<float>
{
LabelText = "Vertical scale",
Bindable = scalingSizeY,
Current = scalingSizeY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
},
};
scalingSettings.ForEach(s => bindPreviewEvent(s.Bindable));
scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
var resolutions = getResolutions();
@ -137,10 +137,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
LabelText = "Resolution",
ShowsDefaultIndicator = false,
Items = resolutions,
Bindable = sizeFullscreen
Current = sizeFullscreen
};
windowModeDropdown.Bindable.BindValueChanged(mode =>
windowModeDropdown.Current.BindValueChanged(mode =>
{
if (mode.NewValue == WindowMode.Fullscreen)
{

View File

@ -23,17 +23,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsEnumDropdown<FrameSync>
{
LabelText = "Frame limiter",
Bindable = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync)
Current = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync)
},
new SettingsEnumDropdown<ExecutionMode>
{
LabelText = "Threading mode",
Bindable = config.GetBindable<ExecutionMode>(FrameworkSetting.ExecutionMode)
Current = config.GetBindable<ExecutionMode>(FrameworkSetting.ExecutionMode)
},
new SettingsCheckbox
{
LabelText = "Show FPS",
Bindable = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
Current = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
},
};
}

View File

@ -20,17 +20,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsCheckbox
{
LabelText = "Rotate cursor when dragging",
Bindable = config.GetBindable<bool>(OsuSetting.CursorRotation)
Current = config.GetBindable<bool>(OsuSetting.CursorRotation)
},
new SettingsCheckbox
{
LabelText = "Parallax",
Bindable = config.GetBindable<bool>(OsuSetting.MenuParallax)
Current = config.GetBindable<bool>(OsuSetting.MenuParallax)
},
new SettingsSlider<float, TimeSlider>
{
LabelText = "Hold-to-confirm activation time",
Bindable = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay),
Current = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay),
KeyboardStep = 50
},
};

View File

@ -35,32 +35,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input
new SettingsCheckbox
{
LabelText = "Raw input",
Bindable = rawInputToggle
Current = rawInputToggle
},
new SensitivitySetting
{
LabelText = "Cursor sensitivity",
Bindable = sensitivityBindable
Current = sensitivityBindable
},
new SettingsCheckbox
{
LabelText = "Map absolute input to window",
Bindable = config.GetBindable<bool>(FrameworkSetting.MapAbsoluteInputToWindow)
Current = config.GetBindable<bool>(FrameworkSetting.MapAbsoluteInputToWindow)
},
new SettingsEnumDropdown<ConfineMouseMode>
{
LabelText = "Confine mouse cursor to window",
Bindable = config.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode),
Current = config.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode),
},
new SettingsCheckbox
{
LabelText = "Disable mouse wheel during gameplay",
Bindable = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableWheel)
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableWheel)
},
new SettingsCheckbox
{
LabelText = "Disable mouse buttons during gameplay",
Bindable = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
},
};

View File

@ -19,13 +19,13 @@ namespace osu.Game.Overlays.Settings.Sections.Online
new SettingsCheckbox
{
LabelText = "Warn about opening external links",
Bindable = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning)
Current = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning)
},
new SettingsCheckbox
{
LabelText = "Prefer downloads without video",
Keywords = new[] { "no-video" },
Bindable = config.GetBindable<bool>(OsuSetting.PreferNoVideo)
Current = config.GetBindable<bool>(OsuSetting.PreferNoVideo)
},
};
}

View File

@ -47,29 +47,29 @@ namespace osu.Game.Overlays.Settings.Sections
new SettingsSlider<float, SizeSlider>
{
LabelText = "Menu cursor size",
Bindable = config.GetBindable<float>(OsuSetting.MenuCursorSize),
Current = config.GetBindable<float>(OsuSetting.MenuCursorSize),
KeyboardStep = 0.01f
},
new SettingsSlider<float, SizeSlider>
{
LabelText = "Gameplay cursor size",
Bindable = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
Current = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
LabelText = "Adjust gameplay cursor size based on current beatmap",
Bindable = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
},
new SettingsCheckbox
{
LabelText = "Beatmap skins",
Bindable = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
},
new SettingsCheckbox
{
LabelText = "Beatmap hitsounds",
Bindable = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds)
Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds)
},
};
@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Settings.Sections
config.BindWith(OsuSetting.Skin, configBindable);
skinDropdown.Bindable = dropdownBindable;
skinDropdown.Current = dropdownBindable;
skinDropdown.Items = skins.GetAllUsableSkins().ToArray();
// Todo: This should not be necessary when OsuConfigManager is databased

View File

@ -21,7 +21,7 @@ using osuTK;
namespace osu.Game.Overlays.Settings
{
public abstract class SettingsItem<T> : Container, IFilterable, ISettingsItem
public abstract class SettingsItem<T> : Container, IFilterable, ISettingsItem, IHasCurrentValue<T>
{
protected abstract Drawable CreateControl();
@ -54,7 +54,14 @@ namespace osu.Game.Overlays.Settings
}
}
public virtual Bindable<T> Bindable
[Obsolete("Use Current instead")] // Can be removed 20210406
public Bindable<T> Bindable
{
get => Current;
set => Current = value;
}
public virtual Bindable<T> Current
{
get => controlWithCurrent.Current;
set => controlWithCurrent.Current = value;

View File

@ -40,17 +40,28 @@ namespace osu.Game.Rulesets.Edit
Playfield.DisplayJudgements.Value = false;
}
[Resolved(canBeNull: true)]
private IEditorChangeHandler changeHandler { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
beatmap.HitObjectAdded += addHitObject;
beatmap.HitObjectUpdated += updateReplay;
beatmap.HitObjectRemoved += removeHitObject;
if (changeHandler != null)
{
// for now only regenerate replay on a finalised state change, not HitObjectUpdated.
changeHandler.OnStateChange += updateReplay;
}
else
{
beatmap.HitObjectUpdated += _ => updateReplay();
}
}
private void updateReplay(HitObject obj = null) =>
drawableRuleset.RegenerateAutoplay();
private void updateReplay() => drawableRuleset.RegenerateAutoplay();
private void addHitObject(HitObject hitObject)
{
@ -58,8 +69,6 @@ namespace osu.Game.Rulesets.Edit
drawableRuleset.Playfield.Add(drawableObject);
drawableRuleset.Playfield.PostProcess();
updateReplay();
}
private void removeHitObject(HitObject hitObject)
@ -68,8 +77,6 @@ namespace osu.Game.Rulesets.Edit
drawableRuleset.Playfield.Remove(drawableObject);
drawableRuleset.Playfield.PostProcess();
drawableRuleset.RegenerateAutoplay();
}
public override bool PropagatePositionalInputSubTree => false;

View File

@ -109,40 +109,40 @@ namespace osu.Game.Rulesets.Judgements
return 0;
case HitResult.SmallTickHit:
return DEFAULT_MAX_HEALTH_INCREASE * 0.05;
return DEFAULT_MAX_HEALTH_INCREASE * 0.5;
case HitResult.SmallTickMiss:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.05;
return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
case HitResult.LargeTickHit:
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
return DEFAULT_MAX_HEALTH_INCREASE;
case HitResult.LargeTickMiss:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.1;
return -DEFAULT_MAX_HEALTH_INCREASE;
case HitResult.Miss:
return -DEFAULT_MAX_HEALTH_INCREASE;
case HitResult.Meh:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
return -DEFAULT_MAX_HEALTH_INCREASE * 0.05;
case HitResult.Ok:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.3;
return DEFAULT_MAX_HEALTH_INCREASE * 0.5;
case HitResult.Good:
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
return DEFAULT_MAX_HEALTH_INCREASE * 0.75;
case HitResult.Great:
return DEFAULT_MAX_HEALTH_INCREASE * 0.8;
case HitResult.Perfect:
return DEFAULT_MAX_HEALTH_INCREASE;
case HitResult.Perfect:
return DEFAULT_MAX_HEALTH_INCREASE * 1.05;
case HitResult.SmallBonus:
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
return DEFAULT_MAX_HEALTH_INCREASE * 0.5;
case HitResult.LargeBonus:
return DEFAULT_MAX_HEALTH_INCREASE * 0.2;
return DEFAULT_MAX_HEALTH_INCREASE;
}
}

View File

@ -385,9 +385,14 @@ namespace osu.Game.Rulesets.Objects.Drawables
}
/// <summary>
/// Stops playback of all samples. Automatically called when <see cref="DrawableHitObject{TObject}"/>'s lifetime has been exceeded.
/// Stops playback of all relevant samples. Generally only looping samples should be stopped by this, and the rest let to play out.
/// Automatically called when <see cref="DrawableHitObject{TObject}"/>'s lifetime has been exceeded.
/// </summary>
public virtual void StopAllSamples() => Samples?.Stop();
public virtual void StopAllSamples()
{
if (Samples?.Looping == true)
Samples.Stop();
}
protected override void Update()
{
@ -457,6 +462,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
foreach (var nested in NestedHitObjects)
nested.OnKilled();
// failsafe to ensure looping samples don't get stuck in a playing state.
// this could occur in a non-frame-stable context where DrawableHitObjects get killed before a SkinnableSound has the chance to be stopped.
StopAllSamples();
UpdateResult(false);
@ -506,19 +513,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
Result.TimeOffset = Math.Min(HitObject.HitWindows.WindowFor(HitResult.Miss), Time.Current - endTime);
switch (Result.Type)
{
case HitResult.None:
break;
case HitResult.Miss:
updateState(ArmedState.Miss);
break;
default:
updateState(ArmedState.Hit);
break;
}
if (Result.HasResult)
updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss);
OnNewResult?.Invoke(this, Result);
}

View File

@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Objects
/// <summary>
/// The hit windows for this <see cref="HitObject"/>.
/// </summary>
[JsonIgnore]
public HitWindows HitWindows { get; set; }
private readonly List<HitObject> nestedHitObjects = new List<HitObject>();

View File

@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.UI
public GameplayClock GameplayClock => stabilityGameplayClock;
[Cached(typeof(GameplayClock))]
[Cached(typeof(ISamplePlaybackDisabler))]
private readonly StabilityGameplayClock stabilityGameplayClock;
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
@ -58,13 +59,16 @@ namespace osu.Game.Rulesets.UI
private int direction;
[BackgroundDependencyLoader(true)]
private void load(GameplayClock clock)
private void load(GameplayClock clock, ISamplePlaybackDisabler sampleDisabler)
{
if (clock != null)
{
parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock;
GameplayClock.IsPaused.BindTo(clock.IsPaused);
}
// this is a bit temporary. should really be done inside of GameplayClock (but requires large structural changes).
stabilityGameplayClock.ParentSampleDisabler = sampleDisabler;
}
protected override void LoadComplete()
@ -206,12 +210,16 @@ namespace osu.Game.Rulesets.UI
}
private void setClock()
{
if (parentGameplayClock == null)
{
// in case a parent gameplay clock isn't available, just use the parent clock.
parentGameplayClock ??= Clock;
}
else
{
Clock = GameplayClock;
ProcessCustomClock = false;
}
}
public ReplayInputHandler ReplayInputHandler { get; set; }
@ -220,6 +228,8 @@ namespace osu.Game.Rulesets.UI
{
public GameplayClock ParentGameplayClock;
public ISamplePlaybackDisabler ParentSampleDisabler;
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<Bindable<double>>();
public StabilityGameplayClock(FramedClock underlyingClock)
@ -227,7 +237,11 @@ namespace osu.Game.Rulesets.UI
{
}
public override bool IsSeeking => ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200;
protected override bool ShouldDisableSamplePlayback =>
// handle the case where playback is catching up to real-time.
base.ShouldDisableSamplePlayback
|| ParentSampleDisabler?.SamplePlaybackDisabled.Value == true
|| (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200);
}
}
}

Some files were not shown because too many files have changed in this diff Show More