mirror of
https://github.com/ppy/osu.git
synced 2024-11-14 17:57:38 +08:00
Merge branch 'ppy:master' into colour-rework
This commit is contained in:
commit
29b259ccac
@ -52,10 +52,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.530.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.605.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
<PackageReference Include="Realm" Version="10.11.2" />
|
<PackageReference Include="Realm" Version="10.14.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||||
|
|
||||||
[TestCase(2.3449735700206298d, 151, "diffcalc-test")]
|
[TestCase(2.3449735700206298d, 242, "diffcalc-test")]
|
||||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||||
|
|
||||||
[TestCase(2.7879104989252959d, 151, "diffcalc-test")]
|
[TestCase(2.7879104989252959d, 242, "diffcalc-test")]
|
||||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
foreach (var v in base.ToDatabaseAttributes())
|
foreach (var v in base.ToDatabaseAttributes())
|
||||||
yield return v;
|
yield return v;
|
||||||
|
|
||||||
// Todo: osu!mania doesn't output MaxCombo attribute for some reason.
|
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
||||||
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
||||||
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
||||||
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
|
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
|
||||||
@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
base.FromDatabaseAttributes(values);
|
base.FromDatabaseAttributes(values);
|
||||||
|
|
||||||
|
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
||||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||||
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
||||||
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
|
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
|
||||||
|
@ -52,10 +52,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
||||||
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
|
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
|
||||||
ScoreMultiplier = getScoreMultiplier(mods),
|
ScoreMultiplier = getScoreMultiplier(mods),
|
||||||
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
|
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int maxComboForObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
if (hitObject is HoldNote hold)
|
||||||
|
return 1 + (int)((hold.EndTime - hold.StartTime) / 100);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||||
{
|
{
|
||||||
var sortedObjects = beatmap.HitObjects.ToArray();
|
var sortedObjects = beatmap.HitObjects.ToArray();
|
||||||
|
@ -78,7 +78,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(source.FindProvider(s => s.GetTexture("spinner-top") != null) is DefaultLegacySkin))
|
var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null);
|
||||||
|
|
||||||
|
if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin))
|
||||||
{
|
{
|
||||||
AddInternal(ApproachCircle = new Sprite
|
AddInternal(ApproachCircle = new Sprite
|
||||||
{
|
{
|
||||||
|
@ -710,7 +710,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
realm.Realm.Write(() =>
|
await realm.Realm.WriteAsync(() =>
|
||||||
{
|
{
|
||||||
foreach (var b in imported.Beatmaps)
|
foreach (var b in imported.Beatmaps)
|
||||||
b.OnlineID = -1;
|
b.OnlineID = -1;
|
||||||
|
@ -61,13 +61,13 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
|
||||||
// No header shouldn't cause any change
|
// No header shouldn't cause any change
|
||||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame());
|
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame());
|
||||||
|
|
||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
|
||||||
// Reset with a miss instead.
|
// Reset with a miss instead.
|
||||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
|
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame
|
||||||
{
|
{
|
||||||
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int> { { HitResult.Miss, 1 } }, DateTimeOffset.Now)
|
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int> { { HitResult.Miss, 1 } }, DateTimeOffset.Now)
|
||||||
});
|
});
|
||||||
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
|
||||||
// Reset with no judged hit.
|
// Reset with no judged hit.
|
||||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
|
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame
|
||||||
{
|
{
|
||||||
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int>(), DateTimeOffset.Now)
|
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int>(), DateTimeOffset.Now)
|
||||||
});
|
});
|
||||||
|
48
osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs
Normal file
48
osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneTapButton : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private TapButton tapButton;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
AddStep("create button", () =>
|
||||||
|
{
|
||||||
|
Child = tapButton = new TapButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(4),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
bool pressed = false;
|
||||||
|
|
||||||
|
AddRepeatStep("Press button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(tapButton);
|
||||||
|
if (!pressed)
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
else
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
|
||||||
|
pressed = !pressed;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
@ -77,34 +77,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType<TimingControlPoint>().First().BPM:N2}";
|
timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType<TimingControlPoint>().First().BPM:N2}";
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestNoop()
|
|
||||||
{
|
|
||||||
AddStep("do nothing", () => { });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestTapThenReset()
|
|
||||||
{
|
|
||||||
AddStep("click tap button", () =>
|
|
||||||
{
|
|
||||||
control.ChildrenOfType<RoundedButton>()
|
|
||||||
.Last()
|
|
||||||
.TriggerClick();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for track playing", () => Clock.IsRunning);
|
|
||||||
|
|
||||||
AddStep("click reset button", () =>
|
|
||||||
{
|
|
||||||
control.ChildrenOfType<RoundedButton>()
|
|
||||||
.First()
|
|
||||||
.TriggerClick();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasic()
|
public void TestBasic()
|
||||||
{
|
{
|
||||||
@ -115,7 +87,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("click tap button", () =>
|
AddStep("click tap button", () =>
|
||||||
{
|
{
|
||||||
control.ChildrenOfType<RoundedButton>()
|
control.ChildrenOfType<OsuButton>()
|
||||||
.Last()
|
.Last()
|
||||||
.TriggerClick();
|
.TriggerClick();
|
||||||
});
|
});
|
||||||
@ -129,6 +101,28 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTapThenReset()
|
||||||
|
{
|
||||||
|
AddStep("click tap button", () =>
|
||||||
|
{
|
||||||
|
control.ChildrenOfType<OsuButton>()
|
||||||
|
.Last()
|
||||||
|
.TriggerClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for track playing", () => Clock.IsRunning);
|
||||||
|
|
||||||
|
AddStep("click reset button", () =>
|
||||||
|
{
|
||||||
|
control.ChildrenOfType<OsuButton>()
|
||||||
|
.First()
|
||||||
|
.TriggerClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
Beatmap.Disabled = false;
|
Beatmap.Disabled = false;
|
||||||
|
48
osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
Normal file
48
osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneTimelineZoom : TimelineTestScene
|
||||||
|
{
|
||||||
|
public override Drawable CreateTestComponent() => Empty();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVisibleRangeUpdatesOnZoomChange()
|
||||||
|
{
|
||||||
|
double initialVisibleRange = 0;
|
||||||
|
|
||||||
|
AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||||
|
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||||
|
|
||||||
|
AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
|
||||||
|
AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1));
|
||||||
|
AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
|
||||||
|
AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1));
|
||||||
|
|
||||||
|
AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||||
|
AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVisibleRangeConstantOnSizeChange()
|
||||||
|
{
|
||||||
|
double initialVisibleRange = 0;
|
||||||
|
|
||||||
|
AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
|
||||||
|
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||||
|
|
||||||
|
AddStep("scale timeline size", () => TimelineArea.Timeline.Width = 2);
|
||||||
|
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||||
|
AddStep("descale timeline size", () => TimelineArea.Timeline.Width = 0.5f);
|
||||||
|
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||||
|
|
||||||
|
AddStep("restore timeline size", () => TimelineArea.Timeline.Width = 1);
|
||||||
|
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.Gray(30)
|
Colour = OsuColour.Gray(30)
|
||||||
},
|
},
|
||||||
scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both }
|
scrollContainer = new ZoomableScrollContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new MenuCursor()
|
new MenuCursor()
|
||||||
@ -62,7 +67,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestWidthInitialization()
|
public void TestWidthInitialization()
|
||||||
{
|
{
|
||||||
AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0);
|
AddAssert("Inner container width was initialized", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWidthUpdatesOnDrawSizeChanges()
|
||||||
|
{
|
||||||
|
AddStep("Shrink scroll container", () => scrollContainer.Width = 0.5f);
|
||||||
|
AddAssert("Scroll container width shrunk", () => scrollContainer.DrawWidth == scrollContainer.Parent.DrawWidth / 2);
|
||||||
|
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
protected readonly BeatmapDownloadTracker DownloadTracker;
|
protected readonly BeatmapDownloadTracker DownloadTracker;
|
||||||
|
|
||||||
protected BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true)
|
protected BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true)
|
||||||
: base(HoverSampleSet.Submit)
|
: base(HoverSampleSet.Button)
|
||||||
{
|
{
|
||||||
Expanded = new BindableBool { Disabled = !allowExpansion };
|
Expanded = new BindableBool { Disabled = !allowExpansion };
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -245,10 +244,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (BeatmapSet.HasVideo)
|
if (BeatmapSet.HasVideo)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (BeatmapSet.HasStoryboard)
|
if (BeatmapSet.HasStoryboard)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
|
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (BeatmapSet.FeaturedInSpotlight)
|
if (BeatmapSet.FeaturedInSpotlight)
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,6 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -226,10 +225,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (BeatmapSet.HasVideo)
|
if (BeatmapSet.HasVideo)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (BeatmapSet.HasStoryboard)
|
if (BeatmapSet.HasStoryboard)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
|
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (BeatmapSet.FeaturedInSpotlight)
|
if (BeatmapSet.FeaturedInSpotlight)
|
||||||
{
|
{
|
||||||
|
@ -3,14 +3,16 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
{
|
{
|
||||||
public class IconPill : CircularContainer
|
public abstract class IconPill : CircularContainer, IHasTooltip
|
||||||
{
|
{
|
||||||
public Vector2 IconSize
|
public Vector2 IconSize
|
||||||
{
|
{
|
||||||
@ -20,7 +22,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
private readonly Container iconContainer;
|
private readonly Container iconContainer;
|
||||||
|
|
||||||
public IconPill(IconUsage icon)
|
protected IconPill(IconUsage icon)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
@ -47,5 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract LocalisableString TooltipText { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs
Normal file
19
osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs
Normal file
@ -0,0 +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 osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
|
{
|
||||||
|
public class StoryboardIconPill : IconPill
|
||||||
|
{
|
||||||
|
public StoryboardIconPill()
|
||||||
|
: base(FontAwesome.Solid.Image)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoStoryboard;
|
||||||
|
}
|
||||||
|
}
|
19
osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs
Normal file
19
osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs
Normal file
@ -0,0 +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 osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
|
{
|
||||||
|
public class VideoIconPill : IconPill
|
||||||
|
{
|
||||||
|
public VideoIconPill()
|
||||||
|
: base(FontAwesome.Solid.Film)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoVideo;
|
||||||
|
}
|
||||||
|
}
|
@ -392,7 +392,7 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
total_writes_async.Value++;
|
total_writes_async.Value++;
|
||||||
using (var realm = getRealmInstance())
|
using (var realm = getRealmInstance())
|
||||||
await realm.WriteAsync(action);
|
await realm.WriteAsync(() => action(realm));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
Size = TwoLayerButton.SIZE_EXTENDED;
|
Size = TwoLayerButton.SIZE_EXTENDED;
|
||||||
|
|
||||||
Child = button = new TwoLayerButton(HoverSampleSet.Submit)
|
Child = button = new TwoLayerButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopLeft,
|
Anchor = Anchor.TopLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
|
@ -56,8 +56,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private readonly SpriteText spriteText;
|
private readonly SpriteText spriteText;
|
||||||
private Vector2 hoverSpacing => new Vector2(3f, 0f);
|
private Vector2 hoverSpacing => new Vector2(3f, 0f);
|
||||||
|
|
||||||
public DialogButton()
|
public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button)
|
||||||
: base(HoverSampleSet.Submit)
|
: base(sampleSet)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Icon = FontAwesome.Solid.ExternalLinkAlt,
|
Icon = FontAwesome.Solid.ExternalLinkAlt,
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
new HoverClickSounds(HoverSampleSet.Submit)
|
new HoverClickSounds()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
@ -37,7 +38,10 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
|
if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
|
||||||
sampleClick?.Play();
|
{
|
||||||
|
sampleClick.Frequency.Value = 0.99 + RNG.NextDouble(0.02);
|
||||||
|
sampleClick.Play();
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[Description("default")]
|
[Description("default")]
|
||||||
Default,
|
Default,
|
||||||
|
|
||||||
[Description("submit")]
|
|
||||||
Submit,
|
|
||||||
|
|
||||||
[Description("button")]
|
[Description("button")]
|
||||||
Button,
|
Button,
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
public class ShearedToggleButton : ShearedButton
|
public class ShearedToggleButton : ShearedButton
|
||||||
{
|
{
|
||||||
|
private Sample? sampleClick;
|
||||||
private Sample? sampleOff;
|
private Sample? sampleOff;
|
||||||
private Sample? sampleOn;
|
private Sample? sampleOn;
|
||||||
|
|
||||||
@ -39,8 +40,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
sampleClick = audio.Samples.Get(@"UI/default-select");
|
||||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
sampleOn = audio.Samples.Get(@"UI/dropdown-open");
|
||||||
|
sampleOff = audio.Samples.Get(@"UI/dropdown-close");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||||
@ -67,6 +69,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private void playSample()
|
private void playSample()
|
||||||
{
|
{
|
||||||
|
sampleClick?.Play();
|
||||||
|
|
||||||
if (Active.Value)
|
if (Active.Value)
|
||||||
sampleOn?.Play();
|
sampleOn?.Play();
|
||||||
else
|
else
|
||||||
|
@ -80,6 +80,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
||||||
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
||||||
new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay),
|
new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay),
|
||||||
|
new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally),
|
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
|
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
|
||||||
@ -322,5 +323,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))]
|
||||||
DeselectAllMods,
|
DeselectAllMods,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))]
|
||||||
|
EditorTapForBPM,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
|
public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Tap for BPM"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Cycle grid display mode"
|
/// "Cycle grid display mode"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency.");
|
public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended.");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,16 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
if (isFailing) return;
|
if (isFailing) return;
|
||||||
|
|
||||||
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
try
|
||||||
WebRequest.Perform();
|
{
|
||||||
|
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
||||||
|
WebRequest.Perform();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// ignore this. internally Perform is running async and the fail state may have changed since
|
||||||
|
// the last check of `isFailing` above.
|
||||||
|
}
|
||||||
|
|
||||||
if (isFailing) return;
|
if (isFailing) return;
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ namespace osu.Game.Online.Chat
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
{
|
||||||
Parts = parts.ToList();
|
Parts = parts.ToList();
|
||||||
}
|
}
|
||||||
|
@ -197,6 +197,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
APIRoom.Playlist.Clear();
|
APIRoom.Playlist.Clear();
|
||||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
||||||
|
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
|
||||||
|
|
||||||
Debug.Assert(LocalUser != null);
|
Debug.Assert(LocalUser != null);
|
||||||
addUserToAPIRoom(LocalUser);
|
addUserToAPIRoom(LocalUser);
|
||||||
@ -737,6 +738,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
APIRoom.Type.Value = Room.Settings.MatchType;
|
APIRoom.Type.Value = Room.Settings.MatchType;
|
||||||
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
||||||
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
|
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
|
||||||
|
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId);
|
||||||
|
|
||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,13 @@ namespace osu.Game.Online.Rooms
|
|||||||
/// Used for serialising to the API.
|
/// Used for serialising to the API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("beatmap_id")]
|
[JsonProperty("beatmap_id")]
|
||||||
private int onlineBeatmapId => Beatmap.OnlineID;
|
private int onlineBeatmapId
|
||||||
|
{
|
||||||
|
get => Beatmap.OnlineID;
|
||||||
|
// This setter is only required for client-side serialise-then-deserialise operations.
|
||||||
|
// Serialisation is supposed to emit only a `beatmap_id`, but a (non-null) `beatmap` is required on deserialise.
|
||||||
|
set => Beatmap = new APIBeatmap { OnlineID = value };
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A beatmap representing this playlist item.
|
/// A beatmap representing this playlist item.
|
||||||
|
@ -162,6 +162,13 @@ namespace osu.Game.Online.Rooms
|
|||||||
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
|
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies values from another <see cref="Room"/> into this one.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// **Beware**: This will store references between <see cref="Room"/>s.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="other">The <see cref="Room"/> to copy values from.</param>
|
||||||
public void CopyFrom(Room other)
|
public void CopyFrom(Room other)
|
||||||
{
|
{
|
||||||
RoomID.Value = other.RoomID.Value;
|
RoomID.Value = other.RoomID.Value;
|
||||||
|
@ -9,6 +9,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -658,11 +659,14 @@ namespace osu.Game
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override IDictionary<FrameworkSetting, object> GetFrameworkConfigDefaults()
|
protected override IDictionary<FrameworkSetting, object> GetFrameworkConfigDefaults()
|
||||||
=> new Dictionary<FrameworkSetting, object>
|
{
|
||||||
|
return new Dictionary<FrameworkSetting, object>
|
||||||
{
|
{
|
||||||
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance)
|
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance).
|
||||||
{ FrameworkSetting.WindowMode, WindowMode.Fullscreen }
|
// However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there.
|
||||||
|
{ FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen }
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
@ -80,6 +80,9 @@ namespace osu.Game
|
|||||||
|
|
||||||
public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
|
public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
|
||||||
|
|
||||||
|
internal EndpointConfiguration CreateEndpoints() =>
|
||||||
|
UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
||||||
|
|
||||||
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -268,7 +271,7 @@ namespace osu.Game
|
|||||||
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
|
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
|
||||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||||
|
|
||||||
EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
EndpointConfiguration endpoints = CreateEndpoints();
|
||||||
|
|
||||||
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;
|
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -69,7 +70,7 @@ namespace osu.Game.Overlays.AccountCreation
|
|||||||
},
|
},
|
||||||
usernameTextBox = new OsuTextBox
|
usernameTextBox = new OsuTextBox
|
||||||
{
|
{
|
||||||
PlaceholderText = UsersStrings.LoginUsername,
|
PlaceholderText = UsersStrings.LoginUsername.ToLower(),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
TabbableContentContainer = this
|
TabbableContentContainer = this
|
||||||
},
|
},
|
||||||
@ -91,7 +92,7 @@ namespace osu.Game.Overlays.AccountCreation
|
|||||||
},
|
},
|
||||||
passwordTextBox = new OsuPasswordTextBox
|
passwordTextBox = new OsuPasswordTextBox
|
||||||
{
|
{
|
||||||
PlaceholderText = "password",
|
PlaceholderText = UsersStrings.LoginPassword.ToLower(),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
TabbableContentContainer = this,
|
TabbableContentContainer = this,
|
||||||
},
|
},
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular),
|
Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular),
|
||||||
Text = LabelFor(Value)
|
Text = LabelFor(Value)
|
||||||
},
|
},
|
||||||
new HoverClickSounds()
|
new HoverClickSounds(HoverSampleSet.TabSelect)
|
||||||
});
|
});
|
||||||
|
|
||||||
Enabled.Value = true;
|
Enabled.Value = true;
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -16,6 +18,7 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -32,6 +35,8 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
public IEnumerable<LocalisableString> FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty };
|
public IEnumerable<LocalisableString> FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty };
|
||||||
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
|
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
|
||||||
|
|
||||||
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||||
|
|
||||||
private Box hoverBox = null!;
|
private Box hoverBox = null!;
|
||||||
private SpriteIcon checkbox = null!;
|
private SpriteIcon checkbox = null!;
|
||||||
private OsuSpriteText channelText = null!;
|
private OsuSpriteText channelText = null!;
|
||||||
@ -46,14 +51,20 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
|
|
||||||
private const float vertical_margin = 1.5f;
|
private const float vertical_margin = 1.5f;
|
||||||
|
|
||||||
|
private Sample? sampleJoin;
|
||||||
|
private Sample? sampleLeave;
|
||||||
|
|
||||||
public ChannelListingItem(Channel channel)
|
public ChannelListingItem(Channel channel)
|
||||||
{
|
{
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
|
sampleJoin = audio.Samples.Get(@"UI/check-on");
|
||||||
|
sampleLeave = audio.Samples.Get(@"UI/check-off");
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = 5;
|
CornerRadius = 5;
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
@ -156,7 +167,19 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(Channel);
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (channelJoined.Value)
|
||||||
|
{
|
||||||
|
OnRequestLeave?.Invoke(Channel);
|
||||||
|
sampleLeave?.Play();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnRequestJoin?.Invoke(Channel);
|
||||||
|
sampleJoin?.Play();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -14,7 +14,6 @@ using osu.Framework.Platform;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.News
|
namespace osu.Game.Overlays.News
|
||||||
@ -29,7 +28,6 @@ namespace osu.Game.Overlays.News
|
|||||||
private TextFlowContainer main;
|
private TextFlowContainer main;
|
||||||
|
|
||||||
public NewsCard(APINewsPost post)
|
public NewsCard(APINewsPost post)
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
{
|
||||||
this.post = post;
|
this.post = post;
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ using System.Diagnostics;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.News.Sidebar
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
{
|
{
|
||||||
@ -129,7 +128,6 @@ namespace osu.Game.Overlays.News.Sidebar
|
|||||||
private readonly APINewsPost post;
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
public PostButton(APINewsPost post)
|
public PostButton(APINewsPost post)
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
{
|
||||||
this.post = post;
|
this.post = post;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
private Sample sampleOpen;
|
private Sample sampleOpen;
|
||||||
private Sample sampleClose;
|
private Sample sampleClose;
|
||||||
|
|
||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds();
|
||||||
|
|
||||||
public ExpandDetailsButton()
|
public ExpandDetailsButton()
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Sections
|
namespace osu.Game.Overlays.Profile.Sections
|
||||||
{
|
{
|
||||||
@ -18,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections
|
|||||||
private readonly IBeatmapInfo beatmapInfo;
|
private readonly IBeatmapInfo beatmapInfo;
|
||||||
|
|
||||||
protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo)
|
protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo)
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
{
|
||||||
this.beatmapInfo = beatmapInfo;
|
this.beatmapInfo = beatmapInfo;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
@ -224,6 +225,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
|
|
||||||
private void updateScreenModeWarning()
|
private void updateScreenModeWarning()
|
||||||
{
|
{
|
||||||
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS)
|
||||||
|
{
|
||||||
|
if (windowModeDropdown.Current.Value == WindowMode.Fullscreen)
|
||||||
|
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true);
|
||||||
|
else
|
||||||
|
windowModeDropdown.ClearNoticeText();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (windowModeDropdown.Current.Value != WindowMode.Fullscreen)
|
if (windowModeDropdown.Current.Value != WindowMode.Fullscreen)
|
||||||
{
|
{
|
||||||
windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true);
|
windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true);
|
||||||
|
@ -20,7 +20,6 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -83,7 +82,6 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
private RealmAccess realm { get; set; }
|
private RealmAccess realm { get; set; }
|
||||||
|
|
||||||
protected ToolbarButton()
|
protected ToolbarButton()
|
||||||
: base(HoverSampleSet.Toolbar)
|
|
||||||
{
|
{
|
||||||
Width = Toolbar.HEIGHT;
|
Width = Toolbar.HEIGHT;
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
@ -11,7 +11,6 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -29,7 +28,6 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
private AnalogClockDisplay analog;
|
private AnalogClockDisplay analog;
|
||||||
|
|
||||||
public ToolbarClock()
|
public ToolbarClock()
|
||||||
: base(HoverSampleSet.Toolbar)
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
AutoSizeAxes = Axes.X;
|
AutoSizeAxes = Axes.X;
|
||||||
|
@ -117,9 +117,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If the provided replay frame does not have any header information, this will be a noop.
|
/// If the provided replay frame does not have any header information, this will be a noop.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="ruleset">The ruleset to be used for retrieving statistics.</param>
|
|
||||||
/// <param name="frame">The replay frame to read header statistics from.</param>
|
/// <param name="frame">The replay frame to read header statistics from.</param>
|
||||||
public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame)
|
public virtual void ResetFromReplayFrame(ReplayFrame frame)
|
||||||
{
|
{
|
||||||
if (frame.Header == null)
|
if (frame.Header == null)
|
||||||
return;
|
return;
|
||||||
|
@ -6,11 +6,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -88,17 +90,34 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
private readonly double accuracyPortion;
|
private readonly double accuracyPortion;
|
||||||
private readonly double comboPortion;
|
private readonly double comboPortion;
|
||||||
|
|
||||||
private int maxAchievableCombo;
|
/// <summary>
|
||||||
|
/// Scoring values for a perfect play.
|
||||||
|
/// </summary>
|
||||||
|
public ScoringValues MaximumScoringValues
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!beatmapApplied)
|
||||||
|
throw new InvalidOperationException($"Cannot access maximum scoring values before calling {nameof(ApplyBeatmap)}.");
|
||||||
|
|
||||||
|
return maximumScoringValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScoringValues maximumScoringValues;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum achievable base score.
|
/// Scoring values for the current play assuming all perfect hits.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private double maxBaseScore;
|
/// <remarks>
|
||||||
|
/// This is only used to determine the accuracy with respect to the current point in time for an ongoing play session.
|
||||||
|
/// </remarks>
|
||||||
|
private ScoringValues currentMaximumScoringValues;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum number of basic (non-tick and non-bonus) hitobjects.
|
/// Scoring values for the current play.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int maxBasicHitObjects;
|
private ScoringValues currentScoringValues;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum <see cref="HitResult"/> of a basic (non-tick and non-bonus) hitobject.
|
/// The maximum <see cref="HitResult"/> of a basic (non-tick and non-bonus) hitobject.
|
||||||
@ -106,9 +125,6 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private HitResult? maxBasicResult;
|
private HitResult? maxBasicResult;
|
||||||
|
|
||||||
private double rollingMaxBaseScore;
|
|
||||||
private double baseScore;
|
|
||||||
private int basicHitObjects;
|
|
||||||
private bool beatmapApplied;
|
private bool beatmapApplied;
|
||||||
|
|
||||||
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
||||||
@ -163,6 +179,10 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1;
|
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1;
|
||||||
|
|
||||||
|
// Always update the maximum scoring values.
|
||||||
|
applyResult(result.Judgement.MaxResult, ref currentMaximumScoringValues);
|
||||||
|
currentMaximumScoringValues.MaxCombo += result.Judgement.MaxResult.IncreasesCombo() ? 1 : 0;
|
||||||
|
|
||||||
if (!result.Type.IsScorable())
|
if (!result.Type.IsScorable())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -171,16 +191,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
else if (result.Type.BreaksCombo())
|
else if (result.Type.BreaksCombo())
|
||||||
Combo.Value = 0;
|
Combo.Value = 0;
|
||||||
|
|
||||||
double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0;
|
applyResult(result.Type, ref currentScoringValues);
|
||||||
|
currentScoringValues.MaxCombo = HighestCombo.Value;
|
||||||
if (!result.Type.IsBonus())
|
|
||||||
{
|
|
||||||
baseScore += scoreIncrease;
|
|
||||||
rollingMaxBaseScore += result.Judgement.MaxNumericResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Type.IsBasic())
|
|
||||||
basicHitObjects++;
|
|
||||||
|
|
||||||
hitEvents.Add(CreateHitEvent(result));
|
hitEvents.Add(CreateHitEvent(result));
|
||||||
lastHitObject = result.HitObject;
|
lastHitObject = result.HitObject;
|
||||||
@ -188,6 +200,20 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
updateScore();
|
updateScore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void applyResult(HitResult result, ref ScoringValues scoringValues)
|
||||||
|
{
|
||||||
|
if (!result.IsScorable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (result.IsBonus())
|
||||||
|
scoringValues.BonusScore += result.IsHit() ? Judgement.ToNumericResult(result) : 0;
|
||||||
|
else
|
||||||
|
scoringValues.BaseScore += result.IsHit() ? Judgement.ToNumericResult(result) : 0;
|
||||||
|
|
||||||
|
if (result.IsBasic())
|
||||||
|
scoringValues.CountBasicHitObjects++;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the <see cref="HitEvent"/> that describes a <see cref="JudgementResult"/>.
|
/// Creates the <see cref="HitEvent"/> that describes a <see cref="JudgementResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -206,19 +232,15 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1;
|
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1;
|
||||||
|
|
||||||
|
// Always update the maximum scoring values.
|
||||||
|
revertResult(result.Judgement.MaxResult, ref currentMaximumScoringValues);
|
||||||
|
currentMaximumScoringValues.MaxCombo -= result.Judgement.MaxResult.IncreasesCombo() ? 1 : 0;
|
||||||
|
|
||||||
if (!result.Type.IsScorable())
|
if (!result.Type.IsScorable())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0;
|
revertResult(result.Type, ref currentScoringValues);
|
||||||
|
currentScoringValues.MaxCombo = HighestCombo.Value;
|
||||||
if (!result.Type.IsBonus())
|
|
||||||
{
|
|
||||||
baseScore -= scoreIncrease;
|
|
||||||
rollingMaxBaseScore -= result.Judgement.MaxNumericResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Type.IsBasic())
|
|
||||||
basicHitObjects--;
|
|
||||||
|
|
||||||
Debug.Assert(hitEvents.Count > 0);
|
Debug.Assert(hitEvents.Count > 0);
|
||||||
lastHitObject = hitEvents[^1].LastHitObject;
|
lastHitObject = hitEvents[^1].LastHitObject;
|
||||||
@ -227,14 +249,24 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
updateScore();
|
updateScore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void revertResult(HitResult result, ref ScoringValues scoringValues)
|
||||||
|
{
|
||||||
|
if (!result.IsScorable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (result.IsBonus())
|
||||||
|
scoringValues.BonusScore -= result.IsHit() ? Judgement.ToNumericResult(result) : 0;
|
||||||
|
else
|
||||||
|
scoringValues.BaseScore -= result.IsHit() ? Judgement.ToNumericResult(result) : 0;
|
||||||
|
|
||||||
|
if (result.IsBasic())
|
||||||
|
scoringValues.CountBasicHitObjects--;
|
||||||
|
}
|
||||||
|
|
||||||
private void updateScore()
|
private void updateScore()
|
||||||
{
|
{
|
||||||
double rollingAccuracyRatio = rollingMaxBaseScore > 0 ? baseScore / rollingMaxBaseScore : 1;
|
Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1;
|
||||||
double accuracyRatio = maxBaseScore > 0 ? baseScore / maxBaseScore : 1;
|
TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues);
|
||||||
double comboRatio = maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1;
|
|
||||||
|
|
||||||
Accuracy.Value = rollingAccuracyRatio;
|
|
||||||
TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maxBasicHitObjects);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -246,22 +278,15 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
||||||
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
||||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||||
|
[Pure]
|
||||||
public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo)
|
public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo)
|
||||||
{
|
{
|
||||||
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
||||||
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
|
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
|
||||||
|
|
||||||
extractFromStatistics(ruleset,
|
ExtractScoringValues(scoreInfo, out var current, out var maximum);
|
||||||
scoreInfo.Statistics,
|
|
||||||
out double extractedBaseScore,
|
|
||||||
out double extractedMaxBaseScore,
|
|
||||||
out int extractedMaxCombo,
|
|
||||||
out int extractedBasicHitObjects);
|
|
||||||
|
|
||||||
double accuracyRatio = extractedMaxBaseScore > 0 ? extractedBaseScore / extractedMaxBaseScore : 1;
|
return ComputeScore(mode, current, maximum);
|
||||||
double comboRatio = extractedMaxCombo > 0 ? (double)scoreInfo.MaxCombo / extractedMaxCombo : 1;
|
|
||||||
|
|
||||||
return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), extractedBasicHitObjects);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -273,6 +298,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
||||||
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
||||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||||
|
[Pure]
|
||||||
public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo)
|
public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo)
|
||||||
{
|
{
|
||||||
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
||||||
@ -281,17 +307,9 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
if (!beatmapApplied)
|
if (!beatmapApplied)
|
||||||
throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}.");
|
throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}.");
|
||||||
|
|
||||||
extractFromStatistics(ruleset,
|
ExtractScoringValues(scoreInfo, out var current, out _);
|
||||||
scoreInfo.Statistics,
|
|
||||||
out double extractedBaseScore,
|
|
||||||
out _,
|
|
||||||
out _,
|
|
||||||
out _);
|
|
||||||
|
|
||||||
double accuracyRatio = maxBaseScore > 0 ? extractedBaseScore / maxBaseScore : 1;
|
return ComputeScore(mode, current, MaximumScoringValues);
|
||||||
double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1;
|
|
||||||
|
|
||||||
return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maxBasicHitObjects);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -305,6 +323,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
||||||
/// <param name="maxAchievableCombo">The maximum achievable combo for the provided beatmap.</param>
|
/// <param name="maxAchievableCombo">The maximum achievable combo for the provided beatmap.</param>
|
||||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||||
|
[Pure]
|
||||||
public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo)
|
public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo)
|
||||||
{
|
{
|
||||||
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
||||||
@ -313,26 +332,30 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
double accuracyRatio = scoreInfo.Accuracy;
|
double accuracyRatio = scoreInfo.Accuracy;
|
||||||
double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1;
|
double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1;
|
||||||
|
|
||||||
|
ExtractScoringValues(scoreInfo, out var current, out var maximum);
|
||||||
|
|
||||||
// For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score.
|
// For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score.
|
||||||
// To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score.
|
// To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score.
|
||||||
// Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together.
|
// Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together.
|
||||||
if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3)
|
if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3 && maximum.BaseScore > 0)
|
||||||
{
|
accuracyRatio = current.BaseScore / maximum.BaseScore;
|
||||||
extractFromStatistics(
|
|
||||||
ruleset,
|
|
||||||
scoreInfo.Statistics,
|
|
||||||
out double computedBaseScore,
|
|
||||||
out double computedMaxBaseScore,
|
|
||||||
out _,
|
|
||||||
out _);
|
|
||||||
|
|
||||||
if (computedMaxBaseScore > 0)
|
return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.CountBasicHitObjects);
|
||||||
accuracyRatio = computedBaseScore / computedMaxBaseScore;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int computedBasicHitObjects = scoreInfo.Statistics.Where(kvp => kvp.Key.IsBasic()).Select(kvp => kvp.Value).Sum();
|
/// <summary>
|
||||||
|
/// Computes the total score from scoring values.
|
||||||
return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), computedBasicHitObjects);
|
/// </summary>
|
||||||
|
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
||||||
|
/// <param name="current">The current scoring values.</param>
|
||||||
|
/// <param name="maximum">The maximum scoring values.</param>
|
||||||
|
/// <returns>The total score computed from the given scoring values.</returns>
|
||||||
|
[Pure]
|
||||||
|
public double ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum)
|
||||||
|
{
|
||||||
|
double accuracyRatio = maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1;
|
||||||
|
double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1;
|
||||||
|
return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.CountBasicHitObjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -344,6 +367,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <param name="bonusScore">The total bonus score.</param>
|
/// <param name="bonusScore">The total bonus score.</param>
|
||||||
/// <param name="totalBasicHitObjects">The total number of basic (non-tick and non-bonus) hitobjects in the beatmap.</param>
|
/// <param name="totalBasicHitObjects">The total number of basic (non-tick and non-bonus) hitobjects in the beatmap.</param>
|
||||||
/// <returns>The total score computed from the given scoring component ratios.</returns>
|
/// <returns>The total score computed from the given scoring component ratios.</returns>
|
||||||
|
[Pure]
|
||||||
public double ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, double bonusScore, int totalBasicHitObjects)
|
public double ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, double bonusScore, int totalBasicHitObjects)
|
||||||
{
|
{
|
||||||
switch (mode)
|
switch (mode)
|
||||||
@ -362,15 +386,6 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the total bonus score from score statistics.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="statistics">The score statistics.</param>
|
|
||||||
/// <returns>The total bonus score.</returns>
|
|
||||||
private double getBonusScore(IReadOnlyDictionary<HitResult, int> statistics)
|
|
||||||
=> statistics.GetValueOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE
|
|
||||||
+ statistics.GetValueOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE;
|
|
||||||
|
|
||||||
private ScoreRank rankFrom(double acc)
|
private ScoreRank rankFrom(double acc)
|
||||||
{
|
{
|
||||||
if (acc == 1)
|
if (acc == 1)
|
||||||
@ -402,15 +417,10 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
lastHitObject = null;
|
lastHitObject = null;
|
||||||
|
|
||||||
if (storeResults)
|
if (storeResults)
|
||||||
{
|
maximumScoringValues = currentScoringValues;
|
||||||
maxAchievableCombo = HighestCombo.Value;
|
|
||||||
maxBaseScore = baseScore;
|
|
||||||
maxBasicHitObjects = basicHitObjects;
|
|
||||||
}
|
|
||||||
|
|
||||||
baseScore = 0;
|
currentScoringValues = default;
|
||||||
rollingMaxBaseScore = 0;
|
currentMaximumScoringValues = default;
|
||||||
basicHitObjects = 0;
|
|
||||||
|
|
||||||
TotalScore.Value = 0;
|
TotalScore.Value = 0;
|
||||||
Accuracy.Value = 1;
|
Accuracy.Value = 1;
|
||||||
@ -437,14 +447,19 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score));
|
score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame)
|
public override void ResetFromReplayFrame(ReplayFrame frame)
|
||||||
{
|
{
|
||||||
base.ResetFromReplayFrame(ruleset, frame);
|
base.ResetFromReplayFrame(frame);
|
||||||
|
|
||||||
if (frame.Header == null)
|
if (frame.Header == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
extractFromStatistics(ruleset, frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _);
|
extractScoringValues(frame.Header.Statistics, out var current, out var maximum);
|
||||||
|
currentScoringValues.BaseScore = current.BaseScore;
|
||||||
|
currentScoringValues.MaxCombo = frame.Header.MaxCombo;
|
||||||
|
currentMaximumScoringValues.BaseScore = maximum.BaseScore;
|
||||||
|
currentMaximumScoringValues.MaxCombo = maximum.MaxCombo;
|
||||||
|
|
||||||
HighestCombo.Value = frame.Header.MaxCombo;
|
HighestCombo.Value = frame.Header.MaxCombo;
|
||||||
|
|
||||||
scoreResultCounts.Clear();
|
scoreResultCounts.Clear();
|
||||||
@ -455,52 +470,126 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
OnResetFromReplayFrame?.Invoke();
|
OnResetFromReplayFrame?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extractFromStatistics(Ruleset ruleset, IReadOnlyDictionary<HitResult, int> statistics, out double baseScore, out double maxBaseScore, out int maxCombo,
|
#region ScoringValue extraction
|
||||||
out int basicHitObjects)
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a best-effort extraction of hit statistics into <see cref="ScoringValues"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is useful in a variety of situations, with a few drawbacks that need to be considered:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>The maximum <see cref="ScoringValues.BonusScore"/> will always be 0.</item>
|
||||||
|
/// <item>The current and maximum <see cref="ScoringValues.CountBasicHitObjects"/> will always be the same value.</item>
|
||||||
|
/// </list>
|
||||||
|
/// Consumers are expected to more accurately fill in the above values through external means.
|
||||||
|
/// <para>
|
||||||
|
/// <b>Ensure</b> to fill in the maximum <see cref="ScoringValues.CountBasicHitObjects"/> for use in
|
||||||
|
/// <see cref="ComputeScore(osu.Game.Rulesets.Scoring.ScoringMode,osu.Game.Scoring.ScoringValues,osu.Game.Scoring.ScoringValues)"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="scoreInfo">The score to extract scoring values from.</param>
|
||||||
|
/// <param name="current">The "current" scoring values, representing the hit statistics as they appear.</param>
|
||||||
|
/// <param name="maximum">The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time.</param>
|
||||||
|
[Pure]
|
||||||
|
internal void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum)
|
||||||
{
|
{
|
||||||
baseScore = 0;
|
extractScoringValues(scoreInfo.Statistics, out current, out maximum);
|
||||||
maxBaseScore = 0;
|
current.MaxCombo = scoreInfo.MaxCombo;
|
||||||
maxCombo = 0;
|
}
|
||||||
basicHitObjects = 0;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a best-effort extraction of hit statistics into <see cref="ScoringValues"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is useful in a variety of situations, with a few drawbacks that need to be considered:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>The maximum <see cref="ScoringValues.BonusScore"/> will always be 0.</item>
|
||||||
|
/// <item>The current and maximum <see cref="ScoringValues.CountBasicHitObjects"/> will always be the same value.</item>
|
||||||
|
/// </list>
|
||||||
|
/// Consumers are expected to more accurately fill in the above values through external means.
|
||||||
|
/// <para>
|
||||||
|
/// <b>Ensure</b> to fill in the maximum <see cref="ScoringValues.CountBasicHitObjects"/> for use in
|
||||||
|
/// <see cref="ComputeScore(osu.Game.Rulesets.Scoring.ScoringMode,osu.Game.Scoring.ScoringValues,osu.Game.Scoring.ScoringValues)"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="header">The replay frame header to extract scoring values from.</param>
|
||||||
|
/// <param name="current">The "current" scoring values, representing the hit statistics as they appear.</param>
|
||||||
|
/// <param name="maximum">The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time.</param>
|
||||||
|
[Pure]
|
||||||
|
internal void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum)
|
||||||
|
{
|
||||||
|
extractScoringValues(header.Statistics, out current, out maximum);
|
||||||
|
current.MaxCombo = header.MaxCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a best-effort extraction of hit statistics into <see cref="ScoringValues"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is useful in a variety of situations, with a few drawbacks that need to be considered:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>The current <see cref="ScoringValues.MaxCombo"/> will always be 0.</item>
|
||||||
|
/// <item>The maximum <see cref="ScoringValues.BonusScore"/> will always be 0.</item>
|
||||||
|
/// <item>The current and maximum <see cref="ScoringValues.CountBasicHitObjects"/> will always be the same value.</item>
|
||||||
|
/// </list>
|
||||||
|
/// Consumers are expected to more accurately fill in the above values (especially the current <see cref="ScoringValues.MaxCombo"/>) via external means (e.g. <see cref="ScoreInfo"/>).
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="statistics">The hit statistics to extract scoring values from.</param>
|
||||||
|
/// <param name="current">The "current" scoring values, representing the hit statistics as they appear.</param>
|
||||||
|
/// <param name="maximum">The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time.</param>
|
||||||
|
[Pure]
|
||||||
|
private void extractScoringValues(IReadOnlyDictionary<HitResult, int> statistics, out ScoringValues current, out ScoringValues maximum)
|
||||||
|
{
|
||||||
|
current = default;
|
||||||
|
maximum = default;
|
||||||
|
|
||||||
foreach ((HitResult result, int count) in statistics)
|
foreach ((HitResult result, int count) in statistics)
|
||||||
{
|
{
|
||||||
// Bonus scores are counted separately directly from the statistics dictionary later on.
|
if (!result.IsScorable())
|
||||||
if (!result.IsScorable() || result.IsBonus())
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// The maximum result of this judgement if it wasn't a miss.
|
if (result.IsBonus())
|
||||||
// E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT).
|
current.BonusScore += count * Judgement.ToNumericResult(result);
|
||||||
HitResult maxResult;
|
else
|
||||||
|
|
||||||
switch (result)
|
|
||||||
{
|
{
|
||||||
case HitResult.LargeTickHit:
|
// The maximum result of this judgement if it wasn't a miss.
|
||||||
case HitResult.LargeTickMiss:
|
// E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT).
|
||||||
maxResult = HitResult.LargeTickHit;
|
HitResult maxResult;
|
||||||
break;
|
|
||||||
|
|
||||||
case HitResult.SmallTickHit:
|
switch (result)
|
||||||
case HitResult.SmallTickMiss:
|
{
|
||||||
maxResult = HitResult.SmallTickHit;
|
case HitResult.LargeTickHit:
|
||||||
break;
|
case HitResult.LargeTickMiss:
|
||||||
|
maxResult = HitResult.LargeTickHit;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
case HitResult.SmallTickHit:
|
||||||
maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result;
|
case HitResult.SmallTickMiss:
|
||||||
break;
|
maxResult = HitResult.SmallTickHit;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current.BaseScore += count * Judgement.ToNumericResult(result);
|
||||||
|
maximum.BaseScore += count * Judgement.ToNumericResult(maxResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
baseScore += count * Judgement.ToNumericResult(result);
|
|
||||||
maxBaseScore += count * Judgement.ToNumericResult(maxResult);
|
|
||||||
|
|
||||||
if (result.AffectsCombo())
|
if (result.AffectsCombo())
|
||||||
maxCombo += count;
|
maximum.MaxCombo += count;
|
||||||
|
|
||||||
if (result.IsBasic())
|
if (result.IsBasic())
|
||||||
basicHitObjects += count;
|
{
|
||||||
|
current.CountBasicHitObjects += count;
|
||||||
|
maximum.CountBasicHitObjects += count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
||||||
|
|
||||||
private readonly Ruleset ruleset;
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private ScoreProcessor scoreProcessor { get; set; }
|
private ScoreProcessor scoreProcessor { get; set; }
|
||||||
|
|
||||||
@ -57,8 +55,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||||
{
|
{
|
||||||
this.ruleset = ruleset.CreateInstance();
|
|
||||||
|
|
||||||
InternalChild = KeyBindingContainer =
|
InternalChild = KeyBindingContainer =
|
||||||
CreateKeyBindingContainer(ruleset, variant, unique)
|
CreateKeyBindingContainer(ruleset, variant, unique)
|
||||||
.WithChild(content = new Container { RelativeSizeAxes = Axes.Both });
|
.WithChild(content = new Container { RelativeSizeAxes = Axes.Both });
|
||||||
@ -85,7 +81,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ReplayStatisticsFrameEvent statisticsStateChangeEvent:
|
case ReplayStatisticsFrameEvent statisticsStateChangeEvent:
|
||||||
scoreProcessor?.ResetFromReplayFrame(ruleset, statisticsStateChangeEvent.Frame);
|
scoreProcessor?.ResetFromReplayFrame(statisticsStateChangeEvent.Frame);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
41
osu.Game/Scoring/ScoringValues.cs
Normal file
41
osu.Game/Scoring/ScoringValues.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// 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 MessagePack;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Scoring
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the required scoring data that fulfils the minimum requirements for a <see cref="ScoreProcessor"/> to calculate score.
|
||||||
|
/// </summary>
|
||||||
|
[MessagePackObject]
|
||||||
|
public struct ScoringValues
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The sum of all "basic" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="Judgement.ToNumericResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Key(0)]
|
||||||
|
public double BaseScore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sum of all "bonus" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBonus"/> and <see cref="Judgement.ToNumericResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Key(1)]
|
||||||
|
public double BonusScore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The highest achieved combo.
|
||||||
|
/// </summary>
|
||||||
|
[Key(2)]
|
||||||
|
public int MaxCombo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The count of "basic" <see cref="HitObject"/>s. See: <see cref="HitResultExtensions.IsBasic"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Key(3)]
|
||||||
|
public int CountBasicHitObjects;
|
||||||
|
}
|
||||||
|
}
|
@ -1,99 +1,29 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Framework.Threading;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osuTK;
|
using osu.Game.Overlays;
|
||||||
using osuTK.Graphics;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
public class TimelineButton : CompositeDrawable
|
public class TimelineButton : IconButton
|
||||||
{
|
{
|
||||||
public Action Action;
|
[BackgroundDependencyLoader]
|
||||||
public readonly BindableBool Enabled = new BindableBool(true);
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
|
||||||
public IconUsage Icon
|
|
||||||
{
|
{
|
||||||
get => button.Icon;
|
// These are using colourProvider but don't match the design.
|
||||||
set => button.Icon = value;
|
// Just something to fit until someone implements the updated design.
|
||||||
|
IconColour = colourProvider.Background1;
|
||||||
|
IconHoverColour = colourProvider.Content2;
|
||||||
|
|
||||||
|
HoverColour = colourProvider.Background1;
|
||||||
|
FlashColour = colourProvider.Content2;
|
||||||
|
|
||||||
|
Add(new RepeatingButtonBehaviour(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IconButton button;
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||||
|
|
||||||
public TimelineButton()
|
|
||||||
{
|
|
||||||
InternalChild = button = new TimelineIconButton { Action = () => Action?.Invoke() };
|
|
||||||
|
|
||||||
button.Enabled.BindTo(Enabled);
|
|
||||||
Width = button.Width;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
button.Size = new Vector2(button.Width, DrawHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TimelineIconButton : IconButton
|
|
||||||
{
|
|
||||||
public TimelineIconButton()
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre;
|
|
||||||
Origin = Anchor.Centre;
|
|
||||||
IconColour = OsuColour.Gray(0.35f);
|
|
||||||
IconHoverColour = Color4.White;
|
|
||||||
HoverColour = OsuColour.Gray(0.25f);
|
|
||||||
FlashColour = OsuColour.Gray(0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScheduledDelegate repeatSchedule;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The initial delay before mouse down repeat begins.
|
|
||||||
/// </summary>
|
|
||||||
private const int repeat_initial_delay = 250;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The delay between mouse down repeats after the initial repeat.
|
|
||||||
/// </summary>
|
|
||||||
private const int repeat_tick_rate = 70;
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
// don't actuate a click since we are manually handling repeats.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
|
||||||
{
|
|
||||||
if (e.Button == MouseButton.Left)
|
|
||||||
{
|
|
||||||
Action clickAction = () => base.OnClick(new ClickEvent(e.CurrentState, e.Button));
|
|
||||||
|
|
||||||
// run once for initial down
|
|
||||||
clickAction();
|
|
||||||
|
|
||||||
Scheduler.Add(repeatSchedule = new ScheduledDelegate(clickAction, Clock.CurrentTime + repeat_initial_delay, repeat_tick_rate));
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnMouseDown(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
|
||||||
{
|
|
||||||
repeatSchedule?.Cancel();
|
|
||||||
base.OnMouseUp(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Transforms;
|
using osu.Framework.Graphics.Transforms;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Layout;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -40,10 +41,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private IFrameBasedClock editorClock { get; set; }
|
private IFrameBasedClock editorClock { get; set; }
|
||||||
|
|
||||||
|
private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize);
|
||||||
|
|
||||||
public ZoomableScrollContainer()
|
public ZoomableScrollContainer()
|
||||||
: base(Direction.Horizontal)
|
: base(Direction.Horizontal)
|
||||||
{
|
{
|
||||||
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
|
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
|
||||||
|
|
||||||
|
AddLayout(zoomedContentWidthCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float minZoom = 1;
|
private float minZoom = 1;
|
||||||
@ -103,12 +108,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.Update();
|
||||||
|
|
||||||
// This width only gets updated on the application of a transform, so this needs to be initialized here.
|
if (!zoomedContentWidthCache.IsValid)
|
||||||
updateZoomedContentWidth();
|
updateZoomedContentWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnScroll(ScrollEvent e)
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
@ -128,7 +133,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
return base.OnScroll(e);
|
return base.OnScroll(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom;
|
private void updateZoomedContentWidth()
|
||||||
|
{
|
||||||
|
zoomedContent.Width = DrawWidth * currentZoom;
|
||||||
|
zoomedContentWidthCache.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
private float zoomTarget = 1;
|
private float zoomTarget = 1;
|
||||||
|
|
||||||
@ -199,8 +208,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
|
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
|
||||||
|
|
||||||
d.currentZoom = newZoom;
|
d.currentZoom = newZoom;
|
||||||
|
|
||||||
d.updateZoomedContentWidth();
|
d.updateZoomedContentWidth();
|
||||||
|
|
||||||
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
|
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
|
||||||
// TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is.
|
// TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is.
|
||||||
d.Invalidate(Invalidation.DrawSize);
|
d.Invalidate(Invalidation.DrawSize);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -9,6 +11,7 @@ using System.Threading.Tasks;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -27,10 +30,10 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
public IEnumerable<string> HandledExtensions => handledExtensions;
|
public IEnumerable<string> HandledExtensions => handledExtensions;
|
||||||
|
|
||||||
private readonly Bindable<FileInfo> currentFile = new Bindable<FileInfo>();
|
private readonly Bindable<FileInfo?> currentFile = new Bindable<FileInfo?>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase game { get; set; }
|
private OsuGameBase game { get; set; } = null!;
|
||||||
|
|
||||||
public FileChooserLabelledTextBox(params string[] handledExtensions)
|
public FileChooserLabelledTextBox(params string[] handledExtensions)
|
||||||
{
|
{
|
||||||
@ -45,7 +48,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
currentFile.BindValueChanged(onFileSelected);
|
currentFile.BindValueChanged(onFileSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFileSelected(ValueChangedEvent<FileInfo> file)
|
private void onFileSelected(ValueChangedEvent<FileInfo?> file)
|
||||||
{
|
{
|
||||||
if (file.NewValue == null)
|
if (file.NewValue == null)
|
||||||
return;
|
return;
|
||||||
@ -65,14 +68,16 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
game.UnregisterImportHandler(this);
|
|
||||||
|
if (game.IsNotNull())
|
||||||
|
game.UnregisterImportHandler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile);
|
public override Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile);
|
||||||
|
|
||||||
private class FileChooserPopover : OsuPopover
|
private class FileChooserPopover : OsuPopover
|
||||||
{
|
{
|
||||||
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo> currentFile)
|
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile)
|
||||||
{
|
{
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
Padding = new MarginPadding(10);
|
Padding = new MarginPadding(10) { Bottom = 0 };
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -38,6 +38,8 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OverlayColourProvider overlayColourProvider { get; set; }
|
private OverlayColourProvider overlayColourProvider { get; set; }
|
||||||
|
|
||||||
|
public bool EnableClicking { get; set; } = true;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
@ -281,6 +283,9 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
|
if (!EnableClicking)
|
||||||
|
return;
|
||||||
|
|
||||||
var channel = clunk?.GetChannel();
|
var channel = clunk?.GetChannel();
|
||||||
|
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
|
92
osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs
Normal file
92
osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// 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.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a component that provides the behaviour of triggering button clicks repeatedly while holding with mouse.
|
||||||
|
/// </summary>
|
||||||
|
public class RepeatingButtonBehaviour : Component
|
||||||
|
{
|
||||||
|
private const double initial_delay = 300;
|
||||||
|
private const double minimum_delay = 80;
|
||||||
|
|
||||||
|
private readonly Drawable button;
|
||||||
|
|
||||||
|
private Sample sample;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An additive modifier for the frequency of the sample played on next actuation.
|
||||||
|
/// This can be adjusted during the button's <see cref="Drawable.OnClick"/> event to affect the repeat sample playback of that click.
|
||||||
|
/// </summary>
|
||||||
|
public double SampleFrequencyModifier { get; set; }
|
||||||
|
|
||||||
|
public RepeatingButtonBehaviour(Drawable button)
|
||||||
|
{
|
||||||
|
this.button = button;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio)
|
||||||
|
{
|
||||||
|
sample = audio.Samples.Get(@"UI/notch-tick");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
beginRepeat();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
adjustDelegate?.Cancel();
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate adjustDelegate;
|
||||||
|
private double adjustDelay = initial_delay;
|
||||||
|
|
||||||
|
private void beginRepeat()
|
||||||
|
{
|
||||||
|
adjustDelegate?.Cancel();
|
||||||
|
|
||||||
|
adjustDelay = initial_delay;
|
||||||
|
adjustNext();
|
||||||
|
|
||||||
|
void adjustNext()
|
||||||
|
{
|
||||||
|
if (IsHovered)
|
||||||
|
{
|
||||||
|
button.TriggerClick();
|
||||||
|
adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f);
|
||||||
|
|
||||||
|
var channel = sample?.GetChannel();
|
||||||
|
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay);
|
||||||
|
channel.Frequency.Value = 1 + repeatModifier + SampleFrequencyModifier;
|
||||||
|
channel.Play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adjustDelay = initial_delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
Flow = new FillFlowContainer
|
Flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding(20),
|
Padding = new MarginPadding(10) { Top = 0 },
|
||||||
Spacing = new Vector2(20),
|
Spacing = new Vector2(20),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
|
418
osu.Game/Screens/Edit/Timing/TapButton.cs
Normal file
418
osu.Game/Screens/Edit/Timing/TapButton.cs
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
// 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 enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
internal class TapButton : CircularContainer, IKeyBindingHandler<GlobalAction>
|
||||||
|
{
|
||||||
|
public const float SIZE = 140;
|
||||||
|
|
||||||
|
public readonly BindableBool IsHandlingTapping = new BindableBool();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private Bindable<ControlPointGroup>? selectedGroup { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private IBeatSyncProvider? beatSyncSource { get; set; }
|
||||||
|
|
||||||
|
private Circle hoverLayer = null!;
|
||||||
|
|
||||||
|
private CircularContainer innerCircle = null!;
|
||||||
|
private Box innerCircleHighlight = null!;
|
||||||
|
|
||||||
|
private int currentLight;
|
||||||
|
|
||||||
|
private Container scaleContainer = null!;
|
||||||
|
private Container lights = null!;
|
||||||
|
private Container lightsGlow = null!;
|
||||||
|
private OsuSpriteText bpmText = null!;
|
||||||
|
private Container textContainer = null!;
|
||||||
|
|
||||||
|
private bool grabbedMouseDown;
|
||||||
|
|
||||||
|
private ScheduledDelegate? resetDelegate;
|
||||||
|
|
||||||
|
private const int light_count = 8;
|
||||||
|
|
||||||
|
private const int initial_taps_to_ignore = 4;
|
||||||
|
|
||||||
|
private const int max_taps_to_consider = 128;
|
||||||
|
|
||||||
|
private const double transition_length = 500;
|
||||||
|
|
||||||
|
private const float angular_light_gap = 0.007f;
|
||||||
|
|
||||||
|
private readonly List<double> tapTimings = new List<double>();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Size = new Vector2(SIZE);
|
||||||
|
|
||||||
|
const float ring_width = 22;
|
||||||
|
const float light_padding = 3;
|
||||||
|
|
||||||
|
InternalChild = scaleContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4
|
||||||
|
},
|
||||||
|
lights = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Name = @"outer masking",
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = light_padding,
|
||||||
|
BorderColour = colourProvider.Background4,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Black,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Name = @"inner masking",
|
||||||
|
Size = new Vector2(SIZE - ring_width * 2 + light_padding * 2),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
lightsGlow = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
innerCircle = new CircularContainer
|
||||||
|
{
|
||||||
|
Size = new Vector2(SIZE - ring_width * 2),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colourProvider.Background2,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
innerCircleHighlight = new Box
|
||||||
|
{
|
||||||
|
Colour = colourProvider.Colour3,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
textContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background1,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.Torus.With(size: 34, weight: FontWeight.SemiBold),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Y = 5,
|
||||||
|
Text = "Tap",
|
||||||
|
},
|
||||||
|
bpmText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.Torus.With(size: 23, weight: FontWeight.Regular),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Y = -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hoverLayer = new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background1.Opacity(0.3f),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < light_count; i++)
|
||||||
|
{
|
||||||
|
var light = new Light
|
||||||
|
{
|
||||||
|
Rotation = (i + 1) * (360f / light_count) + 360 * angular_light_gap / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
lights.Add(light);
|
||||||
|
lightsGlow.Add(light.Glow.CreateProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||||
|
hoverLayer.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
private ColourInfo textColour
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (grabbedMouseDown)
|
||||||
|
return colourProvider.Background4;
|
||||||
|
|
||||||
|
if (IsHovered)
|
||||||
|
return colourProvider.Content2;
|
||||||
|
|
||||||
|
return colourProvider.Background1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
hoverLayer.FadeIn(transition_length, Easing.OutQuint);
|
||||||
|
textContainer.FadeColour(textColour, transition_length, Easing.OutQuint);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
hoverLayer.FadeOut(transition_length, Easing.OutQuint);
|
||||||
|
textContainer.FadeColour(textColour, transition_length, Easing.OutQuint);
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
const double in_duration = 100;
|
||||||
|
|
||||||
|
grabbedMouseDown = true;
|
||||||
|
IsHandlingTapping.Value = true;
|
||||||
|
|
||||||
|
resetDelegate?.Cancel();
|
||||||
|
|
||||||
|
handleTap();
|
||||||
|
|
||||||
|
textContainer.FadeColour(textColour, in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
scaleContainer.ScaleTo(0.99f, in_duration, Easing.OutQuint);
|
||||||
|
innerCircle.ScaleTo(0.96f, in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
innerCircleHighlight
|
||||||
|
.FadeIn(50, Easing.OutQuint)
|
||||||
|
.FlashColour(Color4.White, 1000, Easing.OutQuint);
|
||||||
|
|
||||||
|
lights[currentLight % light_count].Hide();
|
||||||
|
lights[(currentLight + light_count / 2) % light_count].Hide();
|
||||||
|
|
||||||
|
currentLight++;
|
||||||
|
|
||||||
|
lights[currentLight % light_count].Show();
|
||||||
|
lights[(currentLight + light_count / 2) % light_count].Show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
const double out_duration = 800;
|
||||||
|
|
||||||
|
grabbedMouseDown = false;
|
||||||
|
|
||||||
|
textContainer.FadeColour(textColour, out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint);
|
||||||
|
innerCircle.ScaleTo(1, out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
innerCircleHighlight.FadeOut(out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
resetDelegate = Scheduler.AddDelayed(reset, 1000);
|
||||||
|
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == GlobalAction.EditorTapForBPM && !e.Repeat)
|
||||||
|
{
|
||||||
|
// Direct through mouse handling to achieve animation
|
||||||
|
OnMouseDown(new MouseDownEvent(e.CurrentState, MouseButton.Left));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == GlobalAction.EditorTapForBPM)
|
||||||
|
OnMouseUp(new MouseUpEvent(e.CurrentState, MouseButton.Left));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTap()
|
||||||
|
{
|
||||||
|
tapTimings.Add(Clock.CurrentTime);
|
||||||
|
|
||||||
|
if (tapTimings.Count > initial_taps_to_ignore + max_taps_to_consider)
|
||||||
|
tapTimings.RemoveAt(0);
|
||||||
|
|
||||||
|
if (tapTimings.Count < initial_taps_to_ignore * 2)
|
||||||
|
{
|
||||||
|
bpmText.Text = new string('.', tapTimings.Count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double averageBeatLength = (tapTimings.Last() - tapTimings.Skip(initial_taps_to_ignore).First()) / (tapTimings.Count - initial_taps_to_ignore - 1);
|
||||||
|
double clockRate = beatSyncSource?.Clock?.Rate ?? 1;
|
||||||
|
|
||||||
|
double bpm = Math.Round(60000 / averageBeatLength / clockRate);
|
||||||
|
|
||||||
|
bpmText.Text = $"{bpm} BPM";
|
||||||
|
|
||||||
|
var timingPoint = selectedGroup?.Value.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (timingPoint != null)
|
||||||
|
{
|
||||||
|
// Intentionally use the rounded BPM here.
|
||||||
|
timingPoint.BeatLength = 60000 / bpm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
bpmText.FadeOut(transition_length, Easing.OutQuint);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(tapTimings.Count > 0 ? transition_length : 0))
|
||||||
|
{
|
||||||
|
Schedule(() => bpmText.Text = "the beat!");
|
||||||
|
bpmText.FadeIn(800, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var light in lights)
|
||||||
|
light.Hide();
|
||||||
|
|
||||||
|
tapTimings.Clear();
|
||||||
|
currentLight = 0;
|
||||||
|
IsHandlingTapping.Value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Light : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Drawable Glow { get; private set; } = null!;
|
||||||
|
|
||||||
|
private Container fillContent = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Size = new Vector2(0.98f); // Avoid bleed into masking edge.
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new CircularProgress
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Current = { Value = 1f / light_count - angular_light_gap },
|
||||||
|
Colour = colourProvider.Background2,
|
||||||
|
},
|
||||||
|
fillContent = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
Colour = colourProvider.Colour1,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new CircularProgress
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Current = { Value = 1f / light_count - angular_light_gap },
|
||||||
|
Blending = BlendingParameters.Additive
|
||||||
|
},
|
||||||
|
// Please do not try and make sense of this.
|
||||||
|
// Getting the visual effect I was going for relies on what I can only imagine is broken implementation
|
||||||
|
// of `PadExtent`. If that's ever fixed in the future this will likely need to be adjusted.
|
||||||
|
Glow = new CircularProgress
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Current = { Value = 1f / light_count - 0.01f },
|
||||||
|
Blending = BlendingParameters.Additive
|
||||||
|
}.WithEffect(new GlowEffect
|
||||||
|
{
|
||||||
|
Colour = colourProvider.Colour1.Opacity(0.4f),
|
||||||
|
BlurSigma = new Vector2(9f),
|
||||||
|
Strength = 10,
|
||||||
|
PadExtent = true
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Show()
|
||||||
|
{
|
||||||
|
fillContent
|
||||||
|
.FadeIn(50, Easing.OutQuint)
|
||||||
|
.FlashColour(Color4.White, 1000, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Hide()
|
||||||
|
{
|
||||||
|
fillContent
|
||||||
|
.FadeOut(300, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,45 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
public class TapTimingControl : CompositeDrawable
|
public class TapTimingControl : CompositeDrawable
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock editorClock { get; set; }
|
private EditorClock editorClock { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap beatmap { get; set; }
|
private EditorBeatmap beatmap { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
private Bindable<ControlPointGroup> selectedGroup { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly BindableBool isHandlingTapping = new BindableBool();
|
||||||
|
|
||||||
|
private MetronomeDisplay metronome = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
|
const float padding = 10;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
@ -48,8 +60,8 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.Absolute, 200),
|
new Dimension(GridSizeMode.Absolute, 200),
|
||||||
new Dimension(GridSizeMode.Absolute, 60),
|
new Dimension(GridSizeMode.Absolute, 50),
|
||||||
new Dimension(GridSizeMode.Absolute, 60),
|
new Dimension(GridSizeMode.Absolute, TapButton.SIZE + padding),
|
||||||
},
|
},
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
@ -58,6 +70,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(padding),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new GridContainer
|
new GridContainer
|
||||||
@ -72,7 +85,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new MetronomeDisplay
|
metronome = new MetronomeDisplay
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -89,15 +102,14 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding(10),
|
Padding = new MarginPadding { Bottom = padding, Horizontal = padding },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new TimingAdjustButton(1)
|
new TimingAdjustButton(1)
|
||||||
{
|
{
|
||||||
Text = "Offset",
|
Text = "Offset",
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.48f,
|
Size = new Vector2(0.48f, 1),
|
||||||
Height = 50,
|
|
||||||
Action = adjustOffset,
|
Action = adjustOffset,
|
||||||
},
|
},
|
||||||
new TimingAdjustButton(0.1)
|
new TimingAdjustButton(0.1)
|
||||||
@ -105,9 +117,8 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Text = "BPM",
|
Text = "BPM",
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.48f,
|
Size = new Vector2(0.48f, 1),
|
||||||
Height = 50,
|
|
||||||
Action = adjustBpm,
|
Action = adjustBpm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,33 +129,70 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding(10),
|
Padding = new MarginPadding { Bottom = padding, Horizontal = padding },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new RoundedButton
|
new Container
|
||||||
{
|
{
|
||||||
Text = "Reset",
|
RelativeSizeAxes = Axes.Y,
|
||||||
BackgroundColour = colours.Pink,
|
Anchor = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.X,
|
Origin = Anchor.CentreRight,
|
||||||
Width = 0.3f,
|
Height = 0.98f,
|
||||||
Action = reset,
|
Width = TapButton.SIZE / 1.3f,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 15,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new InlineButton(FontAwesome.Solid.Stop, Anchor.TopLeft)
|
||||||
|
{
|
||||||
|
BackgroundColour = colourProvider.Background1,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.49f,
|
||||||
|
Action = reset,
|
||||||
|
},
|
||||||
|
new InlineButton(FontAwesome.Solid.Play, Anchor.BottomLeft)
|
||||||
|
{
|
||||||
|
BackgroundColour = colourProvider.Background1,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.49f,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Action = start,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
new RoundedButton
|
new TapButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.Centre,
|
||||||
Text = "Play from start",
|
IsHandlingTapping = { BindTarget = isHandlingTapping }
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
BackgroundColour = colourProvider.Background1,
|
|
||||||
Width = 0.68f,
|
|
||||||
Action = tap,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
isHandlingTapping.BindValueChanged(handling =>
|
||||||
|
{
|
||||||
|
metronome.EnableClicking = !handling.NewValue;
|
||||||
|
|
||||||
|
if (handling.NewValue)
|
||||||
|
start();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start()
|
||||||
|
{
|
||||||
|
editorClock.Seek(selectedGroup.Value.Time);
|
||||||
|
editorClock.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
editorClock.Stop();
|
||||||
|
editorClock.Seek(selectedGroup.Value.Time);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void adjustOffset(double adjust)
|
private void adjustOffset(double adjust)
|
||||||
@ -176,16 +224,66 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
timing.BeatLength = 60000 / (timing.BPM + adjust);
|
timing.BeatLength = 60000 / (timing.BPM + adjust);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tap()
|
private class InlineButton : OsuButton
|
||||||
{
|
{
|
||||||
editorClock.Seek(selectedGroup.Value.Time);
|
private readonly IconUsage icon;
|
||||||
editorClock.Start();
|
private readonly Anchor anchor;
|
||||||
}
|
|
||||||
|
|
||||||
private void reset()
|
private SpriteIcon spriteIcon = null!;
|
||||||
{
|
|
||||||
editorClock.Stop();
|
[Resolved]
|
||||||
editorClock.Seek(selectedGroup.Value.Time);
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
public InlineButton(IconUsage icon, Anchor anchor)
|
||||||
|
{
|
||||||
|
this.icon = icon;
|
||||||
|
this.anchor = anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Content.CornerRadius = 0;
|
||||||
|
Content.Masking = false;
|
||||||
|
|
||||||
|
BackgroundColour = colourProvider.Background2;
|
||||||
|
|
||||||
|
Content.Add(new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(15),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
spriteIcon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = icon,
|
||||||
|
Size = new Vector2(22),
|
||||||
|
Anchor = anchor,
|
||||||
|
Origin = anchor,
|
||||||
|
Colour = colourProvider.Background1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
// scale looks bad so don't call base.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
spriteIcon.FadeColour(colourProvider.Content2, 200, Easing.OutQuint);
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
spriteIcon.FadeColour(colourProvider.Background1, 200, Easing.OutQuint);
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Threading;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -26,32 +23,24 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
public Action<double> Action;
|
public Action<double> Action;
|
||||||
|
|
||||||
private readonly double adjustAmount;
|
private readonly double adjustAmount;
|
||||||
private ScheduledDelegate adjustDelegate;
|
|
||||||
|
|
||||||
private const int max_multiplier = 10;
|
private const int max_multiplier = 10;
|
||||||
|
|
||||||
private const int adjust_levels = 4;
|
private const int adjust_levels = 4;
|
||||||
|
|
||||||
private const double initial_delay = 300;
|
|
||||||
|
|
||||||
private const double minimum_delay = 80;
|
|
||||||
|
|
||||||
public Container Content { get; set; }
|
public Container Content { get; set; }
|
||||||
|
|
||||||
private double adjustDelay = initial_delay;
|
|
||||||
|
|
||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
|
|
||||||
private readonly OsuSpriteText text;
|
private readonly OsuSpriteText text;
|
||||||
|
|
||||||
private Sample sample;
|
|
||||||
|
|
||||||
public LocalisableString Text
|
public LocalisableString Text
|
||||||
{
|
{
|
||||||
get => text.Text;
|
get => text.Text;
|
||||||
set => text.Text = value;
|
set => text.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly RepeatingButtonBehaviour repeatBehaviour;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OverlayColourProvider colourProvider { get; set; }
|
private OverlayColourProvider colourProvider { get; set; }
|
||||||
|
|
||||||
@ -82,13 +71,13 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddInternal(repeatBehaviour = new RepeatingButtonBehaviour(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load()
|
||||||
{
|
{
|
||||||
sample = audio.Samples.Get(@"UI/notch-tick");
|
|
||||||
|
|
||||||
background.Colour = colourProvider.Background3;
|
background.Colour = colourProvider.Background3;
|
||||||
|
|
||||||
for (int i = 1; i <= adjust_levels; i++)
|
for (int i = 1; i <= adjust_levels; i++)
|
||||||
@ -98,57 +87,22 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnHover(HoverEvent e) => true;
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
beginRepeat();
|
var hoveredBox = Content.OfType<IncrementBox>().FirstOrDefault(d => d.IsHovered);
|
||||||
|
if (hoveredBox == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Action(adjustAmount * hoveredBox.Multiplier);
|
||||||
|
|
||||||
|
hoveredBox.Flash();
|
||||||
|
|
||||||
|
repeatBehaviour.SampleFrequencyModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
|
||||||
{
|
|
||||||
adjustDelegate?.Cancel();
|
|
||||||
base.OnMouseUp(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void beginRepeat()
|
|
||||||
{
|
|
||||||
adjustDelegate?.Cancel();
|
|
||||||
|
|
||||||
adjustDelay = initial_delay;
|
|
||||||
adjustNext();
|
|
||||||
|
|
||||||
void adjustNext()
|
|
||||||
{
|
|
||||||
var hoveredBox = Content.OfType<IncrementBox>().FirstOrDefault(d => d.IsHovered);
|
|
||||||
|
|
||||||
if (hoveredBox != null)
|
|
||||||
{
|
|
||||||
Action(adjustAmount * hoveredBox.Multiplier);
|
|
||||||
|
|
||||||
adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f);
|
|
||||||
|
|
||||||
hoveredBox.Flash();
|
|
||||||
|
|
||||||
var channel = sample?.GetChannel();
|
|
||||||
|
|
||||||
if (channel != null)
|
|
||||||
{
|
|
||||||
double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay);
|
|
||||||
double multiplierModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2f;
|
|
||||||
|
|
||||||
channel.Frequency.Value = 1 + multiplierModifier + repeatModifier;
|
|
||||||
channel.Play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
adjustDelay = initial_delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IncrementBox : CompositeDrawable
|
private class IncrementBox : CompositeDrawable
|
||||||
{
|
{
|
||||||
public readonly float Multiplier;
|
public readonly float Multiplier;
|
||||||
@ -187,7 +141,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
Origin = direction,
|
Origin = direction,
|
||||||
Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold),
|
Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold),
|
||||||
Text = $"{(index > 0 ? "+" : "-")}{Math.Abs(Multiplier * amount)}",
|
Text = $"{(index > 0 ? "+" : "-")}{Math.Abs(Multiplier * amount)}",
|
||||||
Padding = new MarginPadding(5),
|
Padding = new MarginPadding(2),
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -51,6 +53,8 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
private readonly IBindableList<ControlPointGroup> controlPointGroups = new BindableList<ControlPointGroup>();
|
private readonly IBindableList<ControlPointGroup> controlPointGroups = new BindableList<ControlPointGroup>();
|
||||||
|
|
||||||
|
private RoundedButton addButton;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock clock { get; set; }
|
private EditorClock clock { get; set; }
|
||||||
|
|
||||||
@ -105,9 +109,8 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
},
|
},
|
||||||
new RoundedButton
|
addButton = new RoundedButton
|
||||||
{
|
{
|
||||||
Text = "+ Add at current time",
|
|
||||||
Action = addNew,
|
Action = addNew,
|
||||||
Size = new Vector2(160, 30),
|
Size = new Vector2(160, 30),
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
@ -122,7 +125,14 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true);
|
selectedGroup.BindValueChanged(selected =>
|
||||||
|
{
|
||||||
|
deleteButton.Enabled.Value = selected.NewValue != null;
|
||||||
|
|
||||||
|
addButton.Text = selected.NewValue != null
|
||||||
|
? "+ Clone to current time"
|
||||||
|
: "+ Add at current time";
|
||||||
|
}, true);
|
||||||
|
|
||||||
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
|
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
|
||||||
controlPointGroups.BindCollectionChanged((sender, args) =>
|
controlPointGroups.BindCollectionChanged((sender, args) =>
|
||||||
@ -132,13 +142,23 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
selectedGroup.Value = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
trackActivePoint();
|
trackActivePoint();
|
||||||
|
|
||||||
|
addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Type trackedType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given the user has selected a control point group, we want to track any group which is
|
/// Given the user has selected a control point group, we want to track any group which is
|
||||||
/// active at the current point in time which matches the type the user has selected.
|
/// active at the current point in time which matches the type the user has selected.
|
||||||
@ -149,16 +169,27 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
private void trackActivePoint()
|
private void trackActivePoint()
|
||||||
{
|
{
|
||||||
// For simplicity only match on the first type of the active control point.
|
// For simplicity only match on the first type of the active control point.
|
||||||
var selectedPointType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
if (selectedGroup.Value == null)
|
||||||
|
trackedType = null;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the selected group only has one control point, update the tracking type.
|
||||||
|
if (selectedGroup.Value.ControlPoints.Count == 1)
|
||||||
|
trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
|
||||||
|
// If the selected group has more than one control point, choose the first as the tracking type
|
||||||
|
// if we don't already have a singular tracked type.
|
||||||
|
else if (trackedType == null)
|
||||||
|
trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedPointType != null)
|
if (trackedType != null)
|
||||||
{
|
{
|
||||||
// We don't have an efficient way of looking up groups currently, only individual point types.
|
// We don't have an efficient way of looking up groups currently, only individual point types.
|
||||||
// To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo.
|
// To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo.
|
||||||
|
|
||||||
// Find the next group which has the same type as the selected one.
|
// Find the next group which has the same type as the selected one.
|
||||||
var found = Beatmap.ControlPointInfo.Groups
|
var found = Beatmap.ControlPointInfo.Groups
|
||||||
.Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType))
|
.Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType))
|
||||||
.LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate);
|
.LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate);
|
||||||
|
|
||||||
if (found != null)
|
if (found != null)
|
||||||
@ -189,7 +220,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
// Try and create matching types from the currently selected control point.
|
// Try and create matching types from the currently selected control point.
|
||||||
var selected = selectedGroup.Value;
|
var selected = selectedGroup.Value;
|
||||||
|
|
||||||
if (selected != null)
|
if (selected != null && selected != group)
|
||||||
{
|
{
|
||||||
foreach (var controlPoint in selected.ControlPoints)
|
foreach (var controlPoint in selected.ControlPoints)
|
||||||
{
|
{
|
||||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
||||||
sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select");
|
sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-select");
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ using System.Threading;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -244,7 +245,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hideRequested != null)
|
if (hideRequested != null)
|
||||||
items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
|
items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Screens.Select.Filter
|
|||||||
[Description("Author")]
|
[Description("Author")]
|
||||||
Author,
|
Author,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatsBpm))]
|
[LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksBpm))]
|
||||||
BPM,
|
BPM,
|
||||||
|
|
||||||
[Description("Date Added")]
|
[Description("Date Added")]
|
||||||
@ -28,10 +28,10 @@ namespace osu.Game.Screens.Select.Filter
|
|||||||
Length,
|
Length,
|
||||||
|
|
||||||
// todo: pending support (https://github.com/ppy/osu/issues/4917)
|
// todo: pending support (https://github.com/ppy/osu/issues/4917)
|
||||||
// [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))]
|
// [Description("Rank Achieved")]
|
||||||
// RankAchieved,
|
// RankAchieved,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
|
[Description("Source")]
|
||||||
Source,
|
Source,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingTitle))]
|
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingTitle))]
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select
|
|||||||
private readonly Box light;
|
private readonly Box light;
|
||||||
|
|
||||||
public FooterButton()
|
public FooterButton()
|
||||||
: base(HoverSampleSet.Button)
|
: base(HoverSampleSet.Toolbar)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
Shear = SHEAR;
|
Shear = SHEAR;
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Options
|
|||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public BeatmapOptionsButton()
|
public BeatmapOptionsButton()
|
||||||
: base(HoverSampleSet.Submit)
|
: base(HoverSampleSet.Button)
|
||||||
{
|
{
|
||||||
Width = width;
|
Width = width;
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Skinning
|
|||||||
/// The <see cref="ISkin"/> which is being transformed.
|
/// The <see cref="ISkin"/> which is being transformed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[NotNull]
|
[NotNull]
|
||||||
protected internal ISkin Skin { get; }
|
public ISkin Skin { get; }
|
||||||
|
|
||||||
protected LegacySkinTransformer([NotNull] ISkin skin)
|
protected LegacySkinTransformer([NotNull] ISkin skin)
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
@ -44,9 +46,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
|||||||
switch (request)
|
switch (request)
|
||||||
{
|
{
|
||||||
case CreateRoomRequest createRoomRequest:
|
case CreateRoomRequest createRoomRequest:
|
||||||
var apiRoom = new Room();
|
var apiRoom = cloneRoom(createRoomRequest.Room);
|
||||||
|
|
||||||
apiRoom.CopyFrom(createRoomRequest.Room);
|
|
||||||
|
|
||||||
// Passwords are explicitly not copied between rooms.
|
// Passwords are explicitly not copied between rooms.
|
||||||
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
|
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
|
||||||
@ -178,12 +178,31 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
|||||||
|
|
||||||
private Room createResponseRoom(Room room, bool withParticipants)
|
private Room createResponseRoom(Room room, bool withParticipants)
|
||||||
{
|
{
|
||||||
var responseRoom = new Room();
|
var responseRoom = cloneRoom(room);
|
||||||
responseRoom.CopyFrom(room);
|
|
||||||
|
// Password is hidden from the response, and is only propagated via HasPassword.
|
||||||
|
bool hadPassword = responseRoom.HasPassword.Value;
|
||||||
responseRoom.Password.Value = null;
|
responseRoom.Password.Value = null;
|
||||||
|
responseRoom.HasPassword.Value = hadPassword;
|
||||||
|
|
||||||
if (!withParticipants)
|
if (!withParticipants)
|
||||||
responseRoom.RecentParticipants.Clear();
|
responseRoom.RecentParticipants.Clear();
|
||||||
|
|
||||||
return responseRoom;
|
return responseRoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Room cloneRoom(Room source)
|
||||||
|
{
|
||||||
|
var result = JsonConvert.DeserializeObject<Room>(JsonConvert.SerializeObject(source));
|
||||||
|
Debug.Assert(result != null);
|
||||||
|
|
||||||
|
// Playlist item IDs aren't serialised.
|
||||||
|
if (source.CurrentPlaylistItem.Value != null)
|
||||||
|
result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID;
|
||||||
|
for (int i = 0; i < source.Playlist.Count; i++)
|
||||||
|
result.Playlist[i].ID = source.Playlist[i].ID;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
namespace osu.Game.Users.Drawables
|
namespace osu.Game.Users.Drawables
|
||||||
@ -74,11 +73,6 @@ namespace osu.Game.Users.Drawables
|
|||||||
{
|
{
|
||||||
private LocalisableString tooltip = default_tooltip_text;
|
private LocalisableString tooltip = default_tooltip_text;
|
||||||
|
|
||||||
public ClickableArea()
|
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override LocalisableString TooltipText
|
public override LocalisableString TooltipText
|
||||||
{
|
{
|
||||||
get => Enabled.Value ? tooltip : default;
|
get => Enabled.Value ? tooltip : default;
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Users.Drawables
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
new HoverClickSounds(HoverSampleSet.Submit)
|
new HoverClickSounds()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Users
|
|||||||
protected Drawable Background { get; private set; }
|
protected Drawable Background { get; private set; }
|
||||||
|
|
||||||
protected UserPanel(APIUser user)
|
protected UserPanel(APIUser user)
|
||||||
: base(HoverSampleSet.Submit)
|
: base(HoverSampleSet.Button)
|
||||||
{
|
{
|
||||||
if (user == null)
|
if (user == null)
|
||||||
throw new ArgumentNullException(nameof(user));
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Utils
|
|||||||
sentrySession = SentrySdk.Init(options =>
|
sentrySession = SentrySdk.Init(options =>
|
||||||
{
|
{
|
||||||
// Not setting the dsn will completely disable sentry.
|
// Not setting the dsn will completely disable sentry.
|
||||||
if (game.IsDeployedBuild)
|
if (game.IsDeployedBuild && game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal))
|
||||||
options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";
|
options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";
|
||||||
|
|
||||||
options.AutoSessionTracking = true;
|
options.AutoSessionTracking = true;
|
||||||
@ -159,6 +159,7 @@ namespace osu.Game.Utils
|
|||||||
Game = game.Clock.CurrentTime,
|
Game = game.Clock.CurrentTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
scope.SetTag(@"beatmap", $"{beatmap.OnlineID}");
|
||||||
scope.SetTag(@"ruleset", ruleset.ShortName);
|
scope.SetTag(@"ruleset", ruleset.ShortName);
|
||||||
scope.SetTag(@"os", $"{RuntimeInfo.OS} ({Environment.OSVersion})");
|
scope.SetTag(@"os", $"{RuntimeInfo.OS} ({Environment.OSVersion})");
|
||||||
scope.SetTag(@"processor count", Environment.ProcessorCount.ToString());
|
scope.SetTag(@"processor count", Environment.ProcessorCount.ToString());
|
||||||
|
@ -35,8 +35,8 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.11.2" />
|
<PackageReference Include="Realm" Version="10.14.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.530.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.605.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.17.1" />
|
<PackageReference Include="Sentry" Version="3.17.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.530.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.605.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
@ -84,11 +84,11 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.530.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.605.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2022.429.0" ExcludeAssets="all" />
|
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2022.429.0" ExcludeAssets="all" />
|
||||||
<PackageReference Include="Realm" Version="10.11.2" />
|
<PackageReference Include="Realm" Version="10.14.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user