1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-06 10:22:54 +08:00

Merge branch 'master' into tournament-display-team-seed

This commit is contained in:
Dean Herbert 2023-10-31 12:45:21 +09:00 committed by GitHub
commit bb6555c901
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 748 additions and 120 deletions

View File

@ -7,7 +7,7 @@ Templates for use when creating osu! dependent projects. Create a fully-testable
```bash ```bash
# install (or update) templates package. # install (or update) templates package.
# this only needs to be done once # this only needs to be done once
dotnet new -i ppy.osu.Game.Templates dotnet new install ppy.osu.Game.Templates
# create an empty freeform ruleset # create an empty freeform ruleset
dotnet new ruleset -n MyCoolRuleset dotnet new ruleset -n MyCoolRuleset

View File

@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1012.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2023.1030.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged. <!-- Fody does not handle Android build well, and warns when unchanged.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -4,6 +4,7 @@ Version: 2.5
[Mania] [Mania]
Keys: 4 Keys: 4
ColumnLineWidth: 3,1,3,1,1 ColumnLineWidth: 3,1,3,1,1
LightFramePerSecond: 15
// some skins found in the wild had configuration keys where the @2x suffix was included in the values. // some skins found in the wild had configuration keys where the @2x suffix was included in the values.
// the expected compatibility behaviour is that the presence of the @2x suffix shouldn't change anything // the expected compatibility behaviour is that the presence of the @2x suffix shouldn't change anything
// if @2x assets are present. // if @2x assets are present.
@ -15,5 +16,6 @@ Hit300: mania/hit300@2x
Hit300g: mania/hit300g@2x Hit300g: mania/hit300g@2x
StageLeft: mania/stage-left StageLeft: mania/stage-left
StageRight: mania/stage-right StageRight: mania/stage-right
StageLight: mania/stage-light
NoteImage0L: LongNoteTailWang NoteImage0L: LongNoteTailWang
NoteImage1L: LongNoteTailWang NoteImage1L: LongNoteTailWang

View File

@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
direction.BindTo(scrollingInfo.Direction); direction.BindTo(scrollingInfo.Direction);
isHitting.BindTo(holdNote.IsHitting); isHitting.BindTo(holdNote.IsHitting);
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true).With(d => bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30).With(d =>
{ {
if (d == null) if (d == null)
return; return;

View File

@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container lightContainer = null!; private Container lightContainer = null!;
private Sprite light = null!; private Drawable light = null!;
public LegacyColumnBackground() public LegacyColumnBackground()
{ {
@ -39,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
Color4 lightColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value Color4 lightColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
?? Color4.White; ?? Color4.White;
int lightFramePerSecond = skin.GetManiaSkinConfig<int>(LegacyManiaSkinConfigurationLookups.LightFramePerSecond)?.Value ?? 60;
InternalChildren = new[] InternalChildren = new[]
{ {
lightContainer = new Container lightContainer = new Container
@ -46,16 +47,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Bottom = lightPosition }, Padding = new MarginPadding { Bottom = lightPosition },
Child = light = new Sprite Child = light = skin.GetAnimation(lightImage, true, true, frameLength: 1000d / lightFramePerSecond)?.With(l =>
{ {
Anchor = Anchor.BottomCentre, l.Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre, l.Origin = Anchor.BottomCentre;
Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour), l.Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour);
Texture = skin.GetTexture(lightImage), l.RelativeSizeAxes = Axes.X;
RelativeSizeAxes = Axes.X, l.Width = 1;
Width = 1, l.Alpha = 0;
Alpha = 0 }) ?? Empty(),
}
} }
}; };

View File

@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
string filename = this.GetManiaSkinConfig<string>(hit_result_mapping[result])?.Value string filename = this.GetManiaSkinConfig<string>(hit_result_mapping[result])?.Value
?? default_hit_result_skin_filenames[result]; ?? default_hit_result_skin_filenames[result];
var animation = this.GetAnimation(filename, true, true); var animation = this.GetAnimation(filename, true, true, frameLength: 1000 / 20d);
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation); return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
} }

View File

