mirror of
https://github.com/ppy/osu
synced 2025-03-30 23:26:53 +00:00
Merge branch 'masking-changes' into framework-bump
This commit is contained in:
commit
13692d8e7a
osu.Desktop
osu.Game.Rulesets.Osu.Tests/Editor
osu.Game.Rulesets.Osu
Edit/Blueprints/Sliders/Components
UI
osu.Game.Rulesets.Taiko/UI
osu.Game.Tests
Beatmaps/Formats
Resources/Archives
Scores/IO
Skins
Visual
Gameplay
TestSceneDrawableStoryboardSprite.csTestSceneLeadIn.csTestSceneStoryboardCommands.csTestSceneStoryboardWithIntro.csTestSceneStoryboardWithOutro.cs
Multiplayer
Navigation
UserInterface
osu.Game.Tournament/Screens/Ladder
osu.Game
Beatmaps
Beatmap.csBeatmapConverter.cs
Drawables
DummyWorkingBeatmap.csFormats
IBeatmap.csLocalCachedBeatmapMetadataSource.csDatabase
Graphics/UserInterface
IO
Localisation
DeleteConfirmationContentStrings.csDeleteConfirmationDialogStrings.csFirstRunSetupBeatmapScreenStrings.csLanguage.cs
Online
OsuGame.csOverlays
BeatmapListingOverlay.cs
Chat
ChatOverlay.csDialog
FirstRunSetup
Mods
NowPlayingOverlay.csSettings/Sections
Maintenance
BeatmapSettings.csCollectionsSettings.csMassDeleteConfirmationDialog.csMassVideoDeleteConfirmationDialog.csModPresetSettings.csScoreSettings.csSkinSettings.cs
OnlineSection.csSkinEditor
Rulesets
Difficulty
Edit
Objects/Drawables
UI
Scoring
Screens
Backgrounds
Edit
Menu
OnlinePlay
Play
Skinning
Storyboards
@ -489,6 +489,7 @@ namespace osu.Desktop
|
||||
public static uint Stride => (uint)Marshal.SizeOf(typeof(NvApplication)) | (2 << 16);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
internal enum NvStatus
|
||||
{
|
||||
OK = 0, // Success. Request is completed.
|
||||
@ -611,6 +612,7 @@ namespace osu.Desktop
|
||||
FIRMWARE_REVISION_NOT_SUPPORTED = -200, // The device's firmware is not supported.
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
internal enum NvSystemType
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
@ -618,6 +620,7 @@ namespace osu.Desktop
|
||||
DESKTOP = 2
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
internal enum NvGpuType
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
@ -625,6 +628,7 @@ namespace osu.Desktop
|
||||
DGPU = 2, // Discrete
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
internal enum NvSettingID : uint
|
||||
{
|
||||
OGL_AA_LINE_GAMMA_ID = 0x2089BF6C,
|
||||
@ -717,6 +721,7 @@ namespace osu.Desktop
|
||||
INVALID_SETTING_ID = 0xFFFFFFFF
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
internal enum NvShimSetting : uint
|
||||
{
|
||||
SHIM_RENDERING_MODE_INTEGRATED = 0x00000000,
|
||||
@ -731,6 +736,7 @@ namespace osu.Desktop
|
||||
SHIM_RENDERING_MODE_DEFAULT = SHIM_RENDERING_MODE_AUTO_SELECT
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
internal enum NvThreadControlSetting : uint
|
||||
{
|
||||
OGL_THREAD_CONTROL_ENABLE = 0x00000001,
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
@ -163,6 +164,7 @@ namespace osu.Desktop.Windows
|
||||
[DllImport("Shell32.dll")]
|
||||
private static extern void SHChangeNotify(EventId wEventId, Flags uFlags, IntPtr dwItem1, IntPtr dwItem2);
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
private enum EventId
|
||||
{
|
||||
/// <summary>
|
||||
@ -172,6 +174,7 @@ namespace osu.Desktop.Windows
|
||||
SHCNE_ASSOCCHANGED = 0x08000000
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
private enum Flags : uint
|
||||
{
|
||||
SHCNF_IDLIST = 0x0000
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||
using osu.Game.Tests.Visual;
|
||||
@ -52,6 +53,65 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft));
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
|
||||
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft));
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft));
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDistanceSnapAdjustDoesNotHideTheGridIfStartingEnabled()
|
||||
{
|
||||
double distanceSnap = double.PositiveInfinity;
|
||||
|
||||
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
||||
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("store distance snap", () => distanceSnap = this.ChildrenOfType<IDistanceSnapProvider>().First().DistanceSpacingMultiplier.Value);
|
||||
|
||||
AddStep("increase distance", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.AltLeft);
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.ScrollVerticalBy(1);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
});
|
||||
|
||||
AddUntilStep("distance snap increased", () => this.ChildrenOfType<IDistanceSnapProvider>().First().DistanceSpacingMultiplier.Value, () => Is.GreaterThan(distanceSnap));
|
||||
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDistanceSnapAdjustShowsGridMomentarilyIfStartingDisabled()
|
||||
{
|
||||
double distanceSnap = double.PositiveInfinity;
|
||||
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("store distance snap", () => distanceSnap = this.ChildrenOfType<IDistanceSnapProvider>().First().DistanceSpacingMultiplier.Value);
|
||||
|
||||
AddStep("start increasing distance", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.AltLeft);
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
});
|
||||
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
|
||||
AddStep("finish increasing distance", () =>
|
||||
{
|
||||
InputManager.ScrollVerticalBy(1);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
});
|
||||
|
||||
AddUntilStep("distance snap increased", () => this.ChildrenOfType<IDistanceSnapProvider>().First().DistanceSpacingMultiplier.Value, () => Is.GreaterThan(distanceSnap));
|
||||
AddUntilStep("distance snap hidden in the end", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -30,23 +30,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestAddOverlappingControlPoints()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200));
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
|
||||
AddAssert("last connection displayed", () =>
|
||||
{
|
||||
var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position == new Vector2(300));
|
||||
return lastConnection.DrawWidth > 50;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerfectCurveTooManyPoints()
|
||||
{
|
||||
@ -194,24 +177,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addAssertPointPositionChanged(points, i);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStackingUpdatesConnectionPosition()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
Vector2 connectionPosition;
|
||||
addControlPointStep(connectionPosition = new Vector2(300));
|
||||
addControlPointStep(new Vector2(600));
|
||||
|
||||
// Apply a big number in stacking so the person running the test can clearly see if it fails
|
||||
AddStep("apply stacking", () => slider.StackHeightBindable.Value += 10);
|
||||
|
||||
AddAssert($"Connection at {connectionPosition} changed",
|
||||
() => visualiser.Connections[0].Position,
|
||||
() => !Is.EqualTo(connectionPosition)
|
||||
);
|
||||
}
|
||||
|
||||
private void addAssertPointPositionChanged(Vector2[] points, int index)
|
||||
{
|
||||
AddAssert($"Point at {points.ElementAt(index)} changed",
|
||||
|
@ -4,10 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
@ -15,36 +12,21 @@ using osuTK;
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A visualisation of the line between two <see cref="PathControlPointPiece{T}"/>s.
|
||||
/// A visualisation of the lines between <see cref="PathControlPointPiece{T}"/>s.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointConnectionPiece{T}"/> visualises.</typeparam>
|
||||
public partial class PathControlPointConnectionPiece<T> : CompositeDrawable where T : OsuHitObject, IHasPath
|
||||
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointConnection{T}"/> visualises.</typeparam>
|
||||
public partial class PathControlPointConnection<T> : SmoothPath where T : OsuHitObject, IHasPath
|
||||
{
|
||||
public readonly PathControlPoint ControlPoint;
|
||||
|
||||
private readonly Path path;
|
||||
private readonly T hitObject;
|
||||
public int ControlPointIndex { get; set; }
|
||||
|
||||
private IBindable<Vector2> hitObjectPosition;
|
||||
private IBindable<int> pathVersion;
|
||||
private IBindable<int> stackHeight;
|
||||
|
||||
public PathControlPointConnectionPiece(T hitObject, int controlPointIndex)
|
||||
public PathControlPointConnection(T hitObject)
|
||||
{
|
||||
this.hitObject = hitObject;
|
||||
ControlPointIndex = controlPointIndex;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
ControlPoint = hitObject.Path.ControlPoints[controlPointIndex];
|
||||
|
||||
InternalChild = path = new SmoothPath
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
PathRadius = 1
|
||||
};
|
||||
PathRadius = 1;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -68,18 +50,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
/// </summary>
|
||||
private void updateConnectingPath()
|
||||
{
|
||||
Position = hitObject.StackedPosition + ControlPoint.Position;
|
||||
Position = hitObject.StackedPosition;
|
||||
|
||||
path.ClearVertices();
|
||||
ClearVertices();
|
||||
|
||||
int nextIndex = ControlPointIndex + 1;
|
||||
if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count)
|
||||
return;
|
||||
foreach (var controlPoint in hitObject.Path.ControlPoints)
|
||||
AddVertex(controlPoint.Position);
|
||||
|
||||
path.AddVertex(Vector2.Zero);
|
||||
path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
|
||||
|
||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||
OriginPosition = PositionInBoundingBox(Vector2.Zero);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,7 +37,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
|
||||
|
||||
internal readonly Container<PathControlPointPiece<T>> Pieces;
|
||||
internal readonly Container<PathControlPointConnectionPiece<T>> Connections;
|
||||
|
||||
private readonly IBindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly T hitObject;
|
||||
@ -63,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
Connections = new Container<PathControlPointConnectionPiece<T>> { RelativeSizeAxes = Axes.Both },
|
||||
new PathControlPointConnection<T>(hitObject),
|
||||
Pieces = new Container<PathControlPointPiece<T>> { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
}
|
||||
@ -78,6 +77,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
controlPoints.BindTo(hitObject.Path.ControlPoints);
|
||||
}
|
||||
|
||||
// Generally all the control points are within the visible area all the time.
|
||||
public override bool UpdateSubTreeMasking() => true;
|
||||
|
||||
/// <summary>
|
||||
/// Handles correction of invalid path types.
|
||||
/// </summary>
|
||||
@ -185,17 +187,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
Debug.Assert(e.NewItems != null);
|
||||
|
||||
// If inserting in the path (not appending),
|
||||
// update indices of existing connections after insert location
|
||||
if (e.NewStartingIndex < Pieces.Count)
|
||||
{
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
if (connection.ControlPointIndex >= e.NewStartingIndex)
|
||||
connection.ControlPointIndex += e.NewItems.Count;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
{
|
||||
var point = (PathControlPoint)e.NewItems[i];
|
||||
@ -209,8 +200,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
d.DragInProgress = DragInProgress;
|
||||
d.DragEnded = DragEnded;
|
||||
}));
|
||||
|
||||
Connections.Add(new PathControlPointConnectionPiece<T>(hitObject, e.NewStartingIndex + i));
|
||||
}
|
||||
|
||||
break;
|
||||
@ -222,19 +211,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray())
|
||||
piece.RemoveAndDisposeImmediately();
|
||||
foreach (var connection in Connections.Where(c => c.ControlPoint == point).ToArray())
|
||||
connection.RemoveAndDisposeImmediately();
|
||||
}
|
||||
|
||||
// If removing before the end of the path,
|
||||
// update indices of connections after remove location
|
||||
if (e.OldStartingIndex < Pieces.Count)
|
||||
{
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
if (connection.ControlPointIndex >= e.OldStartingIndex)
|
||||
connection.ControlPointIndex -= e.OldItems.Count;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
// For osu! gameplay, everything is always on screen.
|
||||
// Skipping masking calculations improves performance in intense beatmaps (ie. https://osu.ppy.sh/beatmapsets/150945#osu/372245)
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
|
||||
public override bool UpdateSubTreeMasking() => false;
|
||||
|
||||
public SmokeContainer Smoke { get; }
|
||||
public FollowPointRenderer FollowPoints { get; }
|
||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
@ -345,7 +344,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public void Add(Drawable proxy) => AddInternal(proxy);
|
||||
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
|
||||
public override bool UpdateSubTreeMasking()
|
||||
{
|
||||
// DrawableHitObject disables masking.
|
||||
// Hitobject content is proxied and unproxied based on hit status and the IsMaskedAway value could get stuck because of this.
|
||||
|
@ -25,6 +25,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
@ -37,6 +38,22 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal));
|
||||
|
||||
[Test]
|
||||
public void TestUnsupportedStoryboardEvents()
|
||||
{
|
||||
const string name = "Resources/storyboard_only_video.osu";
|
||||
|
||||
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
|
||||
Assert.That(decoded.beatmap.UnhandledEventLines.Count, Is.EqualTo(1));
|
||||
Assert.That(decoded.beatmap.UnhandledEventLines.Single(), Is.EqualTo("Video,0,\"video.avi\""));
|
||||
|
||||
var memoryStream = encodeToLegacy(decoded);
|
||||
|
||||
var storyboard = new LegacyStoryboardDecoder().Decode(new LineBufferedReader(memoryStream));
|
||||
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(allBeatmaps))]
|
||||
public void TestEncodeDecodeStability(string name)
|
||||
{
|
||||
|
@ -14,6 +14,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.IO.Legacy;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
@ -31,6 +32,7 @@ using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
@ -224,6 +226,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
new OsuModDoubleTime { SpeedChange = { Value = 1.1 } }
|
||||
};
|
||||
scoreInfo.OnlineID = 123123;
|
||||
scoreInfo.User = new APIUser
|
||||
{
|
||||
Username = "spaceman_atlas",
|
||||
Id = 3035836,
|
||||
CountryCode = CountryCode.PL
|
||||
};
|
||||
scoreInfo.ClientVersion = "2023.1221.0";
|
||||
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
@ -248,6 +256,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.ClientVersion, Is.EqualTo("2023.1221.0"));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.RealmUser.OnlineID, Is.EqualTo(3035836));
|
||||
});
|
||||
}
|
||||
|
||||
@ -352,6 +361,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[HitResult.Great] = 200,
|
||||
[HitResult.LargeTickHit] = 1,
|
||||
};
|
||||
scoreInfo.Rank = ScoreRank.A;
|
||||
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
var score = new Score
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[TestCase(1, 3)]
|
||||
[TestCase(1, 0)]
|
||||
[TestCase(0, 3)]
|
||||
public void CatchMergesFruitAndDropletMisses(int missCount, int largeTickMissCount)
|
||||
public void TestCatchMergesFruitAndDropletMisses(int missCount, int largeTickMissCount)
|
||||
{
|
||||
var ruleset = new CatchRuleset().RulesetInfo;
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
@ -41,7 +41,22 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScoreWithMissIsNotPerfect()
|
||||
public void TestFailPreserved()
|
||||
{
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo();
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
|
||||
scoreInfo.Rank = ScoreRank.F;
|
||||
|
||||
var score = new Score { ScoreInfo = scoreInfo };
|
||||
var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap);
|
||||
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.F));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScoreWithMissIsNotPerfect()
|
||||
{
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
|
BIN
osu.Game.Tests/Resources/Archives/japanese-filename.osz
Normal file
BIN
osu.Game.Tests/Resources/Archives/japanese-filename.osz
Normal file
Binary file not shown.
@ -15,6 +15,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@ -23,6 +24,7 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Scores.IO
|
||||
{
|
||||
@ -284,6 +286,272 @@ namespace osu.Game.Tests.Scores.IO
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserLookedUpByUsernameForOnlineScoreIfUserIDMissing()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
var api = (DummyAPIAccess)osu.API;
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetUserRequest userRequest:
|
||||
if (userRequest.Lookup != "Test user")
|
||||
return false;
|
||||
|
||||
userRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = "Test user",
|
||||
CountryCode = CountryCode.JP,
|
||||
Id = 1234
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = 12345,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmap.Beatmaps.First()
|
||||
};
|
||||
|
||||
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
|
||||
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
|
||||
Assert.AreEqual(toImport.User.Username, imported.User.Username);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
|
||||
Assert.AreEqual(1234, imported.RealmUser.OnlineID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserLookedUpByUsernameForLegacyOnlineScore()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
var api = (DummyAPIAccess)osu.API;
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetUserRequest userRequest:
|
||||
if (userRequest.Lookup != "Test user")
|
||||
return false;
|
||||
|
||||
userRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = "Test user",
|
||||
CountryCode = CountryCode.JP,
|
||||
Id = 1234
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
LegacyOnlineID = 12345,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmap.Beatmaps.First()
|
||||
};
|
||||
|
||||
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
|
||||
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
|
||||
Assert.AreEqual(toImport.User.Username, imported.User.Username);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
|
||||
Assert.AreEqual(1234, imported.RealmUser.OnlineID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserNotLookedUpForOfflineScoreIfUserIDMissing()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
var api = (DummyAPIAccess)osu.API;
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetUserRequest userRequest:
|
||||
if (userRequest.Lookup != "Test user")
|
||||
return false;
|
||||
|
||||
userRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = "Test user",
|
||||
CountryCode = CountryCode.JP,
|
||||
Id = 1234
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = -1,
|
||||
LegacyOnlineID = -1,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmap.Beatmaps.First()
|
||||
};
|
||||
|
||||
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
|
||||
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
|
||||
Assert.AreEqual(toImport.User.Username, imported.User.Username);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
|
||||
Assert.That(imported.RealmUser.OnlineID, Is.LessThanOrEqualTo(1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserLookedUpByOnlineIDIfPresent([Values] bool isOnlineScore)
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
var api = (DummyAPIAccess)osu.API;
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetUserRequest userRequest:
|
||||
if (userRequest.Lookup != "5555")
|
||||
return false;
|
||||
|
||||
userRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = "Some other guy",
|
||||
CountryCode = CountryCode.DE,
|
||||
Id = 5555
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
User = new APIUser { Id = 5555 },
|
||||
Date = DateTimeOffset.Now,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmap.Beatmaps.First()
|
||||
};
|
||||
if (isOnlineScore)
|
||||
toImport.OnlineID = 12345;
|
||||
|
||||
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
|
||||
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
Assert.AreEqual("Some other guy", imported.RealmUser.Username);
|
||||
Assert.AreEqual(5555, imported.RealmUser.OnlineID);
|
||||
Assert.AreEqual(CountryCode.DE, imported.RealmUser.CountryCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
|
||||
{
|
||||
// clone to avoid attaching the input score to realm.
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
@ -12,6 +13,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual;
|
||||
using MemoryStream = System.IO.MemoryStream;
|
||||
|
||||
namespace osu.Game.Tests.Skins
|
||||
{
|
||||
@ -21,6 +23,52 @@ namespace osu.Game.Tests.Skins
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveAndLegacyExportJapaneseFilename()
|
||||
{
|
||||
IWorkingBeatmap beatmap = null!;
|
||||
MemoryStream outStream = null!;
|
||||
|
||||
// Ensure importer encoding is correct
|
||||
AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"japanese-filename.osz"));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null);
|
||||
|
||||
// Ensure exporter encoding is correct (round trip)
|
||||
AddStep("export", () =>
|
||||
{
|
||||
outStream = new MemoryStream();
|
||||
|
||||
new LegacyBeatmapExporter(LocalStorage)
|
||||
.ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null);
|
||||
});
|
||||
|
||||
AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveAndNonLegacyExportJapaneseFilename()
|
||||
{
|
||||
IWorkingBeatmap beatmap = null!;
|
||||
MemoryStream outStream = null!;
|
||||
|
||||
// Ensure importer encoding is correct
|
||||
AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"japanese-filename.osz"));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null);
|
||||
|
||||
// Ensure exporter encoding is correct (round trip)
|
||||
AddStep("export", () =>
|
||||
{
|
||||
outStream = new MemoryStream();
|
||||
|
||||
new BeatmapExporter(LocalStorage)
|
||||
.ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null);
|
||||
});
|
||||
|
||||
AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveOggAudio()
|
||||
{
|
||||
@ -45,6 +93,12 @@ namespace osu.Game.Tests.Skins
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"spinner-osu")) != null);
|
||||
}
|
||||
|
||||
private IWorkingBeatmap importBeatmapFromStream(Stream stream)
|
||||
{
|
||||
var imported = beatmaps.Import(new ImportTask(stream, "filename.osz")).GetResultSafely();
|
||||
return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0]));
|
||||
}
|
||||
|
||||
private IWorkingBeatmap importBeatmapFromArchives(string filename)
|
||||
{
|
||||
var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
|
@ -85,8 +85,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
if (scaleTransformProvided)
|
||||
{
|
||||
sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current + 1000, 1, 2);
|
||||
sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1);
|
||||
sprite.Commands.AddScale(Easing.None, Time.Current, Time.Current + 1000, 1, 2);
|
||||
sprite.Commands.AddScale(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1);
|
||||
}
|
||||
|
||||
layer.Elements.Clear();
|
||||
@ -211,7 +211,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var layer = storyboard.GetLayer("Background");
|
||||
|
||||
var sprite = new StoryboardSprite(lookupName, origin, initialPosition);
|
||||
sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1);
|
||||
var loop = sprite.AddLoopingGroup(Time.Current, 100);
|
||||
loop.AddAlpha(Easing.None, 0, 10000, 1, 1);
|
||||
|
||||
layer.Elements.Clear();
|
||||
layer.Add(sprite);
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
|
||||
sprite.Commands.AddAlpha(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
|
||||
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
|
||||
@ -73,17 +73,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
|
||||
// these should be ignored as we have an alpha visibility blocker proceeding this command.
|
||||
sprite.TimelineGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1);
|
||||
var loopGroup = sprite.AddLoop(loop_start_time, 50);
|
||||
loopGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1);
|
||||
sprite.Commands.AddScale(Easing.None, loop_start_time, -18000, 0, 1);
|
||||
var loopGroup = sprite.AddLoopingGroup(loop_start_time, 50);
|
||||
loopGroup.AddScale(Easing.None, loop_start_time, -18000, 0, 1);
|
||||
|
||||
var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
|
||||
var target = addEventToLoop ? loopGroup : sprite.Commands;
|
||||
double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0;
|
||||
target.Alpha.Add(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1);
|
||||
target.AddAlpha(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1);
|
||||
|
||||
// these should be ignored due to being in the future.
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
|
||||
loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1);
|
||||
sprite.Commands.AddAlpha(Easing.None, 18000, 20000, 0, 1);
|
||||
loopGroup.AddAlpha(Easing.None, 38000, 40000, 0, 1);
|
||||
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
|
||||
|
264
osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs
Normal file
264
osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs
Normal file
@ -0,0 +1,264 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneStoryboardCommands : OsuTestScene
|
||||
{
|
||||
[Cached(typeof(Storyboard))]
|
||||
private TestStoryboard storyboard { get; set; } = new TestStoryboard
|
||||
{
|
||||
UseSkinSprites = false,
|
||||
AlwaysProvideTexture = true,
|
||||
};
|
||||
|
||||
private readonly ManualClock manualClock = new ManualClock { Rate = 1, IsRunning = true };
|
||||
private int clockDirection;
|
||||
|
||||
private const string lookup_name = "hitcircleoverlay";
|
||||
private const double clock_limit = 2500;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private Container content = null!;
|
||||
private SpriteText timelineText = null!;
|
||||
private Box timelineMarker = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
base.Content.Children = new Drawable[]
|
||||
{
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
timelineText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Bottom = 60 },
|
||||
},
|
||||
timelineMarker = new Box
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Size = new Vector2(2, 50),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("start clock", () => clockDirection = 1);
|
||||
AddStep("pause clock", () => clockDirection = 0);
|
||||
AddStep("set clock = 0", () => manualClock.CurrentTime = 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalCommandPlayback()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
s.Commands.AddY(Easing.OutBounce, 500, 900, 100, 240);
|
||||
s.Commands.AddY(Easing.OutQuint, 1100, 1500, 240, 100);
|
||||
}));
|
||||
|
||||
assert(0, 100);
|
||||
assert(500, 100);
|
||||
assert(1000, 240);
|
||||
assert(1500, 100);
|
||||
assert(clock_limit, 100);
|
||||
assert(1500, 100);
|
||||
assert(1000, 240);
|
||||
assert(500, 100);
|
||||
assert(0, 100);
|
||||
|
||||
void assert(double time, double y)
|
||||
{
|
||||
AddStep($"set clock = {time}", () => manualClock.CurrentTime = time);
|
||||
AddAssert($"sprite y = {y} at t = {time}", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().Y == y);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoopingCommandsPlayback()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
var loop = s.AddLoopingGroup(250, 1);
|
||||
loop.AddY(Easing.OutBounce, 0, 400, 100, 240);
|
||||
loop.AddY(Easing.OutQuint, 600, 1000, 240, 100);
|
||||
}));
|
||||
|
||||
assert(0, 100);
|
||||
assert(250, 100);
|
||||
assert(850, 240);
|
||||
assert(1250, 100);
|
||||
assert(1850, 240);
|
||||
assert(2250, 100);
|
||||
assert(clock_limit, 100);
|
||||
assert(2250, 100);
|
||||
assert(1850, 240);
|
||||
assert(1250, 100);
|
||||
assert(850, 240);
|
||||
assert(250, 100);
|
||||
assert(0, 100);
|
||||
|
||||
void assert(double time, double y)
|
||||
{
|
||||
AddStep($"set clock = {time}", () => manualClock.CurrentTime = time);
|
||||
AddAssert($"sprite y = {y} at t = {time}", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().Y == y);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoopManyTimes()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
var loop = s.AddLoopingGroup(500, 10000);
|
||||
loop.AddY(Easing.OutBounce, 0, 60, 100, 240);
|
||||
loop.AddY(Easing.OutQuint, 80, 120, 240, 100);
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParameterTemporaryEffect()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
s.Commands.AddFlipV(Easing.None, 1000, 1500, true, false);
|
||||
}));
|
||||
|
||||
AddAssert("sprite not flipped at t = 0", () => !this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("set clock = 1250", () => manualClock.CurrentTime = 1250);
|
||||
AddAssert("sprite flipped at t = 1250", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("set clock = 2000", () => manualClock.CurrentTime = 2000);
|
||||
AddAssert("sprite not flipped at t = 2000", () => !this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("resume clock", () => clockDirection = 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParameterPermanentEffect()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
s.Commands.AddFlipV(Easing.None, 1000, 1000, true, true);
|
||||
}));
|
||||
|
||||
AddAssert("sprite flipped at t = 0", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("set clock = 1250", () => manualClock.CurrentTime = 1250);
|
||||
AddAssert("sprite flipped at t = 1250", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("set clock = 2000", () => manualClock.CurrentTime = 2000);
|
||||
AddAssert("sprite flipped at t = 2000", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("resume clock", () => clockDirection = 1);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (manualClock.CurrentTime > clock_limit || manualClock.CurrentTime < 0)
|
||||
clockDirection = -clockDirection;
|
||||
|
||||
manualClock.CurrentTime += Time.Elapsed * clockDirection;
|
||||
timelineText.Text = $"Time: {manualClock.CurrentTime:0}ms";
|
||||
timelineMarker.X = (float)(manualClock.CurrentTime / clock_limit);
|
||||
}
|
||||
|
||||
private DrawableStoryboard createStoryboard(Action<StoryboardSprite>? addCommands = null)
|
||||
{
|
||||
var layer = storyboard.GetLayer("Background");
|
||||
|
||||
var sprite = new StoryboardSprite(lookup_name, Anchor.Centre, new Vector2(320, 240));
|
||||
sprite.Commands.AddScale(Easing.None, 0, clock_limit, 0.5f, 0.5f);
|
||||
sprite.Commands.AddAlpha(Easing.None, 0, clock_limit, 1, 1);
|
||||
addCommands?.Invoke(sprite);
|
||||
|
||||
layer.Elements.Clear();
|
||||
layer.Add(sprite);
|
||||
|
||||
return storyboard.CreateDrawable().With(c => c.Clock = new FramedClock(manualClock));
|
||||
}
|
||||
|
||||
private partial class TestStoryboard : Storyboard
|
||||
{
|
||||
public override DrawableStoryboard CreateDrawable(IReadOnlyList<Mod>? mods = null)
|
||||
{
|
||||
return new TestDrawableStoryboard(this, mods);
|
||||
}
|
||||
|
||||
public bool AlwaysProvideTexture { get; set; }
|
||||
|
||||
public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty;
|
||||
|
||||
private partial class TestDrawableStoryboard : DrawableStoryboard
|
||||
{
|
||||
private readonly bool alwaysProvideTexture;
|
||||
|
||||
public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList<Mod>? mods)
|
||||
: base(storyboard, mods)
|
||||
{
|
||||
alwaysProvideTexture = storyboard.AlwaysProvideTexture;
|
||||
}
|
||||
|
||||
protected override IResourceStore<byte[]> CreateResourceLookupStore() => alwaysProvideTexture
|
||||
? new AlwaysReturnsTextureStore()
|
||||
: new ResourceStore<byte[]>();
|
||||
|
||||
internal class AlwaysReturnsTextureStore : IResourceStore<byte[]>
|
||||
{
|
||||
private const string test_image = "Resources/Textures/test-image.png";
|
||||
|
||||
private readonly DllResourceStore store;
|
||||
|
||||
public AlwaysReturnsTextureStore()
|
||||
{
|
||||
store = TestResources.GetStore();
|
||||
}
|
||||
|
||||
public void Dispose() => store.Dispose();
|
||||
|
||||
public byte[] Get(string name) => store.Get(test_image);
|
||||
|
||||
public Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken);
|
||||
|
||||
public Stream GetStream(string name) => store.GetStream(test_image);
|
||||
|
||||
public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
var storyboard = new Storyboard();
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, startTime, 0, 0, 1);
|
||||
sprite.Commands.AddAlpha(Easing.None, startTime, 0, 0, 1);
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
return storyboard;
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
var storyboard = new Storyboard();
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, 0, duration, 1, 0);
|
||||
sprite.Commands.AddAlpha(Easing.None, 0, duration, 1, 0);
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
return storyboard;
|
||||
}
|
||||
|
@ -69,8 +69,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = { Value = "Multiplayer room" },
|
||||
Status = { Value = new RoomStatusOpen() },
|
||||
Name = { Value = "Private room" },
|
||||
Status = { Value = new RoomStatusOpenPrivate() },
|
||||
HasPassword = { Value = true },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
|
||||
Type = { Value = MatchType.HeadToHead },
|
||||
Playlist =
|
||||
|
@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public void TestIntroStoryboardElement() => testLeadIn(b =>
|
||||
{
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, -2000, 0, 0, 1);
|
||||
sprite.Commands.AddAlpha(Easing.None, -2000, 0, 0, 1);
|
||||
b.Storyboard.GetLayer("Background").Add(sprite);
|
||||
});
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -321,6 +322,30 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("nested input disabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType<PassThroughInputManager>().All(manager => !manager.UseParentInput));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkinSavesOnChange()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
|
||||
Guid editedSkinId = Guid.Empty;
|
||||
AddStep("save skin id", () => editedSkinId = Game.Dependencies.Get<SkinManager>().CurrentSkinInfo.Value.ID);
|
||||
AddStep("add skinnable component", () =>
|
||||
{
|
||||
skinEditor.ChildrenOfType<SkinComponentToolbox.ToolboxComponentButton>().First().TriggerClick();
|
||||
});
|
||||
|
||||
AddStep("change to triangles skin", () => Game.Dependencies.Get<SkinManager>().SetSkinFromConfiguration(SkinInfo.TRIANGLES_SKIN.ToString()));
|
||||
AddUntilStep("components loaded", () => Game.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
||||
// sort of implicitly relies on song select not being skinnable.
|
||||
// TODO: revisit if the above ever changes
|
||||
AddUntilStep("skin changed", () => !skinEditor.ChildrenOfType<SkinBlueprint>().Any());
|
||||
|
||||
AddStep("change back to modified skin", () => Game.Dependencies.Get<SkinManager>().SetSkinFromConfiguration(editedSkinId.ToString()));
|
||||
AddUntilStep("components loaded", () => Game.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
||||
AddUntilStep("changes saved", () => skinEditor.ChildrenOfType<SkinBlueprint>().Any());
|
||||
}
|
||||
|
||||
private void advanceToSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
|
@ -123,6 +123,43 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
assertSelectedModsEquivalentTo(new Mod[] { new OsuModTouchDevice(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSystemModsNotPreservedIfIncompatibleWithPresetMods()
|
||||
{
|
||||
ModPresetPanel? panel = null;
|
||||
|
||||
AddStep("create panel", () => Child = panel = new ModPresetPanel(new ModPreset
|
||||
{
|
||||
Name = "Autopilot included",
|
||||
Description = "no way",
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModAutopilot()
|
||||
},
|
||||
Ruleset = new OsuRuleset().RulesetInfo
|
||||
}.ToLiveUnmanaged())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f
|
||||
});
|
||||
|
||||
AddStep("Add touch device to selected mods", () => SelectedMods.Value = new Mod[] { new OsuModTouchDevice() });
|
||||
AddStep("activate panel", () => panel.AsNonNull().TriggerClick());
|
||||
|
||||
// touch device should be removed due to incompatibility with autopilot.
|
||||
assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutopilot() });
|
||||
|
||||
AddStep("deactivate panel", () => panel.AsNonNull().TriggerClick());
|
||||
assertSelectedModsEquivalentTo(Array.Empty<Mod>());
|
||||
|
||||
// just for test purposes, can't/shouldn't happen in reality
|
||||
AddStep("Add score v2 to selected mod", () => SelectedMods.Value = new Mod[] { new ModScoreV2() });
|
||||
AddStep("activate panel", () => panel.AsNonNull().TriggerClick());
|
||||
|
||||
assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutopilot(), new ModScoreV2() });
|
||||
}
|
||||
|
||||
private void assertSelectedModsEquivalentTo(IEnumerable<Mod> mods)
|
||||
=> AddAssert("selected mods changed correctly", () => new HashSet<Mod>(SelectedMods.Value).SetEquals(mods));
|
||||
|
||||
|
@ -5,7 +5,9 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
|
||||
@ -20,9 +22,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private NowPlayingOverlay nowPlayingOverlay;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(FrameworkConfigManager frameworkConfig)
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
AddToggleStep("toggle unicode", v => frameworkConfig.SetValue(FrameworkSetting.ShowUnicode, v));
|
||||
|
||||
nowPlayingOverlay = new NowPlayingOverlay
|
||||
{
|
||||
@ -37,9 +39,38 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestShowHideDisable()
|
||||
{
|
||||
AddStep(@"set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo));
|
||||
AddStep(@"show", () => nowPlayingOverlay.Show());
|
||||
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
|
||||
AddStep(@"hide", () => nowPlayingOverlay.Hide());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLongMetadata()
|
||||
{
|
||||
AddStep(@"set metadata within tolerance", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
Metadata =
|
||||
{
|
||||
Artist = "very very very very very very very very very very verry long artist",
|
||||
ArtistUnicode = "very very very very very very very very very very verry long artist unicode",
|
||||
Title = "very very very very very verry long title",
|
||||
TitleUnicode = "very very very very very verry long title unicode",
|
||||
}
|
||||
}));
|
||||
|
||||
AddStep(@"set metadata outside bounds", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
Metadata =
|
||||
{
|
||||
Artist = "very very very very very very very very very very verrry long artist",
|
||||
ArtistUnicode = "not very long artist unicode",
|
||||
Title = "very very very very very verrry long title",
|
||||
TitleUnicode = "not very long title unicode",
|
||||
}
|
||||
}));
|
||||
|
||||
AddStep(@"show", () => nowPlayingOverlay.Show());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +114,51 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
=> AddAssert($"state is {expected}", () => state.Value == expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemRespondsToRightClick()
|
||||
{
|
||||
OsuMenu menu = null;
|
||||
|
||||
Bindable<TernaryState> state = new Bindable<TernaryState>(TernaryState.Indeterminate);
|
||||
|
||||
AddStep("create menu", () =>
|
||||
{
|
||||
state.Value = TernaryState.Indeterminate;
|
||||
|
||||
Child = menu = new OsuMenu(Direction.Vertical, true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Items = new[]
|
||||
{
|
||||
new TernaryStateToggleMenuItem("First"),
|
||||
new TernaryStateToggleMenuItem("Second") { State = { BindTarget = state } },
|
||||
new TernaryStateToggleMenuItem("Third") { State = { Value = TernaryState.True } },
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
checkState(TernaryState.Indeterminate);
|
||||
|
||||
click();
|
||||
checkState(TernaryState.True);
|
||||
|
||||
click();
|
||||
checkState(TernaryState.False);
|
||||
|
||||
AddStep("change state via bindable", () => state.Value = TernaryState.True);
|
||||
|
||||
void click() =>
|
||||
AddStep("click", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
void checkState(TernaryState expected)
|
||||
=> AddAssert($"state is {expected}", () => state.Value == expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomState()
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Tournament.Screens.Ladder
|
||||
|
||||
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
|
||||
public override bool UpdateSubTreeMasking() => false;
|
||||
|
||||
protected override void OnDrag(DragEvent e)
|
||||
{
|
||||
|
@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public List<BreakPeriod> Breaks { get; set; } = new List<BreakPeriod>();
|
||||
|
||||
public List<string> UnhandledEventLines { get; set; } = new List<string>();
|
||||
|
||||
[JsonIgnore]
|
||||
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
|
||||
|
||||
|
@ -66,6 +66,7 @@ namespace osu.Game.Beatmaps
|
||||
beatmap.ControlPointInfo = original.ControlPointInfo;
|
||||
beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList();
|
||||
beatmap.Breaks = original.Breaks;
|
||||
beatmap.UnhandledEventLines = original.UnhandledEventLines;
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
@ -21,6 +22,8 @@ using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
[SuppressMessage("ReSharper", "CommentTypo")]
|
||||
public partial class BundledBeatmapDownloader : CompositeDrawable
|
||||
{
|
||||
private readonly bool shouldPostNotifications;
|
||||
@ -50,7 +53,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
queueDownloads(always_bundled_beatmaps);
|
||||
|
||||
queueDownloads(bundled_osu, 8);
|
||||
queueDownloads(bundled_osu, 6);
|
||||
queueDownloads(bundled_taiko, 3);
|
||||
queueDownloads(bundled_catch, 3);
|
||||
queueDownloads(bundled_mania, 3);
|
||||
@ -128,6 +131,26 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* criteria for bundled maps (managed by pishifat)
|
||||
*
|
||||
* auto:
|
||||
* - licensed song
|
||||
* - includes ENHI diffs
|
||||
* - between 60s and 240s
|
||||
*
|
||||
* manual:
|
||||
* - bg is explicitly permitted as okay to use. lots of artists say some variation of "it's ok for personal use/non-commercial use/with credit"
|
||||
* (which is prob fine when maps are presented as user-generated content), but for a new osu! player, it's easy to assume bundled maps are
|
||||
* commercial content like other rhythm games, so it's best to be cautious about using not-explicitly-permitted artwork.
|
||||
*
|
||||
* - no ai/thirst bgs
|
||||
* - no controversial/explicit song content or titles
|
||||
* - no repeating bundled songs (within each mode)
|
||||
* - no songs that are relatively low production value
|
||||
* - no songs with limited accessibility (annoying high pitch vocals, noise rock, etc)
|
||||
*/
|
||||
|
||||
private const string tutorial_filename = "1011011 nekodex - new beginnings.osz";
|
||||
|
||||
/// <summary>
|
||||
@ -135,215 +158,312 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </summary>
|
||||
private static readonly string[] always_bundled_beatmaps =
|
||||
{
|
||||
// This thing is 40mb, I'm not sure we want it here...
|
||||
// winner of https://osu.ppy.sh/home/news/2013-09-06-osu-monthly-beatmapping-contest-1
|
||||
@"123593 Rostik - Liquid (Paul Rosenthal Remix).osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2013-10-28-monthly-beatmapping-contest-2-submissions-open
|
||||
@"140662 cYsmix feat. Emmy - Tear Rain.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2013-12-15-monthly-beatmapping-contest-3-submissions-open
|
||||
@"151878 Chasers - Lost.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2014-02-14-monthly-beatmapping-contest-4-submissions-now
|
||||
@"163112 Kuba Oms - My Love.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2014-05-07-monthly-beatmapping-contest-5-submissions-now
|
||||
@"190390 Rameses B - Flaklypa.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2014-09-24-monthly-beatmapping-contest-7
|
||||
@"241526 Soleily - Renatus.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2015-02-11-monthly-beatmapping-contest-8
|
||||
@"299224 raja - the light.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2015-04-13-monthly-beatmapping-contest-9-taiko-only
|
||||
@"319473 Furries in a Blender - Storm World.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2015-06-15-monthly-beatmapping-contest-10-ctb-only
|
||||
@"342751 Hylian Lemon - Foresight Is for Losers.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2015-08-22-monthly-beatmapping-contest-11-mania-only
|
||||
@"385056 Toni Leys - Dragon Valley (Toni Leys Remix feat. Esteban Bellucci).osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2016-03-04-beatmapping-contest-12-osu
|
||||
@"456054 IAHN - Candy Luv (Short Ver.).osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2020-11-30-a-labour-of-love
|
||||
// (this thing is 40mb, I'm not sure if we want it here...)
|
||||
@"1388906 Raphlesia & BilliumMoto - My Love.osz",
|
||||
// Winner of Triangles mapping competition: https://osu.ppy.sh/home/news/2022-10-06-results-triangles
|
||||
// winner of https://osu.ppy.sh/home/news/2022-05-31-triangles
|
||||
@"1841885 cYsmix - triangles.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2023-02-01-twin-trials-contest-beatmapping-phase
|
||||
@"1971987 James Landino - Aresene's Bazaar.osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_osu =
|
||||
{
|
||||
"682286 Yuyoyuppe - Emerald Galaxy.osz",
|
||||
"682287 baker - For a Dead Girl+.osz",
|
||||
"682289 Hige Driver - I Wanna Feel Your Love (feat. shully).osz",
|
||||
"682290 Hige Driver - Miracle Sugite Yabai (feat. shully).osz",
|
||||
"682416 Hige Driver - Palette.osz",
|
||||
"682595 baker - Kimi ga Kimi ga -vocanico remix-.osz",
|
||||
"716211 yuki. - Spring Signal.osz",
|
||||
"716213 dark cat - BUBBLE TEA (feat. juu & cinders).osz",
|
||||
"716215 LukHash - CLONED.osz",
|
||||
"716219 IAHN - Snowdrop.osz",
|
||||
"716249 *namirin - Senaka Awase no Kuukyo (with Kakichoco).osz",
|
||||
"716390 sakuraburst - SHA.osz",
|
||||
"716441 Fractal Dreamers - Paradigm Shift.osz",
|
||||
"729808 Thaehan - Leprechaun.osz",
|
||||
"751771 Cranky - Hanaarashi.osz",
|
||||
"751772 Cranky - Ran.osz",
|
||||
"751773 Cranky - Feline, the White....osz",
|
||||
"751774 Function Phantom - Variable.osz",
|
||||
"751779 Rin - Daishibyo set 14 ~ Sado no Futatsuiwa.osz",
|
||||
"751782 Fractal Dreamers - Fata Morgana.osz",
|
||||
"751785 Cranky - Chandelier - King.osz",
|
||||
"751846 Fractal Dreamers - Celestial Horizon.osz",
|
||||
"751866 Rin - Moriya set 08 ReEdit ~ Youkai no Yama.osz",
|
||||
"751894 Fractal Dreamers - Blue Haven.osz",
|
||||
"751896 Cranky - Rave 2 Rave.osz",
|
||||
"751932 Cranky - La fuite des jours.osz",
|
||||
"751972 Cranky - CHASER.osz",
|
||||
"779173 Thaehan - Superpower.osz",
|
||||
"780932 VINXIS - A Centralized View.osz",
|
||||
"785572 S3RL - I'll See You Again (feat. Chi Chi).osz",
|
||||
"785650 yuki. feat. setsunan - Hello! World.osz",
|
||||
"785677 Dictate - Militant.osz",
|
||||
"785731 S3RL - Catchit (Radio Edit).osz",
|
||||
"785774 LukHash - GLITCH.osz",
|
||||
"786498 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI.osz",
|
||||
"789374 Pulse - LP.osz",
|
||||
"789528 James Portland - Sky.osz",
|
||||
"789529 Lexurus - Gravity.osz",
|
||||
"789544 Andromedik - Invasion.osz",
|
||||
"789905 Gourski x Himmes - Silence.osz",
|
||||
"791667 cYsmix - Babaroque (Short Ver.).osz",
|
||||
"791798 cYsmix - Behind the Walls.osz",
|
||||
"791845 cYsmix - Little Knight.osz",
|
||||
"792241 cYsmix - Eden.osz",
|
||||
"792396 cYsmix - The Ballad of a Mindless Girl.osz",
|
||||
"795432 Phonetic - Journey.osz",
|
||||
"831322 DJ'TEKINA//SOMETHING - Hidamari no Uta.osz",
|
||||
"847764 Cranky - Crocus.osz",
|
||||
"847776 Culprate & Joe Ford - Gaucho.osz",
|
||||
"847812 J. Pachelbel - Canon (Cranky Remix).osz",
|
||||
"847900 Cranky - Time Alter.osz",
|
||||
"847930 LukHash - 8BIT FAIRY TALE.osz",
|
||||
"848003 Culprate - Aurora.osz",
|
||||
"848068 nanobii - popsicle beach.osz",
|
||||
"848090 Trial & Error - DAI*TAN SENSATION feat. Nanahira, Mii, Aitsuki Nakuru (Short Ver.).osz",
|
||||
"848259 Culprate & Skorpion - Jester.osz",
|
||||
"848976 Dictate - Treason.osz",
|
||||
"851543 Culprate - Florn.osz",
|
||||
"864748 Thaehan - Angry Birds Epic (Remix).osz",
|
||||
"873667 OISHII - ONIGIRI FREEWAY.osz",
|
||||
"876227 Culprate, Keota & Sophie Meiers - Mechanic Heartbeat.osz",
|
||||
"880487 cYsmix - Peer Gynt.osz",
|
||||
"883088 Wisp X - Somewhere I'd Rather Be.osz",
|
||||
"891333 HyuN - White Aura.osz",
|
||||
"891334 HyuN - Wild Card.osz",
|
||||
"891337 HyuN feat. LyuU - Cross Over.osz",
|
||||
"891338 HyuN & Ritoru - Apocalypse in Love.osz",
|
||||
"891339 HyuN feat. Ato - Asu wa Ame ga Yamukara.osz",
|
||||
"891345 HyuN - Infinity Heaven.osz",
|
||||
"891348 HyuN - Guitian.osz",
|
||||
"891356 HyuN - Legend of Genesis.osz",
|
||||
"891366 HyuN - Illusion of Inflict.osz",
|
||||
"891417 HyuN feat. Yu-A - My life is for you.osz",
|
||||
"891441 HyuN - You'Re aRleAdY dEAd.osz",
|
||||
"891632 HyuN feat. YURI - Disorder.osz",
|
||||
"891712 HyuN - Tokyo's Starlight.osz",
|
||||
"901091 *namirin - Ciel etoile.osz",
|
||||
"916990 *namirin - Koishiteiku Planet.osz",
|
||||
"929284 tieff - Sense of Nostalgia.osz",
|
||||
"933940 Ben Briggs - Yes (Maybe).osz",
|
||||
"934415 Ben Briggs - Fearless Living.osz",
|
||||
"934627 Ben Briggs - New Game Plus.osz",
|
||||
"934666 Ben Briggs - Wave Island.osz",
|
||||
"936126 siromaru + cranky - conflict.osz",
|
||||
"940377 onumi - ARROGANCE.osz",
|
||||
"940597 tieff - Take Your Swimsuit.osz",
|
||||
"941085 tieff - Our Story.osz",
|
||||
"949297 tieff - Sunflower.osz",
|
||||
"952380 Ben Briggs - Why Are We Yelling.osz",
|
||||
"954272 *namirin - Kanzen Shouri*Esper Girl.osz",
|
||||
"955866 KIRA & Heartbreaker - B.B.F (feat. Hatsune Miku & Kagamine Rin).osz",
|
||||
"961320 Kuba Oms - All In All.osz",
|
||||
"964553 The Flashbulb - You Take the World's Weight Away.osz",
|
||||
"965651 Fractal Dreamers - Ad Astra.osz",
|
||||
"966225 The Flashbulb - Passage D.osz",
|
||||
"966324 DJ'TEKINA//SOMETHING - Hidamari no Uta.osz",
|
||||
"972810 James Landino & Kabuki - Birdsong.osz",
|
||||
"972932 James Landino - Hide And Seek.osz",
|
||||
"977276 The Flashbulb - Mellann.osz",
|
||||
"981616 *namirin - Mizutamari Tobikoete (with Nanahira).osz",
|
||||
"985788 Loki - Wizard's Tower.osz",
|
||||
"996628 OISHII - ONIGIRI FREEWAY.osz",
|
||||
"996898 HyuN - White Aura.osz",
|
||||
"1003554 yuki. - Nadeshiko Sensation.osz",
|
||||
"1014936 Thaehan - Bwa !.osz",
|
||||
"1019827 UNDEAD CORPORATION - Sad Dream.osz",
|
||||
"1020213 Creo - Idolize.osz",
|
||||
"1021450 Thaehan - Chiptune & Baroque.osz",
|
||||
@"682286 Yuyoyuppe - Emerald Galaxy.osz",
|
||||
@"682287 baker - For a Dead Girl+.osz",
|
||||
@"682595 baker - Kimi ga Kimi ga -vocanico remix-.osz",
|
||||
@"1048705 Thaehan - Never Give Up.osz",
|
||||
@"1050185 Carpool Tunnel - Hooked Again.osz",
|
||||
@"1052846 Carpool Tunnel - Impressions.osz",
|
||||
@"1062477 Ricky Montgomery - Line Without a Hook.osz",
|
||||
@"1081119 Celldweller - Pulsar.osz",
|
||||
@"1086289 Frums - 24eeev0-$.osz",
|
||||
@"1133317 PUP - Free At Last.osz",
|
||||
@"1171188 PUP - Full Blown Meltdown.osz",
|
||||
@"1177043 PUP - My Life Is Over And I Couldn't Be Happier.osz",
|
||||
@"1250387 Circle of Dust - Humanarchy (Cut Ver.).osz",
|
||||
@"1255411 Wisp X - Somewhere I'd Rather Be.osz",
|
||||
@"1320298 nekodex - Little Drummer Girl.osz",
|
||||
@"1323877 Masahiro ""Godspeed"" Aoki - Blaze.osz",
|
||||
@"1342280 Minagu feat. Aitsuki Nakuru - Theater Endroll.osz",
|
||||
@"1356447 SECONDWALL - Boku wa Boku de shika Nakute.osz",
|
||||
@"1368054 SECONDWALL - Shooting Star.osz",
|
||||
@"1398580 La priere - Senjou no Utahime.osz",
|
||||
@"1403962 m108 - Sunflower.osz",
|
||||
@"1405913 fiend - FEVER DREAM (feat. yzzyx).osz",
|
||||
@"1409184 Omoi - Hey William (New Translation).osz",
|
||||
@"1413418 URBANGARDE - KAMING OUT (Cut Ver.).osz",
|
||||
@"1417793 P4koo (NONE) - Sogaikan Utopia.osz",
|
||||
@"1428384 DUAL ALTER WORLD - Veracila.osz",
|
||||
@"1442963 PUP - DVP.osz",
|
||||
@"1460370 Sound Souler - Empty Stars.osz",
|
||||
@"1485184 Koven - Love Wins Again.osz",
|
||||
@"1496811 T & Sugah - Wicked Days (Cut Ver.).osz",
|
||||
@"1501511 Masahiro ""Godspeed"" Aoki - Frostbite (Cut Ver.).osz",
|
||||
@"1511518 T & Sugah X Zazu - Lost On My Own (Cut Ver.).osz",
|
||||
@"1516617 wotoha - Digital Life Hacker.osz",
|
||||
@"1524273 Michael Cera Palin - Admiral.osz",
|
||||
@"1564234 P4koo - Fly High (feat. rerone).osz",
|
||||
@"1572918 Lexurus - Take Me Away (Cut Ver.).osz",
|
||||
@"1577313 Kurubukko - The 84th Flight.osz",
|
||||
@"1587839 Amidst - Droplet.osz",
|
||||
@"1595193 BlackY - Sakura Ranman Cleopatra.osz",
|
||||
@"1667560 xi - FREEDOM DiVE.osz",
|
||||
@"1668789 City Girl - L2M (feat. Kelsey Kuan).osz",
|
||||
@"1672934 xi - Parousia.osz",
|
||||
@"1673457 Boom Kitty - Any Other Way (feat. Ivy Marie).osz",
|
||||
@"1685122 xi - Time files.osz",
|
||||
@"1689372 NIWASHI - Y.osz",
|
||||
@"1729551 JOYLESS - Dream.osz",
|
||||
@"1742868 Ritorikal - Synergy.osz",
|
||||
@"1757511 KINEMA106 - KARASU.osz",
|
||||
@"1778169 Ricky Montgomery - Cabo.osz",
|
||||
@"1848184 FRASER EDWARDS - Ruination.osz",
|
||||
@"1862574 Pegboard Nerds - Try This (Cut Ver.).osz",
|
||||
@"1873680 happy30 - You spin my world.osz",
|
||||
@"1890055 A.SAKA - Mutsuki Akari no Yuki.osz",
|
||||
@"1911933 Marmalade butcher - Waltz for Chroma (feat. Natsushiro Takaaki).osz",
|
||||
@"1940007 Mili - Ga1ahad and Scientific Witchery.osz",
|
||||
@"1948970 Shadren - You're Here Forever.osz",
|
||||
@"1967856 Annabel - alpine blue.osz",
|
||||
@"1969316 Silentroom - NULCTRL.osz",
|
||||
@"1978614 Krimek - Idyllic World.osz",
|
||||
@"1991315 Feint - Tower Of Heaven (You Are Slaves) (Cut Ver.).osz",
|
||||
@"1997470 tephe - Genjitsu Escape.osz",
|
||||
@"1999116 soowamisu - .vaporcore.osz",
|
||||
@"2010589 Junk - Yellow Smile (bms edit).osz",
|
||||
@"2022054 Yokomin - STINGER.osz",
|
||||
@"2025686 Aice room - For U.osz",
|
||||
@"2035357 C-Show feat. Ishizawa Yukari - Border Line.osz",
|
||||
@"2039403 SECONDWALL - Freedom.osz",
|
||||
@"2046487 Rameses B - Against the Grain (feat. Veela).osz",
|
||||
@"2052201 ColBreakz & Vizzen - Remember.osz",
|
||||
@"2055535 Sephid - Thunderstrike 1988.osz",
|
||||
@"2057584 SAMString - Ataraxia.osz",
|
||||
@"2067270 Blue Stahli - The Fall.osz",
|
||||
@"2075039 garlagan - Skyless.osz",
|
||||
@"2079089 Hamu feat. yuiko - Innocent Letter.osz",
|
||||
@"2082895 FATE GEAR - Heart's Grave.osz",
|
||||
@"2085974 HoneyComeBear - Twilight.osz",
|
||||
@"2094934 F.O.O.L & Laura Brehm - Waking Up.osz",
|
||||
@"2097481 Mameyudoufu - Wave feat. Aitsuki Nakuru.osz",
|
||||
@"2106075 MYUKKE. - The 89's Momentum.osz",
|
||||
@"2117392 t+pazolite & Komiya Mao - Elustametat.osz",
|
||||
@"2123533 LeaF - Calamity Fortune.osz",
|
||||
@"2143876 Alkome - Your Voice.osz",
|
||||
@"2145826 Sephid - Cross-D Skyline.osz",
|
||||
@"2153172 Emiru no Aishita Tsukiyo ni Dai San Gensou Kyoku wo - Eternal Bliss.osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_taiko =
|
||||
{
|
||||
"707824 Fractal Dreamers - Fortuna Redux.osz",
|
||||
"789553 Cranky - Ran.osz",
|
||||
"827822 Function Phantom - Neuronecia.osz",
|
||||
"847323 Nakanojojo - Bittersweet (feat. Kuishinboakachan a.k.a Kiato).osz",
|
||||
"847433 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI.osz",
|
||||
"847576 dark cat - hot chocolate.osz",
|
||||
"847957 Wisp X - Final Moments.osz",
|
||||
"876282 VINXIS - Greetings.osz",
|
||||
"876648 Thaehan - Angry Birds Epic (Remix).osz",
|
||||
"877069 IAHN - Transform (Original Mix).osz",
|
||||
"877496 Thaehan - Leprechaun.osz",
|
||||
"877935 Thaehan - Overpowered.osz",
|
||||
"878344 yuki. - Be Your Light.osz",
|
||||
"918446 VINXIS - Facade.osz",
|
||||
"918903 LukHash - Ghosts.osz",
|
||||
"919251 *namirin - Hitokoto no Kyori.osz",
|
||||
"919704 S3RL - I Will Pick You Up (feat. Tamika).osz",
|
||||
"921535 SOOOO - Raven Haven.osz",
|
||||
"927206 *namirin - Kanzen Shouri*Esper Girl.osz",
|
||||
"927544 Camellia feat. Nanahira - Kansoku Eisei.osz",
|
||||
"930806 Nakanojojo - Pararara (feat. Amekoya).osz",
|
||||
"931741 Camellia - Quaoar.osz",
|
||||
"935699 Rin - Mythic set ~ Heart-Stirring Urban Legends.osz",
|
||||
"935732 Thaehan - Yuujou.osz",
|
||||
"941145 Function Phantom - Euclid.osz",
|
||||
"942334 Dictate - Cauldron.osz",
|
||||
"946540 nanobii - astral blast.osz",
|
||||
"948844 Rin - Kishinjou set 01 ~ Mist Lake.osz",
|
||||
"949122 Wisp X - Petal.osz",
|
||||
"951618 Rin - Kishinjou set 02 ~ Mermaid from the Uncharted Land.osz",
|
||||
"957412 Rin - Lunatic set 16 ~ The Space Shrine Maiden Returns Home.osz",
|
||||
"961335 Thaehan - Insert Coin.osz",
|
||||
"965178 The Flashbulb - DIDJ PVC.osz",
|
||||
"966087 The Flashbulb - Creep.osz",
|
||||
"966277 The Flashbulb - Amen Iraq.osz",
|
||||
"966407 LukHash - ROOM 12.osz",
|
||||
"966451 The Flashbulb - Six Acid Strings.osz",
|
||||
"972301 BilliumMoto - four veiled stars.osz",
|
||||
"973173 nanobii - popsicle beach.osz",
|
||||
"973954 BilliumMoto - Rocky Buinne (Short Ver.).osz",
|
||||
"975435 BilliumMoto - life flashes before weeb eyes.osz",
|
||||
"978759 L. V. Beethoven - Moonlight Sonata (Cranky Remix).osz",
|
||||
"982559 BilliumMoto - HDHR.osz",
|
||||
"984361 The Flashbulb - Ninedump.osz",
|
||||
"1023681 Inferi - The Ruin of Mankind.osz",
|
||||
"1034358 ALEPH - The Evil Spirit.osz",
|
||||
"1037567 ALEPH - Scintillations.osz",
|
||||
"1048153 Chroma - [@__@].osz",
|
||||
"1229307 Venetian Snares - Shaky Sometimes.osz",
|
||||
"1236083 meganeko - Sirius A (osu! edit).osz",
|
||||
"1248594 Noisia - Anomaly.osz",
|
||||
"1272851 siqlo - One Way Street.osz",
|
||||
"1290736 Kola Kid - good old times.osz",
|
||||
"1318825 SECONDWALL - Light.osz",
|
||||
"1320872 MYUKKE. - The 89's Momentum.osz",
|
||||
"1337389 cute girls doing cute things - Main Heroine.osz",
|
||||
"1397782 Reku Mochizuki - Yorixiro.osz",
|
||||
"1407228 II-L - VANGUARD-1.osz",
|
||||
"1422686 II-L - VANGUARD-2.osz",
|
||||
"1429217 Street - Phi.osz",
|
||||
"1442235 2ToneDisco x Cosmicosmo - Shoelaces (feat. Puniden).osz",
|
||||
"1447478 Cres. - End Time.osz",
|
||||
"1449942 m108 - Crescent Sakura.osz",
|
||||
"1463778 MuryokuP - A tree without a branch.osz",
|
||||
"1465152 fiend - Fever Dream (feat. yzzyx).osz",
|
||||
"1472397 MYUKKE. - Boudica.osz",
|
||||
"1488148 Aoi vs. siqlo - Hacktivism.osz",
|
||||
"1522733 wotoha - Digital Life Hacker.osz",
|
||||
"1540010 Marmalade butcher - Floccinaucinihilipilification.osz",
|
||||
"1584690 MYUKKE. - AKKERA-COUNTRY-BOY.osz",
|
||||
"1608857 BLOOD STAIN CHILD - S.O.P.H.I.A.osz",
|
||||
"1609365 Reku Mochizuki - Faith of Eastward.osz",
|
||||
"1622545 METAROOM - I - DINKI THE STARGUIDE.osz",
|
||||
"1629336 METAROOM - PINK ORIGINS.osz",
|
||||
"1644680 Neko Hacker - Pictures feat. 4s4ki.osz",
|
||||
"1650835 RiraN - Ready For The Madness.osz",
|
||||
"1661508 PTB10 - Starfall.osz",
|
||||
"1671987 xi - World Fragments II.osz",
|
||||
"1703065 tokiwa - wasurena feat. Sennzai.osz",
|
||||
"1703527 tokiwa feat. Nakamura Sanso - Kotodama Refrain.osz",
|
||||
"1704340 A-One feat. Shihori - Magic Girl !!.osz",
|
||||
"1712783 xi - Parousia.osz",
|
||||
"1718774 Harumaki Gohan - Suisei ni Nareta nara.osz",
|
||||
"1719687 EmoCosine - Love Kills U.osz",
|
||||
"1733940 WHITEFISTS feat. Sennzai - Paralyzed Ash.osz",
|
||||
"1734692 EmoCosine - Cutter.osz",
|
||||
"1739529 luvlxckdown - tbh i dont like being social.osz",
|
||||
"1756970 Kurubukko vs. yukitani - Minamichita EVOLVED.osz",
|
||||
"1762209 Marmalade butcher - Immortality Math Club.osz",
|
||||
"1765720 ZxNX - FORTALiCE.osz",
|
||||
"1786165 NILFRUITS - Arandano.osz",
|
||||
"1787258 SAMString - Night Fighter.osz",
|
||||
"1791462 ZxNX - Schadenfreude.osz",
|
||||
"1793821 Kobaryo - The Lightning Sword.osz",
|
||||
"1796440 kuru x miraie - re:start.osz",
|
||||
"1799285 Origami Angel - 666 Flags.osz",
|
||||
"1812415 nanobii - Rainbow Road.osz",
|
||||
"1814682 NIWASHI - Y.osz",
|
||||
"1818361 meganeko - Feral (osu! edit).osz",
|
||||
"1818924 fiend - Disconnect.osz",
|
||||
"1838730 Pegboard Nerds - Disconnected.osz",
|
||||
"1854710 Blaster & Extra Terra - Spacecraft (Cut Ver.).osz",
|
||||
"1859322 Hino Isuka - Delightness Brightness.osz",
|
||||
"1884102 Maduk - Go (feat. Lachi) (Cut Ver.).osz",
|
||||
"1884578 Neko Hacker - People People feat. Nanahira.osz",
|
||||
"1897902 uma vs. Morimori Atsushi - Re: End of a Dream.osz",
|
||||
"1905582 KINEMA106 - Fly Away (Cut Ver.).osz",
|
||||
"1934686 ARForest - Rainbow Magic!!.osz",
|
||||
"1963076 METAROOM - S.N.U.F.F.Y.osz",
|
||||
"1968973 Stars Hollow - Out the Sunroof..osz",
|
||||
"1971951 James Landino - Shiba Paradise.osz",
|
||||
"1972518 Toromaru - Sleight of Hand.osz",
|
||||
"1982302 KINEMA106 - INVITE.osz",
|
||||
"1983475 KNOWER - The Government Knows.osz",
|
||||
"2010165 Junk - Yellow Smile (bms edit).osz",
|
||||
"2022737 Andora - Euphoria (feat. WaMi).osz",
|
||||
"2025023 tephe - Genjitsu Escape.osz",
|
||||
"2052754 P4koo - 8th:Planet ~Re:search~.osz",
|
||||
"2054122 Raimukun - Myths Orbis.osz",
|
||||
"2121470 Raimukun - Nyarlathotep's Dreamland.osz",
|
||||
"2122284 Agressor Bunx - Tornado (Cut Ver.).osz",
|
||||
"2125034 Agressor Bunx - Acid Mirage (Cut Ver.).osz",
|
||||
"2136263 Se-U-Ra - Cris Fortress.osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_catch =
|
||||
{
|
||||
"554256 Helblinde - When Time Sleeps.osz",
|
||||
"693123 yuki. - Nadeshiko Sensation.osz",
|
||||
"767009 OISHII - PIZZA PLAZA.osz",
|
||||
"767346 Thaehan - Bwa !.osz",
|
||||
"815162 VINXIS - Greetings.osz",
|
||||
"840964 cYsmix - Breeze.osz",
|
||||
"932657 Wisp X - Eventide.osz",
|
||||
"933700 onumi - CONFUSION PART ONE.osz",
|
||||
"933984 onumi - PERSONALITY.osz",
|
||||
"934785 onumi - FAKE.osz",
|
||||
"936545 onumi - REGRET PART ONE.osz",
|
||||
"943803 Fractal Dreamers - Everything for a Dream.osz",
|
||||
"943876 S3RL - I Will Pick You Up (feat. Tamika).osz",
|
||||
"946773 Trial & Error - DREAMING COLOR (Short Ver.).osz",
|
||||
"955808 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI (Short Ver.).osz",
|
||||
"957808 Fractal Dreamers - Module_410.osz",
|
||||
"957842 antiPLUR - One Life Left to Live.osz",
|
||||
"965730 The Flashbulb - Lawn Wake IV (Black).osz",
|
||||
"966240 Creo - Challenger.osz",
|
||||
"968232 Rin - Lunatic set 15 ~ The Moon as Seen from the Shrine.osz",
|
||||
"972302 VINXIS - A Centralized View.osz",
|
||||
"972887 HyuN - Illusion of Inflict.osz",
|
||||
"1008600 LukHash - WHEN AN ANGEL DIES.osz",
|
||||
"1032103 LukHash - H8 U.osz",
|
||||
@"693123 yuki. - Nadeshiko Sensation.osz",
|
||||
@"833719 FOLiACETATE - Heterochromia Iridis.osz",
|
||||
@"981762 siromaru + cranky - conflict.osz",
|
||||
@"1008600 LukHash - WHEN AN ANGEL DIES.osz",
|
||||
@"1071294 dark cat - pursuit of happiness.osz",
|
||||
@"1102115 meganeko - Nova.osz",
|
||||
@"1115500 Chopin - Etude Op. 25, No. 12 (meganeko Remix).osz",
|
||||
@"1128274 LeaF - Wizdomiot.osz",
|
||||
@"1141049 HyuN feat. JeeE - Fallen Angel.osz",
|
||||
@"1148215 Zekk - Fluctuation.osz",
|
||||
@"1151833 ginkiha - nightfall.osz",
|
||||
@"1158124 PUP - Dark Days.osz",
|
||||
@"1184890 IAHN - Transform (Original Mix).osz",
|
||||
@"1195922 Disasterpeace - Home.osz",
|
||||
@"1197461 MIMI - Nanimo nai Youna.osz",
|
||||
@"1197924 Camellia feat. Nanahira - Looking For A New Adventure.osz",
|
||||
@"1203594 ginkiha - Anemoi.osz",
|
||||
@"1211572 MIMI - Lapis Lazuli.osz",
|
||||
@"1231601 Lime - Harmony.osz",
|
||||
@"1240162 P4koo - 8th:Planet ~Re:search~.osz",
|
||||
@"1246000 Zekk - Calling.osz",
|
||||
@"1249928 Thaehan - Yuujou.osz",
|
||||
@"1258751 Umeboshi Chazuke - ICHIBANBOSHI*ROCKET.osz",
|
||||
@"1264818 Umeboshi Chazuke - Panic! Pop'n! Picnic! (2019 REMASTER).osz",
|
||||
@"1280183 IAHN - Mad Halloween.osz",
|
||||
@"1303201 Umeboshi Chazuke - Run*2 Run To You!!.osz",
|
||||
@"1328918 Kobaryo - Theme for Psychopath Justice.osz",
|
||||
@"1338215 Lime - Renai Syndrome.osz",
|
||||
@"1338796 uma vs. Morimori Atsushi - Re:End of a Dream.osz",
|
||||
@"1340492 MYUKKE. - The 89's Momentum.osz",
|
||||
@"1393933 Mastermind (xi+nora2r) - Dreadnought.osz",
|
||||
@"1400205 m108 - XIII Charlotte.osz",
|
||||
@"1471328 Lime - Chronomia.osz",
|
||||
@"1503591 Origami Angel - The Title Track.osz",
|
||||
@"1524173 litmus* as Ester - Krave.osz",
|
||||
@"1541235 Getty vs. DJ DiA - Grayed Out -Antifront-.osz",
|
||||
@"1554250 Shawn Wasabi - Otter Pop (feat. Hollis).osz",
|
||||
@"1583461 Sound Souler - Absent Color.osz",
|
||||
@"1638487 tokiwa - wasurena feat. Sennzai.osz",
|
||||
@"1698949 ZxNX - Schadenfreude.osz",
|
||||
@"1704324 xi - Time files.osz",
|
||||
@"1756405 Fractal Dreamers - Kingdom of Silence.osz",
|
||||
@"1769575 cYsmix - Peer Gynt.osz",
|
||||
@"1770054 Ardolf - Split.osz",
|
||||
@"1772648 in love with a ghost - interdimensional portal leading to a cute place feat. snail's house.osz",
|
||||
@"1776379 in love with a ghost - i thought we were lovers w/ basil.osz",
|
||||
@"1779476 URBANGARDE - KIMI WA OKUMAGASO.osz",
|
||||
@"1789435 xi - Parousia.osz",
|
||||
@"1794190 Se-U-Ra - The Endless for Traveler.osz",
|
||||
@"1799889 Waterflame - Ricochet Love.osz",
|
||||
@"1816401 Gram vs. Yooh - Apocalypse.osz",
|
||||
@"1826327 -45 - Total Eclipse of The Sun.osz",
|
||||
@"1830796 xi - Halcyon.osz",
|
||||
@"1924231 Mili - Nine Point Eight.osz",
|
||||
@"1952903 Cres. - End Time.osz",
|
||||
@"1970946 Good Kid - Slingshot.osz",
|
||||
@"1982063 linear ring - enchanted love.osz",
|
||||
@"2000438 Toromaru - Erinyes.osz",
|
||||
@"2124277 II-L - VANGUARD-3.osz",
|
||||
@"2147529 Nashimoto Ui - AaAaAaAAaAaAAa (Cut Ver.).osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_mania =
|
||||
{
|
||||
"943516 antiPLUR - Clockwork Spooks.osz",
|
||||
"946394 VINXIS - Three Times The Original Charm.osz",
|
||||
"966408 antiPLUR - One Life Left to Live.osz",
|
||||
"971561 antiPLUR - Runengon.osz",
|
||||
"983864 James Landino - Shiba Island.osz",
|
||||
"989512 BilliumMoto - 1xMISS.osz",
|
||||
"994104 James Landino - Reaction feat. Slyleaf.osz",
|
||||
"1003217 nekodex - circles!.osz",
|
||||
"1009907 James Landino & Kabuki - Birdsong.osz",
|
||||
"1015169 Thaehan - Insert Coin.osz",
|
||||
@"1008419 BilliumMoto - Four Veiled Stars.osz",
|
||||
@"1025170 Frums - We Want To Run.osz",
|
||||
@"1092856 F-777 - Viking Arena.osz",
|
||||
@"1139247 O2i3 - Heart Function.osz",
|
||||
@"1154007 LeaF - ATHAZA.osz",
|
||||
@"1170054 Zekk - Fallen.osz",
|
||||
@"1212132 Street - Koiyamai (TV Size).osz",
|
||||
@"1226466 Se-U-Ra - Elif to Shiro Kura no Yoru -Called-.osz",
|
||||
@"1247210 Frums - Credits.osz",
|
||||
@"1254196 ARForest - Regret.osz",
|
||||
@"1258829 Umeboshi Chazuke - Cineraria.osz",
|
||||
@"1300398 ARForest - The Last Page.osz",
|
||||
@"1305627 Frums - Star of the COME ON!!.osz",
|
||||
@"1348806 Se-U-Ra - LOA2.osz",
|
||||
@"1375449 yuki. - Nadeshiko Sensation.osz",
|
||||
@"1448292 Cres. - End Time.osz",
|
||||
@"1479741 Reku Mochizuki - FORViDDEN ENERZY -Fataldoze-.osz",
|
||||
@"1494747 Fractal Dreamers - Whispers from a Distant Star.osz",
|
||||
@"1505336 litmus* - Rush-More.osz",
|
||||
@"1508963 ARForest - Rainbow Magic!!.osz",
|
||||
@"1727126 Chroma - Strange Inventor.osz",
|
||||
@"1737101 ZxNX - FORTALiCE.osz",
|
||||
@"1740952 Sobrem x Silentroom - Random.osz",
|
||||
@"1756251 Plum - Mad Piano Party.osz",
|
||||
@"1909163 Frums - theyaremanycolors.osz",
|
||||
@"1916285 siromaru + cranky - conflict.osz",
|
||||
@"1948972 Ardolf - Split.osz",
|
||||
@"1957138 GLORYHAMMER - Rise Of The Chaos Wizards.osz",
|
||||
@"1972411 James Landino - Shiba Paradise.osz",
|
||||
@"1978179 Andora - Flicker (feat. RANASOL).osz",
|
||||
@"1987180 cygnus - The Evolution of War.osz",
|
||||
@"1994458 tephe - Genjitsu Escape.osz",
|
||||
@"1999339 Aice room - Nyan Nyan Dive (EmoCosine Remix).osz",
|
||||
@"2015361 HoneyComeBear - Rainy Girl.osz",
|
||||
@"2028108 HyuN - Infinity Heaven.osz",
|
||||
@"2055329 miraie & blackwinterwells - facade.osz",
|
||||
@"2069877 Sephid - Thunderstrike 1988.osz",
|
||||
@"2119716 Aethoro - Snowy.osz",
|
||||
@"2120379 Synthion - VIVIDVELOCITY.osz",
|
||||
@"2124805 Frums (unknown ""lambda"") - 19ZZ.osz",
|
||||
@"2127811 Wiklund - Joy of Living (Cut Ver.).osz",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -52,8 +52,11 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private Drawable getDrawableForModel(IBeatmapInfo? model)
|
||||
{
|
||||
if (model == null)
|
||||
return Empty();
|
||||
|
||||
// prefer online cover where available.
|
||||
if (model?.BeatmapSet is IBeatmapSetOnlineInfo online)
|
||||
if (model.BeatmapSet is IBeatmapSetOnlineInfo online)
|
||||
return new OnlineBeatmapSetCover(online, beatmapSetCoverType);
|
||||
|
||||
if (model is BeatmapInfo localModel)
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
protected override IBeatmap GetBeatmap() => new Beatmap();
|
||||
|
||||
public override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
|
||||
public override Texture GetBackground() => textures?.Get(@"Backgrounds/bg2");
|
||||
|
||||
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
||||
|
||||
|
@ -167,8 +167,6 @@ namespace osu.Game.Beatmaps.Formats
|
||||
beatmapInfo.SamplesMatchPlaybackRate = false;
|
||||
}
|
||||
|
||||
protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_');
|
||||
|
||||
protected override void ParseLine(Beatmap beatmap, Section section, string line)
|
||||
{
|
||||
switch (section)
|
||||
@ -417,43 +415,57 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
string[] split = line.Split(',');
|
||||
|
||||
if (!Enum.TryParse(split[0], out LegacyEventType type))
|
||||
throw new InvalidDataException($@"Unknown event type: {split[0]}");
|
||||
// Until we have full storyboard encoder coverage, let's track any lines which aren't handled
|
||||
// and store them to a temporary location such that they aren't lost on editor save / export.
|
||||
bool lineSupportedByEncoder = false;
|
||||
|
||||
switch (type)
|
||||
if (Enum.TryParse(split[0], out LegacyEventType type))
|
||||
{
|
||||
case LegacyEventType.Sprite:
|
||||
// Generally, the background is the first thing defined in a beatmap file.
|
||||
// In some older beatmaps, it is not present and replaced by a storyboard-level background instead.
|
||||
// Allow the first sprite (by file order) to act as the background in such cases.
|
||||
if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]);
|
||||
break;
|
||||
switch (type)
|
||||
{
|
||||
case LegacyEventType.Sprite:
|
||||
// Generally, the background is the first thing defined in a beatmap file.
|
||||
// In some older beatmaps, it is not present and replaced by a storyboard-level background instead.
|
||||
// Allow the first sprite (by file order) to act as the background in such cases.
|
||||
if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]);
|
||||
lineSupportedByEncoder = true;
|
||||
}
|
||||
|
||||
case LegacyEventType.Video:
|
||||
string filename = CleanFilename(split[2]);
|
||||
break;
|
||||
|
||||
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
|
||||
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
|
||||
// video extensions and handle similar to a background if it doesn't match.
|
||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||
}
|
||||
case LegacyEventType.Video:
|
||||
string filename = CleanFilename(split[2]);
|
||||
|
||||
break;
|
||||
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
|
||||
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
|
||||
// video extensions and handle similar to a background if it doesn't match.
|
||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||
lineSupportedByEncoder = true;
|
||||
}
|
||||
|
||||
case LegacyEventType.Background:
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
|
||||
break;
|
||||
break;
|
||||
|
||||
case LegacyEventType.Break:
|
||||
double start = getOffsetTime(Parsing.ParseDouble(split[1]));
|
||||
double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])));
|
||||
case LegacyEventType.Background:
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
|
||||
lineSupportedByEncoder = true;
|
||||
break;
|
||||
|
||||
beatmap.Breaks.Add(new BreakPeriod(start, end));
|
||||
break;
|
||||
case LegacyEventType.Break:
|
||||
double start = getOffsetTime(Parsing.ParseDouble(split[1]));
|
||||
double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])));
|
||||
|
||||
beatmap.Breaks.Add(new BreakPeriod(start, end));
|
||||
lineSupportedByEncoder = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lineSupportedByEncoder)
|
||||
beatmap.UnhandledEventLines.Add(line);
|
||||
}
|
||||
|
||||
private void handleTimingPoint(string line)
|
||||
|
@ -156,6 +156,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
foreach (var b in beatmap.Breaks)
|
||||
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
|
||||
|
||||
foreach (string l in beatmap.UnhandledEventLines)
|
||||
writer.WriteLine(l);
|
||||
}
|
||||
|
||||
private void handleControlPoints(TextWriter writer)
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Commands;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -17,7 +18,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
public class LegacyStoryboardDecoder : LegacyDecoder<Storyboard>
|
||||
{
|
||||
private StoryboardSprite? storyboardSprite;
|
||||
private CommandTimelineGroup? timelineGroup;
|
||||
private StoryboardCommandGroup? currentCommandsGroup;
|
||||
|
||||
private Storyboard storyboard = null!;
|
||||
|
||||
@ -164,7 +165,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
else
|
||||
{
|
||||
if (depth < 2)
|
||||
timelineGroup = storyboardSprite?.TimelineGroup;
|
||||
currentCommandsGroup = storyboardSprite?.Commands;
|
||||
|
||||
string commandType = split[0];
|
||||
|
||||
@ -176,7 +177,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
double startTime = split.Length > 2 ? Parsing.ParseDouble(split[2]) : double.MinValue;
|
||||
double endTime = split.Length > 3 ? Parsing.ParseDouble(split[3]) : double.MaxValue;
|
||||
int groupNumber = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0;
|
||||
timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
|
||||
currentCommandsGroup = storyboardSprite?.AddTriggerGroup(triggerName, startTime, endTime, groupNumber);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -184,7 +185,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
double startTime = Parsing.ParseDouble(split[1]);
|
||||
int repeatCount = Parsing.ParseInt(split[2]);
|
||||
timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount - 1));
|
||||
currentCommandsGroup = storyboardSprite?.AddLoopingGroup(startTime, Math.Max(0, repeatCount - 1));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -203,7 +204,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
|
||||
currentCommandsGroup?.AddAlpha(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -211,7 +212,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.Scale.Add(easing, startTime, endTime, startValue, endValue);
|
||||
currentCommandsGroup?.AddScale(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -221,7 +222,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
float startY = Parsing.ParseFloat(split[5]);
|
||||
float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX;
|
||||
float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY;
|
||||
timelineGroup?.VectorScale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
|
||||
currentCommandsGroup?.AddVectorScale(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -229,7 +230,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.Rotation.Add(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue));
|
||||
currentCommandsGroup?.AddRotation(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -239,8 +240,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
float startY = Parsing.ParseFloat(split[5]);
|
||||
float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX;
|
||||
float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY;
|
||||
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
|
||||
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
|
||||
currentCommandsGroup?.AddX(easing, startTime, endTime, startX, endX);
|
||||
currentCommandsGroup?.AddY(easing, startTime, endTime, startY, endY);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -248,7 +249,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
|
||||
currentCommandsGroup?.AddX(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -256,7 +257,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
|
||||
currentCommandsGroup?.AddY(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -268,7 +269,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
float endRed = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startRed;
|
||||
float endGreen = split.Length > 8 ? Parsing.ParseFloat(split[8]) : startGreen;
|
||||
float endBlue = split.Length > 9 ? Parsing.ParseFloat(split[9]) : startBlue;
|
||||
timelineGroup?.Colour.Add(easing, startTime, endTime,
|
||||
currentCommandsGroup?.AddColour(easing, startTime, endTime,
|
||||
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
|
||||
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
|
||||
break;
|
||||
@ -281,16 +282,16 @@ namespace osu.Game.Beatmaps.Formats
|
||||
switch (type)
|
||||
{
|
||||
case "A":
|
||||
timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive,
|
||||
currentCommandsGroup?.AddBlendingParameters(easing, startTime, endTime, BlendingParameters.Additive,
|
||||
startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit);
|
||||
break;
|
||||
|
||||
case "H":
|
||||
timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime);
|
||||
currentCommandsGroup?.AddFlipH(easing, startTime, endTime, true, startTime == endTime);
|
||||
break;
|
||||
|
||||
case "V":
|
||||
timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
|
||||
currentCommandsGroup?.AddFlipV(easing, startTime, endTime, true, startTime == endTime);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,12 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
List<BreakPeriod> Breaks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All lines from the [Events] section which aren't handled in the encoding process yet.
|
||||
/// These lines shoule be written out to the beatmap file on save or export.
|
||||
/// </summary>
|
||||
List<string> UnhandledEventLines { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of break time in the beatmap.
|
||||
/// </summary>
|
||||
|
@ -44,11 +44,34 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
this.storage = storage;
|
||||
|
||||
// avoid downloading / using cache for unit tests.
|
||||
if (!DebugUtils.IsNUnitRunning && !storage.Exists(cache_database_name))
|
||||
if (shouldFetchCache())
|
||||
prepareLocalCache();
|
||||
}
|
||||
|
||||
private bool shouldFetchCache()
|
||||
{
|
||||
// avoid downloading / using cache for unit tests.
|
||||
if (DebugUtils.IsNUnitRunning)
|
||||
return false;
|
||||
|
||||
if (!storage.Exists(cache_database_name))
|
||||
{
|
||||
log(@"Fetching local cache because it does not exist.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// periodically update the cache to include newer beatmaps.
|
||||
var fileInfo = new FileInfo(storage.GetFullPath(cache_database_name));
|
||||
|
||||
if (fileInfo.LastWriteTime < DateTime.Now.AddMonths(-1))
|
||||
{
|
||||
log($@"Refetching local cache because it was last written to on {fileInfo.LastWriteTime}.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Available =>
|
||||
// no download in progress.
|
||||
cacheDownloadRequest == null
|
||||
@ -124,6 +147,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private void prepareLocalCache()
|
||||
{
|
||||
bool isRefetch = storage.Exists(cache_database_name);
|
||||
|
||||
string cacheFilePath = storage.GetFullPath(cache_database_name);
|
||||
string compressedCacheFilePath = $@"{cacheFilePath}.bz2";
|
||||
|
||||
@ -132,9 +157,15 @@ namespace osu.Game.Beatmaps
|
||||
cacheDownloadRequest.Failed += ex =>
|
||||
{
|
||||
File.Delete(compressedCacheFilePath);
|
||||
File.Delete(cacheFilePath);
|
||||
|
||||
Logger.Log($@"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache download failed: {ex}", LoggingTarget.Database);
|
||||
// don't clobber the cache when refetching if the download didn't succeed. seems excessive.
|
||||
// consequently, also null the download request to allow the existing cache to be used (see `Available`).
|
||||
if (isRefetch)
|
||||
cacheDownloadRequest = null;
|
||||
else
|
||||
File.Delete(cacheFilePath);
|
||||
|
||||
log($@"Online cache download failed: {ex}");
|
||||
};
|
||||
|
||||
cacheDownloadRequest.Finished += () =>
|
||||
@ -143,15 +174,22 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
using (var stream = File.OpenRead(cacheDownloadRequest.Filename))
|
||||
using (var outStream = File.OpenWrite(cacheFilePath))
|
||||
using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false))
|
||||
bz2.CopyTo(outStream);
|
||||
{
|
||||
// ensure to clobber any and all existing data to avoid accidental corruption.
|
||||
outStream.SetLength(0);
|
||||
|
||||
using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false))
|
||||
bz2.CopyTo(outStream);
|
||||
}
|
||||
|
||||
// set to null on completion to allow lookups to begin using the new source
|
||||
cacheDownloadRequest = null;
|
||||
log(@"Local cache fetch completed successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($@"{nameof(LocalCachedBeatmapMetadataSource)}'s online cache extraction failed: {ex}", LoggingTarget.Database);
|
||||
log($@"Online cache extraction failed: {ex}");
|
||||
// at this point clobber the cache regardless of whether we're refetching, because by this point who knows what state the cache file is in.
|
||||
File.Delete(cacheFilePath);
|
||||
}
|
||||
finally
|
||||
@ -173,6 +211,9 @@ namespace osu.Game.Beatmaps
|
||||
});
|
||||
}
|
||||
|
||||
private static void log(string message)
|
||||
=> Logger.Log($@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}", LoggingTarget.Database);
|
||||
|
||||
private void logForModel(BeatmapSetInfo set, string message) =>
|
||||
RealmArchiveModelImporter<BeatmapSetInfo>.LogForModel(set, $@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}");
|
||||
|
||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Database
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool UseFixedEncoding => false;
|
||||
|
||||
protected override string FileExtension => @".olz";
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using Realms;
|
||||
using SharpCompress.Common;
|
||||
@ -22,6 +24,11 @@ namespace osu.Game.Database
|
||||
public abstract class LegacyArchiveExporter<TModel> : LegacyExporter<TModel>
|
||||
where TModel : RealmObject, IHasNamedFiles, IHasGuidPrimaryKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to always use Shift-JIS encoding for archive filenames (like osu!stable did).
|
||||
/// </summary>
|
||||
protected virtual bool UseFixedEncoding => true;
|
||||
|
||||
protected LegacyArchiveExporter(Storage storage)
|
||||
: base(storage)
|
||||
{
|
||||
@ -29,7 +36,12 @@ namespace osu.Game.Database
|
||||
|
||||
public override void ExportToStream(TModel model, Stream outputStream, ProgressNotification? notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var writer = new ZipWriter(outputStream, new ZipWriterOptions(CompressionType.Deflate)))
|
||||
var zipWriterOptions = new ZipWriterOptions(CompressionType.Deflate)
|
||||
{
|
||||
ArchiveEncoding = UseFixedEncoding ? ZipArchiveReader.DEFAULT_ENCODING : new ArchiveEncoding(Encoding.UTF8, Encoding.UTF8)
|
||||
};
|
||||
|
||||
using (var writer = new ZipWriter(outputStream, zipWriterOptions))
|
||||
{
|
||||
int i = 0;
|
||||
int fileCount = model.Files.Count();
|
||||
|
@ -35,6 +35,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Utils;
|
||||
using osuTK.Input;
|
||||
using Realms;
|
||||
using Realms.Exceptions;
|
||||
@ -321,12 +322,32 @@ namespace osu.Game.Database
|
||||
{
|
||||
Logger.Error(e, "Your local database is too new to work with this version of osu!. Please close osu! and install the latest release to recover your data.");
|
||||
|
||||
// If a newer version database already exists, don't backup again. We can presume that the first backup is the one we care about.
|
||||
// If a newer version database already exists, don't create another backup. We can presume that the first backup is the one we care about.
|
||||
if (!storage.Exists(newerVersionFilename))
|
||||
createBackup(newerVersionFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This error can occur due to file handles still being open by a previous instance.
|
||||
// If this is the case, rather than assuming the realm file is corrupt, block game startup.
|
||||
if (e.Message.StartsWith("SetEndOfFile() failed", StringComparison.Ordinal))
|
||||
{
|
||||
// This will throw if the realm file is not available for write access after 5 seconds.
|
||||
FileUtils.AttemptOperation(() =>
|
||||
{
|
||||
if (storage.Exists(Filename))
|
||||
{
|
||||
using (var _ = storage.GetStream(Filename, FileAccess.ReadWrite))
|
||||
{
|
||||
}
|
||||
}
|
||||
}, 20);
|
||||
|
||||
// If the above eventually succeeds, try and continue startup as per normal.
|
||||
// This may throw again but let's allow it to, and block startup.
|
||||
return getRealmInstance();
|
||||
}
|
||||
|
||||
Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made.");
|
||||
createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}");
|
||||
}
|
||||
@ -1142,33 +1163,18 @@ namespace osu.Game.Database
|
||||
{
|
||||
Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database);
|
||||
|
||||
int attempts = 10;
|
||||
|
||||
while (true)
|
||||
FileUtils.AttemptOperation(() =>
|
||||
{
|
||||
try
|
||||
using (var source = storage.GetStream(Filename, mode: FileMode.Open))
|
||||
{
|
||||
using (var source = storage.GetStream(Filename, mode: FileMode.Open))
|
||||
{
|
||||
// source may not exist.
|
||||
if (source == null)
|
||||
return;
|
||||
// source may not exist.
|
||||
if (source == null)
|
||||
return;
|
||||
|
||||
using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew))
|
||||
source.CopyTo(destination);
|
||||
}
|
||||
|
||||
return;
|
||||
using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew))
|
||||
source.CopyTo(destination);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (attempts-- <= 0)
|
||||
throw;
|
||||
|
||||
// file may be locked during use.
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
}, 20);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -449,16 +449,6 @@ namespace osu.Game.Database
|
||||
return reader.Name.ComputeSHA2Hash();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create all required <see cref="File"/>s for the provided archive, adding them to the global file store.
|
||||
/// </summary>
|
||||
private List<RealmNamedFileUsage> createFileInfos(ArchiveReader reader, RealmFileStore files, Realm realm)
|
||||
{
|
||||
var fileInfos = new List<RealmNamedFileUsage>();
|
||||
|
||||
return fileInfos;
|
||||
}
|
||||
|
||||
private IEnumerable<(string original, string shortened)> getShortenedFilenames(ArchiveReader reader)
|
||||
{
|
||||
string prefix = reader.Filenames.GetCommonPrefix();
|
||||
|
@ -4,7 +4,9 @@
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@ -19,6 +21,16 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override TextContainer CreateTextContainer() => new ToggleTextContainer(Item);
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
// Right mouse button is a special case where we allow actioning without dismissing the menu.
|
||||
// This is achieved by not calling `Clicked` (as done by the base implementation in OnClick).
|
||||
if (IsActionable && e.Button == MouseButton.Right)
|
||||
Item.Action.Value?.Invoke();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private partial class ToggleTextContainer : TextContainer
|
||||
{
|
||||
private readonly StatefulMenuItem menuItem;
|
||||
|
@ -7,23 +7,45 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Toolkit.HighPerformance;
|
||||
using osu.Framework.IO.Stores;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SixLabors.ImageSharp.Memory;
|
||||
|
||||
namespace osu.Game.IO.Archives
|
||||
{
|
||||
public sealed class ZipArchiveReader : ArchiveReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Archives created by osu!stable still write out as Shift-JIS.
|
||||
/// We want to force this fallback rather than leave it up to the library/system.
|
||||
/// In the future we may want to change exports to set the zip UTF-8 flag and use that instead.
|
||||
/// </summary>
|
||||
public static readonly ArchiveEncoding DEFAULT_ENCODING;
|
||||
|
||||
private readonly Stream archiveStream;
|
||||
private readonly ZipArchive archive;
|
||||
|
||||
static ZipArchiveReader()
|
||||
{
|
||||
// Required to support rare code pages.
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
DEFAULT_ENCODING = new ArchiveEncoding(Encoding.GetEncoding(932), Encoding.GetEncoding(932));
|
||||
}
|
||||
|
||||
public ZipArchiveReader(Stream archiveStream, string name = null)
|
||||
: base(name)
|
||||
{
|
||||
this.archiveStream = archiveStream;
|
||||
archive = ZipArchive.Open(archiveStream);
|
||||
|
||||
archive = ZipArchive.Open(archiveStream, new ReaderOptions
|
||||
{
|
||||
ArchiveEncoding = DEFAULT_ENCODING
|
||||
});
|
||||
}
|
||||
|
||||
public override Stream GetStream(string name)
|
||||
|
@ -123,58 +123,58 @@ namespace osu.Game.IO.Legacy
|
||||
|
||||
switch (t)
|
||||
{
|
||||
case ObjType.boolType:
|
||||
case ObjType.BoolType:
|
||||
return ReadBoolean();
|
||||
|
||||
case ObjType.byteType:
|
||||
case ObjType.ByteType:
|
||||
return ReadByte();
|
||||
|
||||
case ObjType.uint16Type:
|
||||
case ObjType.UInt16Type:
|
||||
return ReadUInt16();
|
||||
|
||||
case ObjType.uint32Type:
|
||||
case ObjType.UInt32Type:
|
||||
return ReadUInt32();
|
||||
|
||||
case ObjType.uint64Type:
|
||||
case ObjType.UInt64Type:
|
||||
return ReadUInt64();
|
||||
|
||||
case ObjType.sbyteType:
|
||||
case ObjType.SByteType:
|
||||
return ReadSByte();
|
||||
|
||||
case ObjType.int16Type:
|
||||
case ObjType.Int16Type:
|
||||
return ReadInt16();
|
||||
|
||||
case ObjType.int32Type:
|
||||
case ObjType.Int32Type:
|
||||
return ReadInt32();
|
||||
|
||||
case ObjType.int64Type:
|
||||
case ObjType.Int64Type:
|
||||
return ReadInt64();
|
||||
|
||||
case ObjType.charType:
|
||||
case ObjType.CharType:
|
||||
return ReadChar();
|
||||
|
||||
case ObjType.stringType:
|
||||
case ObjType.StringType:
|
||||
return base.ReadString();
|
||||
|
||||
case ObjType.singleType:
|
||||
case ObjType.SingleType:
|
||||
return ReadSingle();
|
||||
|
||||
case ObjType.doubleType:
|
||||
case ObjType.DoubleType:
|
||||
return ReadDouble();
|
||||
|
||||
case ObjType.decimalType:
|
||||
case ObjType.DecimalType:
|
||||
return ReadDecimal();
|
||||
|
||||
case ObjType.dateTimeType:
|
||||
case ObjType.DateTimeType:
|
||||
return ReadDateTime();
|
||||
|
||||
case ObjType.byteArrayType:
|
||||
case ObjType.ByteArrayType:
|
||||
return ReadByteArray();
|
||||
|
||||
case ObjType.charArrayType:
|
||||
case ObjType.CharArrayType:
|
||||
return ReadCharArray();
|
||||
|
||||
case ObjType.otherType:
|
||||
case ObjType.OtherType:
|
||||
throw new IOException("Deserialization of arbitrary type is not supported.");
|
||||
|
||||
default:
|
||||
@ -185,25 +185,25 @@ namespace osu.Game.IO.Legacy
|
||||
|
||||
public enum ObjType : byte
|
||||
{
|
||||
nullType,
|
||||
boolType,
|
||||
byteType,
|
||||
uint16Type,
|
||||
uint32Type,
|
||||
uint64Type,
|
||||
sbyteType,
|
||||
int16Type,
|
||||
int32Type,
|
||||
int64Type,
|
||||
charType,
|
||||
stringType,
|
||||
singleType,
|
||||
doubleType,
|
||||
decimalType,
|
||||
dateTimeType,
|
||||
byteArrayType,
|
||||
charArrayType,
|
||||
otherType,
|
||||
ILegacySerializableType
|
||||
NullType,
|
||||
BoolType,
|
||||
ByteType,
|
||||
UInt16Type,
|
||||
UInt32Type,
|
||||
UInt64Type,
|
||||
SByteType,
|
||||
Int16Type,
|
||||
Int32Type,
|
||||
Int64Type,
|
||||
CharType,
|
||||
StringType,
|
||||
SingleType,
|
||||
DoubleType,
|
||||
DecimalType,
|
||||
DateTimeType,
|
||||
ByteArrayType,
|
||||
CharArrayType,
|
||||
OtherType,
|
||||
LegacySerializableType
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ namespace osu.Game.IO.Legacy
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
Write((byte)ObjType.nullType);
|
||||
Write((byte)ObjType.NullType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Write((byte)ObjType.stringType);
|
||||
Write((byte)ObjType.StringType);
|
||||
base.Write(str);
|
||||
}
|
||||
}
|
||||
@ -125,94 +125,94 @@ namespace osu.Game.IO.Legacy
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
Write((byte)ObjType.nullType);
|
||||
Write((byte)ObjType.NullType);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
case bool boolObj:
|
||||
Write((byte)ObjType.boolType);
|
||||
Write((byte)ObjType.BoolType);
|
||||
Write(boolObj);
|
||||
break;
|
||||
|
||||
case byte byteObj:
|
||||
Write((byte)ObjType.byteType);
|
||||
Write((byte)ObjType.ByteType);
|
||||
Write(byteObj);
|
||||
break;
|
||||
|
||||
case ushort ushortObj:
|
||||
Write((byte)ObjType.uint16Type);
|
||||
Write((byte)ObjType.UInt16Type);
|
||||
Write(ushortObj);
|
||||
break;
|
||||
|
||||
case uint uintObj:
|
||||
Write((byte)ObjType.uint32Type);
|
||||
Write((byte)ObjType.UInt32Type);
|
||||
Write(uintObj);
|
||||
break;
|
||||
|
||||
case ulong ulongObj:
|
||||
Write((byte)ObjType.uint64Type);
|
||||
Write((byte)ObjType.UInt64Type);
|
||||
Write(ulongObj);
|
||||
break;
|
||||
|
||||
case sbyte sbyteObj:
|
||||
Write((byte)ObjType.sbyteType);
|
||||
Write((byte)ObjType.SByteType);
|
||||
Write(sbyteObj);
|
||||
break;
|
||||
|
||||
case short shortObj:
|
||||
Write((byte)ObjType.int16Type);
|
||||
Write((byte)ObjType.Int16Type);
|
||||
Write(shortObj);
|
||||
break;
|
||||
|
||||
case int intObj:
|
||||
Write((byte)ObjType.int32Type);
|
||||
Write((byte)ObjType.Int32Type);
|
||||
Write(intObj);
|
||||
break;
|
||||
|
||||
case long longObj:
|
||||
Write((byte)ObjType.int64Type);
|
||||
Write((byte)ObjType.Int64Type);
|
||||
Write(longObj);
|
||||
break;
|
||||
|
||||
case char charObj:
|
||||
Write((byte)ObjType.charType);
|
||||
Write((byte)ObjType.CharType);
|
||||
base.Write(charObj);
|
||||
break;
|
||||
|
||||
case string stringObj:
|
||||
Write((byte)ObjType.stringType);
|
||||
Write((byte)ObjType.StringType);
|
||||
base.Write(stringObj);
|
||||
break;
|
||||
|
||||
case float floatObj:
|
||||
Write((byte)ObjType.singleType);
|
||||
Write((byte)ObjType.SingleType);
|
||||
Write(floatObj);
|
||||
break;
|
||||
|
||||
case double doubleObj:
|
||||
Write((byte)ObjType.doubleType);
|
||||
Write((byte)ObjType.DoubleType);
|
||||
Write(doubleObj);
|
||||
break;
|
||||
|
||||
case decimal decimalObj:
|
||||
Write((byte)ObjType.decimalType);
|
||||
Write((byte)ObjType.DecimalType);
|
||||
Write(decimalObj);
|
||||
break;
|
||||
|
||||
case DateTime dateTimeObj:
|
||||
Write((byte)ObjType.dateTimeType);
|
||||
Write((byte)ObjType.DateTimeType);
|
||||
Write(dateTimeObj);
|
||||
break;
|
||||
|
||||
case byte[] byteArray:
|
||||
Write((byte)ObjType.byteArrayType);
|
||||
Write((byte)ObjType.ByteArrayType);
|
||||
base.Write(byteArray);
|
||||
break;
|
||||
|
||||
case char[] charArray:
|
||||
Write((byte)ObjType.charArrayType);
|
||||
Write((byte)ObjType.CharArrayType);
|
||||
base.Write(charArray);
|
||||
break;
|
||||
|
||||
|
44
osu.Game/Localisation/DeleteConfirmationContentStrings.cs
Normal file
44
osu.Game/Localisation/DeleteConfirmationContentStrings.cs
Normal file
@ -0,0 +1,44 @@
|
||||
// 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.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public static class DeleteConfirmationContentStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.DeleteConfirmationContent";
|
||||
|
||||
/// <summary>
|
||||
/// "Are you sure you want to delete all beatmaps?"
|
||||
/// </summary>
|
||||
public static LocalisableString Beatmaps => new TranslatableString(getKey(@"beatmaps"), @"Are you sure you want to delete all beatmaps?");
|
||||
|
||||
/// <summary>
|
||||
/// "Are you sure you want to delete all beatmaps videos? This cannot be undone!"
|
||||
/// </summary>
|
||||
public static LocalisableString BeatmapVideos => new TranslatableString(getKey(@"beatmap_videos"), @"Are you sure you want to delete all beatmaps videos? This cannot be undone!");
|
||||
|
||||
/// <summary>
|
||||
/// "Are you sure you want to delete all skins? This cannot be undone!"
|
||||
/// </summary>
|
||||
public static LocalisableString Skins => new TranslatableString(getKey(@"skins"), @"Are you sure you want to delete all skins? This cannot be undone!");
|
||||
|
||||
/// <summary>
|
||||
/// "Are you sure you want to delete all collections? This cannot be undone!"
|
||||
/// </summary>
|
||||
public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Are you sure you want to delete all collections? This cannot be undone!");
|
||||
|
||||
/// <summary>
|
||||
/// "Are you sure you want to delete all scores? This cannot be undone!"
|
||||
/// </summary>
|
||||
public static LocalisableString Scores => new TranslatableString(getKey(@"collections"), @"Are you sure you want to delete all scores? This cannot be undone!");
|
||||
|
||||
/// <summary>
|
||||
/// "Are you sure you want to delete all mod presets?"
|
||||
/// </summary>
|
||||
public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"Are you sure you want to delete all mod presets?");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -10,9 +10,9 @@ namespace osu.Game.Localisation
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.DeleteConfirmationDialog";
|
||||
|
||||
/// <summary>
|
||||
/// "Confirm deletion of"
|
||||
/// "Caution"
|
||||
/// </summary>
|
||||
public static LocalisableString HeaderText => new TranslatableString(getKey(@"header_text"), @"Confirm deletion of");
|
||||
public static LocalisableString HeaderText => new TranslatableString(getKey(@"header_text"), @"Caution");
|
||||
|
||||
/// <summary>
|
||||
/// "Yes. Go for it."
|
||||
|
@ -39,6 +39,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString BundledButton => new TranslatableString(getKey(@"bundled_button"), @"Get recommended beatmaps");
|
||||
|
||||
/// <summary>
|
||||
/// "Beatmaps will be downloaded in the background. You can continue with setup while this happens!"
|
||||
/// </summary>
|
||||
public static LocalisableString DownloadingInBackground => new TranslatableString(getKey(@"downloading_in_background"), @"Beatmaps will be downloaded in the background. You can continue with setup while this happens!");
|
||||
|
||||
/// <summary>
|
||||
/// "You can also obtain more beatmaps from the main menu "browse" button at any time."
|
||||
/// </summary>
|
||||
|
@ -2,10 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
|
||||
public enum Language
|
||||
{
|
||||
|
@ -396,7 +396,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
switch (state)
|
||||
{
|
||||
case MultiplayerRoomState.Open:
|
||||
APIRoom.Status.Value = new RoomStatusOpen();
|
||||
APIRoom.Status.Value = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen();
|
||||
break;
|
||||
|
||||
case MultiplayerRoomState.Playing:
|
||||
@ -816,6 +816,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room.Settings = settings;
|
||||
APIRoom.Name.Value = Room.Settings.Name;
|
||||
APIRoom.Password.Value = Room.Settings.Password;
|
||||
APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate();
|
||||
APIRoom.Type.Value = Room.Settings.MatchType;
|
||||
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
||||
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
|
||||
|
@ -1,10 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
|
||||
namespace osu.Game.Online.Rooms
|
||||
@ -33,6 +35,25 @@ namespace osu.Game.Online.Rooms
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override void PostProcess()
|
||||
{
|
||||
base.PostProcess();
|
||||
|
||||
if (Response != null)
|
||||
{
|
||||
// API doesn't populate status so let's do it here.
|
||||
foreach (var room in Response)
|
||||
{
|
||||
if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value)
|
||||
room.Status.Value = new RoomStatusEnded();
|
||||
else if (room.HasPassword.Value)
|
||||
room.Status.Value = new RoomStatusOpenPrivate();
|
||||
else
|
||||
room.Status.Value = new RoomStatusOpen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override string Target => "rooms";
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ namespace osu.Game.Online.Rooms
|
||||
public readonly Bindable<PlaylistAggregateScore> UserScore = new Bindable<PlaylistAggregateScore>();
|
||||
|
||||
[JsonProperty("has_password")]
|
||||
public readonly BindableBool HasPassword = new BindableBool();
|
||||
public readonly Bindable<bool> HasPassword = new Bindable<bool>();
|
||||
|
||||
[Cached]
|
||||
[JsonProperty("recent_participants")]
|
||||
@ -201,9 +201,6 @@ namespace osu.Game.Online.Rooms
|
||||
CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value;
|
||||
AutoSkip.Value = other.AutoSkip.Value;
|
||||
|
||||
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
|
||||
Status.Value = new RoomStatusEnded();
|
||||
|
||||
other.RemoveExpiredPlaylistItems();
|
||||
|
||||
if (!Playlist.SequenceEqual(other.Playlist))
|
||||
|
14
osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs
Normal file
14
osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Rooms.RoomStatuses
|
||||
{
|
||||
public class RoomStatusOpenPrivate : RoomStatus
|
||||
{
|
||||
public override string Message => "Open (Private)";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDark;
|
||||
}
|
||||
}
|
@ -841,7 +841,10 @@ namespace osu.Game
|
||||
{
|
||||
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance).
|
||||
// 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 }
|
||||
{ FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen },
|
||||
{ FrameworkSetting.VolumeUniversal, 0.6 },
|
||||
{ FrameworkSetting.VolumeMusic, 0.6 },
|
||||
{ FrameworkSetting.VolumeEffect, 0.6 },
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -198,6 +198,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
c.Anchor = Anchor.TopCentre;
|
||||
c.Origin = Anchor.TopCentre;
|
||||
c.Scale = new Vector2(0.8f);
|
||||
})).ToArray();
|
||||
|
||||
private static ReverseChildIDFillFlowContainer<BeatmapCard> createCardContainerFor(IEnumerable<BeatmapCard> newCards)
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Height = 30;
|
||||
Height = 25;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
Children = new Drawable[]
|
||||
@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = Channel.Name,
|
||||
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||
Colour = colourProvider.Light3,
|
||||
Margin = new MarginPadding { Bottom = 2 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
public IReadOnlyCollection<Drawable> DrawableContentFlow => drawableContentFlow;
|
||||
|
||||
protected virtual float FontSize => 20;
|
||||
protected virtual float FontSize => 14;
|
||||
|
||||
protected virtual float Spacing => 15;
|
||||
|
||||
|
@ -19,6 +19,8 @@ namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public partial class ChatTextBar : Container
|
||||
{
|
||||
public const float HEIGHT = 40;
|
||||
|
||||
public readonly BindableBool ShowSearch = new BindableBool();
|
||||
|
||||
public event Action<string>? OnChatMessageCommitted;
|
||||
@ -45,7 +47,7 @@ namespace osu.Game.Overlays.Chat
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 60;
|
||||
Height = HEIGHT;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -76,7 +78,7 @@ namespace osu.Game.Overlays.Chat
|
||||
Child = chattingText = new TruncatingSpriteText
|
||||
{
|
||||
MaxWidth = chatting_text_width - padding * 2,
|
||||
Font = OsuFont.Torus.With(size: 20),
|
||||
Font = OsuFont.Torus,
|
||||
Colour = colourProvider.Background1,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
@ -91,7 +93,7 @@ namespace osu.Game.Overlays.Chat
|
||||
Icon = FontAwesome.Solid.Search,
|
||||
Origin = Anchor.CentreRight,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Size = new Vector2(20),
|
||||
Size = new Vector2(OsuFont.DEFAULT_FONT_SIZE),
|
||||
Margin = new MarginPadding { Right = 2 },
|
||||
},
|
||||
},
|
||||
@ -101,6 +103,7 @@ namespace osu.Game.Overlays.Chat
|
||||
Padding = new MarginPadding { Right = padding },
|
||||
Child = chatTextBox = new ChatTextBox
|
||||
{
|
||||
FontSize = OsuFont.DEFAULT_FONT_SIZE,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private const float text_size = 18;
|
||||
private const float text_size = 14;
|
||||
private const float icon_size = 14;
|
||||
|
||||
private const float vertical_margin = 1.5f;
|
||||
|
@ -55,7 +55,6 @@ namespace osu.Game.Overlays
|
||||
private const int transition_length = 500;
|
||||
private const float top_bar_height = 40;
|
||||
private const float side_bar_width = 190;
|
||||
private const float chat_bar_height = 60;
|
||||
|
||||
protected override string PopInSampleName => @"UI/overlay-big-pop-in";
|
||||
protected override string PopOutSampleName => @"UI/overlay-big-pop-out";
|
||||
@ -136,7 +135,7 @@ namespace osu.Game.Overlays
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Left = side_bar_width,
|
||||
Bottom = chat_bar_height,
|
||||
Bottom = ChatTextBar.HEIGHT,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
@ -210,7 +210,7 @@ namespace osu.Game.Overlays.Dialog
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
TextAnchor = Anchor.TopCentre,
|
||||
Padding = new MarginPadding { Horizontal = 5 },
|
||||
Padding = new MarginPadding { Horizontal = 15 },
|
||||
},
|
||||
body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18))
|
||||
{
|
||||
@ -219,7 +219,7 @@ namespace osu.Game.Overlays.Dialog
|
||||
TextAnchor = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 5 },
|
||||
Padding = new MarginPadding { Horizontal = 15 },
|
||||
},
|
||||
buttonsContainer = new FillFlowContainer<PopupDialogButton>
|
||||
{
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Overlays.FirstRunSetup
|
||||
@ -25,6 +26,8 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
private ProgressRoundedButton downloadBundledButton = null!;
|
||||
private ProgressRoundedButton downloadTutorialButton = null!;
|
||||
|
||||
private OsuTextFlowContainer downloadInBackgroundText = null!;
|
||||
|
||||
private OsuTextFlowContainer currentlyLoadedBeatmaps = null!;
|
||||
|
||||
private BundledBeatmapDownloader? tutorialDownloader;
|
||||
@ -100,6 +103,15 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
Text = FirstRunSetupBeatmapScreenStrings.BundledButton,
|
||||
Action = downloadBundled
|
||||
},
|
||||
downloadInBackgroundText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
||||
{
|
||||
Colour = OverlayColourProvider.Light2,
|
||||
Alpha = 0,
|
||||
TextAnchor = Anchor.TopCentre,
|
||||
Text = FirstRunSetupBeatmapScreenStrings.DownloadingInBackground,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
@ -169,6 +181,10 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
if (bundledDownloader != null)
|
||||
return;
|
||||
|
||||
downloadInBackgroundText
|
||||
.FlashColour(Color4.White, 500)
|
||||
.FadeIn(200);
|
||||
|
||||
bundledDownloader = new BundledBeatmapDownloader(false);
|
||||
|
||||
AddInternal(bundledDownloader);
|
||||
|
@ -55,7 +55,12 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
protected override void Select()
|
||||
{
|
||||
var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System);
|
||||
// this implicitly presumes that if a system mod declares incompatibility with a non-system mod,
|
||||
// the non-system mod should take precedence.
|
||||
// if this assumption is ever broken, this should be reconsidered.
|
||||
var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System &&
|
||||
!mod.IncompatibleMods.Any(t => Preset.Value.Mods.Any(t.IsInstanceOfType)));
|
||||
|
||||
// will also have the side effect of activating the preset (see `updateActiveState()`).
|
||||
selectedMods.Value = Preset.Value.Mods.Concat(selectedSystemMods).ToArray();
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -33,6 +34,7 @@ namespace osu.Game.Overlays
|
||||
public LocalisableString Title => NowPlayingStrings.HeaderTitle;
|
||||
public LocalisableString Description => NowPlayingStrings.HeaderDescription;
|
||||
|
||||
private const float player_width = 400;
|
||||
private const float player_height = 130;
|
||||
private const float transition_length = 800;
|
||||
private const float progress_height = 10;
|
||||
@ -47,7 +49,7 @@ namespace osu.Game.Overlays
|
||||
private IconButton nextButton = null!;
|
||||
private IconButton playlistButton = null!;
|
||||
|
||||
private SpriteText title = null!, artist = null!;
|
||||
private ScrollingTextContainer title = null!, artist = null!;
|
||||
|
||||
private PlaylistOverlay? playlist;
|
||||
|
||||
@ -70,7 +72,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
public NowPlayingOverlay()
|
||||
{
|
||||
Width = 400;
|
||||
Width = player_width;
|
||||
Margin = new MarginPadding(margin);
|
||||
}
|
||||
|
||||
@ -101,7 +103,7 @@ namespace osu.Game.Overlays
|
||||
Children = new[]
|
||||
{
|
||||
background = Empty(),
|
||||
title = new OsuSpriteText
|
||||
title = new ScrollingTextContainer
|
||||
{
|
||||
Origin = Anchor.BottomCentre,
|
||||
Anchor = Anchor.TopCentre,
|
||||
@ -110,7 +112,7 @@ namespace osu.Game.Overlays
|
||||
Colour = Color4.White,
|
||||
Text = @"Nothing to play",
|
||||
},
|
||||
artist = new OsuSpriteText
|
||||
artist = new ScrollingTextContainer
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
Anchor = Anchor.TopCentre,
|
||||
@ -319,15 +321,15 @@ namespace osu.Game.Overlays
|
||||
switch (direction)
|
||||
{
|
||||
case TrackChangeDirection.Next:
|
||||
newBackground.Position = new Vector2(400, 0);
|
||||
newBackground.Position = new Vector2(player_width, 0);
|
||||
newBackground.MoveToX(0, 500, Easing.OutCubic);
|
||||
background.MoveToX(-400, 500, Easing.OutCubic);
|
||||
background.MoveToX(-player_width, 500, Easing.OutCubic);
|
||||
break;
|
||||
|
||||
case TrackChangeDirection.Prev:
|
||||
newBackground.Position = new Vector2(-400, 0);
|
||||
newBackground.Position = new Vector2(-player_width, 0);
|
||||
newBackground.MoveToX(0, 500, Easing.OutCubic);
|
||||
background.MoveToX(400, 500, Easing.OutCubic);
|
||||
background.MoveToX(player_width, 500, Easing.OutCubic);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -422,7 +424,7 @@ namespace osu.Game.Overlays
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(LargeTextureStore textures)
|
||||
{
|
||||
sprite.Texture = beatmap.GetBackground() ?? textures.Get(@"Backgrounds/bg4");
|
||||
sprite.Texture = beatmap.GetBackground() ?? textures.Get(@"Backgrounds/bg2");
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,5 +471,111 @@ namespace osu.Game.Overlays
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class ScrollingTextContainer : CompositeDrawable
|
||||
{
|
||||
private const float initial_move_delay = 1000;
|
||||
private const float pixels_per_second = 50;
|
||||
|
||||
private OsuSpriteText mainSpriteText = null!;
|
||||
private OsuSpriteText fillerSpriteText = null!;
|
||||
|
||||
private Bindable<bool> showUnicode = null!;
|
||||
|
||||
[Resolved]
|
||||
private FrameworkConfigManager frameworkConfig { get; set; } = null!;
|
||||
|
||||
private LocalisableString text;
|
||||
|
||||
public LocalisableString Text
|
||||
{
|
||||
get => text;
|
||||
set
|
||||
{
|
||||
text = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateText();
|
||||
}
|
||||
}
|
||||
|
||||
private FontUsage font = OsuFont.Default;
|
||||
|
||||
public FontUsage Font
|
||||
{
|
||||
get => font;
|
||||
set
|
||||
{
|
||||
font = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateFontAndText();
|
||||
}
|
||||
}
|
||||
|
||||
public ScrollingTextContainer()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new FillFlowContainer<OsuSpriteText>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
mainSpriteText = new OsuSpriteText { Padding = new MarginPadding { Horizontal = margin } },
|
||||
fillerSpriteText = new OsuSpriteText { Padding = new MarginPadding { Horizontal = margin }, Alpha = 0 },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
showUnicode = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode);
|
||||
showUnicode.BindValueChanged(_ => updateText());
|
||||
|
||||
updateFontAndText();
|
||||
}
|
||||
|
||||
private void updateFontAndText()
|
||||
{
|
||||
mainSpriteText.Font = font;
|
||||
fillerSpriteText.Font = font;
|
||||
|
||||
updateText();
|
||||
}
|
||||
|
||||
private void updateText()
|
||||
{
|
||||
mainSpriteText.Text = text;
|
||||
fillerSpriteText.Alpha = 0;
|
||||
|
||||
ClearTransforms();
|
||||
X = 0;
|
||||
|
||||
float textOverflowWidth = mainSpriteText.Width - player_width;
|
||||
|
||||
// apply half margin of tolerance on both sides before the text scrolls
|
||||
if (textOverflowWidth > margin)
|
||||
{
|
||||
fillerSpriteText.Alpha = 1;
|
||||
fillerSpriteText.Text = text;
|
||||
|
||||
float initialX = (textOverflowWidth + mainSpriteText.Width) / 2;
|
||||
float targetX = (textOverflowWidth - mainSpriteText.Width) / 2;
|
||||
|
||||
this.MoveToX(initialX)
|
||||
.Delay(initial_move_delay)
|
||||
.MoveToX(targetX, mainSpriteText.Width * 1000 / pixels_per_second)
|
||||
.Loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
deleteBeatmapsButton.Enabled.Value = false;
|
||||
Task.Run(() => beatmaps.Delete()).ContinueWith(_ => Schedule(() => deleteBeatmapsButton.Enabled.Value = true));
|
||||
}));
|
||||
}, DeleteConfirmationContentStrings.Beatmaps));
|
||||
}
|
||||
});
|
||||
|
||||
@ -40,11 +40,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos,
|
||||
Action = () =>
|
||||
{
|
||||
dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() =>
|
||||
dialogOverlay?.Push(new MassDeleteConfirmationDialog(() =>
|
||||
{
|
||||
deleteBeatmapVideosButton.Enabled.Value = false;
|
||||
Task.Run(beatmaps.DeleteAllVideos).ContinueWith(_ => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true));
|
||||
}));
|
||||
}, DeleteConfirmationContentStrings.BeatmapVideos));
|
||||
}
|
||||
});
|
||||
AddRange(new Drawable[]
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Text = MaintenanceSettingsStrings.DeleteAllCollections,
|
||||
Action = () =>
|
||||
{
|
||||
dialogOverlay?.Push(new MassDeleteConfirmationDialog(deleteAllCollections));
|
||||
dialogOverlay?.Push(new MassDeleteConfirmationDialog(deleteAllCollections, DeleteConfirmationContentStrings.Collections));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2,15 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
public partial class MassDeleteConfirmationDialog : DangerousActionDialog
|
||||
{
|
||||
public MassDeleteConfirmationDialog(Action deleteAction)
|
||||
public MassDeleteConfirmationDialog(Action deleteAction, LocalisableString deleteContent)
|
||||
{
|
||||
BodyText = "Everything?";
|
||||
BodyText = deleteContent;
|
||||
DangerousAction = deleteAction;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
public partial class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog
|
||||
{
|
||||
public MassVideoDeleteConfirmationDialog(Action deleteAction)
|
||||
: base(deleteAction)
|
||||
{
|
||||
BodyText = "All beatmap videos? This cannot be undone!";
|
||||
}
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
deleteAllButton.Enabled.Value = false;
|
||||
Task.Run(deleteAllModPresets).ContinueWith(t => Schedule(onAllModPresetsDeleted, t));
|
||||
}));
|
||||
}, DeleteConfirmationContentStrings.ModPresets));
|
||||
}
|
||||
},
|
||||
undeleteButton = new SettingsButton
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
deleteScoresButton.Enabled.Value = false;
|
||||
Task.Run(() => scores.Delete()).ContinueWith(_ => Schedule(() => deleteScoresButton.Enabled.Value = true));
|
||||
}));
|
||||
}, DeleteConfirmationContentStrings.Scores));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
deleteSkinsButton.Enabled.Value = false;
|
||||
Task.Run(() => skins.Delete()).ContinueWith(_ => Schedule(() => deleteSkinsButton.Enabled.Value = true));
|
||||
}));
|
||||
}, DeleteConfirmationContentStrings.Skins));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
@ -25,8 +26,10 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
{
|
||||
new WebSettings(),
|
||||
new AlertsAndPrivacySettings(),
|
||||
new IntegrationSettings()
|
||||
};
|
||||
|
||||
if (RuntimeInfo.IsDesktop)
|
||||
Add(new IntegrationSettings());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
public override bool Contains(Vector2 screenSpacePos) => drawableQuad.Contains(screenSpacePos);
|
||||
|
||||
public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition);
|
||||
public override Vector2 ScreenSpaceSelectionPoint =>
|
||||
// Important to use a stable position (not based on origin) as origin may be automatically updated during drag operations.
|
||||
drawable.ScreenSpaceDrawQuad.Centre;
|
||||
|
||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) =>
|
||||
drawableQuad.Contains(screenSpacePos);
|
||||
|
@ -255,8 +255,11 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
// schedule ensures this only happens when the skin editor is visible.
|
||||
// also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types).
|
||||
// probably something which will be factored out in a future database refactor so not too concerning for now.
|
||||
currentSkin.BindValueChanged(_ =>
|
||||
currentSkin.BindValueChanged(val =>
|
||||
{
|
||||
if (val.OldValue != null && hasBegunMutating)
|
||||
save(val.OldValue);
|
||||
|
||||
hasBegunMutating = false;
|
||||
Scheduler.AddOnce(skinChanged);
|
||||
}, true);
|
||||
@ -454,7 +457,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
}
|
||||
|
||||
SelectedComponents.Add(component);
|
||||
SkinSelectionHandler.ApplyClosestAnchor(drawableComponent);
|
||||
SkinSelectionHandler.ApplyClosestAnchorOrigin(drawableComponent);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -537,7 +540,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
protected void Redo() => changeHandler?.RestoreState(1);
|
||||
|
||||
public void Save(bool userTriggered = true)
|
||||
public void Save(bool userTriggered = true) => save(currentSkin.Value, userTriggered);
|
||||
|
||||
private void save(Skin skin, bool userTriggered = true)
|
||||
{
|
||||
if (!hasBegunMutating)
|
||||
return;
|
||||
@ -551,11 +556,11 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
return;
|
||||
|
||||
foreach (var t in targetContainers)
|
||||
currentSkin.Value.UpdateDrawableTarget(t);
|
||||
skin.UpdateDrawableTarget(t);
|
||||
|
||||
// In the case the save was user triggered, always show the save message to make them feel confident.
|
||||
if (skins.Save(skins.CurrentSkin.Value) || userTriggered)
|
||||
onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString() ?? "Unknown"));
|
||||
if (skins.Save(skin) || userTriggered)
|
||||
onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, skin.SkinInfo.ToString() ?? "Unknown"));
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -30,7 +31,6 @@ using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
@ -70,12 +70,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private OsuScreen? lastTargetScreen;
|
||||
private InvokeOnDisposal? nestedInputManagerDisable;
|
||||
|
||||
private Vector2 lastDrawSize;
|
||||
private readonly LayoutValue drawSizeLayout;
|
||||
|
||||
public SkinEditorOverlay(ScalingContainer scalingContainer)
|
||||
{
|
||||
this.scalingContainer = scalingContainer;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -199,10 +201,10 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (game.DrawSize != lastDrawSize)
|
||||
if (!drawSizeLayout.IsValid)
|
||||
{
|
||||
lastDrawSize = game.DrawSize;
|
||||
updateScreenSizing();
|
||||
drawSizeLayout.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
public partial class SkinSelectionHandler : SelectionHandler<ISerialisableDrawable>
|
||||
{
|
||||
private OsuMenuItem originMenu = null!;
|
||||
|
||||
[Resolved]
|
||||
private SkinEditor skinEditor { get; set; } = null!;
|
||||
|
||||
@ -137,7 +139,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
var drawableItem = (Drawable)b.Item;
|
||||
|
||||
// each drawable's relative position should be maintained in the scaled quad.
|
||||
var screenPosition = b.ScreenSpaceSelectionPoint;
|
||||
var screenPosition = drawableItem.ToScreenSpace(drawableItem.OriginPosition);
|
||||
|
||||
var relativePositionInOriginal =
|
||||
new Vector2(
|
||||
@ -202,17 +204,22 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
var item = c.Item;
|
||||
Drawable drawable = (Drawable)item;
|
||||
|
||||
if (!item.UsesFixedAnchor)
|
||||
ApplyClosestAnchorOrigin(drawable);
|
||||
|
||||
drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
||||
|
||||
if (item.UsesFixedAnchor) continue;
|
||||
|
||||
ApplyClosestAnchor(drawable);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ApplyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable));
|
||||
public static void ApplyClosestAnchorOrigin(Drawable drawable)
|
||||
{
|
||||
var closest = getClosestAnchor(drawable);
|
||||
|
||||
applyAnchor(drawable, closest);
|
||||
applyOrigin(drawable, closest);
|
||||
}
|
||||
|
||||
protected override void OnSelectionChanged()
|
||||
{
|
||||
@ -243,10 +250,15 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
yield return new OsuMenuItem("Origin")
|
||||
yield return originMenu = new OsuMenuItem("Origin");
|
||||
|
||||
closestItem.State.BindValueChanged(s =>
|
||||
{
|
||||
Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray()
|
||||
};
|
||||
// For UX simplicity, origin should only be user-editable when "closest" anchor mode is disabled.
|
||||
originMenu.Items = s.NewValue == TernaryState.True
|
||||
? Array.Empty<MenuItem>()
|
||||
: createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray();
|
||||
}, true);
|
||||
|
||||
yield return new OsuMenuItemSpacer();
|
||||
|
||||
@ -325,15 +337,10 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
var drawable = (Drawable)item;
|
||||
|
||||
if (origin == drawable.Origin) continue;
|
||||
applyOrigin(drawable, origin);
|
||||
|
||||
var previousOrigin = drawable.OriginPosition;
|
||||
drawable.Origin = origin;
|
||||
drawable.Position += drawable.OriginPosition - previousOrigin;
|
||||
|
||||
if (item.UsesFixedAnchor) continue;
|
||||
|
||||
ApplyClosestAnchor(drawable);
|
||||
if (!item.UsesFixedAnchor)
|
||||
ApplyClosestAnchorOrigin(drawable);
|
||||
}
|
||||
|
||||
OnOperationEnded();
|
||||
@ -368,7 +375,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
foreach (var item in SelectedItems)
|
||||
{
|
||||
item.UsesFixedAnchor = false;
|
||||
ApplyClosestAnchor((Drawable)item);
|
||||
ApplyClosestAnchorOrigin((Drawable)item);
|
||||
}
|
||||
|
||||
OnOperationEnded();
|
||||
@ -414,6 +421,15 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
drawable.Position -= drawable.AnchorPosition - previousAnchor;
|
||||
}
|
||||
|
||||
private static void applyOrigin(Drawable drawable, Anchor origin)
|
||||
{
|
||||
if (origin == drawable.Origin) return;
|
||||
|
||||
var previousOrigin = drawable.OriginPosition;
|
||||
drawable.Origin = origin;
|
||||
drawable.Position += drawable.OriginPosition - previousOrigin;
|
||||
}
|
||||
|
||||
private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference)
|
||||
{
|
||||
// cancel out scale in axes we don't care about (based on which drag handle was used).
|
||||
|
@ -330,6 +330,8 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
}
|
||||
|
||||
public List<BreakPeriod> Breaks => baseBeatmap.Breaks;
|
||||
public List<string> UnhandledEventLines => baseBeatmap.UnhandledEventLines;
|
||||
|
||||
public double TotalBreakTime => baseBeatmap.TotalBreakTime;
|
||||
public IEnumerable<BeatmapStatistic> GetStatistics() => baseBeatmap.GetStatistics();
|
||||
public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength();
|
||||
|
@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
public readonly Bindable<TernaryState> DistanceSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
private bool distanceSnapMomentary;
|
||||
private TernaryState? distanceSnapStateBeforeMomentaryToggle;
|
||||
|
||||
private EditorToolboxGroup? toolboxGroup;
|
||||
|
||||
@ -213,10 +214,19 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
bool altPressed = key.AltPressed;
|
||||
|
||||
if (altPressed != distanceSnapMomentary)
|
||||
if (altPressed && !distanceSnapMomentary)
|
||||
{
|
||||
distanceSnapMomentary = altPressed;
|
||||
distanceSnapStateBeforeMomentaryToggle = DistanceSnapToggle.Value;
|
||||
DistanceSnapToggle.Value = DistanceSnapToggle.Value == TernaryState.False ? TernaryState.True : TernaryState.False;
|
||||
distanceSnapMomentary = true;
|
||||
}
|
||||
|
||||
if (!altPressed && distanceSnapMomentary)
|
||||
{
|
||||
Debug.Assert(distanceSnapStateBeforeMomentaryToggle != null);
|
||||
DistanceSnapToggle.Value = distanceSnapStateBeforeMomentaryToggle.Value;
|
||||
distanceSnapStateBeforeMomentaryToggle = null;
|
||||
distanceSnapMomentary = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ using osu.Framework.Extensions.ListExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
@ -632,7 +631,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
#endregion
|
||||
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
|
||||
public override bool UpdateSubTreeMasking() => false;
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
|
@ -529,7 +529,7 @@ namespace osu.Game.Rulesets.UI
|
||||
public ResumeOverlay ResumeOverlay { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the <see cref="ResumeOverlay"/> should be used to return the user's cursor position to its previous location after a pause.
|
||||
/// Whether a <see cref="ResumeOverlay"/> should be displayed on resuming after a pause.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to <c>true</c>.
|
||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.UI
|
||||
break;
|
||||
|
||||
base.UpdateSubTree();
|
||||
UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat);
|
||||
UpdateSubTreeMasking();
|
||||
} while (state == PlaybackState.RequiresCatchUp && stopwatch.ElapsedMilliseconds < max_catchup_milliseconds);
|
||||
|
||||
return true;
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -38,6 +39,13 @@ namespace osu.Game.Scoring.Legacy
|
||||
[JsonProperty("client_version")]
|
||||
public string ClientVersion = string.Empty;
|
||||
|
||||
[JsonProperty("rank")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ScoreRank? Rank;
|
||||
|
||||
[JsonProperty("user_id")]
|
||||
public int UserID = -1;
|
||||
|
||||
public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo
|
||||
{
|
||||
OnlineID = score.OnlineID,
|
||||
@ -45,6 +53,8 @@ namespace osu.Game.Scoring.Legacy
|
||||
Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(),
|
||||
MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(),
|
||||
ClientVersion = score.ClientVersion,
|
||||
Rank = score.Rank,
|
||||
UserID = score.User.OnlineID,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ namespace osu.Game.Scoring.Legacy
|
||||
};
|
||||
|
||||
WorkingBeatmap workingBeatmap;
|
||||
ScoreRank? decodedRank = null;
|
||||
|
||||
using (SerializationReader sr = new SerializationReader(stream))
|
||||
{
|
||||
@ -129,6 +130,9 @@ namespace osu.Game.Scoring.Legacy
|
||||
score.ScoreInfo.MaximumStatistics = readScore.MaximumStatistics;
|
||||
score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray();
|
||||
score.ScoreInfo.ClientVersion = readScore.ClientVersion;
|
||||
decodedRank = readScore.Rank;
|
||||
if (readScore.UserID > 1)
|
||||
score.ScoreInfo.RealmUser.OnlineID = readScore.UserID;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -140,6 +144,9 @@ namespace osu.Game.Scoring.Legacy
|
||||
|
||||
StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, workingBeatmap);
|
||||
|
||||
if (decodedRank != null)
|
||||
score.ScoreInfo.Rank = decodedRank.Value;
|
||||
|
||||
// before returning for database import, we must restore the database-sourced BeatmapInfo.
|
||||
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
|
||||
score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo;
|
||||
|
@ -103,6 +103,14 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
|
||||
// Very naive local caching to improve performance of large score imports (where the username is usually the same for most or all scores).
|
||||
|
||||
// TODO: `UserLookupCache` cannot currently be used here because of async foibles.
|
||||
// It only supports lookups by user ID (username would require web changes), and even then the ID lookups cannot be used.
|
||||
// That is because that component provides an async interface, and async functions cannot be consumed safely here due to the rigid structure of `RealmArchiveModelImporter`.
|
||||
// The importer has two paths, one async and one sync; the async path runs the sync path in a task.
|
||||
// This means that sometimes `PostImport()` is called from a sync context, and sometimes from an async one, whilst itself being a sync method.
|
||||
// That in turn makes `.GetResultSafely()` not callable inside `PostImport()`, as it will throw when called from an async context,
|
||||
private readonly Dictionary<int, APIUser> idLookupCache = new Dictionary<int, APIUser>();
|
||||
private readonly Dictionary<string, APIUser> usernameLookupCache = new Dictionary<string, APIUser>();
|
||||
|
||||
protected override void PostImport(ScoreInfo model, Realm realm, ImportParameters parameters)
|
||||
@ -127,21 +135,34 @@ namespace osu.Game.Scoring
|
||||
if (model.RealmUser.OnlineID == APIUser.SYSTEM_USER_ID)
|
||||
return;
|
||||
|
||||
string username = model.RealmUser.Username;
|
||||
|
||||
if (usernameLookupCache.TryGetValue(username, out var existing))
|
||||
if (model.RealmUser.OnlineID > 1)
|
||||
{
|
||||
model.User = existing;
|
||||
model.User = lookupUserById(model.RealmUser.OnlineID) ?? model.User;
|
||||
return;
|
||||
}
|
||||
|
||||
var userRequest = new GetUserRequest(username);
|
||||
if (model.OnlineID < 0 && model.LegacyOnlineID <= 0)
|
||||
return;
|
||||
|
||||
model.User = lookupUserByName(model.RealmUser.Username) ?? model.User;
|
||||
}
|
||||
|
||||
private APIUser? lookupUserById(int id)
|
||||
{
|
||||
if (idLookupCache.TryGetValue(id, out var existing))
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
|
||||
var userRequest = new GetUserRequest(id);
|
||||
|
||||
api.Perform(userRequest);
|
||||
|
||||
if (userRequest.Response is APIUser user)
|
||||
{
|
||||
usernameLookupCache.TryAdd(username, new APIUser
|
||||
APIUser cachedUser;
|
||||
|
||||
idLookupCache.TryAdd(id, cachedUser = new APIUser
|
||||
{
|
||||
// Because this is a permanent cache, let's only store the pieces we're interested in,
|
||||
// rather than the full API response. If we start to store more than these three fields
|
||||
@ -151,8 +172,41 @@ namespace osu.Game.Scoring
|
||||
CountryCode = user.CountryCode,
|
||||
});
|
||||
|
||||
model.User = user;
|
||||
return cachedUser;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private APIUser? lookupUserByName(string username)
|
||||
{
|
||||
if (usernameLookupCache.TryGetValue(username, out var existing))
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
|
||||
var userRequest = new GetUserRequest(username);
|
||||
|
||||
api.Perform(userRequest);
|
||||
|
||||
if (userRequest.Response is APIUser user)
|
||||
{
|
||||
APIUser cachedUser;
|
||||
|
||||
usernameLookupCache.TryAdd(username, cachedUser = new APIUser
|
||||
{
|
||||
// Because this is a permanent cache, let's only store the pieces we're interested in,
|
||||
// rather than the full API response. If we start to store more than these three fields
|
||||
// in realm, this should be undone.
|
||||
Id = user.Id,
|
||||
Username = user.Username,
|
||||
CountryCode = user.CountryCode,
|
||||
});
|
||||
|
||||
return cachedUser;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ namespace osu.Game.Scoring
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankSH))]
|
||||
[Description(@"S+")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
SH,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankX))]
|
||||
@ -43,6 +44,7 @@ namespace osu.Game.Scoring
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankXH))]
|
||||
[Description(@"SS+")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
XH,
|
||||
}
|
||||
}
|
||||
|
@ -56,10 +56,6 @@ namespace osu.Game.Screens.Backgrounds
|
||||
introSequence = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence);
|
||||
|
||||
AddInternal(seasonalBackgroundLoader);
|
||||
|
||||
// Load first background asynchronously as part of BDL load.
|
||||
currentDisplay = RNG.Next(0, background_count);
|
||||
Next();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -73,6 +69,9 @@ namespace osu.Game.Screens.Backgrounds
|
||||
introSequence.ValueChanged += _ => Scheduler.AddOnce(next);
|
||||
seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(next);
|
||||
|
||||
currentDisplay = RNG.Next(0, background_count);
|
||||
Next();
|
||||
|
||||
// helper function required for AddOnce usage.
|
||||
void next() => Next();
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
@ -20,7 +19,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
public partial class TimelineTickDisplay : TimelinePart<PointVisualisation>
|
||||
{
|
||||
// With current implementation every tick in the sub-tree should be visible, no need to check whether they are masked away.
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
|
||||
public override bool UpdateSubTreeMasking() => false;
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap beatmap { get; set; } = null!;
|
||||
|
@ -174,6 +174,8 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
public List<BreakPeriod> Breaks => PlayableBeatmap.Breaks;
|
||||
|
||||
public List<string> UnhandledEventLines => PlayableBeatmap.UnhandledEventLines;
|
||||
|
||||
public double TotalBreakTime => PlayableBeatmap.TotalBreakTime;
|
||||
|
||||
public IReadOnlyList<HitObject> HitObjects => PlayableBeatmap.HitObjects;
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -111,7 +112,9 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
content.AddRange(loaded);
|
||||
|
||||
displayIndex = -1;
|
||||
// Many users don't spend much time at the main menu, so let's randomise where in the
|
||||
// carousel of available images we start at to give each a fair chance.
|
||||
displayIndex = RNG.Next(0, images.NewValue.Images.Length) - 1;
|
||||
showNext();
|
||||
}, (cancellationTokenSource ??= new CancellationTokenSource()).Token);
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
@ -36,18 +34,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
RoomStatus status = getDisplayStatus();
|
||||
RoomStatus status = Status.Value;
|
||||
|
||||
Pill.Background.FadeColour(status.GetAppropriateColour(colours), 100);
|
||||
TextFlow.Text = status.Message;
|
||||
}
|
||||
|
||||
private RoomStatus getDisplayStatus()
|
||||
{
|
||||
if (EndDate.Value < DateTimeOffset.Now)
|
||||
return new RoomStatusEnded();
|
||||
|
||||
return Status.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -486,16 +486,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
Off = 0,
|
||||
|
||||
[Description("30 seconds")]
|
||||
Seconds_30 = 30,
|
||||
Seconds30 = 30,
|
||||
|
||||
[Description("1 minute")]
|
||||
Seconds_60 = 60,
|
||||
Seconds60 = 60,
|
||||
|
||||
[Description("3 minutes")]
|
||||
Seconds_180 = 180,
|
||||
Seconds180 = 180,
|
||||
|
||||
[Description("5 minutes")]
|
||||
Seconds_300 = 300
|
||||
Seconds300 = 300
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
@ -24,8 +23,9 @@ namespace osu.Game.Screens.Play
|
||||
/// </summary>
|
||||
public partial class DelayedResumeOverlay : ResumeOverlay
|
||||
{
|
||||
// todo: this shouldn't define its own colour provider, but nothing in Player screen does, so let's do that for now.
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
// todo: this shouldn't define its own colour provider, but nothing in DrawableRuleset guarantees this, so let's do it locally for now.
|
||||
// (of note, Player does cache one but any test which uses a DrawableRuleset without Player will fail without this).
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private const float outer_size = 200;
|
||||
private const float inner_size = 150;
|
||||
@ -34,9 +34,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private const double countdown_time = 2000;
|
||||
|
||||
private const int total_count = 3;
|
||||
|
||||
protected override LocalisableString Message => string.Empty;
|
||||
|
||||
private ScheduledDelegate? scheduledResume;
|
||||
private int? countdownCount;
|
||||
private double countdownStartTime;
|
||||
private bool countdownComplete;
|
||||
@ -120,21 +121,17 @@ namespace osu.Game.Screens.Play
|
||||
innerContent.FadeIn().ScaleTo(Vector2.Zero).Then().ScaleTo(Vector2.One, 400, Easing.OutElasticHalf);
|
||||
countdownComponents.FadeOut().Delay(50).FadeTo(1, 100);
|
||||
|
||||
countdownProgress.Progress = 0;
|
||||
|
||||
// Reset states for various components.
|
||||
countdownBackground.FadeIn();
|
||||
countdownText.FadeIn();
|
||||
countdownText.Text = string.Empty;
|
||||
countdownProgress.FadeIn().ScaleTo(1);
|
||||
|
||||
countdownComplete = false;
|
||||
countdownCount = null;
|
||||
countdownStartTime = Time.Current;
|
||||
|
||||
scheduledResume?.Cancel();
|
||||
scheduledResume = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
countdownComplete = true;
|
||||
Resume();
|
||||
}, countdown_time);
|
||||
countdownStartTime = Time.Current + 200;
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
@ -152,8 +149,6 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
else
|
||||
countdownProgress.FadeOut();
|
||||
|
||||
scheduledResume?.Cancel();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -164,12 +159,17 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private void updateCountdown()
|
||||
{
|
||||
double amountTimePassed = Math.Min(countdown_time, Time.Current - countdownStartTime) / countdown_time;
|
||||
int newCount = 3 - (int)Math.Floor(amountTimePassed * 3);
|
||||
if (State.Value == Visibility.Hidden || countdownComplete || Time.Current < countdownStartTime)
|
||||
return;
|
||||
|
||||
double amountTimePassed = Math.Clamp((Time.Current - countdownStartTime) / countdown_time, 0, countdown_time);
|
||||
int newCount = Math.Clamp(total_count - (int)Math.Floor(amountTimePassed * total_count), 0, total_count);
|
||||
|
||||
countdownProgress.Progress = amountTimePassed;
|
||||
countdownProgress.InnerRadius = progress_stroke_width / progress_size / countdownProgress.Scale.X;
|
||||
|
||||
Alpha = 0.2f + 0.8f * newCount / total_count;
|
||||
|
||||
if (countdownCount != newCount)
|
||||
{
|
||||
if (newCount > 0)
|
||||
@ -191,6 +191,12 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
|
||||
countdownCount = newCount;
|
||||
|
||||
if (countdownCount == 0)
|
||||
{
|
||||
countdownComplete = true;
|
||||
Resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
};
|
||||
|
||||
hideTargets = new List<Drawable> { mainComponents, playfieldComponents, topRightElements };
|
||||
hideTargets = new List<Drawable> { mainComponents, topRightElements };
|
||||
|
||||
if (rulesetComponents != null)
|
||||
hideTargets.Add(rulesetComponents);
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -138,7 +139,7 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
automaticDownload = new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Automatically download beatmaps",
|
||||
LabelText = OnlineSettingsStrings.AutomaticallyDownloadMissingBeatmaps,
|
||||
Current = config.GetBindable<bool>(OsuSetting.AutomaticallyDownloadMissingBeatmaps),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -125,6 +125,8 @@ namespace osu.Game.Skinning.Components
|
||||
protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40);
|
||||
}
|
||||
|
||||
// WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END.
|
||||
// Doing so will break existing user skins.
|
||||
public enum BeatmapAttribute
|
||||
{
|
||||
CircleSize,
|
||||
@ -134,11 +136,11 @@ namespace osu.Game.Skinning.Components
|
||||
StarRating,
|
||||
Title,
|
||||
Artist,
|
||||
Source,
|
||||
DifficultyName,
|
||||
Creator,
|
||||
Length,
|
||||
RankedStatus,
|
||||
BPM,
|
||||
Source,
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,10 @@ namespace osu.Game.Skinning
|
||||
LeftStageImage,
|
||||
RightStageImage,
|
||||
BottomStageImage,
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
Hit300g,
|
||||
|
||||
Hit300,
|
||||
Hit200,
|
||||
Hit100,
|
||||
|
@ -1,52 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class CommandLoop : CommandTimelineGroup
|
||||
{
|
||||
public double LoopStartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of times this loop is played back. Always greater than zero.
|
||||
/// </summary>
|
||||
public readonly int TotalIterations;
|
||||
|
||||
public override double StartTime => LoopStartTime + CommandsStartTime;
|
||||
|
||||
public override double EndTime =>
|
||||
// In an ideal world, we would multiply the command duration by TotalIterations here.
|
||||
// Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro
|
||||
// sequences for minutes or hours.
|
||||
StartTime + CommandsDuration;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new command loop.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time of the loop.</param>
|
||||
/// <param name="repeatCount">The number of times the loop should repeat. Should be greater than zero. Zero means a single playback.</param>
|
||||
public CommandLoop(double startTime, int repeatCount)
|
||||
{
|
||||
if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount));
|
||||
|
||||
LoopStartTime = startTime;
|
||||
TotalIterations = repeatCount + 1;
|
||||
}
|
||||
|
||||
public override IEnumerable<CommandTimeline<T>.TypedCommand> GetCommands<T>(CommandTimelineSelector<T> timelineSelector, double offset = 0)
|
||||
{
|
||||
for (int loop = 0; loop < TotalIterations; loop++)
|
||||
{
|
||||
double loopOffset = LoopStartTime + loop * CommandsDuration;
|
||||
foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset))
|
||||
yield return command;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{LoopStartTime} x{TotalIterations}";
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class CommandTimeline<T> : ICommandTimeline
|
||||
{
|
||||
private readonly List<TypedCommand> commands = new List<TypedCommand>();
|
||||
|
||||
public IEnumerable<TypedCommand> Commands => commands.OrderBy(c => c.StartTime);
|
||||
|
||||
public bool HasCommands => commands.Count > 0;
|
||||
|
||||
public double StartTime { get; private set; } = double.MaxValue;
|
||||
public double EndTime { get; private set; } = double.MinValue;
|
||||
|
||||
public T StartValue { get; private set; }
|
||||
public T EndValue { get; private set; }
|
||||
|
||||
public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue)
|
||||
{
|
||||
if (endTime < startTime)
|
||||
{
|
||||
endTime = startTime;
|
||||
}
|
||||
|
||||
commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue });
|
||||
|
||||
if (startTime < StartTime)
|
||||
{
|
||||
StartValue = startValue;
|
||||
StartTime = startTime;
|
||||
}
|
||||
|
||||
if (endTime > EndTime)
|
||||
{
|
||||
EndValue = endValue;
|
||||
EndTime = endTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{commands.Count} command(s)";
|
||||
|
||||
public class TypedCommand : ICommand
|
||||
{
|
||||
public Easing Easing { get; set; }
|
||||
public double StartTime { get; set; }
|
||||
public double EndTime { get; set; }
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
public T StartValue;
|
||||
public T EndValue;
|
||||
|
||||
public int CompareTo(ICommand other)
|
||||
{
|
||||
int result = StartTime.CompareTo(other.StartTime);
|
||||
if (result != 0) return result;
|
||||
|
||||
return EndTime.CompareTo(other.EndTime);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}";
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICommandTimeline
|
||||
{
|
||||
double StartTime { get; }
|
||||
double EndTime { get; }
|
||||
bool HasCommands { get; }
|
||||
}
|
||||
|
||||
public interface ICommand : IComparable<ICommand>
|
||||
{
|
||||
Easing Easing { get; set; }
|
||||
double StartTime { get; set; }
|
||||
double EndTime { get; set; }
|
||||
double Duration { get; }
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public delegate CommandTimeline<T> CommandTimelineSelector<T>(CommandTimelineGroup commandTimelineGroup);
|
||||
|
||||
public class CommandTimelineGroup
|
||||
{
|
||||
public CommandTimeline<float> X = new CommandTimeline<float>();
|
||||
public CommandTimeline<float> Y = new CommandTimeline<float>();
|
||||
public CommandTimeline<float> Scale = new CommandTimeline<float>();
|
||||
public CommandTimeline<Vector2> VectorScale = new CommandTimeline<Vector2>();
|
||||
public CommandTimeline<float> Rotation = new CommandTimeline<float>();
|
||||
public CommandTimeline<Color4> Colour = new CommandTimeline<Color4>();
|
||||
public CommandTimeline<float> Alpha = new CommandTimeline<float>();
|
||||
public CommandTimeline<BlendingParameters> BlendingParameters = new CommandTimeline<BlendingParameters>();
|
||||
public CommandTimeline<bool> FlipH = new CommandTimeline<bool>();
|
||||
public CommandTimeline<bool> FlipV = new CommandTimeline<bool>();
|
||||
|
||||
private readonly ICommandTimeline[] timelines;
|
||||
|
||||
public CommandTimelineGroup()
|
||||
{
|
||||
timelines = new ICommandTimeline[]
|
||||
{
|
||||
X,
|
||||
Y,
|
||||
Scale,
|
||||
VectorScale,
|
||||
Rotation,
|
||||
Colour,
|
||||
Alpha,
|
||||
BlendingParameters,
|
||||
FlipH,
|
||||
FlipV
|
||||
};
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsStartTime
|
||||
{
|
||||
get
|
||||
{
|
||||
double min = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
min = Math.Min(min, timelines[i].StartTime);
|
||||
|
||||
return min;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsEndTime
|
||||
{
|
||||
get
|
||||
{
|
||||
double max = double.MinValue;
|
||||
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
max = Math.Max(max, timelines[i].EndTime);
|
||||
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsDuration => CommandsEndTime - CommandsStartTime;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual double StartTime => CommandsStartTime;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual double EndTime => CommandsEndTime;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasCommands
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
{
|
||||
if (timelines[i].HasCommands)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<CommandTimeline<T>.TypedCommand> GetCommands<T>(CommandTimelineSelector<T> timelineSelector, double offset = 0)
|
||||
{
|
||||
if (offset != 0)
|
||||
{
|
||||
return timelineSelector(this).Commands.Select(command =>
|
||||
new CommandTimeline<T>.TypedCommand
|
||||
{
|
||||
Easing = command.Easing,
|
||||
StartTime = offset + command.StartTime,
|
||||
EndTime = offset + command.EndTime,
|
||||
StartValue = command.StartValue,
|
||||
EndValue = command.EndValue,
|
||||
});
|
||||
}
|
||||
|
||||
return timelineSelector(this).Commands;
|
||||
}
|
||||
}
|
||||
}
|
46
osu.Game/Storyboards/Commands/IStoryboardCommand.cs
Normal file
46
osu.Game/Storyboards/Commands/IStoryboardCommand.cs
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public interface IStoryboardCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// The start time of the storyboard command.
|
||||
/// </summary>
|
||||
double StartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The end time of the storyboard command.
|
||||
/// </summary>
|
||||
double EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="Drawable"/> property affected by this storyboard command.
|
||||
/// Used to apply initial property values based on the list of commands given in <see cref="StoryboardSprite"/>.
|
||||
/// </summary>
|
||||
string PropertyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the corresponding property in <see cref="Drawable"/> to the start value of this command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Parameter commands (e.g. <see cref="StoryboardFlipHCommand"/> / <see cref="StoryboardFlipVCommand"/> / <see cref="StoryboardBlendingParametersCommand"/>) only apply the start value if they have zero duration, i.e. take "permanent" effect regardless of time.
|
||||
/// </remarks>
|
||||
/// <param name="d">The target drawable.</param>
|
||||
void ApplyInitialValue<TDrawable>(TDrawable d)
|
||||
where TDrawable : Drawable, IFlippable, IVectorScalable;
|
||||
|
||||
/// <summary>
|
||||
/// Applies the transforms described by this storyboard command to the target drawable.
|
||||
/// </summary>
|
||||
/// <param name="d">The target drawable.</param>
|
||||
/// <returns>The sequence of transforms applied to the target drawable.</returns>
|
||||
TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
where TDrawable : Drawable, IFlippable, IVectorScalable;
|
||||
}
|
||||
}
|
23
osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs
Normal file
23
osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardAlphaCommand : StoryboardCommand<float>
|
||||
{
|
||||
public StoryboardAlphaCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(Drawable.Alpha);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => d.Alpha = StartValue;
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.FadeTo(StartValue).Then().FadeTo(EndValue, Duration, Easing);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user