mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 15:12:57 +08:00
Merge branch 'master' into editor-exit-harsh-blocking
This commit is contained in:
commit
8591630e5c
.vscode
osu.Game.Rulesets.Osu
osu.Game.Tests
Database
Resources
Visual
osu.Game
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"ms-dotnettools.csharp"
|
||||||
|
]
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -16,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset<OsuHitObject>
|
public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
{
|
{
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModStrictTracking) };
|
||||||
|
|
||||||
[SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")]
|
[SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")]
|
||||||
public Bindable<bool> NoSliderHeadAccuracy { get; } = new BindableBool(true);
|
public Bindable<bool> NoSliderHeadAccuracy { get; } = new BindableBool(true);
|
||||||
|
|
||||||
|
148
osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
Normal file
148
osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModStrictTracking : Mod, IApplicableAfterBeatmapConversion, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
|
{
|
||||||
|
public override string Name => @"Strict Tracking";
|
||||||
|
public override string Acronym => @"ST";
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.PenFancy;
|
||||||
|
public override ModType Type => ModType.DifficultyIncrease;
|
||||||
|
public override string Description => @"Follow circles just got serious...";
|
||||||
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ModClassic) };
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
|
{
|
||||||
|
if (drawable is DrawableSlider slider)
|
||||||
|
{
|
||||||
|
slider.Tracking.ValueChanged += e =>
|
||||||
|
{
|
||||||
|
if (e.NewValue || slider.Judged) return;
|
||||||
|
|
||||||
|
var tail = slider.NestedHitObjects.OfType<StrictTrackingDrawableSliderTail>().First();
|
||||||
|
|
||||||
|
if (!tail.Judged)
|
||||||
|
tail.MissForcefully();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var osuBeatmap = (OsuBeatmap)beatmap;
|
||||||
|
|
||||||
|
if (osuBeatmap.HitObjects.Count == 0) return;
|
||||||
|
|
||||||
|
var hitObjects = osuBeatmap.HitObjects.Select(ho =>
|
||||||
|
{
|
||||||
|
if (ho is Slider slider)
|
||||||
|
{
|
||||||
|
var newSlider = new StrictTrackingSlider(slider);
|
||||||
|
return newSlider;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ho;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
osuBeatmap.HitObjects = hitObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
drawableRuleset.Playfield.RegisterPool<StrictTrackingSliderTailCircle, StrictTrackingDrawableSliderTail>(10, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StrictTrackingSliderTailCircle : SliderTailCircle
|
||||||
|
{
|
||||||
|
public StrictTrackingSliderTailCircle(Slider slider)
|
||||||
|
: base(slider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Judgement CreateJudgement() => new OsuJudgement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StrictTrackingDrawableSliderTail : DrawableSliderTail
|
||||||
|
{
|
||||||
|
public override bool DisplayResult => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StrictTrackingSlider : Slider
|
||||||
|
{
|
||||||
|
public StrictTrackingSlider(Slider original)
|
||||||
|
{
|
||||||
|
StartTime = original.StartTime;
|
||||||
|
Samples = original.Samples;
|
||||||
|
Path = original.Path;
|
||||||
|
NodeSamples = original.NodeSamples;
|
||||||
|
RepeatCount = original.RepeatCount;
|
||||||
|
Position = original.Position;
|
||||||
|
NewCombo = original.NewCombo;
|
||||||
|
ComboOffset = original.ComboOffset;
|
||||||
|
LegacyLastTickOffset = original.LegacyLastTickOffset;
|
||||||
|
TickDistanceMultiplier = original.TickDistanceMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken);
|
||||||
|
|
||||||
|
foreach (var e in sliderEvents)
|
||||||
|
{
|
||||||
|
switch (e.Type)
|
||||||
|
{
|
||||||
|
case SliderEventType.Head:
|
||||||
|
AddNested(HeadCircle = new SliderHeadCircle
|
||||||
|
{
|
||||||
|
StartTime = e.Time,
|
||||||
|
Position = Position,
|
||||||
|
StackHeight = StackHeight,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SliderEventType.LegacyLastTick:
|
||||||
|
AddNested(TailCircle = new StrictTrackingSliderTailCircle(this)
|
||||||
|
{
|
||||||
|
RepeatIndex = e.SpanIndex,
|
||||||
|
StartTime = e.Time,
|
||||||
|
Position = EndPosition,
|
||||||
|
StackHeight = StackHeight
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SliderEventType.Repeat:
|
||||||
|
AddNested(new SliderRepeat(this)
|
||||||
|
{
|
||||||
|
RepeatIndex = e.SpanIndex,
|
||||||
|
StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration,
|
||||||
|
Position = Position + Path.PositionAt(e.PathProgress),
|
||||||
|
StackHeight = StackHeight,
|
||||||
|
Scale = Scale,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateNestedSamples();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
public Slider()
|
public Slider()
|
||||||
{
|
{
|
||||||
SamplesBindable.CollectionChanged += (_, __) => updateNestedSamples();
|
SamplesBindable.CollectionChanged += (_, __) => UpdateNestedSamples();
|
||||||
Path.Version.ValueChanged += _ => updateNestedPositions();
|
Path.Version.ValueChanged += _ => updateNestedPositions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNestedSamples();
|
UpdateNestedSamples();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateNestedPositions()
|
private void updateNestedPositions()
|
||||||
@ -241,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
TailCircle.Position = EndPosition;
|
TailCircle.Position = EndPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateNestedSamples()
|
protected void UpdateNestedSamples()
|
||||||
{
|
{
|
||||||
var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)
|
var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)
|
||||||
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
||||||
|
@ -159,6 +159,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
|
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
|
||||||
new OsuModHidden(),
|
new OsuModHidden(),
|
||||||
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
|
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
|
||||||
|
new OsuModStrictTracking()
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Conversion:
|
case ModType.Conversion:
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
switch (osuComponent.Component)
|
switch (osuComponent.Component)
|
||||||
{
|
{
|
||||||
case OsuSkinComponents.FollowPoint:
|
case OsuSkinComponents.FollowPoint:
|
||||||
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
return this.GetAnimation(component.LookupName, true, true, true, startAtCurrentTime: false);
|
||||||
|
|
||||||
case OsuSkinComponents.SliderFollowCircle:
|
case OsuSkinComponents.SliderFollowCircle:
|
||||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||||
|
@ -590,6 +590,8 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Assert.IsTrue(imported.DeletePending);
|
Assert.IsTrue(imported.DeletePending);
|
||||||
|
|
||||||
|
var originalAddedDate = imported.DateAdded;
|
||||||
|
|
||||||
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
@ -597,6 +599,7 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
Assert.IsFalse(imported.DeletePending);
|
Assert.IsFalse(imported.DeletePending);
|
||||||
Assert.IsFalse(importedSecondTime.DeletePending);
|
Assert.IsFalse(importedSecondTime.DeletePending);
|
||||||
|
Assert.That(importedSecondTime.DateAdded, Is.GreaterThan(originalAddedDate));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -646,6 +649,8 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Assert.IsTrue(imported.DeletePending);
|
Assert.IsTrue(imported.DeletePending);
|
||||||
|
|
||||||
|
var originalAddedDate = imported.DateAdded;
|
||||||
|
|
||||||
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
@ -653,6 +658,7 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
Assert.IsFalse(imported.DeletePending);
|
Assert.IsFalse(imported.DeletePending);
|
||||||
Assert.IsFalse(importedSecondTime.DeletePending);
|
Assert.IsFalse(importedSecondTime.DeletePending);
|
||||||
|
Assert.That(importedSecondTime.DateAdded, Is.GreaterThan(originalAddedDate));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,6 @@ namespace osu.Game.Tests.Resources
|
|||||||
StarRating = diff,
|
StarRating = diff,
|
||||||
Length = length,
|
Length = length,
|
||||||
BPM = bpm,
|
BPM = bpm,
|
||||||
MaxCombo = 1000,
|
|
||||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||||
Ruleset = rulesetInfo,
|
Ruleset = rulesetInfo,
|
||||||
Metadata = metadata,
|
Metadata = metadata,
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
// 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.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osu.Game.Skinning.Editor;
|
using osu.Game.Skinning.Editor;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -29,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("reload skin editor", () =>
|
AddStep("reload skin editor", () =>
|
||||||
{
|
{
|
||||||
skinEditor?.Expire();
|
skinEditor?.Expire();
|
||||||
Player.ScaleTo(0.8f);
|
Player.ScaleTo(0.4f);
|
||||||
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -40,6 +45,36 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility());
|
AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEditComponent()
|
||||||
|
{
|
||||||
|
BarHitErrorMeter hitErrorMeter = null;
|
||||||
|
|
||||||
|
AddStep("select bar hit error blueprint", () =>
|
||||||
|
{
|
||||||
|
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First(b => b.Item is BarHitErrorMeter);
|
||||||
|
|
||||||
|
hitErrorMeter = (BarHitErrorMeter)blueprint.Item;
|
||||||
|
skinEditor.SelectedComponents.Clear();
|
||||||
|
skinEditor.SelectedComponents.Add(blueprint.Item);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault);
|
||||||
|
|
||||||
|
AddStep("hover first slider", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(
|
||||||
|
skinEditor.ChildrenOfType<SkinSettingsToolbox>().First()
|
||||||
|
.ChildrenOfType<SettingsSlider<float>>().First()
|
||||||
|
.ChildrenOfType<SliderBar<float>>().First()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("adjust slider via keyboard", () => InputManager.Key(Key.Left));
|
||||||
|
|
||||||
|
AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default);
|
||||||
|
}
|
||||||
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -14,6 +15,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Overlays.Toolbar;
|
using osu.Game.Overlays.Toolbar;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
@ -21,10 +23,12 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Leaderboards;
|
using osu.Game.Screens.Select.Leaderboards;
|
||||||
using osu.Game.Screens.Select.Options;
|
using osu.Game.Screens.Select.Options;
|
||||||
|
using osu.Game.Skinning.Editor;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -66,6 +70,73 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEditComponentDuringGameplay()
|
||||||
|
{
|
||||||
|
Screens.Select.SongSelect songSelect = null;
|
||||||
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
|
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||||
|
|
||||||
|
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||||
|
|
||||||
|
SkinEditor skinEditor = null;
|
||||||
|
|
||||||
|
AddStep("open skin editor", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.PressKey(Key.ShiftLeft);
|
||||||
|
InputManager.Key(Key.S);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("get skin editor", () => (skinEditor = Game.ChildrenOfType<SkinEditor>().FirstOrDefault()) != null);
|
||||||
|
|
||||||
|
AddStep("Click gameplay scene button", () =>
|
||||||
|
{
|
||||||
|
skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text == "Gameplay").TriggerClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for player", () =>
|
||||||
|
{
|
||||||
|
// dismiss any notifications that may appear (ie. muted notification).
|
||||||
|
clickMouseInCentre();
|
||||||
|
return Game.ScreenStack.CurrentScreen is Player;
|
||||||
|
});
|
||||||
|
|
||||||
|
BarHitErrorMeter hitErrorMeter = null;
|
||||||
|
|
||||||
|
AddUntilStep("select bar hit error blueprint", () =>
|
||||||
|
{
|
||||||
|
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().FirstOrDefault(b => b.Item is BarHitErrorMeter);
|
||||||
|
|
||||||
|
if (blueprint == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hitErrorMeter = (BarHitErrorMeter)blueprint.Item;
|
||||||
|
skinEditor.SelectedComponents.Clear();
|
||||||
|
skinEditor.SelectedComponents.Add(blueprint.Item);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault);
|
||||||
|
|
||||||
|
AddStep("hover first slider", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(
|
||||||
|
skinEditor.ChildrenOfType<SkinSettingsToolbox>().First()
|
||||||
|
.ChildrenOfType<SettingsSlider<float>>().First()
|
||||||
|
.ChildrenOfType<SliderBar<float>>().First()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("adjust slider via keyboard", () => InputManager.Key(Key.Left));
|
||||||
|
|
||||||
|
AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRetryCountIncrements()
|
public void TestRetryCountIncrements()
|
||||||
{
|
{
|
||||||
@ -120,7 +191,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
||||||
|
|
||||||
AddStep("show local scores", () => Game.ChildrenOfType<BeatmapDetailAreaTabControl>().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local));
|
AddStep("show local scores",
|
||||||
|
() => Game.ChildrenOfType<BeatmapDetailAreaTabControl>().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local));
|
||||||
|
|
||||||
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<LeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
|
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<LeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
|
||||||
|
|
||||||
@ -152,7 +224,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
||||||
|
|
||||||
AddStep("show local scores", () => Game.ChildrenOfType<BeatmapDetailAreaTabControl>().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local));
|
AddStep("show local scores",
|
||||||
|
() => Game.ChildrenOfType<BeatmapDetailAreaTabControl>().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local));
|
||||||
|
|
||||||
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<LeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
|
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<LeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using Realms;
|
using Realms;
|
||||||
@ -169,7 +170,12 @@ namespace osu.Game.Beatmaps
|
|||||||
[Ignored]
|
[Ignored]
|
||||||
public APIBeatmap? OnlineInfo { get; set; }
|
public APIBeatmap? OnlineInfo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum achievable combo on this beatmap, populated for online info purposes only.
|
||||||
|
/// Todo: This should never be used nor exist, but is still relied on in <see cref="ScoresContainer.Scores"/> since <see cref="IBeatmapInfo"/> can't be used yet. For now this is obsoleted until it is removed.
|
||||||
|
/// </summary>
|
||||||
[Ignored]
|
[Ignored]
|
||||||
|
[Obsolete("Use ScoreManager.GetMaximumAchievableComboAsync instead.")]
|
||||||
public int? MaxCombo { get; set; }
|
public int? MaxCombo { get; set; }
|
||||||
|
|
||||||
[Ignored]
|
[Ignored]
|
||||||
|
@ -53,9 +53,6 @@ namespace osu.Game.Beatmaps
|
|||||||
[NotMapped]
|
[NotMapped]
|
||||||
public APIBeatmap OnlineInfo { get; set; }
|
public APIBeatmap OnlineInfo { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
public int? MaxCombo { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The playable length in milliseconds of this beatmap.
|
/// The playable length in milliseconds of this beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -295,7 +295,6 @@ namespace osu.Game.Database
|
|||||||
TimelineZoom = beatmap.TimelineZoom,
|
TimelineZoom = beatmap.TimelineZoom,
|
||||||
Countdown = beatmap.Countdown,
|
Countdown = beatmap.Countdown,
|
||||||
CountdownOffset = beatmap.CountdownOffset,
|
CountdownOffset = beatmap.CountdownOffset,
|
||||||
MaxCombo = beatmap.MaxCombo,
|
|
||||||
Bookmarks = beatmap.Bookmarks,
|
Bookmarks = beatmap.Bookmarks,
|
||||||
BeatmapSet = realmBeatmapSet,
|
BeatmapSet = realmBeatmapSet,
|
||||||
};
|
};
|
||||||
|
@ -1046,6 +1046,10 @@ namespace osu.Game
|
|||||||
|
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
|
case GlobalAction.ToggleSkinEditor:
|
||||||
|
skinEditor.ToggleVisibility();
|
||||||
|
return true;
|
||||||
|
|
||||||
case GlobalAction.ResetInputSettings:
|
case GlobalAction.ResetInputSettings:
|
||||||
Host.ResetInputHandlers();
|
Host.ResetInputHandlers();
|
||||||
frameworkConfig.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode).SetDefault();
|
frameworkConfig.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode).SetDefault();
|
||||||
|
@ -173,7 +173,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
{
|
{
|
||||||
Text = score.MaxCombo.ToLocalisableString(@"0\x"),
|
Text = score.MaxCombo.ToLocalisableString(@"0\x"),
|
||||||
Font = OsuFont.GetFont(size: text_size),
|
Font = OsuFont.GetFont(size: text_size),
|
||||||
|
#pragma warning disable 618
|
||||||
Colour = score.MaxCombo == score.BeatmapInfo.MaxCombo ? highAccuracyColour : Color4.White
|
Colour = score.MaxCombo == score.BeatmapInfo.MaxCombo ? highAccuracyColour : Color4.White
|
||||||
|
#pragma warning restore 618
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,7 +78,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
// TODO: temporary. should be removed once `OrderByTotalScore` can accept `IScoreInfo`.
|
// TODO: temporary. should be removed once `OrderByTotalScore` can accept `IScoreInfo`.
|
||||||
var beatmapInfo = new BeatmapInfo
|
var beatmapInfo = new BeatmapInfo
|
||||||
{
|
{
|
||||||
|
#pragma warning disable 618
|
||||||
MaxCombo = apiBeatmap.MaxCombo,
|
MaxCombo = apiBeatmap.MaxCombo,
|
||||||
|
#pragma warning restore 618
|
||||||
Status = apiBeatmap.Status,
|
Status = apiBeatmap.Status,
|
||||||
MD5Hash = apiBeatmap.MD5Hash
|
MD5Hash = apiBeatmap.MD5Hash
|
||||||
};
|
};
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
new SettingsButton
|
new SettingsButton
|
||||||
{
|
{
|
||||||
Text = SkinSettingsStrings.SkinLayoutEditor,
|
Text = SkinSettingsStrings.SkinLayoutEditor,
|
||||||
Action = () => skinEditor?.Toggle(),
|
Action = () => skinEditor?.ToggleVisibility(),
|
||||||
},
|
},
|
||||||
new ExportSkinButton(),
|
new ExportSkinButton(),
|
||||||
};
|
};
|
||||||
|
@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </param>
|
/// </param>
|
||||||
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
||||||
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
||||||
protected void RegisterPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
public void RegisterPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
||||||
where TObject : HitObject
|
where TObject : HitObject
|
||||||
where TDrawable : DrawableHitObject, new()
|
where TDrawable : DrawableHitObject, new()
|
||||||
=> RegisterPool<TObject, TDrawable>(new DrawablePool<TDrawable>(initialSize, maximumSize));
|
=> RegisterPool<TObject, TDrawable>(new DrawablePool<TDrawable>(initialSize, maximumSize));
|
||||||
|
@ -157,7 +157,7 @@ namespace osu.Game.Scoring
|
|||||||
public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy();
|
public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this <see cref="EFScoreInfo"/> represents a legacy (osu!stable) score.
|
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Ignored]
|
[Ignored]
|
||||||
public bool IsLegacyScore => Mods.OfType<ModClassic>().Any();
|
public bool IsLegacyScore => Mods.OfType<ModClassic>().Any();
|
||||||
|
@ -134,35 +134,9 @@ namespace osu.Game.Scoring
|
|||||||
if (string.IsNullOrEmpty(score.BeatmapInfo.MD5Hash))
|
if (string.IsNullOrEmpty(score.BeatmapInfo.MD5Hash))
|
||||||
return score.TotalScore;
|
return score.TotalScore;
|
||||||
|
|
||||||
int beatmapMaxCombo;
|
int? beatmapMaxCombo = await GetMaximumAchievableComboAsync(score, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (beatmapMaxCombo == null)
|
||||||
if (score.IsLegacyScore)
|
return score.TotalScore;
|
||||||
{
|
|
||||||
// This score is guaranteed to be an osu!stable score.
|
|
||||||
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
|
|
||||||
if (score.BeatmapInfo.MaxCombo != null)
|
|
||||||
beatmapMaxCombo = score.BeatmapInfo.MaxCombo.Value;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (difficulties == null)
|
|
||||||
return score.TotalScore;
|
|
||||||
|
|
||||||
// We can compute the max combo locally after the async beatmap difficulty computation.
|
|
||||||
var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// Something failed during difficulty calculation. Fall back to provided score.
|
|
||||||
if (difficulty == null)
|
|
||||||
return score.TotalScore;
|
|
||||||
|
|
||||||
beatmapMaxCombo = difficulty.Value.MaxCombo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This is guaranteed to be a non-legacy score.
|
|
||||||
// The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
|
|
||||||
beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (beatmapMaxCombo == 0)
|
if (beatmapMaxCombo == 0)
|
||||||
return 0;
|
return 0;
|
||||||
@ -171,7 +145,37 @@ namespace osu.Game.Scoring
|
|||||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
scoreProcessor.Mods.Value = score.Mods;
|
scoreProcessor.Mods.Value = score.Mods;
|
||||||
|
|
||||||
return (long)Math.Round(scoreProcessor.ComputeFinalLegacyScore(mode, score, beatmapMaxCombo));
|
return (long)Math.Round(scoreProcessor.ComputeFinalLegacyScore(mode, score, beatmapMaxCombo.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the maximum achievable combo for the provided score.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The <see cref="ScoreInfo"/> to compute the maximum achievable combo for.</param>
|
||||||
|
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the process.</param>
|
||||||
|
/// <returns>The maximum achievable combo. A <see langword="null"/> return value indicates the difficulty cache has failed to retrieve the combo.</returns>
|
||||||
|
public async Task<int?> GetMaximumAchievableComboAsync([NotNull] ScoreInfo score, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (score.IsLegacyScore)
|
||||||
|
{
|
||||||
|
// This score is guaranteed to be an osu!stable score.
|
||||||
|
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
if (score.BeatmapInfo.MaxCombo != null)
|
||||||
|
return score.BeatmapInfo.MaxCombo.Value;
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
|
||||||
|
if (difficulties == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// We can compute the max combo locally after the async beatmap difficulty computation.
|
||||||
|
var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
|
||||||
|
return difficulty?.MaxCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is guaranteed to be a non-legacy score.
|
||||||
|
// The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
|
||||||
|
return Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -65,10 +65,12 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata;
|
var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata;
|
||||||
string creator = metadata.Author.Username;
|
string creator = metadata.Author.Username;
|
||||||
|
|
||||||
|
int? beatmapMaxCombo = scoreManager.GetMaximumAchievableComboAsync(score).GetResultSafely();
|
||||||
|
|
||||||
var topStatistics = new List<StatisticDisplay>
|
var topStatistics = new List<StatisticDisplay>
|
||||||
{
|
{
|
||||||
new AccuracyStatistic(score.Accuracy),
|
new AccuracyStatistic(score.Accuracy),
|
||||||
new ComboStatistic(score.MaxCombo, beatmap.MaxCombo, score.Statistics.All(stat => !stat.Key.BreaksCombo() || stat.Value == 0)),
|
new ComboStatistic(score.MaxCombo, beatmapMaxCombo),
|
||||||
new PerformanceStatistic(score),
|
new PerformanceStatistic(score),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,8 +82,6 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
statisticDisplays.AddRange(topStatistics);
|
statisticDisplays.AddRange(topStatistics);
|
||||||
statisticDisplays.AddRange(bottomStatistics);
|
statisticDisplays.AddRange(bottomStatistics);
|
||||||
|
|
||||||
var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely();
|
|
||||||
|
|
||||||
AddInternal(new FillFlowContainer
|
AddInternal(new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -224,6 +224,8 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
if (score.Date != default)
|
if (score.Date != default)
|
||||||
AddInternal(new PlayedOnText(score.Date));
|
AddInternal(new PlayedOnText(score.Date));
|
||||||
|
|
||||||
|
var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely();
|
||||||
|
|
||||||
if (starDifficulty != null)
|
if (starDifficulty != null)
|
||||||
{
|
{
|
||||||
starAndModDisplay.Add(new StarRatingDisplay(starDifficulty.Value)
|
starAndModDisplay.Add(new StarRatingDisplay(starDifficulty.Value)
|
||||||
|
@ -26,11 +26,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="combo">The combo to be displayed.</param>
|
/// <param name="combo">The combo to be displayed.</param>
|
||||||
/// <param name="maxCombo">The maximum value of <paramref name="combo"/>.</param>
|
/// <param name="maxCombo">The maximum value of <paramref name="combo"/>.</param>
|
||||||
/// <param name="isPerfect">Whether this is a perfect combo.</param>
|
public ComboStatistic(int combo, int? maxCombo)
|
||||||
public ComboStatistic(int combo, int? maxCombo, bool isPerfect)
|
|
||||||
: base("combo", combo, maxCombo)
|
: base("combo", combo, maxCombo)
|
||||||
{
|
{
|
||||||
this.isPerfect = isPerfect;
|
isPerfect = combo == maxCombo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Appear()
|
public override void Appear()
|
||||||
|
@ -19,15 +19,15 @@ namespace osu.Game.Skinning.Editor
|
|||||||
/// A container which handles loading a skin editor on user request for a specified target.
|
/// A container which handles loading a skin editor on user request for a specified target.
|
||||||
/// This also handles the scaling / positioning adjustment of the target.
|
/// This also handles the scaling / positioning adjustment of the target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SkinEditorOverlay : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
public class SkinEditorOverlay : OverlayContainer, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
private readonly ScalingContainer scalingContainer;
|
private readonly ScalingContainer scalingContainer;
|
||||||
|
|
||||||
|
protected override bool BlockNonPositionalInput => true;
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
private SkinEditor skinEditor;
|
private SkinEditor skinEditor;
|
||||||
|
|
||||||
public const float VISIBLE_TARGET_SCALE = 0.8f;
|
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private OsuGame game { get; set; }
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
@ -49,33 +49,13 @@ namespace osu.Game.Skinning.Editor
|
|||||||
|
|
||||||
Hide();
|
Hide();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.ToggleSkinEditor:
|
|
||||||
Toggle();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Toggle()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
if (skinEditor == null)
|
|
||||||
Show();
|
|
||||||
else
|
|
||||||
skinEditor.ToggleVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Hide()
|
|
||||||
{
|
|
||||||
// base call intentionally omitted.
|
|
||||||
skinEditor?.Hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Show()
|
|
||||||
{
|
|
||||||
// base call intentionally omitted as we have custom behaviour.
|
|
||||||
|
|
||||||
if (skinEditor != null)
|
if (skinEditor != null)
|
||||||
{
|
{
|
||||||
skinEditor.Show();
|
skinEditor.Show();
|
||||||
@ -83,29 +63,24 @@ namespace osu.Game.Skinning.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
var editor = new SkinEditor();
|
var editor = new SkinEditor();
|
||||||
|
|
||||||
editor.State.BindValueChanged(visibility => updateComponentVisibility());
|
editor.State.BindValueChanged(visibility => updateComponentVisibility());
|
||||||
|
|
||||||
skinEditor = editor;
|
skinEditor = editor;
|
||||||
|
|
||||||
// Schedule ensures that if `Show` is called before this overlay is loaded,
|
LoadComponentAsync(editor, _ =>
|
||||||
// it will not throw (LoadComponentAsync requires the load target to be in a loaded state).
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
{
|
||||||
if (editor != skinEditor)
|
if (editor != skinEditor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LoadComponentAsync(editor, _ =>
|
AddInternal(editor);
|
||||||
{
|
|
||||||
if (editor != skinEditor)
|
|
||||||
return;
|
|
||||||
|
|
||||||
AddInternal(editor);
|
SetTarget(lastTargetScreen);
|
||||||
|
|
||||||
SetTarget(lastTargetScreen);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void PopOut() => skinEditor?.Hide();
|
||||||
|
|
||||||
private void updateComponentVisibility()
|
private void updateComponentVisibility()
|
||||||
{
|
{
|
||||||
Debug.Assert(skinEditor != null);
|
Debug.Assert(skinEditor != null);
|
||||||
|
@ -104,7 +104,7 @@ namespace osu.Game.Skinning.Editor
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SceneButton : OsuButton
|
public class SceneButton : OsuButton
|
||||||
{
|
{
|
||||||
public SceneButton()
|
public SceneButton()
|
||||||
{
|
{
|
||||||
|
@ -163,6 +163,12 @@ namespace osu.Game.Stores
|
|||||||
return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds);
|
return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void UndeleteForReuse(BeatmapSetInfo existing)
|
||||||
|
{
|
||||||
|
base.UndeleteForReuse(existing);
|
||||||
|
existing.DateAdded = DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool IsAvailableLocally(BeatmapSetInfo model)
|
public override bool IsAvailableLocally(BeatmapSetInfo model)
|
||||||
{
|
{
|
||||||
return Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID));
|
return Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID));
|
||||||
|
@ -351,7 +351,8 @@ namespace osu.Game.Stores
|
|||||||
|
|
||||||
using (var transaction = realm.BeginWrite())
|
using (var transaction = realm.BeginWrite())
|
||||||
{
|
{
|
||||||
existing.DeletePending = false;
|
if (existing.DeletePending)
|
||||||
|
UndeleteForReuse(existing);
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +388,9 @@ namespace osu.Game.Stores
|
|||||||
{
|
{
|
||||||
LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
|
LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
|
||||||
|
|
||||||
existing.DeletePending = false;
|
if (existing.DeletePending)
|
||||||
|
UndeleteForReuse(existing);
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
|
|
||||||
return existing.ToLive(Realm);
|
return existing.ToLive(Realm);
|
||||||
@ -527,6 +530,15 @@ namespace osu.Game.Stores
|
|||||||
private bool checkAllFilesExist(TModel model) =>
|
private bool checkAllFilesExist(TModel model) =>
|
||||||
model.Files.All(f => Files.Storage.Exists(f.File.GetStoragePath()));
|
model.Files.All(f => Files.Storage.Exists(f.File.GetStoragePath()));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when an existing model is in a soft deleted state but being recovered.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="existing">The existing model.</param>
|
||||||
|
protected virtual void UndeleteForReuse(TModel existing)
|
||||||
|
{
|
||||||
|
existing.DeletePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this specified path should be removed after successful import.
|
/// Whether this specified path should be removed after successful import.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Loading…
Reference in New Issue
Block a user