@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Diagnostics;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Input; using osu.Framework.Input;
@ -24,15 +21,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
public partial class TestSceneSliderVelocityAdjust : OsuGameTestScene public partial class TestSceneSliderVelocityAdjust : OsuGameTestScene
{ {
private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor; private Screens.Edit.Editor? editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor;
private EditorBeatmap editorBeatmap => editor.ChildrenOfType<EditorBeatmap>().FirstOrDefault(); private EditorBeatmap editorBeatmap => editor.ChildrenOfType<EditorBeatmap>().FirstOrDefault()!;
private EditorClock editorClock => editor.ChildrenOfType<EditorClock>().FirstOrDefault(); private EditorClock editorClock => editor.ChildrenOfType<EditorClock>().FirstOrDefault()!;
private Slider slider => editorBeatmap.HitObjects.OfType<Slider>().FirstOrDefault(); private Slider? slider => editorBeatmap.HitObjects.OfType<Slider>().FirstOrDefault();
private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType<TimelineHitObjectBlueprint>().FirstOrDefault(); private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType<TimelineHitObjectBlueprint>().FirstOrDefault()!;
private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType<DifficultyPointPiece>().First(); private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType<DifficultyPointPiece>().First();
@ -46,6 +43,55 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
double? velocity = null; double? velocity = null;
AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
AddUntilStep("wait for editor load", () => editor?.ReadyForUse, () => Is.True);
AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time));
AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType<Playfield>().First().ScreenSpaceDrawQuad.Centre));
AddStep("start placement", () => InputManager.Click(MouseButton.Left));
AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType<Playfield>().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
AddStep("end placement", () => InputManager.Click(MouseButton.Right));
AddStep("exit placement mode", () => InputManager.Key(Key.Number1));
AddAssert("slider placed", () => slider, () => Is.Not.Null);
AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider));
AddAssert("ensure one slider placed", () => slider, () => Is.Not.Null);
AddStep("store velocity", () => velocity = slider!.Velocity);
if (adjustVelocity)
{
AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick());
AddStep("change velocity", () => velocityTextBox.Current.Value = 2);
AddAssert("velocity adjusted", () => slider!.Velocity,
() => Is.EqualTo(velocity!.Value * 2).Within(Precision.DOUBLE_EPSILON));
AddStep("store velocity", () => velocity = slider!.Velocity);
}
AddStep("save", () => InputManager.Keys(PlatformAction.Save));
AddStep("exit", () => InputManager.Key(Key.Escape));
AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader()));
AddUntilStep("wait for editor load", () => editor?.ReadyForUse, () => Is.True);
AddStep("seek to slider", () => editorClock.Seek(slider!.StartTime));
AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocity));
}
[Test]
public void TestVelocityUndo()
{
double? velocityBefore = null;
double? durationBefore = null;
AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true);
@ -60,36 +106,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("exit placement mode", () => InputManager.Key(Key.Number1)); AddStep("exit placement mode", () => InputManager.Key(Key.Number1));
AddAssert("slider placed", () => slider != null); AddAssert("slider placed", () => slider, () => Is.Not.Null);
AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider)); AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider));
AddAssert("ensure one slider placed", () => slider != null); AddStep("store velocity", () =>
AddStep("store velocity", () => velocity = slider.Velocity);
if (adjustVelocity)
{ {
AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick()); velocityBefore = slider!.Velocity;
AddStep("change velocity", () => velocityTextBox.Current.Value = 2); durationBefore = slider.Duration;
});
AddAssert("velocity adjusted", () => AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick());
{ AddStep("change velocity", () => velocityTextBox.Current.Value = 2);
Debug.Assert(velocity != null);
return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity);
});
AddStep("store velocity", () => velocity = slider.Velocity); AddAssert("velocity adjusted", () => slider!.Velocity, () => Is.EqualTo(velocityBefore!.Value * 2).Within(Precision.DOUBLE_EPSILON));
}
AddStep("save", () => InputManager.Keys(PlatformAction.Save)); AddStep("undo", () =>
AddStep("exit", () => InputManager.Key(Key.Escape)); {
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Z);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocityBefore));
AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); AddAssert("slider has correct duration", () => slider!.Duration, () => Is.EqualTo(durationBefore));
AddStep("seek to slider", () => editorClock.Seek(slider.StartTime));
AddAssert("slider has correct velocity", () => slider.Velocity == velocity);
} }
} }
} }

View File

@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Child = piece = new TestLegacyMainCirclePiece(priorityLookup), Child = piece = new TestLegacyMainCirclePiece(priorityLookup),
}; };
var sprites = this.ChildrenOfType<Sprite>().Where(s => !string.IsNullOrEmpty(s.Texture.AssetName)).DistinctBy(s => s.Texture.AssetName).ToArray(); var sprites = this.ChildrenOfType<Sprite>().Where(s => !string.IsNullOrEmpty(s.Texture?.AssetName)).DistinctBy(s => s.Texture.AssetName).ToArray();
Debug.Assert(sprites.Length <= 2); Debug.Assert(sprites.Length <= 2);
}); });
@ -103,8 +103,8 @@ namespace osu.Game.Rulesets.Osu.Tests
private partial class TestLegacyMainCirclePiece : LegacyMainCirclePiece private partial class TestLegacyMainCirclePiece : LegacyMainCirclePiece
{ {
public new Sprite? CircleSprite => base.CircleSprite.ChildrenOfType<Sprite>().DistinctBy(s => s.Texture.AssetName).SingleOrDefault(); public new Sprite? CircleSprite => base.CircleSprite.ChildrenOfType<Sprite>().Where(s => !string.IsNullOrEmpty(s.Texture?.AssetName)).DistinctBy(s => s.Texture.AssetName).SingleOrDefault();
public new Sprite? OverlaySprite => base.OverlaySprite.ChildrenOfType<Sprite>().DistinctBy(s => s.Texture.AssetName).SingleOrDefault(); public new Sprite? OverlaySprite => base.OverlaySprite.ChildrenOfType<Sprite>().Where(s => !string.IsNullOrEmpty(s.Texture?.AssetName)).DistinctBy(s => s.Texture.AssetName).SingleOrDefault();
public TestLegacyMainCirclePiece(string? priorityLookupPrefix) public TestLegacyMainCirclePiece(string? priorityLookupPrefix)
: base(priorityLookupPrefix, false) : base(priorityLookupPrefix, false)

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -10,6 +11,8 @@ using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Replays;
@ -47,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public void Setup() => Schedule(() => public void Setup() => Schedule(() =>
{ {
manualClock = null; manualClock = null;
SelectedMods.Value = Array.Empty<Mod>();
}); });
/// <summary> /// <summary>
@ -102,6 +106,33 @@ namespace osu.Game.Rulesets.Osu.Tests
assertSpinnerHit(false); assertSpinnerHit(false);
} }
[Test]
public void TestVibrateWithoutSpinningOnCentreWithDoubleTime()
{
List<ReplayFrame> frames = new List<ReplayFrame>();
const int rate = 2;
// the track clock is going to be playing twice as fast,
// so the vibration time in clock time needs to be twice as long
// to keep constant speed in real time.
const int vibrate_time = 50 * rate;
int direction = -1;
for (double i = time_spinner_start; i <= time_spinner_end; i += vibrate_time)
{
frames.Add(new OsuReplayFrame(i, new Vector2(centre_x + direction * 50, centre_y), OsuAction.LeftButton));
frames.Add(new OsuReplayFrame(i + vibrate_time, new Vector2(centre_x - direction * 50, centre_y), OsuAction.LeftButton));
direction *= -1;
}
AddStep("set DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = rate } } });
performTest(frames);
assertSpinnerHit(false);
}
/// <summary> /// <summary>
/// Spins in a single direction. /// Spins in a single direction.
/// </summary> /// </summary>

View File

@ -49,13 +49,9 @@ namespace osu.Game.Rulesets.Osu.Objects
set set
{ {
path.ControlPoints.Clear(); path.ControlPoints.Clear();
path.ExpectedDistance.Value = null; path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
if (value != null) path.ExpectedDistance.Value = value.ExpectedDistance.Value;
{
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
}
} }
} }

