mirror of
https://github.com/ppy/osu.git
synced 2026-05-18 12:40:18 +08:00
Compare commits
45 Commits
@@ -82,6 +82,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
|
||||
AddMouseMoveStep(-100, 100);
|
||||
addVertexCheckStep(3, 1, times[0], positions[0]);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -100,6 +101,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddMouseMoveStep(times[2] - 50, positions[2] - 50);
|
||||
addVertexCheckStep(4, 1, times[1] - 50, positions[1] - 50);
|
||||
addVertexCheckStep(4, 2, times[2] - 50, positions[2] - 50);
|
||||
|
||||
AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft));
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
addDragStartStep(times[1], positions[1]);
|
||||
AddMouseMoveStep(times[1], 400);
|
||||
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -129,6 +134,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddStep("scroll playfield", () => manualClock.CurrentTime += 200);
|
||||
AddMouseMoveStep(times[1] + 200, positions[1] + 100);
|
||||
addVertexCheckStep(2, 1, times[1] + 200, positions[1] + 100);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -161,18 +167,18 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
addAddVertexSteps(500, 150);
|
||||
addVertexCheckStep(3, 1, 500, 150);
|
||||
|
||||
addAddVertexSteps(90, 200);
|
||||
addVertexCheckStep(4, 1, times[0], positions[0]);
|
||||
addAddVertexSteps(160, 200);
|
||||
addVertexCheckStep(4, 1, 160, 200);
|
||||
|
||||
addAddVertexSteps(750, 180);
|
||||
addVertexCheckStep(5, 4, 750, 180);
|
||||
addVertexCheckStep(5, 4, 800, 160);
|
||||
AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteVertex()
|
||||
{
|
||||
double[] times = { 100, 300, 500 };
|
||||
double[] times = { 100, 300, 400 };
|
||||
float[] positions = { 100, 200, 150 };
|
||||
addBlueprintStep(times, positions);
|
||||
|
||||
@@ -265,7 +271,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddStep("delete vertex", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
@@ -42,6 +43,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
[Resolved]
|
||||
private IBeatSnapProvider? beatSnapProvider { get; set; }
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap? EditorBeatmap { get; private set; }
|
||||
|
||||
protected EditablePath(Func<float, double> positionToTime)
|
||||
{
|
||||
PositionToTime = positionToTime;
|
||||
@@ -103,15 +107,23 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
//
|
||||
// The value is clamped here by the bindable min and max values.
|
||||
// In case the required velocity is too large, the path is not preserved.
|
||||
double previousVelocity = svBindable.Value;
|
||||
svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor);
|
||||
|
||||
path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity);
|
||||
// adjust velocity locally, so that once the SV change is applied by applying defaults
|
||||
// (triggered by `EditorBeatmap.Update()` call at end of method),
|
||||
// it results in the outcome desired by the user.
|
||||
double relativeChange = svBindable.Value / previousVelocity;
|
||||
double localVelocity = hitObject.Velocity * relativeChange;
|
||||
path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, localVelocity);
|
||||
|
||||
if (beatSnapProvider == null) return;
|
||||
|
||||
double endTime = hitObject.StartTime + path.Duration;
|
||||
double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime);
|
||||
hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity;
|
||||
hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * localVelocity;
|
||||
|
||||
EditorBeatmap?.Update(hitObject);
|
||||
}
|
||||
|
||||
public Vector2 ToRelativePosition(Vector2 screenSpacePosition)
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -19,22 +18,27 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
public MenuItem[] ContextMenuItems => getContextMenuItems().ToArray();
|
||||
|
||||
private readonly JuiceStream juiceStream;
|
||||
|
||||
// To handle when the editor is scrolled while dragging.
|
||||
private Vector2 dragStartPosition;
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
public SelectionEditablePath(Func<float, double> positionToTime)
|
||||
public SelectionEditablePath(JuiceStream juiceStream, Func<float, double> positionToTime)
|
||||
: base(positionToTime)
|
||||
{
|
||||
this.juiceStream = juiceStream;
|
||||
}
|
||||
|
||||
public void AddVertex(Vector2 relativePosition)
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
|
||||
double time = Math.Max(0, PositionToTime(relativePosition.Y));
|
||||
int index = AddVertex(time, relativePosition.X);
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
selectOnly(index);
|
||||
|
||||
EditorBeatmap?.EndChange();
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
||||
@@ -45,9 +49,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
if (index == -1 || VertexStates[index].IsFixed)
|
||||
return false;
|
||||
|
||||
if (e.Button == MouseButton.Left && e.ShiftPressed)
|
||||
if (e.Button == MouseButton.Right && e.ShiftPressed)
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
RemoveVertex(index);
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
EditorBeatmap?.EndChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -74,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
for (int i = 0; i < VertexCount; i++)
|
||||
VertexStates[i].VertexBeforeChange = Vertices[i];
|
||||
|
||||
changeHandler?.BeginChange();
|
||||
EditorBeatmap?.BeginChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -88,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
changeHandler?.EndChange();
|
||||
EditorBeatmap?.EndChange();
|
||||
}
|
||||
|
||||
private int getMouseTargetVertex(Vector2 screenSpacePosition)
|
||||
@@ -118,11 +126,17 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
private void deleteSelectedVertices()
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
|
||||
for (int i = VertexCount - 1; i >= 0; i--)
|
||||
{
|
||||
if (VertexStates[i].IsSelected)
|
||||
RemoveVertex(i);
|
||||
}
|
||||
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
|
||||
EditorBeatmap?.EndChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
|
||||
@@ -12,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
public partial class VertexPiece : Circle
|
||||
{
|
||||
private VertexState state = new VertexState();
|
||||
|
||||
[Resolved]
|
||||
private OsuColour osuColour { get; set; } = null!;
|
||||
|
||||
@@ -24,7 +27,32 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
public void UpdateFrom(VertexState state)
|
||||
{
|
||||
Colour = state.IsSelected ? osuColour.Yellow.Lighten(1) : osuColour.Yellow;
|
||||
this.state = state;
|
||||
updateMarkerDisplay();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateMarkerDisplay();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateMarkerDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the circular control point marker.
|
||||
/// </summary>
|
||||
private void updateMarkerDisplay()
|
||||
{
|
||||
var colour = osuColour.Yellow;
|
||||
|
||||
if (IsHovered || state.IsSelected)
|
||||
colour = colour.Lighten(1);
|
||||
|
||||
Colour = colour;
|
||||
Alpha = state.IsFixed ? 0.5f : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
{
|
||||
scrollingPath = new ScrollingPath(),
|
||||
nestedOutlineContainer = new NestedOutlineContainer(),
|
||||
editablePath = new SelectionEditablePath(positionToTime)
|
||||
editablePath = new SelectionEditablePath(hitObject, positionToTime)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation
|
||||
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation, IHasTimePreempt
|
||||
{
|
||||
public const float OBJECT_RADIUS = 64;
|
||||
|
||||
|
||||
@@ -5,19 +5,23 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public partial class DrawableOsuJudgement : DrawableJudgement
|
||||
{
|
||||
internal Color4 AccentColour { get; private set; }
|
||||
|
||||
internal SkinnableLighting Lighting { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
private bool positionTransferred;
|
||||
private Vector2 screenSpacePosition;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -32,37 +36,36 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
});
|
||||
}
|
||||
|
||||
public override void Apply(JudgementResult result, DrawableHitObject? judgedObject)
|
||||
{
|
||||
base.Apply(result, judgedObject);
|
||||
|
||||
if (judgedObject is not DrawableOsuHitObject osuObject)
|
||||
return;
|
||||
|
||||
AccentColour = osuObject.AccentColour.Value;
|
||||
|
||||
switch (osuObject)
|
||||
{
|
||||
case DrawableSlider slider:
|
||||
screenSpacePosition = slider.TailCircle.ToScreenSpace(slider.TailCircle.OriginPosition);
|
||||
break;
|
||||
|
||||
default:
|
||||
screenSpacePosition = osuObject.ToScreenSpace(osuObject.OriginPosition);
|
||||
break;
|
||||
}
|
||||
|
||||
Scale = new Vector2(osuObject.HitObject.Scale);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Lighting.ResetAnimation();
|
||||
Lighting.SetColourFrom(JudgedObject, Result);
|
||||
|
||||
positionTransferred = false;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse)
|
||||
{
|
||||
switch (osuObject)
|
||||
{
|
||||
case DrawableSlider slider:
|
||||
Position = slider.TailCircle.ToSpaceOfOtherDrawable(slider.TailCircle.OriginPosition, Parent!);
|
||||
break;
|
||||
|
||||
default:
|
||||
Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!);
|
||||
break;
|
||||
}
|
||||
|
||||
positionTransferred = true;
|
||||
|
||||
Scale = new Vector2(osuObject.HitObject.Scale);
|
||||
}
|
||||
Lighting.SetColourFrom(this, Result);
|
||||
Position = Parent!.ToLocalSpace(screenSpacePosition);
|
||||
}
|
||||
|
||||
protected override void ApplyHitAnimations()
|
||||
|
||||
@@ -1,8 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
@@ -12,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
internal partial class SkinnableLighting : SkinnableSprite
|
||||
{
|
||||
private DrawableHitObject targetObject;
|
||||
private JudgementResult targetResult;
|
||||
private DrawableOsuJudgement? targetJudgement;
|
||||
private JudgementResult? targetResult;
|
||||
|
||||
public SkinnableLighting()
|
||||
: base("lighting")
|
||||
@@ -29,11 +27,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
/// <summary>
|
||||
/// Updates the lighting colour from a given hitobject and result.
|
||||
/// </summary>
|
||||
/// <param name="targetObject">The <see cref="DrawableHitObject"/> that's been judged.</param>
|
||||
/// <param name="targetResult">The <see cref="JudgementResult"/> that <paramref name="targetObject"/> was judged with.</param>
|
||||
public void SetColourFrom(DrawableHitObject targetObject, JudgementResult targetResult)
|
||||
/// <param name="targetJudgement">The <see cref="DrawableHitObject"/> that's been judged.</param>
|
||||
/// <param name="targetResult">The <see cref="JudgementResult"/> that <paramref name="targetJudgement"/> was judged with.</param>
|
||||
public void SetColourFrom(DrawableOsuJudgement targetJudgement, JudgementResult? targetResult)
|
||||
{
|
||||
this.targetObject = targetObject;
|
||||
this.targetJudgement = targetJudgement;
|
||||
this.targetResult = targetResult;
|
||||
|
||||
updateColour();
|
||||
@@ -41,10 +39,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
if (targetObject == null || targetResult == null)
|
||||
if (targetJudgement == null || targetResult == null)
|
||||
Colour = Color4.White;
|
||||
else
|
||||
Colour = targetResult.IsHit ? targetObject.AccentColour.Value : Color4.Transparent;
|
||||
Colour = targetResult.IsHit ? targetJudgement.AccentColour : Color4.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
|
||||
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition, IHasTimePreempt
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius of hit objects (ie. the radius of a <see cref="HitCircle"/>).
|
||||
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public const double PREEMPT_MAX = 1800;
|
||||
|
||||
public double TimePreempt = 600;
|
||||
public double TimePreempt { get; set; } = 600;
|
||||
public double TimeFadeIn = 400;
|
||||
|
||||
private HitObjectProperty<Vector2> position;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new Note { StartTime = 1000 },
|
||||
}
|
||||
});
|
||||
|
||||
@@ -67,8 +67,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 2000 },
|
||||
}
|
||||
});
|
||||
|
||||
@@ -136,8 +136,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 5000 },
|
||||
}
|
||||
});
|
||||
|
||||
@@ -164,8 +164,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -197,9 +197,9 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 5000 },
|
||||
new Note { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -232,8 +232,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1100 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
new Note { StartTime = 1100 },
|
||||
new Note { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -264,8 +264,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -299,9 +299,9 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 5000 },
|
||||
new Note { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -334,8 +334,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -366,8 +366,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 2000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -393,8 +393,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 2000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -447,8 +447,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 10000 },
|
||||
new HitCircle { StartTime = 11000 },
|
||||
new Note { StartTime = 10000 },
|
||||
new Note { StartTime = 11000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -474,8 +474,8 @@ namespace osu.Game.Tests.Editing
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 10000 },
|
||||
new HitCircle { StartTime = 11000 },
|
||||
new Note { StartTime = 10000 },
|
||||
new Note { StartTime = 11000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
@@ -489,5 +489,55 @@ namespace osu.Game.Tests.Editing
|
||||
|
||||
Assert.That(beatmap.Breaks, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTimePreemptIsRespected()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
Difficulty =
|
||||
{
|
||||
ApproachRate = 10,
|
||||
},
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var ho in beatmap.HitObjects)
|
||||
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MIN));
|
||||
});
|
||||
|
||||
beatmap.Difficulty.ApproachRate = 0;
|
||||
|
||||
foreach (var ho in beatmap.HitObjects)
|
||||
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MAX));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,6 +402,70 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PopoverForMultipleSelectionChangesAllSamples()
|
||||
{
|
||||
AddStep("add slider", () =>
|
||||
{
|
||||
EditorBeatmap.Add(new Slider
|
||||
{
|
||||
Position = new Vector2(256, 256),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
|
||||
Samples =
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
},
|
||||
NodeSamples = new List<IList<HitSampleInfo>>
|
||||
{
|
||||
new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM),
|
||||
},
|
||||
new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT),
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||
|
||||
clickSamplePiece(0);
|
||||
|
||||
setBankViaPopover(HitSampleInfo.BANK_DRUM);
|
||||
samplePopoverHasSingleBank(HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSampleNormalBank(2, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectNodeHasSampleNormalBank(2, 0, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM);
|
||||
|
||||
setVolumeViaPopover(30);
|
||||
samplePopoverHasSingleVolume(30);
|
||||
hitObjectHasSampleVolume(0, 30);
|
||||
hitObjectHasSampleVolume(1, 30);
|
||||
hitObjectHasSampleVolume(2, 30);
|
||||
hitObjectNodeHasSampleVolume(2, 0, 30);
|
||||
hitObjectNodeHasSampleVolume(2, 1, 30);
|
||||
|
||||
toggleAdditionViaPopover(0);
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
|
||||
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
|
||||
setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSampleAdditionBank(2, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectNodeHasSampleAdditionBank(2, 0, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHotkeysAffectNodeSamples()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneJudgementContainer : OsuTestScene
|
||||
{
|
||||
private JudgementContainer<DrawableOsuJudgement> judgementContainer = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUp()
|
||||
{
|
||||
AddStep("create judgement container", () => Child = judgementContainer = new JudgementContainer<DrawableOsuJudgement>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJudgementFromSameHitObjectIsRemoved()
|
||||
{
|
||||
DrawableHitCircle drawableHitCircle1 = null!;
|
||||
DrawableHitCircle drawableHitCircle2 = null!;
|
||||
|
||||
AddStep("create hit circles", () =>
|
||||
{
|
||||
Add(drawableHitCircle1 = new DrawableHitCircle(createHitCircle()));
|
||||
Add(drawableHitCircle2 = new DrawableHitCircle(createHitCircle()));
|
||||
});
|
||||
|
||||
int judgementCount = 0;
|
||||
|
||||
AddStep("judge the same hitobject twice via different drawables", () =>
|
||||
{
|
||||
addDrawableJudgement(drawableHitCircle1);
|
||||
drawableHitCircle2.Apply(drawableHitCircle1.HitObject);
|
||||
addDrawableJudgement(drawableHitCircle2);
|
||||
judgementCount = judgementContainer.Count;
|
||||
});
|
||||
|
||||
AddAssert("one judgement in container", () => judgementCount, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJudgementFromDifferentHitObjectIsNotRemoved()
|
||||
{
|
||||
DrawableHitCircle drawableHitCircle = null!;
|
||||
|
||||
AddStep("create hit circle", () => Add(drawableHitCircle = new DrawableHitCircle(createHitCircle())));
|
||||
|
||||
int judgementCount = 0;
|
||||
|
||||
AddStep("judge two hitobjects via the same drawable", () =>
|
||||
{
|
||||
addDrawableJudgement(drawableHitCircle);
|
||||
drawableHitCircle.Apply(createHitCircle());
|
||||
addDrawableJudgement(drawableHitCircle);
|
||||
judgementCount = judgementContainer.Count;
|
||||
});
|
||||
|
||||
AddAssert("two judgements in container", () => judgementCount, () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
private void addDrawableJudgement(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
var judgement = new DrawableOsuJudgement();
|
||||
|
||||
judgement.Apply(new JudgementResult(drawableHitObject.HitObject, new OsuJudgement())
|
||||
{
|
||||
Type = HitResult.Great,
|
||||
TimeOffset = Time.Current
|
||||
}, drawableHitObject);
|
||||
|
||||
judgementContainer.Add(judgement);
|
||||
}
|
||||
|
||||
private HitCircle createHitCircle()
|
||||
{
|
||||
var circle = new HitCircle();
|
||||
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
return circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,87 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("complete request", () => pendingRequest.TriggerSuccess(TEST_USER));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomColourScheme()
|
||||
{
|
||||
int hue = 0;
|
||||
|
||||
AddSliderStep("hue", 0, 360, 222, h => hue = h);
|
||||
|
||||
AddStep("set up request handling", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = req =>
|
||||
{
|
||||
if (req is GetUserRequest getUserRequest)
|
||||
{
|
||||
getUserRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = $"Colorful #{hue}",
|
||||
Id = 1,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||
ProfileHue = hue,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomColourSchemeWithReload()
|
||||
{
|
||||
int hue = 0;
|
||||
GetUserRequest pendingRequest = null!;
|
||||
|
||||
AddSliderStep("hue", 0, 360, 222, h => hue = h);
|
||||
|
||||
AddStep("set up request handling", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = req =>
|
||||
{
|
||||
if (req is GetUserRequest getUserRequest)
|
||||
{
|
||||
pendingRequest = getUserRequest;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 }));
|
||||
|
||||
AddWaitStep("wait some", 3);
|
||||
AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = $"Colorful #{hue}",
|
||||
Id = 1,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||
ProfileHue = hue,
|
||||
}));
|
||||
|
||||
int hue2 = 0;
|
||||
|
||||
AddSliderStep("hue 2", 0, 360, 50, h => hue2 = h);
|
||||
AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 }));
|
||||
AddWaitStep("wait some", 3);
|
||||
|
||||
AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = $"Colorful #{hue2}",
|
||||
Id = 1,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||
ProfileHue = hue2,
|
||||
}));
|
||||
}
|
||||
|
||||
public static readonly APIUser TEST_USER = new APIUser
|
||||
{
|
||||
Username = @"Somebody",
|
||||
|
||||
@@ -201,6 +201,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"playmode")]
|
||||
public string PlayMode;
|
||||
|
||||
[JsonProperty(@"profile_hue")]
|
||||
public int? ProfileHue;
|
||||
|
||||
[JsonProperty(@"profile_order")]
|
||||
public string[] ProfileOrder;
|
||||
|
||||
|
||||
@@ -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.Diagnostics.CodeAnalysis;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -22,7 +23,7 @@ namespace osu.Game.Overlays
|
||||
public virtual LocalisableString Title => Header.Title.Title;
|
||||
public virtual LocalisableString Description => Header.Title.Description;
|
||||
|
||||
public T Header { get; }
|
||||
public T Header { get; private set; }
|
||||
|
||||
protected virtual Color4 BackgroundColour => ColourProvider.Background5;
|
||||
|
||||
@@ -34,11 +35,12 @@ namespace osu.Game.Overlays
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly Container content;
|
||||
|
||||
protected FullscreenOverlay(OverlayColourScheme colourScheme)
|
||||
{
|
||||
Header = CreateHeader();
|
||||
RecreateHeader();
|
||||
|
||||
ColourProvider = new OverlayColourProvider(colourScheme);
|
||||
|
||||
@@ -60,10 +62,9 @@ namespace osu.Game.Overlays
|
||||
|
||||
base.Content.AddRange(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = BackgroundColour
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
@@ -75,14 +76,17 @@ namespace osu.Game.Overlays
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Waves.FirstWaveColour = ColourProvider.Light4;
|
||||
Waves.SecondWaveColour = ColourProvider.Light3;
|
||||
Waves.ThirdWaveColour = ColourProvider.Dark4;
|
||||
Waves.FourthWaveColour = ColourProvider.Dark3;
|
||||
UpdateColours();
|
||||
}
|
||||
|
||||
protected abstract T CreateHeader();
|
||||
|
||||
[MemberNotNull(nameof(Header))]
|
||||
protected void RecreateHeader()
|
||||
{
|
||||
Header = CreateHeader();
|
||||
}
|
||||
|
||||
public override void Show()
|
||||
{
|
||||
if (State.Value == Visibility.Visible)
|
||||
@@ -96,6 +100,18 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the colours of the background and the top waves with the latest colour shades provided by <see cref="ColourProvider"/>.
|
||||
/// </summary>
|
||||
protected void UpdateColours()
|
||||
{
|
||||
Waves.FirstWaveColour = ColourProvider.Light4;
|
||||
Waves.SecondWaveColour = ColourProvider.Light3;
|
||||
Waves.ThirdWaveColour = ColourProvider.Dark4;
|
||||
Waves.FourthWaveColour = ColourProvider.Dark3;
|
||||
background.Colour = BackgroundColour;
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
// 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 osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class OverlayColourProvider
|
||||
{
|
||||
public OverlayColourScheme ColourScheme { get; private set; }
|
||||
/// <summary>
|
||||
/// The hue degree associated with the colour shades provided by this <see cref="OverlayColourProvider"/>.
|
||||
/// </summary>
|
||||
public int Hue { get; private set; }
|
||||
|
||||
public OverlayColourProvider(OverlayColourScheme colourScheme)
|
||||
: this(colourScheme.GetHue())
|
||||
{
|
||||
ColourScheme = colourScheme;
|
||||
}
|
||||
|
||||
public OverlayColourProvider(int hue)
|
||||
{
|
||||
Hue = hue;
|
||||
}
|
||||
|
||||
// Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`.
|
||||
@@ -48,65 +54,19 @@ namespace osu.Game.Overlays
|
||||
public Color4 Background6 => getColour(0.1f, 0.1f);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the value of <see cref="ColourScheme"/> to a different colour scheme.
|
||||
/// Changes the <see cref="Hue"/> to a different degree.
|
||||
/// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually.
|
||||
/// </summary>
|
||||
/// <param name="colourScheme">The proposed colour scheme.</param>
|
||||
public void ChangeColourScheme(OverlayColourScheme colourScheme)
|
||||
{
|
||||
ColourScheme = colourScheme;
|
||||
}
|
||||
public void ChangeColourScheme(OverlayColourScheme colourScheme) => ChangeColourScheme(colourScheme.GetHue());
|
||||
|
||||
private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1));
|
||||
/// <summary>
|
||||
/// Changes the <see cref="Hue"/> to a different degree.
|
||||
/// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually.
|
||||
/// </summary>
|
||||
/// <param name="hue">The proposed hue degree.</param>
|
||||
public void ChangeColourScheme(int hue) => Hue = hue;
|
||||
|
||||
// See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628
|
||||
private static float getBaseHue(OverlayColourScheme colourScheme)
|
||||
{
|
||||
switch (colourScheme)
|
||||
{
|
||||
default:
|
||||
throw new ArgumentException($@"{colourScheme} colour scheme does not provide a hue value in {nameof(getBaseHue)}.");
|
||||
|
||||
case OverlayColourScheme.Red:
|
||||
return 0;
|
||||
|
||||
case OverlayColourScheme.Pink:
|
||||
return 333 / 360f;
|
||||
|
||||
case OverlayColourScheme.Orange:
|
||||
return 45 / 360f;
|
||||
|
||||
case OverlayColourScheme.Lime:
|
||||
return 90 / 360f;
|
||||
|
||||
case OverlayColourScheme.Green:
|
||||
return 125 / 360f;
|
||||
|
||||
case OverlayColourScheme.Aquamarine:
|
||||
return 160 / 360f;
|
||||
|
||||
case OverlayColourScheme.Purple:
|
||||
return 255 / 360f;
|
||||
|
||||
case OverlayColourScheme.Blue:
|
||||
return 200 / 360f;
|
||||
|
||||
case OverlayColourScheme.Plum:
|
||||
return 320 / 360f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum OverlayColourScheme
|
||||
{
|
||||
Red,
|
||||
Pink,
|
||||
Orange,
|
||||
Lime,
|
||||
Green,
|
||||
Purple,
|
||||
Blue,
|
||||
Plum,
|
||||
Aquamarine
|
||||
private Color4 getColour(float saturation, float lightness) => Framework.Graphics.Colour4.FromHSL(Hue / 360f, saturation, lightness);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public enum OverlayColourScheme
|
||||
{
|
||||
Red,
|
||||
Orange,
|
||||
Lime,
|
||||
Green,
|
||||
Aquamarine,
|
||||
Blue,
|
||||
Purple,
|
||||
Plum,
|
||||
Pink,
|
||||
}
|
||||
|
||||
public static class OverlayColourSchemeExtensions
|
||||
{
|
||||
public static int GetHue(this OverlayColourScheme colourScheme)
|
||||
{
|
||||
// See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628
|
||||
switch (colourScheme)
|
||||
{
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(colourScheme));
|
||||
|
||||
case OverlayColourScheme.Red:
|
||||
return 0;
|
||||
|
||||
case OverlayColourScheme.Orange:
|
||||
return 45;
|
||||
|
||||
case OverlayColourScheme.Lime:
|
||||
return 90;
|
||||
|
||||
case OverlayColourScheme.Green:
|
||||
return 125;
|
||||
|
||||
case OverlayColourScheme.Aquamarine:
|
||||
return 160;
|
||||
|
||||
case OverlayColourScheme.Blue:
|
||||
return 200;
|
||||
|
||||
case OverlayColourScheme.Purple:
|
||||
return 255;
|
||||
|
||||
case OverlayColourScheme.Plum:
|
||||
return 320;
|
||||
|
||||
case OverlayColourScheme.Pink:
|
||||
return 333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,6 @@ namespace osu.Game.Overlays
|
||||
sectionsContainer.ExpandableHeader = null;
|
||||
|
||||
userReq?.Cancel();
|
||||
Clear();
|
||||
lastSection = null;
|
||||
|
||||
sections = !user.IsBot
|
||||
@@ -119,20 +118,67 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
: Array.Empty<ProfileSection>();
|
||||
|
||||
tabs = new ProfileSectionTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
};
|
||||
changeOverlayColours(OverlayColourScheme.Pink.GetHue());
|
||||
recreateBaseContent();
|
||||
|
||||
Add(new OsuContextMenuContainer
|
||||
if (API.State.Value != APIState.Offline)
|
||||
{
|
||||
userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset);
|
||||
userReq.Success += u => userLoadComplete(u, ruleset);
|
||||
|
||||
API.Queue(userReq);
|
||||
loadingLayer.Show();
|
||||
}
|
||||
}
|
||||
|
||||
private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset)
|
||||
{
|
||||
Debug.Assert(sections != null && sectionsContainer != null && tabs != null);
|
||||
|
||||
// reuse header and content if same colour scheme, otherwise recreate both.
|
||||
int profileHue = loadedUser.ProfileHue ?? OverlayColourScheme.Pink.GetHue();
|
||||
|
||||
if (changeOverlayColours(profileHue))
|
||||
recreateBaseContent();
|
||||
|
||||
var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull();
|
||||
|
||||
var userProfile = new UserProfileData(loadedUser, actualRuleset);
|
||||
Header.User.Value = userProfile;
|
||||
|
||||
if (loadedUser.ProfileOrder != null)
|
||||
{
|
||||
foreach (string id in loadedUser.ProfileOrder)
|
||||
{
|
||||
var sec = sections.FirstOrDefault(s => s.Identifier == id);
|
||||
|
||||
if (sec != null)
|
||||
{
|
||||
sec.User.Value = userProfile;
|
||||
|
||||
sectionsContainer.Add(sec);
|
||||
tabs.AddItem(sec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadingLayer.Hide();
|
||||
}
|
||||
|
||||
private void recreateBaseContent()
|
||||
{
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = sectionsContainer = new ProfileSectionsContainer
|
||||
{
|
||||
ExpandableHeader = Header,
|
||||
FixedHeader = tabs,
|
||||
FixedHeader = tabs = new ProfileSectionTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
HeaderBackground = new Box
|
||||
{
|
||||
// this is only visible as the ProfileTabControl background
|
||||
@@ -140,7 +186,7 @@ namespace osu.Game.Overlays
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
sectionsContainer.SelectedSection.ValueChanged += section =>
|
||||
{
|
||||
@@ -167,45 +213,18 @@ namespace osu.Game.Overlays
|
||||
sectionsContainer.ScrollTo(lastSection);
|
||||
}
|
||||
};
|
||||
|
||||
sectionsContainer.ScrollToTop();
|
||||
|
||||
if (API.State.Value != APIState.Offline)
|
||||
{
|
||||
userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset);
|
||||
userReq.Success += u => userLoadComplete(u, ruleset);
|
||||
|
||||
API.Queue(userReq);
|
||||
loadingLayer.Show();
|
||||
}
|
||||
}
|
||||
|
||||
private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset)
|
||||
private bool changeOverlayColours(int hue)
|
||||
{
|
||||
Debug.Assert(sections != null && sectionsContainer != null && tabs != null);
|
||||
if (hue == ColourProvider.Hue)
|
||||
return false;
|
||||
|
||||
var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull();
|
||||
ColourProvider.ChangeColourScheme(hue);
|
||||
|
||||
var userProfile = new UserProfileData(loadedUser, actualRuleset);
|
||||
Header.User.Value = userProfile;
|
||||
|
||||
if (loadedUser.ProfileOrder != null)
|
||||
{
|
||||
foreach (string id in loadedUser.ProfileOrder)
|
||||
{
|
||||
var sec = sections.FirstOrDefault(s => s.Identifier == id);
|
||||
|
||||
if (sec != null)
|
||||
{
|
||||
sec.User.Value = userProfile;
|
||||
|
||||
sectionsContainer.Add(sec);
|
||||
tabs.AddItem(sec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadingLayer.Hide();
|
||||
RecreateHeader();
|
||||
UpdateColours();
|
||||
return true;
|
||||
}
|
||||
|
||||
private partial class ProfileSectionTabControl : OsuTabControl<ProfileSection>
|
||||
|
||||
@@ -305,7 +305,9 @@ namespace osu.Game.Rulesets.Edit
|
||||
PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT;
|
||||
}
|
||||
|
||||
composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position);
|
||||
composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position)
|
||||
&& !LeftToolbox.Contains(InputManager.CurrentState.Mouse.Position)
|
||||
&& !RightToolbox.Contains(InputManager.CurrentState.Mouse.Position);
|
||||
}
|
||||
|
||||
public override Playfield Playfield => drawableRulesetWrapper.Playfield;
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Judgements
|
||||
|
||||
public JudgementResult? Result { get; private set; }
|
||||
|
||||
public DrawableHitObject? JudgedObject { get; private set; }
|
||||
public HitObject? JudgedHitObject { get; private set; }
|
||||
|
||||
public override bool RemoveCompletedTransforms => false;
|
||||
|
||||
@@ -94,17 +95,17 @@ namespace osu.Game.Rulesets.Judgements
|
||||
/// </summary>
|
||||
/// <param name="result">The applicable judgement.</param>
|
||||
/// <param name="judgedObject">The drawable object.</param>
|
||||
public void Apply(JudgementResult result, DrawableHitObject? judgedObject)
|
||||
public virtual void Apply(JudgementResult result, DrawableHitObject? judgedObject)
|
||||
{
|
||||
Result = result;
|
||||
JudgedObject = judgedObject;
|
||||
JudgedHitObject = judgedObject?.HitObject;
|
||||
}
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
base.FreeAfterUse();
|
||||
|
||||
JudgedObject = null;
|
||||
JudgedHitObject = null;
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
|
||||
@@ -0,0 +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.
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="HitObject"/> that appears on screen at a fixed time interval before its <see cref="HitObject.StartTime"/>.
|
||||
/// </summary>
|
||||
public interface IHasTimePreempt
|
||||
{
|
||||
double TimePreempt { get; }
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
// remove any existing judgements for the judged object.
|
||||
// this can be the case when rewinding.
|
||||
RemoveAll(c => c.JudgedObject == judgement.JudgedObject, false);
|
||||
RemoveAll(c => c.JudgedHitObject == judgement.JudgedHitObject, false);
|
||||
|
||||
base.Add(judgement);
|
||||
}
|
||||
|
||||
@@ -34,12 +34,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
private readonly int nodeIndex;
|
||||
|
||||
protected override IList<HitSampleInfo> GetRelevantSamples(HitObject ho)
|
||||
protected override IEnumerable<(HitObject hitObject, IList<HitSampleInfo> samples)> GetRelevantSamples(HitObject[] hitObjects)
|
||||
{
|
||||
if (ho is not IHasRepeats hasRepeats)
|
||||
return ho.Samples;
|
||||
if (hitObjects.Length > 1 || hitObjects[0] is not IHasRepeats hasRepeats)
|
||||
return base.GetRelevantSamples(hitObjects);
|
||||
|
||||
return nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : ho.Samples;
|
||||
return [(hitObjects[0], nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : hitObjects[0].Samples)];
|
||||
}
|
||||
|
||||
public NodeSampleEditPopover(HitObject hitObject, int nodeIndex)
|
||||
|
||||
@@ -21,6 +21,7 @@ using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -106,15 +107,34 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
private FillFlowContainer togglesCollection = null!;
|
||||
|
||||
private HitObject[] relevantObjects = null!;
|
||||
private IList<HitSampleInfo>[] allRelevantSamples = null!;
|
||||
private (HitObject hitObject, IList<HitSampleInfo> samples)[] allRelevantSamples = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sub-set of samples relevant to this sample point piece.
|
||||
/// For example, to edit node samples this should return the samples at the index of the node.
|
||||
/// </summary>
|
||||
/// <param name="ho">The hit object to get the relevant samples from.</param>
|
||||
/// <param name="hitObjects">The hit objects to get the relevant samples from.</param>
|
||||
/// <returns>The relevant list of samples.</returns>
|
||||
protected virtual IList<HitSampleInfo> GetRelevantSamples(HitObject ho) => ho.Samples;
|
||||
protected virtual IEnumerable<(HitObject hitObject, IList<HitSampleInfo> samples)> GetRelevantSamples(HitObject[] hitObjects)
|
||||
{
|
||||
if (hitObjects.Length == 1)
|
||||
{
|
||||
yield return (hitObjects[0], hitObjects[0].Samples);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var ho in hitObjects)
|
||||
{
|
||||
yield return (ho, ho.Samples);
|
||||
|
||||
if (ho is IHasRepeats hasRepeats)
|
||||
{
|
||||
foreach (var node in hasRepeats.NodeSamples)
|
||||
yield return (ho, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private EditorBeatmap beatmap { get; set; } = null!;
|
||||
@@ -172,7 +192,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||
relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
||||
allRelevantSamples = relevantObjects.Select(GetRelevantSamples).ToArray();
|
||||
allRelevantSamples = GetRelevantSamples(relevantObjects).ToArray();
|
||||
|
||||
// even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value.
|
||||
int? commonVolume = getCommonVolume();
|
||||
@@ -214,9 +234,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) }));
|
||||
}
|
||||
|
||||
private string? getCommonBank() => allRelevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(allRelevantSamples.First()) : null;
|
||||
private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Where(o => o is not null).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null;
|
||||
private int? getCommonVolume() => allRelevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(allRelevantSamples.First()) : null;
|
||||
private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1
|
||||
? GetBankValue(allRelevantSamples.First().samples)
|
||||
: null;
|
||||
|
||||
private string? getCommonAdditionBank()
|
||||
{
|
||||
string[] additionBanks = allRelevantSamples.Select(h => GetAdditionBankValue(h.samples)).Where(o => o is not null).Cast<string>().Distinct().ToArray();
|
||||
return additionBanks.Length == 1 ? additionBanks[0] : null;
|
||||
}
|
||||
|
||||
private int? getCommonVolume() => allRelevantSamples.Select(h => GetVolumeValue(h.samples)).Distinct().Count() == 1
|
||||
? GetVolumeValue(allRelevantSamples.First().samples)
|
||||
: null;
|
||||
|
||||
private void updatePrimaryBankState()
|
||||
{
|
||||
@@ -231,7 +261,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty;
|
||||
additionBank.Current.Value = commonAdditionBank;
|
||||
|
||||
bool anyAdditions = allRelevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL));
|
||||
bool anyAdditions = allRelevantSamples.Any(o => o.samples.Any(s => s.Name != HitSampleInfo.HIT_NORMAL));
|
||||
if (anyAdditions)
|
||||
additionBank.Show();
|
||||
else
|
||||
@@ -247,9 +277,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
beatmap.BeginChange();
|
||||
|
||||
foreach (var relevantHitObject in relevantObjects)
|
||||
foreach (var (relevantHitObject, relevantSamples) in GetRelevantSamples(relevantObjects))
|
||||
{
|
||||
var relevantSamples = GetRelevantSamples(relevantHitObject);
|
||||
updateAction(relevantHitObject, relevantSamples);
|
||||
beatmap.Update(relevantHitObject);
|
||||
}
|
||||
@@ -333,7 +362,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
foreach ((string sampleName, var bindable) in selectionSampleStates)
|
||||
{
|
||||
bindable.Value = SelectionHandler<HitObject>.GetStateFromSelection(relevantObjects, h => GetRelevantSamples(h).Any(s => s.Name == sampleName));
|
||||
bindable.Value = SelectionHandler<HitObject>.GetStateFromSelection(GetRelevantSamples(relevantObjects), h => h.samples.Any(s => s.Name == sampleName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -100,10 +101,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
return base.OnDragStart(e);
|
||||
}
|
||||
|
||||
private float dragTimeAccumulated;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
if (IsDragged || hitObjectDragged)
|
||||
handleScrollViaDrag();
|
||||
else
|
||||
dragTimeAccumulated = 0;
|
||||
|
||||
if (Composer != null && timeline != null)
|
||||
{
|
||||
@@ -193,16 +198,42 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
private void handleScrollViaDrag()
|
||||
{
|
||||
// The amount of time dragging before we reach maximum drag speed.
|
||||
const float time_ramp_multiplier = 5000;
|
||||
|
||||
// A maximum drag speed to ensure things don't get out of hand.
|
||||
const float max_velocity = 10;
|
||||
|
||||
if (timeline == null) return;
|
||||
|
||||
var timelineQuad = timeline.ScreenSpaceDrawQuad;
|
||||
float mouseX = InputManager.CurrentState.Mouse.Position.X;
|
||||
var mousePos = timeline.ToLocalSpace(InputManager.CurrentState.Mouse.Position);
|
||||
|
||||
// scroll if in a drag and dragging outside visible extents
|
||||
if (mouseX > timelineQuad.TopRight.X)
|
||||
timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime));
|
||||
else if (mouseX < timelineQuad.TopLeft.X)
|
||||
timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime));
|
||||
// for better UX do not require the user to drag all the way to the edge and beyond to initiate a drag-scroll.
|
||||
// this is especially important in scenarios like fullscreen, where mouse confine will usually be on
|
||||
// and the user physically *won't be able to* drag beyond the edge of the timeline
|
||||
// (since its left edge is co-incident with the window edge).
|
||||
const float scroll_tolerance = 40;
|
||||
|
||||
float leftBound = timeline.BoundingBox.TopLeft.X + scroll_tolerance;
|
||||
float rightBound = timeline.BoundingBox.TopRight.X - scroll_tolerance;
|
||||
|
||||
float amount = 0;
|
||||
|
||||
if (mousePos.X > rightBound)
|
||||
amount = mousePos.X - rightBound;
|
||||
else if (mousePos.X < leftBound)
|
||||
amount = mousePos.X - leftBound;
|
||||
|
||||
if (amount == 0)
|
||||
{
|
||||
dragTimeAccumulated = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
amount = Math.Sign(amount) * Math.Min(max_velocity, MathF.Pow(Math.Clamp(Math.Abs(amount), 0, scroll_tolerance), 2));
|
||||
dragTimeAccumulated += (float)Clock.ElapsedFrameTime;
|
||||
|
||||
timeline.ScrollBy(amount * (float)Clock.ElapsedFrameTime * Math.Min(1, dragTimeAccumulated / time_ramp_multiplier));
|
||||
}
|
||||
|
||||
private partial class SelectableAreaBackground : CompositeDrawable
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
@@ -44,7 +45,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
private void autoGenerateBreaks()
|
||||
{
|
||||
var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime, ho.GetEndTime())).ToHashSet();
|
||||
var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime - ((ho as IHasTimePreempt)?.TimePreempt ?? 0), ho.GetEndTime())).ToHashSet();
|
||||
|
||||
if (objectDuration.SetEquals(objectDurationCache))
|
||||
return;
|
||||
@@ -67,19 +68,26 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
for (int i = 1; i < Beatmap.HitObjects.Count; ++i)
|
||||
{
|
||||
var previousObject = Beatmap.HitObjects[i - 1];
|
||||
var nextObject = Beatmap.HitObjects[i];
|
||||
|
||||
// Keep track of the maximum end time encountered thus far.
|
||||
// This handles cases like osu!mania's hold notes, which could have concurrent other objects after their start time.
|
||||
// Note that we're relying on the implicit assumption that objects are sorted by start time,
|
||||
// which is why similar tracking is not done for start time.
|
||||
currentMaxEndTime = Math.Max(currentMaxEndTime, Beatmap.HitObjects[i - 1].GetEndTime());
|
||||
currentMaxEndTime = Math.Max(currentMaxEndTime, previousObject.GetEndTime());
|
||||
|
||||
double nextObjectStartTime = Beatmap.HitObjects[i].StartTime;
|
||||
|
||||
if (nextObjectStartTime - currentMaxEndTime < BreakPeriod.MIN_GAP_DURATION)
|
||||
if (nextObject.StartTime - currentMaxEndTime < BreakPeriod.MIN_GAP_DURATION)
|
||||
continue;
|
||||
|
||||
double breakStartTime = currentMaxEndTime + BreakPeriod.GAP_BEFORE_BREAK;
|
||||
double breakEndTime = nextObjectStartTime - Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObjectStartTime).BeatLength * 2);
|
||||
|
||||
double breakEndTime = nextObject.StartTime;
|
||||
|
||||
if (nextObject is IHasTimePreempt hasTimePreempt)
|
||||
breakEndTime -= hasTimePreempt.TimePreempt;
|
||||
else
|
||||
breakEndTime -= Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObject.StartTime).BeatLength * 2);
|
||||
|
||||
if (breakEndTime - breakStartTime < BreakPeriod.MIN_BREAK_DURATION)
|
||||
continue;
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
var targetPosition = targetButton?.ToSpaceOfOtherDrawable(targetButton.LayoutRectangle.TopRight, this) ?? fallbackPosition;
|
||||
|
||||
updateColourScheme(overlay.ColourProvider.ColourScheme);
|
||||
updateColourScheme(overlay.ColourProvider.Hue);
|
||||
|
||||
footerContent = overlay.CreateFooterContent();
|
||||
|
||||
@@ -256,16 +256,16 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
temporarilyHiddenButtons.Clear();
|
||||
|
||||
updateColourScheme(OverlayColourScheme.Aquamarine);
|
||||
updateColourScheme(OverlayColourScheme.Aquamarine.GetHue());
|
||||
|
||||
contentContainer.Delay(timeUntilRun).Expire();
|
||||
contentContainer = null;
|
||||
activeOverlay = null;
|
||||
}
|
||||
|
||||
private void updateColourScheme(OverlayColourScheme colourScheme)
|
||||
private void updateColourScheme(int hue)
|
||||
{
|
||||
colourProvider.ChangeColourScheme(colourScheme);
|
||||
colourProvider.ChangeColourScheme(hue);
|
||||
|
||||
background.FadeColour(colourProvider.Background5, 150, Easing.OutQuint);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ using osu.Game.Database;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@@ -48,6 +49,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
/// </summary>
|
||||
private readonly Bindable<IReadOnlyList<Mod>> userMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
private OnlinePlayScreenWaveContainer waves = null!;
|
||||
private DailyChallengeLeaderboard leaderboard = null!;
|
||||
private RoomModSelectOverlay userModsSelectOverlay = null!;
|
||||
@@ -84,6 +87,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
[Resolved]
|
||||
private UserLookupCache userLookupCache { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
protected IAPIProvider API { get; private set; } = null!;
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public DailyChallenge(Room room)
|
||||
@@ -358,6 +364,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay);
|
||||
userModsSelectOverlay.SelectedItem.Value = playlistItem;
|
||||
userMods.BindValueChanged(_ => Scheduler.AddOnce(updateMods), true);
|
||||
|
||||
apiState.BindTo(API.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
}
|
||||
|
||||
private void trySetDailyChallengeBeatmap()
|
||||
@@ -368,6 +377,25 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
applyLoopingToTrack();
|
||||
}
|
||||
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
if (state.NewValue != APIState.Online)
|
||||
Schedule(forcefullyExit);
|
||||
});
|
||||
|
||||
private void forcefullyExit()
|
||||
{
|
||||
Logger.Log($"{this} forcefully exiting due to loss of API connection");
|
||||
|
||||
// This is temporary since we don't currently have a way to force screens to be exited
|
||||
// See also: `OnlinePlayScreen.forcefullyExit()`
|
||||
if (this.IsCurrentScreen())
|
||||
{
|
||||
while (this.IsCurrentScreen())
|
||||
this.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnEntering(e);
|
||||
|
||||
@@ -138,9 +138,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadComponentsAsync(best.Select(s => new LeaderboardScoreV2(s, sheared: false)
|
||||
LoadComponentsAsync(best.Select((s, index) => new LeaderboardScoreV2(s, sheared: false)
|
||||
{
|
||||
Rank = s.Position,
|
||||
Rank = index + 1,
|
||||
IsPersonalBest = s.UserID == api.LocalUser.Value.Id,
|
||||
Action = () => PresentScore?.Invoke(s.OnlineID),
|
||||
}), loaded =>
|
||||
|
||||
@@ -99,6 +99,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
Logger.Log($"{this} forcefully exiting due to loss of API connection");
|
||||
|
||||
// This is temporary since we don't currently have a way to force screens to be exited
|
||||
// See also: `DailyChallenge.forcefullyExit()`
|
||||
if (this.IsCurrentScreen())
|
||||
{
|
||||
while (this.IsCurrentScreen())
|
||||
|
||||
Reference in New Issue
Block a user