View File

@ -101,11 +101,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
rotationTransferred = true; rotationTransferred = true;
} }
Debug.Assert(Math.Abs(delta) <= 180);
double rate = gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate; double rate = gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate;
delta = (float)(delta * Math.Abs(rate)); delta = (float)(delta * Math.Abs(rate));
Debug.Assert(Math.Abs(delta) <= 180);
currentRotation += delta; currentRotation += delta;
drawableSpinner.Result.History.ReportDelta(Time.Current, delta); drawableSpinner.Result.History.ReportDelta(Time.Current, delta);
} }

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy namespace osu.Game.Rulesets.Osu.Skinning.Legacy
@ -62,12 +63,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// otherwise fall back to the default prefix "hitcircle". // otherwise fall back to the default prefix "hitcircle".
string circleName = (priorityLookupPrefix != null && skin.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : @"hitcircle"; string circleName = (priorityLookupPrefix != null && skin.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : @"hitcircle";
Vector2 maxSize = OsuHitObject.OBJECT_DIMENSIONS * 2;
// at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it. // at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it.
// the conditional above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist. // the conditional above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist.
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png. // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png.
InternalChildren = new[] InternalChildren = new[]
{ {
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2) }) CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(maxSize) })
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -76,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d, maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2)) Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) })
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
@ -41,11 +42,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null); var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true, maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2) ?? Empty()).With(d => InternalChild = arrow = new Sprite
{ {
d.Anchor = Anchor.Centre; Anchor = Anchor.Centre,
d.Origin = Anchor.Centre; Origin = Anchor.Centre,
}); Texture = skin?.GetTexture(lookupName)?.WithMaximumSize(maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2),
};
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin; textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;

View File

@ -48,40 +48,45 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ISkinSource skin, DrawableHitObject drawableHitObject, IBeatSyncProvider? beatSyncProvider) private void load(ISkinSource skin, DrawableHitObject drawableHitObject, IBeatSyncProvider? beatSyncProvider)
{ {
Drawable? getDrawableFor(string lookup) Drawable? getDrawableFor(string lookup, bool animatable)
{ {
const string normal_hit = "taikohit"; const string normal_hit = "taikohit";
const string big_hit = "taikobig"; const string big_hit = "taikobig";
string prefix = ((drawableHitObject.HitObject as TaikoStrongableHitObject)?.IsStrong ?? false) ? big_hit : normal_hit; string prefix = ((drawableHitObject.HitObject as TaikoStrongableHitObject)?.IsStrong ?? false) ? big_hit : normal_hit;
return skin.GetAnimation($"{prefix}{lookup}", true, false, maxSize: max_circle_sprite_size) ?? return skin.GetAnimation($"{prefix}{lookup}", animatable, false, maxSize: max_circle_sprite_size) ??
// fallback to regular size if "big" version doesn't exist. // fallback to regular size if "big" version doesn't exist.
skin.GetAnimation($"{normal_hit}{lookup}", true, false, maxSize: max_circle_sprite_size); skin.GetAnimation($"{normal_hit}{lookup}", animatable, false, maxSize: max_circle_sprite_size);
} }
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer. // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle"))); AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle", false))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
foregroundLayer = getDrawableFor("circleoverlay", true);
foregroundLayer = getDrawableFor("circleoverlay");
if (foregroundLayer != null) if (foregroundLayer != null)
{
foregroundLayer.Anchor = Anchor.Centre;
foregroundLayer.Origin = Anchor.Centre;
// Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat).
// For now just stop at first frame for sanity.
if (foregroundLayer is IFramedAnimation animatedForegroundLayer)
animatedForegroundLayer.Stop();
AddInternal(foregroundLayer); AddInternal(foregroundLayer);
}
drawableHitObject.StartTimeBindable.BindValueChanged(startTime => drawableHitObject.StartTimeBindable.BindValueChanged(startTime =>
{ {
timingPoint = beatSyncProvider?.ControlPoints?.TimingPointAt(startTime.NewValue) ?? TimingControlPoint.DEFAULT; timingPoint = beatSyncProvider?.ControlPoints?.TimingPointAt(startTime.NewValue) ?? TimingControlPoint.DEFAULT;
}, true); }, true);
// Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat).
// For now just stop at first frame for sanity.
foreach (var c in InternalChildren)
{
(c as IFramedAnimation)?.Stop();
c.Anchor = Anchor.Centre;
c.Origin = Anchor.Centre;
}
if (gameplayState != null) if (gameplayState != null)
currentCombo.BindTo(gameplayState.ScoreProcessor.Combo); currentCombo.BindTo(gameplayState.ScoreProcessor.Combo);
} }
@ -101,11 +106,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
foreach (var c in InternalChildren) foreach (var c in InternalChildren)
c.Scale = new Vector2(DrawHeight / circle_piece_size.Y); c.Scale = new Vector2(DrawHeight / circle_piece_size.Y);
if (foregroundLayer is IFramedAnimation animatableForegroundLayer) if (foregroundLayer is IFramedAnimation animatedForegroundLayer)
animateForegroundLayer(animatableForegroundLayer); animateForegroundLayer(animatedForegroundLayer);
} }
private void animateForegroundLayer(IFramedAnimation animatableForegroundLayer) private void animateForegroundLayer(IFramedAnimation animation)
{ {
int multiplier; int multiplier;
@ -119,12 +124,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
} }
else else
{ {
animatableForegroundLayer.GotoFrame(0); animation.GotoFrame(0);
return; return;
} }
animationFrame = Math.Abs(Time.Current - timingPoint.Time) % ((timingPoint.BeatLength * 2) / multiplier) >= timingPoint.BeatLength / multiplier ? 0 : 1; animationFrame = Math.Abs(Time.Current - timingPoint.Time) % ((timingPoint.BeatLength * 2) / multiplier) >= timingPoint.BeatLength / multiplier ? 0 : 1;
animatableForegroundLayer.GotoFrame(animationFrame); animation.GotoFrame(animationFrame);
} }
private Color4 accentColour; private Color4 accentColour;

View File

@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests namespace osu.Game.Tests
@ -46,12 +47,15 @@ namespace osu.Game.Tests
public partial class TestOsuGameBase : OsuGameBase public partial class TestOsuGameBase : OsuGameBase
{ {
public RealmAccess Realm => Dependencies.Get<RealmAccess>(); public RealmAccess Realm => Dependencies.Get<RealmAccess>();
public new IAPIProvider API => base.API;
private readonly bool withBeatmap; private readonly bool withBeatmap;
public TestOsuGameBase(bool withBeatmap) public TestOsuGameBase(bool withBeatmap)
{ {
this.withBeatmap = withBeatmap; this.withBeatmap = withBeatmap;
base.API = new DummyAPIAccess();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -11,7 +11,10 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -67,6 +70,116 @@ namespace osu.Game.Tests.Scores.IO
} }
} }
[TestCase(false)]
[TestCase(true)]
public void TestLastPlayedUpdate(bool isLocalUser)
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
var osu = LoadOsuIntoHost(host, true);
if (!isLocalUser)
osu.API.Logout();
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
var beatmapInfo = beatmap.Beatmaps.First();
DateTimeOffset replayDate = DateTimeOffset.Now;
var toImport = new ScoreInfo
{
Rank = ScoreRank.B,
TotalScore = 987654,
Accuracy = 0.8,
MaxCombo = 500,
Combo = 250,
User = new APIUser
{
Username = "Test user",
Id = DummyAPIAccess.DUMMY_USER_ID,
},
Date = replayDate,
OnlineID = 12345,
Ruleset = new OsuRuleset().RulesetInfo,
BeatmapInfo = beatmapInfo
};
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);
if (isLocalUser)
Assert.That(imported.BeatmapInfo!.LastPlayed, Is.EqualTo(replayDate));
else
Assert.That(imported.BeatmapInfo!.LastPlayed, Is.Null);
}
finally
{
host.Exit();
}
}
}
[Test]
public void TestLastPlayedNotUpdatedDueToNewerPlays()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
var osu = LoadOsuIntoHost(host, true);
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
var beatmapInfo = beatmap.Beatmaps.First();
var realmAccess = osu.Dependencies.Get<RealmAccess>();
realmAccess.Write(r => r.Find<BeatmapInfo>(beatmapInfo.ID)!.LastPlayed = new DateTimeOffset(2023, 10, 30, 0, 0, 0, TimeSpan.Zero));
var toImport = new ScoreInfo
{
Rank = ScoreRank.B,
TotalScore = 987654,
Accuracy = 0.8,
MaxCombo = 500,
Combo = 250,
User = new APIUser
{
Username = "Test user",
Id = DummyAPIAccess.DUMMY_USER_ID,
},
Date = new DateTimeOffset(2023, 10, 27, 0, 0, 0, TimeSpan.Zero),
OnlineID = 12345,
Ruleset = new OsuRuleset().RulesetInfo,
BeatmapInfo = beatmapInfo
};
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.That(imported.BeatmapInfo!.LastPlayed, Is.EqualTo(new DateTimeOffset(2023, 10, 30, 0, 0, 0, TimeSpan.Zero)));
}
finally
{
host.Exit();
}
}
}
[Test] [Test]
public void TestImportMods() public void TestImportMods()
{ {

View File

@ -7,8 +7,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -44,6 +46,47 @@ namespace osu.Game.Tests.Visual.Editing
}); });
} }
[Test]
public void TestContextMenuWithObjectBehind()
{
TimelineHitObjectBlueprint blueprint;
AddStep("add object", () =>
{
EditorBeatmap.Add(new HitCircle { StartTime = 3000 });
});
AddStep("enter slider placement", () =>
{
InputManager.Key(Key.Number3);
InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre);
});
AddStep("start conflicting slider", () =>
{
InputManager.Click(MouseButton.Left);
blueprint = this.ChildrenOfType<TimelineHitObjectBlueprint>().First();
InputManager.MoveMouseTo(blueprint.ScreenSpaceDrawQuad.TopLeft - new Vector2(10, 0));
});
AddStep("end conflicting slider", () =>
{
InputManager.Click(MouseButton.Right);
});
AddStep("click object", () =>
{
InputManager.Key(Key.Number1);
blueprint = this.ChildrenOfType<TimelineHitObjectBlueprint>().First();
InputManager.MoveMouseTo(blueprint);
InputManager.Click(MouseButton.Left);
});
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddAssert("context menu open", () => this.ChildrenOfType<OsuContextMenu>().SingleOrDefault()?.State == MenuState.Open);
}
[Test] [Test]
public void TestNudgeSelection() public void TestNudgeSelection()
{ {
@ -139,7 +182,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("click away", () => AddStep("click away", () =>
{ {
InputManager.MoveMouseTo(Editor.ChildrenOfType<TimelineArea>().Single().ScreenSpaceDrawQuad.TopLeft + Vector2.One); InputManager.MoveMouseTo(Editor.ChildrenOfType<Timeline>().First().ScreenSpaceDrawQuad.TopLeft + new Vector2(5));
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });

View File

@ -5,8 +5,10 @@ using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile;
@ -28,7 +30,14 @@ namespace osu.Game.Tests.Visual.Online
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
{ {
AddStep("create header", () => Child = header = new ProfileHeader()); AddStep("create header", () =>
{
Child = new OsuScrollContainer(Direction.Vertical)
{
RelativeSizeAxes = Axes.Both,
Child = header = new ProfileHeader()
};
});
} }
[Test] [Test]
@ -136,5 +145,260 @@ namespace osu.Game.Tests.Visual.Online
PreviousUsernames = new[] { "tsrk.", "quoicoubeh", "apagnan", "epita" } PreviousUsernames = new[] { "tsrk.", "quoicoubeh", "apagnan", "epita" }
}, new OsuRuleset().RulesetInfo)); }, new OsuRuleset().RulesetInfo));
} }
[Test]
public void TestManyTournamentBanners()
{
AddStep("Show user w/ many tournament banners", () => header.User.Value = new UserProfileData(new APIUser
{
Id = 728,
Username = "Certain Guy",
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
Statistics = new UserStatistics
{
IsRanked = false,
// web will sometimes return non-empty rank history even for unranked users.
RankHistory = new APIRankHistory
{
Mode = @"osu",
Data = Enumerable.Range(2345, 85).ToArray()
},
},
TournamentBanners = new[]
{
new TournamentBanner
{
Id = 15329,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK@2x.jpg"
},
new TournamentBanner
{
Id = 15588,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN@2x.jpg"
},
new TournamentBanner
{
Id = 15589,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH@2x.jpg"
},
new TournamentBanner
{
Id = 15590,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL@2x.jpg"
},
new TournamentBanner
{
Id = 15591,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_JP.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_JP@2x.jpg"
},
new TournamentBanner
{
Id = 15592,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RU.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RU@2x.jpg"
},
new TournamentBanner
{
Id = 15593,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KR.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KR@2x.jpg"
},
new TournamentBanner
{
Id = 15594,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NZ.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NZ@2x.jpg"
},
new TournamentBanner
{
Id = 15595,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TH.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TH@2x.jpg"
},
new TournamentBanner
{
Id = 15596,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TW.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TW@2x.jpg"
},
new TournamentBanner
{
Id = 15603,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ID.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ID@2x.jpg"
},
new TournamentBanner
{
Id = 15604,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KZ.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KZ@2x.jpg"
},
new TournamentBanner
{
Id = 15605,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AR.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AR@2x.jpg"
},
new TournamentBanner
{
Id = 15606,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_BR.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_BR@2x.jpg"
},
new TournamentBanner
{
Id = 15607,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL@2x.jpg"
},
new TournamentBanner
{
Id = 15639,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_MX.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_MX@2x.jpg"
},
new TournamentBanner
{
Id = 15640,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AU.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AU@2x.jpg"
},
new TournamentBanner
{
Id = 15641,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_IT.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_IT@2x.jpg"
},
new TournamentBanner
{
Id = 15642,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_UA.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_UA@2x.jpg"
},
new TournamentBanner
{
Id = 15643,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NL.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NL@2x.jpg"
},
new TournamentBanner
{
Id = 15644,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FI.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FI@2x.jpg"
},
new TournamentBanner
{
Id = 15645,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RO.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RO@2x.jpg"
},
new TournamentBanner
{
Id = 15646,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SG.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SG@2x.jpg"
},
new TournamentBanner
{
Id = 15647,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_DE.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_DE@2x.jpg"
},
new TournamentBanner
{
Id = 15648,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ES.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ES@2x.jpg"
},
new TournamentBanner
{
Id = 15649,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SE.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SE@2x.jpg"
},
new TournamentBanner
{
Id = 15650,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CA.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CA@2x.jpg"
},
new TournamentBanner
{
Id = 15651,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NO.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NO@2x.jpg"
},
new TournamentBanner
{
Id = 15652,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_GB.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_GB@2x.jpg"
},
new TournamentBanner
{
Id = 15653,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_US.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_US@2x.jpg"
},
new TournamentBanner
{
Id = 15654,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL@2x.jpg"
},
new TournamentBanner
{
Id = 15655,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FR.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FR@2x.jpg"
},
new TournamentBanner
{
Id = 15686,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK@2x.jpg"
}
}
}, new OsuRuleset().RulesetInfo));
}
} }
} }

View File

@ -121,12 +121,29 @@ namespace osu.Game.Tests.Visual.Online
Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
}, },
}, },
TournamentBanner = new TournamentBanner TournamentBanners = new[]
{ {
Id = 13926, new TournamentBanner
TournamentId = 35, {
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg", Id = 15588,
Image = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US@2x.jpg", TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN@2x.jpg"
},
new TournamentBanner
{
Id = 15589,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH@2x.jpg"
},
new TournamentBanner
{
Id = 15590,
TournamentId = 41,
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL.jpg",
Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL@2x.jpg"
}
}, },
Badges = new[] Badges = new[]
{ {

View File

@ -140,6 +140,17 @@ namespace osu.Game.Tests.Visual.Settings
AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType<FocusedTextBox>().FirstOrDefault()?.HasFocus == true); AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType<FocusedTextBox>().FirstOrDefault()?.HasFocus == true);
} }
[Test]
public void TestSearchTextBoxSelectedOnShow()
{
SearchTextBox searchTextBox = null!;
AddStep("set text", () => (searchTextBox = settings.SectionsContainer.ChildrenOfType<SearchTextBox>().First()).Current.Value = "some text");
AddAssert("no text selected", () => searchTextBox.SelectedText == string.Empty);
AddRepeatStep("toggle visibility", () => settings.ToggleVisibility(), 2);
AddAssert("search text selected", () => searchTextBox.SelectedText == searchTextBox.Current.Value);
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -13,6 +13,7 @@ using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -1111,6 +1112,23 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("0 matching shown", () => songSelect.ChildrenOfType<FilterControl>().Single().InformationalText == "0 matches"); AddAssert("0 matching shown", () => songSelect.ChildrenOfType<FilterControl>().Single().InformationalText == "0 matches");
} }
[Test]
public void TestCutInFilterTextBox()
{
createSongSelect();
AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nonono");
AddStep("select all", () => InputManager.Keys(PlatformAction.SelectAll));
AddStep("press ctrl-x", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.X);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType<SearchTextBox>().First().Text, () => Is.Empty);
}
private void waitForInitialSelection() private void waitForInitialSelection()
{ {
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Globalization;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
@ -23,13 +24,13 @@ namespace osu.Game.Tournament.Components
base.Current = new Bindable<string>(string.Empty); base.Current = new Bindable<string>(string.Empty);
current.BindValueChanged(dto => current.BindValueChanged(dto =>
base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", DateTimeFormatInfo.InvariantInfo), true);
((OsuTextBox)Control).OnCommit += (sender, _) => ((OsuTextBox)Control).OnCommit += (sender, _) =>
{ {
try try
{ {
current.Value = DateTimeOffset.Parse(sender.Text); current.Value = DateTimeOffset.Parse(sender.Text, DateTimeFormatInfo.InvariantInfo);
} }
catch catch
{ {

View File

@ -7,6 +7,7 @@ using System;
using System.Globalization; using System.Globalization;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Extensions; using osu.Game.Extensions;
@ -46,7 +47,7 @@ namespace osu.Game.Online.API
if (WebRequest != null) if (WebRequest != null)
{ {
Response = ((OsuJsonWebRequest<T>)WebRequest).ResponseObject; Response = ((OsuJsonWebRequest<T>)WebRequest).ResponseObject;
Logger.Log($"{GetType()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network); Logger.Log($"{GetType().ReadableName()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network);
} }
} }

View File

@ -112,7 +112,7 @@ namespace osu.Game.Online.API
LocalUser.Value = new APIUser LocalUser.Value = new APIUser
{ {
Username = username, Username = username,
Id = 1001, Id = DUMMY_USER_ID,
}; };
state.Value = APIState.Online; state.Value = APIState.Online;

View File

@ -234,9 +234,8 @@ namespace osu.Game.Online.API.Requests.Responses
set => Statistics.RankHistory = value; set => Statistics.RankHistory = value;
} }
[JsonProperty(@"active_tournament_banner")] [JsonProperty(@"active_tournament_banners")]
[CanBeNull] public TournamentBanner[] TournamentBanners;
public TournamentBanner TournamentBanner;
[JsonProperty("badges")] [JsonProperty("badges")]
public Badge[] Badges; public Badge[] Badges;

View File

@ -11,7 +11,7 @@ using osu.Game.Overlays.Profile.Header.Components;
namespace osu.Game.Overlays.Profile.Header namespace osu.Game.Overlays.Profile.Header
{ {
public partial class BannerHeaderContainer : CompositeDrawable public partial class BannerHeaderContainer : FillFlowContainer
{ {
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(); public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
@ -19,9 +19,9 @@ namespace osu.Game.Overlays.Profile.Header
private void load() private void load()
{ {
Alpha = 0; Alpha = 0;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.X;
FillMode = FillMode.Fit; AutoSizeAxes = Axes.Y;
FillAspectRatio = 1000 / 60f; Direction = FillDirection.Vertical;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -40,13 +40,21 @@ namespace osu.Game.Overlays.Profile.Header
ClearInternal(); ClearInternal();
var banner = user?.TournamentBanner; var banners = user?.TournamentBanners;
if (banner != null) if (banners?.Length > 0)
{ {
Show(); Show();
LoadComponentAsync(new DrawableTournamentBanner(banner), AddInternal, cancellationTokenSource.Token); for (int index = 0; index < banners.Length; index++)
{
int displayIndex = index;
LoadComponentAsync(new DrawableTournamentBanner(banners[index]), asyncBanner =>
{
// load in stable order regardless of async load order.
Insert(displayIndex, asyncBanner);
}, cancellationTokenSource.Token);
}
} }
else else
{ {

View File

@ -15,12 +15,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
[LongRunningLoad] [LongRunningLoad]
public partial class DrawableTournamentBanner : OsuClickableContainer public partial class DrawableTournamentBanner : OsuClickableContainer
{ {
private const float banner_aspect_ratio = 60 / 1000f;
private readonly TournamentBanner banner; private readonly TournamentBanner banner;
public DrawableTournamentBanner(TournamentBanner banner) public DrawableTournamentBanner(TournamentBanner banner)
{ {
this.banner = banner; this.banner = banner;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.X;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -41,6 +42,12 @@ namespace osu.Game.Overlays.Profile.Header.Components
this.FadeInFromZero(200); this.FadeInFromZero(200);
} }
protected override void Update()
{
base.Update();
Height = DrawWidth * banner_aspect_ratio;
}
public override LocalisableString TooltipText => "view in browser"; public override LocalisableString TooltipText => "view in browser";
} }
} }

View File

@ -135,7 +135,7 @@ namespace osu.Game.Overlays
}, },
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Child = searchTextBox = new SeekLimitedSearchTextBox Child = searchTextBox = new SettingsSearchTextBox
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,

View File

@ -0,0 +1,22 @@
// 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.Input.Events;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays
{
public partial class SettingsSearchTextBox : SeekLimitedSearchTextBox
{
protected override void OnFocus(FocusEvent e)
{
base.OnFocus(e);
// on mobile platforms, focus is not held by the search text box, and the select all feature
// will not make sense on it, and might annoy the user when they try to focus manually.
if (HoldFocus)
SelectAll();
}
}
}

View File

@ -5,6 +5,7 @@
using System; using System;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework; using osu.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Edit
private SelectionState state; private SelectionState state;
[CanBeNull]
public event Action<SelectionState> StateChanged; public event Action<SelectionState> StateChanged;
public SelectionState State public SelectionState State

View File

@ -182,6 +182,12 @@ namespace osu.Game.Scoring
base.PostImport(model, realm, parameters); base.PostImport(model, realm, parameters);
populateUserDetails(model); populateUserDetails(model);
Debug.Assert(model.BeatmapInfo != null);
// This needs to be run after user detail population to ensure we have a valid user id.
if (api.IsLoggedIn && api.LocalUser.Value.OnlineID == model.UserID && (model.BeatmapInfo.LastPlayed == null || model.Date > model.BeatmapInfo.LastPlayed))
model.BeatmapInfo.LastPlayed = model.Date;
} }
/// <summary> /// <summary>

View File

@ -166,6 +166,8 @@ namespace osu.Game.Screens.Backgrounds
public override void Add(Drawable drawable) public override void Add(Drawable drawable)
{ {
ArgumentNullException.ThrowIfNull(drawable);
if (drawable is Background) if (drawable is Background)
throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); throw new InvalidOperationException($"Use {nameof(Background)} to set a background.");

View File

@ -40,11 +40,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
public PlacementBlueprint CurrentPlacement { get; private set; } public PlacementBlueprint CurrentPlacement { get; private set; }
[Resolved(canBeNull: true)]
private EditorScreenWithTimeline editorScreen { get; set; }
/// <remarks> /// <remarks>
/// Positional input must be received outside the container's bounds, /// Positional input must be received outside the container's bounds,
/// in order to handle composer blueprints which are partially offscreen. /// in order to handle composer blueprints which are partially offscreen.
/// </remarks> /// </remarks>
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => editorScreen?.MainContent.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
public ComposeBlueprintContainer(HitObjectComposer composer) public ComposeBlueprintContainer(HitObjectComposer composer)
: base(composer) : base(composer)

View File

@ -11,13 +11,14 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
[Cached]
public abstract partial class EditorScreenWithTimeline : EditorScreen public abstract partial class EditorScreenWithTimeline : EditorScreen
{ {
public const float PADDING = 10; public const float PADDING = 10;
private Container timelineContainer = null!; public Container TimelineContent { get; private set; } = null!;
private Container mainContent = null!; public Container MainContent { get; private set; } = null!;
private LoadingSpinner spinner = null!; private LoadingSpinner spinner = null!;
@ -70,7 +71,7 @@ namespace osu.Game.Screens.Edit
{ {
new Drawable[] new Drawable[]
{ {
timelineContainer = new Container TimelineContent = new Container
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit
}, },
new Drawable[] new Drawable[]
{ {
mainContent = new Container MainContent = new Container
{ {
Name = "Main content", Name = "Main content",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -116,10 +117,10 @@ namespace osu.Game.Screens.Edit
{ {
spinner.State.Value = Visibility.Hidden; spinner.State.Value = Visibility.Hidden;
mainContent.Add(content); MainContent.Add(content);
content.FadeInFromZero(300, Easing.OutQuint); content.FadeInFromZero(300, Easing.OutQuint);
LoadComponentAsync(new TimelineArea(CreateTimelineContent()), timelineContainer.Add); LoadComponentAsync(new TimelineArea(CreateTimelineContent()), TimelineContent.Add);
}); });
} }

View File

@ -123,6 +123,8 @@ namespace osu.Game.Screens.Edit
oldWithRepeats.NodeSamples.Clear(); oldWithRepeats.NodeSamples.Clear();
oldWithRepeats.NodeSamples.AddRange(newWithRepeats.NodeSamples); oldWithRepeats.NodeSamples.AddRange(newWithRepeats.NodeSamples);
} }
editorBeatmap.Update(oldObject);
} }
} }

View File

@ -7,6 +7,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using JetBrains.Annotations;
using osu.Framework; using osu.Framework;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -190,6 +191,7 @@ namespace osu.Game.Screens.Play
private const float padding = 2; private const float padding = 2;
public const float WIDTH = cube_size + padding; public const float WIDTH = cube_size + padding;
[CanBeNull]
public event Action<ColumnState> StateChanged; public event Action<ColumnState> StateChanged;
private readonly List<Box> drawableRows = new List<Box>(); private readonly List<Box> drawableRows = new List<Box>();

View File

@ -188,7 +188,10 @@ namespace osu.Game.Screens.Play
{ {
// token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure).
if (token == null) if (token == null)
{
Logger.Log("No token, skipping score submission");
return Task.CompletedTask; return Task.CompletedTask;
}
if (scoreSubmissionSource != null) if (scoreSubmissionSource != null)
return scoreSubmissionSource.Task; return scoreSubmissionSource.Task;
@ -197,6 +200,8 @@ namespace osu.Game.Screens.Play
if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0)) if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0))
return Task.CompletedTask; return Task.CompletedTask;
Logger.Log($"Beginning score submission (token:{token.Value})...");
scoreSubmissionSource = new TaskCompletionSource<bool>(); scoreSubmissionSource = new TaskCompletionSource<bool>();
var request = CreateSubmissionRequest(score, token.Value); var request = CreateSubmissionRequest(score, token.Value);
@ -206,11 +211,12 @@ namespace osu.Game.Screens.Play
score.ScoreInfo.Position = s.Position; score.ScoreInfo.Position = s.Position;
scoreSubmissionSource.SetResult(true); scoreSubmissionSource.SetResult(true);
Logger.Log($"Score submission completed! (token:{token.Value} id:{s.ID})");
}; };
request.Failure += e => request.Failure += e =>
{ {
Logger.Error(e, $"Failed to submit score ({e.Message})"); Logger.Error(e, $"Failed to submit score (token:{token.Value}): {e.Message}");
scoreSubmissionSource.SetResult(false); scoreSubmissionSource.SetResult(false);
}; };

View File

@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Collections; using osu.Game.Collections;
@ -23,6 +24,7 @@ using osu.Game.Rulesets;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Screens.Select namespace osu.Game.Screens.Select
{ {
@ -254,9 +256,6 @@ namespace osu.Game.Screens.Select
public OsuSpriteText FilterText { get; private set; } public OsuSpriteText FilterText { get; private set; }
// clipboard is disabled because one of the "cut" platform key bindings (shift-delete) conflicts with the beatmap deletion action.
protected override bool AllowClipboardExport => false;
public FilterControlTextBox() public FilterControlTextBox()
{ {
Height += filter_text_size; Height += filter_text_size;
@ -277,6 +276,15 @@ namespace osu.Game.Screens.Select
Colour = colours.Yellow Colour = colours.Yellow
}); });
} }
public override bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
{
// the "cut" platform key binding (shift-delete) conflicts with the beatmap deletion action.
if (e.Action == PlatformAction.Cut && e.ShiftPressed && e.CurrentState.Keyboard.Keys.IsPressed(Key.Delete))
return false;
return base.OnPressed(e);
}
} }
} }
} }

View File

@ -40,6 +40,7 @@ namespace osu.Game.Skinning
public float ScorePosition = 300 * POSITION_SCALE_FACTOR; public float ScorePosition = 300 * POSITION_SCALE_FACTOR;
public bool ShowJudgementLine = true; public bool ShowJudgementLine = true;
public bool KeysUnderNotes; public bool KeysUnderNotes;
public int LightFramePerSecond = 60;
public LegacyNoteBodyStyle? NoteBodyStyle; public LegacyNoteBodyStyle? NoteBodyStyle;

View File

@ -74,6 +74,7 @@ namespace osu.Game.Skinning
Hit50, Hit50,
Hit0, Hit0,
KeysUnderNotes, KeysUnderNotes,
NoteBodyStyle NoteBodyStyle,
LightFramePerSecond
} }
} }

View File

@ -123,6 +123,11 @@ namespace osu.Game.Skinning
currentConfig.WidthForNoteHeightScale = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; currentConfig.WidthForNoteHeightScale = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break; break;
case "LightFramePerSecond":
int lightFramePerSecond = int.Parse(pair.Value, CultureInfo.InvariantCulture);
currentConfig.LightFramePerSecond = lightFramePerSecond > 0 ? lightFramePerSecond : 24;
break;
case string when pair.Key.StartsWith("Colour", StringComparison.Ordinal): case string when pair.Key.StartsWith("Colour", StringComparison.Ordinal):
HandleColours(currentConfig, line, true); HandleColours(currentConfig, line, true);
break; break;

View File

@ -273,6 +273,9 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.KeysUnderNotes: case LegacyManiaSkinConfigurationLookups.KeysUnderNotes:
return SkinUtils.As<TValue>(new Bindable<bool>(existing.KeysUnderNotes)); return SkinUtils.As<TValue>(new Bindable<bool>(existing.KeysUnderNotes));
case LegacyManiaSkinConfigurationLookups.LightFramePerSecond:
return SkinUtils.As<TValue>(new Bindable<int>(existing.LightFramePerSecond));
} }
return null; return null;

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="11.5.0" /> <PackageReference Include="Realm" Version="11.5.0" />
<PackageReference Include="ppy.osu.Framework" Version="2023.1012.0" /> <PackageReference Include="ppy.osu.Framework" Version="2023.1030.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1023.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2023.1023.0" />
<PackageReference Include="Sentry" Version="3.40.0" /> <PackageReference Include="Sentry" Version="3.40.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. --> <!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->

View File

@ -23,6 +23,6 @@
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier> <RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.1012.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2023.1030.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>