1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-16 08:22:35 +08:00

Compare commits

...

126 Commits

75 changed files with 986 additions and 211 deletions
+1 -1
View File
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.32"
"Microsoft.Build.Traversal": "2.0.34"
}
}
+1 -1
View File
@@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.403.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.407.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,21 @@
// 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.Game.Rulesets.Catch.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
public abstract class CatchSkinnableTestScene : SkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CatchRuleset),
typeof(CatchLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset();
}
}
@@ -4,21 +4,21 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Tests.Visual;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestSceneCatcher : SkinnableTestScene
public class TestSceneCatcher : CatchSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(CatcherArea),
typeof(CatcherSprite)
};
}).ToList();
[BackgroundDependencyLoader]
private void load()
@@ -17,12 +17,11 @@ using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestSceneCatcherArea : SkinnableTestScene
public class TestSceneCatcherArea : CatchSkinnableTestScene
{
private RulesetInfo catchRuleset;
@@ -3,20 +3,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestSceneFruitObjects : SkinnableTestScene
public class TestSceneFruitObjects : CatchSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(CatchHitObject),
typeof(Fruit),
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableBanana),
typeof(DrawableBananaShower),
typeof(Pulp),
};
}).ToList();
protected override void LoadComplete()
{
@@ -1,12 +1,15 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
using osu.Game.Tests.Visual;
@@ -24,6 +27,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ManiaRuleset),
typeof(ManiaLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
protected ManiaSkinnableTestScene()
{
scrollingInfo.Direction.Value = ScrollingDirection.Down;
@@ -10,11 +10,10 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneDrawableJudgement : SkinnableTestScene
public class TestSceneDrawableJudgement : ManiaSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
{
Child = new ManiaStage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction)
Child = new Stage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction)
};
});
}
@@ -28,7 +28,9 @@ namespace osu.Game.Rulesets.Mania.Tests
{
typeof(Column),
typeof(ColumnBackground),
typeof(ColumnHitObjectArea)
typeof(ColumnHitObjectArea),
typeof(DefaultKeyArea),
typeof(DefaultHitTarget)
};
[Cached(typeof(IReadOnlyList<Mod>))]
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests
[Cached(typeof(IReadOnlyList<Mod>))]
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
private readonly List<ManiaStage> stages = new List<ManiaStage>();
private readonly List<Stage> stages = new List<Stage>();
private FillFlowContainer<ScrollingTestContainer> fill;
@@ -81,9 +81,9 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre));
}
private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
private bool notesInStageAreAnchored(Stage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor);
private bool barsInStageAreAnchored(Stage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor);
private void createNote()
{
@@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
var specialAction = ManiaAction.Special1;
var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction);
var stage = new Stage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction);
stages.Add(stage);
return new ScrollingTestContainer(direction)
@@ -50,12 +50,18 @@ namespace osu.Game.Rulesets.Mania.Skinning
Color4 lineColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
?? Color4.White;
Color4 backgroundColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value
?? Color4.Black;
Color4 lightColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
?? Color4.White;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black
Colour = backgroundColour
},
new Box
{
@@ -82,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Colour = lightColour,
Texture = skin.GetTexture(lightImage),
RelativeSizeAxes = Axes.X,
Width = 1,
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d =>
explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d =>
{
if (d == null)
return;
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning
{
@@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
bool showJudgementLine = GetManiaSkinConfig<bool>(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value
?? true;
Color4 lineColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value
?? Color4.White;
InternalChild = directionContainer = new Container
{
Origin = Anchor.CentreLeft,
@@ -52,6 +56,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
Colour = lineColour,
Alpha = showJudgementLine ? 0.9f : 0
}
}
@@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
private Container directionContainer;
private Sprite noteSprite;
private float? minimumColumnWidth;
public LegacyNotePiece()
{
RelativeSizeAxes = Axes.X;
@@ -29,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
minimumColumnWidth = skin.GetConfig<ManiaSkinConfigurationLookup, float>(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value;
InternalChild = directionContainer = new Container
{
Origin = Anchor.BottomCentre,
@@ -47,8 +51,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
if (noteSprite.Texture != null)
{
var scale = DrawWidth / noteSprite.Texture.DisplayWidth;
noteSprite.Scale = new Vector2(scale);
// The height is scaled to the minimum column width, if provided.
float minimumWidth = minimumColumnWidth ?? DrawWidth;
noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth);
}
}
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
{
isLegacySkin = new Lazy<bool>(() => source.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null);
hasKeyTexture = new Lazy<bool>(() => source.GetAnimation(
source.GetConfig<ManiaSkinConfigurationLookup, string>(
GetConfig<ManiaSkinConfigurationLookup, string>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value
?? "mania-key1", true, true) != null);
}
+2 -1
View File
@@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI
Index = index;
RelativeSizeAxes = Axes.Y;
Width = COLUMN_WIDTH;
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground())
{
@@ -138,6 +139,6 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
=> DrawRectangle.Inflate(new Vector2(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
}
}
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
InternalChild = directionContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = ManiaStage.HIT_TARGET_POSITION,
Height = Stage.HIT_TARGET_POSITION,
Children = new[]
{
gradient = new Box
@@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Origin = Anchor.Centre,
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
@@ -88,11 +87,15 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
if (direction.NewValue == ScrollingDirection.Up)
{
keyIcon.Anchor = Anchor.BottomCentre;
keyIcon.Y = -20;
directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft;
gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0));
}
else
{
keyIcon.Anchor = Anchor.TopCentre;
keyIcon.Y = 20;
directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft;
gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black);
}
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
float hitPosition = CurrentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
?? ManiaStage.HIT_TARGET_POSITION;
?? Stage.HIT_TARGET_POSITION;
Padding = Direction.Value == ScrollingDirection.Up
? new MarginPadding { Top = hitPosition }
@@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
// Mania doesn't care about global velocity
p.Velocity = 1;
p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier;
// For non-mania beatmap, speed changes should only happen through timing points
if (!isForCurrentRuleset)
+3 -3
View File
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaPlayfield : ScrollingPlayfield
{
private readonly List<ManiaStage> stages = new List<ManiaStage>();
private readonly List<Stage> stages = new List<Stage>();
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < stageDefinitions.Count; i++)
{
var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
playfieldGrid.Content[0][i] = newStage;
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI
/// </summary>
public int TotalColumns => stages.Sum(s => s.Columns.Count);
private ManiaStage getStageByColumn(int column)
private Stage getStageByColumn(int column)
{
int sum = 0;
@@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary>
/// A collection of <see cref="Column"/>s.
/// </summary>
public class ManiaStage : ScrollingPlayfield
public class Stage : ScrollingPlayfield
{
public const float COLUMN_SPACING = 1;
public const float HIT_TARGET_POSITION = 50;
public const float HIT_TARGET_POSITION = 110;
public IReadOnlyList<Column> Columns => columnFlow.Children;
private readonly FillFlowContainer<Column> columnFlow;
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int firstColumnIndex;
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
{
this.firstColumnIndex = firstColumnIndex;
@@ -0,0 +1,21 @@
// 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.Game.Rulesets.Osu.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public abstract class OsuSkinnableTestScene : SkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OsuRuleset),
typeof(OsuLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}
@@ -10,17 +10,16 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneDrawableJudgement : SkinnableTestScene
public class TestSceneDrawableJudgement : OsuSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(DrawableJudgement),
typeof(DrawableOsuJudgement)
};
}).ToList();
public TestSceneDrawableJudgement()
{
@@ -3,26 +3,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing.Input;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneGameplayCursor : SkinnableTestScene
public class TestSceneGameplayCursor : OsuSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(GameplayCursorContainer),
typeof(OsuCursorContainer),
typeof(OsuCursor),
typeof(LegacyCursor),
typeof(LegacyCursorTrail),
typeof(CursorTrail)
};
}).ToList();
[Cached]
private GameplayBeatmap gameplayBeatmap;
@@ -14,12 +14,11 @@ using osu.Game.Rulesets.Mods;
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneHitCircle : SkinnableTestScene
public class TestSceneHitCircle : OsuSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
@@ -22,12 +22,11 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneSlider : SkinnableTestScene
public class TestSceneSlider : OsuSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
@@ -0,0 +1,253 @@
// 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.Linq;
using Humanizer;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Storyboards;
using osuTK;
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneSliderSnaking : TestSceneOsuPlayer
{
[Resolved]
private AudioManager audioManager { get; set; }
private TrackVirtualManual track;
protected override bool Autoplay => autoplay;
private bool autoplay;
private readonly BindableBool snakingIn = new BindableBool();
private readonly BindableBool snakingOut = new BindableBool();
private const double duration_of_span = 3605;
private const double fade_in_modifier = -1200;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
track = (TrackVirtualManual)working.Track;
return working;
}
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
{
var config = (OsuRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
}
private DrawableSlider slider;
[SetUpSteps]
public override void SetUpSteps() { }
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
public void TestSnakingEnabled(int sliderIndex)
{
AddStep("enable autoplay", () => autoplay = true);
base.SetUpSteps();
AddUntilStep("wait for track to start running", () => track.IsRunning);
double startTime = hitObjects[sliderIndex].StartTime;
retrieveDrawableSlider(sliderIndex);
setSnaking(true);
ensureSnakingIn(startTime + fade_in_modifier);
for (int i = 0; i < sliderIndex; i++)
{
// non-final repeats should not snake out
ensureNoSnakingOut(startTime, i);
}
// final repeat should snake out
ensureSnakingOut(startTime, sliderIndex);
}
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
public void TestSnakingDisabled(int sliderIndex)
{
AddStep("have autoplay", () => autoplay = true);
base.SetUpSteps();
AddUntilStep("wait for track to start running", () => track.IsRunning);
double startTime = hitObjects[sliderIndex].StartTime;
retrieveDrawableSlider(sliderIndex);
setSnaking(false);
ensureNoSnakingIn(startTime + fade_in_modifier);
for (int i = 0; i <= sliderIndex; i++)
{
// no snaking out ever, including final repeat
ensureNoSnakingOut(startTime, i);
}
}
[Test]
public void TestRepeatArrowDoesNotMoveWhenHit()
{
AddStep("enable autoplay", () => autoplay = true);
setSnaking(true);
base.SetUpSteps();
// repeat might have a chance to update its position depending on where in the frame its hit,
// so some leniency is allowed here instead of checking strict equality
checkPositionChange(16600, sliderRepeat, positionAlmostSame);
}
[Test]
public void TestRepeatArrowMovesWhenNotHit()
{
AddStep("disable autoplay", () => autoplay = false);
setSnaking(true);
base.SetUpSteps();
checkPositionChange(16600, sliderRepeat, positionDecreased);
}
private void retrieveDrawableSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () =>
{
slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index);
});
private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
private void ensureSnakingOut(double startTime, int repeatIndex)
{
var repeatTime = timeAtRepeat(startTime, repeatIndex);
if (repeatIndex % 2 == 0)
checkPositionChange(repeatTime, sliderStart, positionIncreased);
else
checkPositionChange(repeatTime, sliderEnd, positionDecreased);
}
private void ensureNoSnakingOut(double startTime, int repeatIndex) =>
checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex;
private Func<Vector2> positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func<Vector2>)sliderStart : sliderEnd;
private List<Vector2> sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve;
private Vector2 sliderStart() => sliderCurve.First();
private Vector2 sliderEnd() => sliderCurve.Last();
private Vector2 sliderRepeat()
{
var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1);
var repeat = drawable.ChildrenOfType<Container<DrawableSliderRepeat>>().First().Children.First();
return repeat.Position;
}
private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current;
private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y;
private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y;
private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1);
private void checkPositionChange(double startTime, Func<Vector2> positionToCheck, Func<Vector2, Vector2, bool> positionAssertion)
{
Vector2 previousPosition = Vector2.Zero;
string positionDescription = positionToCheck.Method.Name.Humanize(LetterCasing.LowerCase);
string assertionDescription = positionAssertion.Method.Name.Humanize(LetterCasing.LowerCase);
addSeekStep(startTime);
AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke());
addSeekStep(startTime + 100);
AddAssert($"{positionDescription} {assertionDescription}", () =>
{
var currentPosition = positionToCheck.Invoke();
return positionAssertion.Invoke(previousPosition, currentPosition);
});
}
private void setSnaking(bool value)
{
AddStep($"{(value ? "enable" : "disable")} snaking", () =>
{
snakingIn.Value = value;
snakingOut.Value = value;
});
}
private void addSeekStep(double time)
{
AddStep($"seek to {time}", () => track.Seek(time));
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
{
HitObjects = hitObjects
};
private readonly List<HitObject> hitObjects = new List<HitObject>
{
new Slider
{
StartTime = 3000,
Position = new Vector2(100, 100),
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,
new Vector2(300, 200)
}),
},
new Slider
{
StartTime = 13000,
Position = new Vector2(100, 100),
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,
new Vector2(300, 200)
}),
RepeatCount = 1,
},
new Slider
{
StartTime = 23000,
Position = new Vector2(100, 100),
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,
new Vector2(300, 200)
}),
RepeatCount = 2,
},
new HitCircle
{
StartTime = 199999,
}
};
}
}
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Anchor = Anchor.Centre,
Alpha = 0.5f,
}
}, confineMode: ConfineMode.NoScaling);
});
}
public double AnimationStartTime { get; set; }
@@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.ApplySkin(skin, allowFallback);
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
@@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
InternalChild = scaleContainer = new ReverseArrowPiece();
@@ -8,11 +8,16 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osu.Game.Skinning;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class ReverseArrowPiece : BeatSyncedContainer
{
[Resolved]
private DrawableHitObject drawableRepeat { get; set; }
public ReverseArrowPiece()
{
Divisor = 2;
@@ -21,13 +26,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Blending = BlendingParameters.Additive;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Icon = FontAwesome.Solid.ChevronRight,
Size = new Vector2(0.35f)
})
@@ -37,7 +41,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) =>
Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
{
if (!drawableRepeat.IsHit)
Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
}
}
}
@@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
this.drawableSlider = drawableSlider;
this.slider = slider;
Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
@@ -241,6 +240,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
BorderThickness = 10,
BorderColour = Color4.White,
Alpha = 1,
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
switch (osuComponent.Component)
{
case OsuSkinComponents.FollowPoint:
return this.GetAnimation(component.LookupName, true, false, true);
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
case OsuSkinComponents.SliderFollowCircle:
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
@@ -132,6 +132,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return SkinUtils.As<TValue>(new BindableFloat(LEGACY_CIRCLE_RADIUS));
break;
case OsuSkinConfiguration.HitCircleOverlayAboveNumber:
// See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D
// HitCircleOverlayAboveNumer (with typo) should still be supported for now.
return source.GetConfig<OsuSkinConfiguration, TValue>(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ??
source.GetConfig<OsuSkinConfiguration, TValue>(OsuSkinConfiguration.HitCircleOverlayAboveNumer);
}
break;
@@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
AllowSliderBallTint,
CursorExpand,
CursorRotate,
HitCircleOverlayAboveNumber
HitCircleOverlayAboveNumber,
HitCircleOverlayAboveNumer // Some old skins will have this typo
}
}
@@ -0,0 +1,5 @@
[General]
Name: an old skin
Author: an old guy
// no version specified means v1
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

@@ -0,0 +1,21 @@
// 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.Game.Rulesets.Taiko.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
public abstract class TaikoSkinnableTestScene : SkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(TaikoRuleset),
typeof(TaikoLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset();
}
}
@@ -3,24 +3,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
public class TestSceneInputDrum : SkinnableTestScene
public class TestSceneInputDrum : TaikoSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(InputDrum),
};
typeof(LegacyInputDrum),
}).ToList();
[BackgroundDependencyLoader]
private void load()
@@ -18,9 +18,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning
/// </summary>
internal class LegacyInputDrum : Container
{
private LegacyHalfDrum left;
private LegacyHalfDrum right;
public LegacyInputDrum()
{
AutoSizeAxes = Axes.Both;
Size = new Vector2(180, 200);
}
[BackgroundDependencyLoader]
@@ -32,25 +35,47 @@ namespace osu.Game.Rulesets.Taiko.Skinning
{
Texture = skin.GetTexture("taiko-bar-left")
},
new LegacyHalfDrum(false)
left = new LegacyHalfDrum(false)
{
Name = "Left Half",
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
RimAction = TaikoAction.LeftRim,
CentreAction = TaikoAction.LeftCentre
},
new LegacyHalfDrum(true)
right = new LegacyHalfDrum(true)
{
Name = "Right Half",
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Origin = Anchor.TopRight,
Scale = new Vector2(-1, 1),
RimAction = TaikoAction.RightRim,
CentreAction = TaikoAction.RightCentre
}
};
// this will be used in the future for stable skin alignment. keeping here for reference.
const float taiko_bar_y = 0;
// stable things
const float ratio = 1.6f;
// because the right half is flipped, we need to position using width - position to get the true "topleft" origin position
float negativeScaleAdjust = Width / ratio;
if (skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m)
{
left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio;
right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio;
left.Rim.Position = new Vector2(0, taiko_bar_y) * ratio;
right.Rim.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio;
}
else
{
left.Centre.Position = new Vector2(18, taiko_bar_y + 31) * ratio;
right.Centre.Position = new Vector2(negativeScaleAdjust - 54, taiko_bar_y + 31) * ratio;
left.Rim.Position = new Vector2(8, taiko_bar_y + 23) * ratio;
right.Rim.Position = new Vector2(negativeScaleAdjust - 53, taiko_bar_y + 23) * ratio;
}
}
/// <summary>
@@ -68,8 +93,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning
/// </summary>
public TaikoAction CentreAction;
private readonly Sprite rimHit;
private readonly Sprite centreHit;
public readonly Sprite Rim;
public readonly Sprite Centre;
[Resolved]
private DrumSampleMapping sampleMappings { get; set; }
@@ -80,18 +105,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning
Children = new Drawable[]
{
rimHit = new Sprite
Rim = new Sprite
{
Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft,
Origin = flipped ? Anchor.CentreLeft : Anchor.CentreRight,
Scale = new Vector2(-1, 1),
Origin = flipped ? Anchor.TopLeft : Anchor.TopRight,
Alpha = 0,
},
centreHit = new Sprite
Centre = new Sprite
{
Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft,
Origin = flipped ? Anchor.CentreRight : Anchor.CentreLeft,
Alpha = 0,
Origin = flipped ? Anchor.TopRight : Anchor.TopLeft,
}
};
}
@@ -99,8 +122,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
rimHit.Texture = skin.GetTexture(@"taiko-drum-outer");
centreHit.Texture = skin.GetTexture(@"taiko-drum-inner");
Rim.Texture = skin.GetTexture(@"taiko-drum-outer");
Centre.Texture = skin.GetTexture(@"taiko-drum-inner");
}
public bool OnPressed(TaikoAction action)
@@ -110,12 +133,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning
if (action == CentreAction)
{
target = centreHit;
target = Centre;
drumSample.Centre?.Play();
}
else if (action == RimAction)
{
target = rimHit;
target = Rim;
drumSample.Rim?.Play();
}
@@ -0,0 +1,109 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Textures;
using osu.Game.Skinning;
namespace osu.Game.Tests.NonVisual.Skinning
{
[TestFixture]
public sealed class LegacySkinTextureFallbackTest
{
private static object[][] fallbackTestCases =
{
new object[]
{
// textures in store
new[] { "Gameplay/osu/followpoint@2x", "Gameplay/osu/followpoint" },
// requested component
"Gameplay/osu/followpoint",
// returned texture name & scale
"Gameplay/osu/followpoint@2x", 2
},
new object[]
{
new[] { "Gameplay/osu/followpoint@2x" },
"Gameplay/osu/followpoint",
"Gameplay/osu/followpoint@2x", 2
},
new object[]
{
new[] { "Gameplay/osu/followpoint" },
"Gameplay/osu/followpoint",
"Gameplay/osu/followpoint", 1
},
new object[]
{
new[] { "Gameplay/osu/followpoint", "followpoint@2x" },
"Gameplay/osu/followpoint",
"Gameplay/osu/followpoint", 1
},
new object[]
{
new[] { "followpoint@2x", "followpoint" },
"Gameplay/osu/followpoint",
"followpoint@2x", 2
},
new object[]
{
new[] { "followpoint@2x" },
"Gameplay/osu/followpoint",
"followpoint@2x", 2
},
new object[]
{
new[] { "followpoint" },
"Gameplay/osu/followpoint",
"followpoint", 1
},
};
[TestCaseSource(nameof(fallbackTestCases))]
public void TestFallbackOrder(string[] filesInStore, string requestedComponent, string expectedTexture, float expectedScale)
{
var textureStore = new TestTextureStore(filesInStore);
var legacySkin = new TestLegacySkin(textureStore);
var texture = legacySkin.GetTexture(requestedComponent);
Assert.IsNotNull(texture);
Assert.AreEqual(textureStore.Textures[expectedTexture], texture);
Assert.AreEqual(expectedScale, texture.ScaleAdjust);
}
[Test]
public void TestReturnNullOnFallbackFailure()
{
var textureStore = new TestTextureStore("sliderb", "hit100");
var legacySkin = new TestLegacySkin(textureStore);
var texture = legacySkin.GetTexture("Gameplay/osu/followpoint");
Assert.IsNull(texture);
}
private class TestLegacySkin : LegacySkin
{
public TestLegacySkin(TextureStore textureStore)
: base(new SkinInfo(), null, null, string.Empty)
{
Textures = textureStore;
}
}
private class TestTextureStore : TextureStore
{
public readonly Dictionary<string, Texture> Textures;
public TestTextureStore(params string[] fileNames)
{
Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1));
}
public override Texture Get(string name) => Textures.GetValueOrDefault(name);
}
}
}
@@ -0,0 +1,4 @@
[Mania]
Keys: 4
ColumnWidth: 10,10,10,10
WidthForNoteHeightScale: 0
@@ -99,5 +99,20 @@ namespace osu.Game.Tests.Skins
Assert.That(configs[0].CustomColours, Contains.Key("ColourBarline").And.ContainValue(new Color4(50, 50, 50, 50)));
}
}
[Test]
public void TestMinimumColumnWidthFallsBackWhenZeroIsProvided()
{
var decoder = new LegacyManiaSkinDecoder();
using (var resStream = TestResources.OpenResource("mania-skin-zero-minwidth.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var configs = decoder.Decode(stream);
Assert.That(configs.Count, Is.EqualTo(1));
Assert.That(configs[0].MinimumColumnWidth, Is.EqualTo(16));
}
}
}
}
@@ -106,7 +106,7 @@ namespace osu.Game.Tests.Skins
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-empty.ini"))
using (var stream = new LineBufferedReader(resStream))
Assert.IsNull(decoder.Decode(stream).LegacyVersion);
Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m));
}
}
}
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -12,7 +13,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.Rulesets.Osu;
using osu.Game.Skinning;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -22,15 +26,15 @@ namespace osu.Game.Tests.Skins
[HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene
{
private SkinSource source1;
private SkinSource source2;
private UserSkinSource userSource;
private BeatmapSkinSource beatmapSource;
private SkinRequester requester;
[SetUp]
public void SetUp() => Schedule(() =>
{
Add(new SkinProvidingContainer(source1 = new SkinSource())
.WithChild(new SkinProvidingContainer(source2 = new SkinSource())
Add(new SkinProvidingContainer(userSource = new UserSkinSource())
.WithChild(new SkinProvidingContainer(beatmapSource = new BeatmapSkinSource())
.WithChild(requester = new SkinRequester())));
});
@@ -39,31 +43,31 @@ namespace osu.Game.Tests.Skins
{
AddStep("Add config values", () =>
{
source1.Configuration.ConfigDictionary["Lookup"] = "source1";
source2.Configuration.ConfigDictionary["Lookup"] = "source2";
userSource.Configuration.ConfigDictionary["Lookup"] = "user skin";
beatmapSource.Configuration.ConfigDictionary["Lookup"] = "beatmap skin";
});
AddAssert("Check lookup finds source2", () => requester.GetConfig<string, string>("Lookup")?.Value == "source2");
AddAssert("Check lookup finds beatmap skin", () => requester.GetConfig<string, string>("Lookup")?.Value == "beatmap skin");
}
[Test]
public void TestFloatLookup()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1");
AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["FloatTest"] = "1.1");
AddAssert("Check float parse lookup", () => requester.GetConfig<string, float>("FloatTest")?.Value == 1.1f);
}
[Test]
public void TestBoolLookup()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1");
AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1");
AddAssert("Check bool parse lookup", () => requester.GetConfig<string, bool>("BoolTest")?.Value == true);
}
[Test]
public void TestEnumLookup()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2");
AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Test"] = "Test2");
AddAssert("Check enum parse lookup", () => requester.GetConfig<LookupType, ValueType>(LookupType.Test)?.Value == ValueType.Test2);
}
@@ -76,7 +80,7 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestLookupNull()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null);
AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Lookup"] = null);
AddAssert("Check lookup null", () =>
{
@@ -88,7 +92,7 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestColourLookup()
{
AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red);
AddAssert("Check colour lookup", () => requester.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
}
@@ -101,7 +105,7 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestWrongColourType()
{
AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red);
AddAssert("perform incorrect lookup", () =>
{
@@ -127,26 +131,51 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestEmptyComboColoursNoFallback()
{
AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours(
AddStep("Add custom combo colours to user skin", () => userSource.Configuration.AddComboColours(
new Color4(100, 150, 200, 255),
new Color4(55, 110, 166, 255),
new Color4(75, 125, 175, 255)
));
AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false);
AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false);
AddAssert("Check retrieved combo colours from source1", () =>
requester.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false);
AddAssert("Check retrieved combo colours from user skin", () =>
requester.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false);
}
[Test]
public void TestLegacyVersionLookup()
public void TestNullBeatmapVersionFallsBackToUserSkin()
{
AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m);
AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null);
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
}
[Test]
public void TestSetBeatmapVersionNoFallback()
{
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m);
}
[Test]
public void TestNullBeatmapAndUserVersionFallsBackToLatest()
{
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = null);
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup",
() => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION);
}
[Test]
public void TestIniWithNoVersionFallsBackTo1()
{
AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream())));
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m);
}
public enum LookupType
{
Test
@@ -159,14 +188,22 @@ namespace osu.Game.Tests.Skins
Test3
}
public class SkinSource : LegacySkin
public class UserSkinSource : LegacySkin
{
public SkinSource()
public UserSkinSource()
: base(new SkinInfo(), null, null, string.Empty)
{
}
}
public class BeatmapSkinSource : LegacyBeatmapSkin
{
public BeatmapSkinSource()
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null)
{
}
}
public class SkinRequester : Drawable, ISkin
{
private ISkinSource skin;
@@ -26,16 +26,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
{
new BreakPeriod
{
StartTime = 1000,
EndTime = 5000,
},
new BreakPeriod
{
StartTime = 6000,
EndTime = 13500,
},
new BreakPeriod(1000, 5000),
new BreakPeriod(6000, 13500),
};
public TestSceneBreakTracker()
@@ -70,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoEffectsBreak()
{
var shortBreak = new BreakPeriod { EndTime = 500 };
var shortBreak = new BreakPeriod(0, 500);
setClock(true);
loadBreaksStep("short break", new[] { shortBreak });
@@ -127,13 +119,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addShowBreakStep(double seconds)
{
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod>
AddStep($"show '{seconds}s' break", () =>
{
new BreakPeriod
breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod>
{
StartTime = Clock.CurrentTime,
EndTime = Clock.CurrentTime + seconds * 1000,
}
new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000)
};
});
}
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Position = new Vector2(0, 25),
Position = new Vector2(-5, 25),
Current = { BindTarget = modSelect.SelectedMods }
}
};
@@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Screens.Ladder
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
protected override void OnDrag(DragEvent e)
{
this.MoveTo(target += e.Delta, 1000, Easing.OutQuint);
@@ -305,12 +305,9 @@ namespace osu.Game.Beatmaps.Formats
case LegacyEventType.Break:
double start = getOffsetTime(Parsing.ParseDouble(split[1]));
double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])));
var breakEvent = new BreakPeriod
{
StartTime = start,
EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])))
};
var breakEvent = new BreakPeriod(start, end);
if (!breakEvent.HasEffect)
return;
+11
View File
@@ -32,6 +32,17 @@ namespace osu.Game.Beatmaps.Timing
/// </summary>
public bool HasEffect => Duration >= MIN_BREAK_DURATION;
/// <summary>
/// Constructs a new break period.
/// </summary>
/// <param name="startTime">The start time of the break period.</param>
/// <param name="endTime">The end time of the break period.</param>
public BreakPeriod(double startTime, double endTime)
{
StartTime = startTime;
EndTime = endTime;
}
/// <summary>
/// Whether this break contains a specified time.
/// </summary>
+15 -7
View File
@@ -7,23 +7,31 @@ using osu.Game.Scoring;
namespace osu.Game.Online.Leaderboards
{
public class UpdateableRank : ModelBackedDrawable<ScoreRank>
public class UpdateableRank : ModelBackedDrawable<ScoreRank?>
{
public ScoreRank Rank
public ScoreRank? Rank
{
get => Model;
set => Model = value;
}
public UpdateableRank(ScoreRank rank)
public UpdateableRank(ScoreRank? rank)
{
Rank = rank;
}
protected override Drawable CreateDrawable(ScoreRank rank) => new DrawableRank(rank)
protected override Drawable CreateDrawable(ScoreRank? rank)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
if (rank.HasValue)
{
return new DrawableRank(rank.Value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
}
return null;
}
}
}
+1 -1
View File
@@ -270,7 +270,7 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new Video(videoStream, false)
InternalChild = new Video(videoStream)
{
RelativeSizeAxes = Axes.Both,
};
+1 -1
View File
@@ -294,7 +294,7 @@ namespace osu.Game.Screens.Menu
{
new PopupDialogOkButton
{
Text = @"Good bye",
Text = @"Goodbye",
Action = confirm
},
new PopupDialogCancelButton
@@ -161,7 +161,7 @@ namespace osu.Game.Screens.Multi
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Spacing = new Vector2(15, 0),
Children = new Drawable[]
{
authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both },
+8 -9
View File
@@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD
}
}
protected readonly FillFlowContainer<ModIcon> IconsContainer;
private readonly FillFlowContainer<ModIcon> iconsContainer;
private readonly OsuSpriteText unrankedText;
public ModDisplay()
@@ -50,13 +50,12 @@ namespace osu.Game.Screens.Play.HUD
Children = new Drawable[]
{
IconsContainer = new ReverseChildIDFillFlowContainer<ModIcon>
iconsContainer = new ReverseChildIDFillFlowContainer<ModIcon>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Left = 10, Right = 10 },
},
unrankedText = new OsuSpriteText
{
@@ -69,11 +68,11 @@ namespace osu.Game.Screens.Play.HUD
Current.ValueChanged += mods =>
{
IconsContainer.Clear();
iconsContainer.Clear();
foreach (Mod mod in mods.NewValue)
{
IconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) });
iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) });
}
if (IsLoaded)
@@ -92,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete();
appearTransform();
IconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
}
private void appearTransform()
@@ -104,20 +103,20 @@ namespace osu.Game.Screens.Play.HUD
expand();
using (IconsContainer.BeginDelayedSequence(1200))
using (iconsContainer.BeginDelayedSequence(1200))
contract();
}
private void expand()
{
if (ExpansionMode != ExpansionMode.AlwaysContracted)
IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint);
iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint);
}
private void contract()
{
if (ExpansionMode != ExpansionMode.AlwaysExpanded)
IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint);
iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint);
}
protected override bool OnHover(HoverEvent e)
+1 -1
View File
@@ -285,7 +285,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 20, Right = 10 },
Margin = new MarginPadding { Top = 20, Right = 20 },
};
protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows);
@@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Expanded
private readonly ScoreInfo score;
private readonly List<StatisticDisplay> statisticDisplays = new List<StatisticDisplay>();
private FillFlowContainer starAndModDisplay;
private RollingCounter<long> scoreCounter;
/// <summary>
@@ -119,11 +122,12 @@ namespace osu.Game.Screens.Ranking.Expanded
Alpha = 0,
AlwaysPresent = true
},
new FillFlowContainer
starAndModDisplay = new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
new StarRatingDisplay(beatmap)
@@ -131,15 +135,6 @@ namespace osu.Game.Screens.Ranking.Expanded
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
new ModDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
DisplayUnrankedText = false,
ExpansionMode = ExpansionMode.AlwaysExpanded,
Scale = new Vector2(0.5f),
Current = { Value = score.Mods }
}
}
},
new FillFlowContainer
@@ -214,6 +209,19 @@ namespace osu.Game.Screens.Ranking.Expanded
}
}
};
if (score.Mods.Any())
{
starAndModDisplay.Add(new ModDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
DisplayUnrankedText = false,
ExpansionMode = ExpansionMode.AlwaysExpanded,
Scale = new Vector2(0.5f),
Current = { Value = score.Mods }
});
}
}
protected override void LoadComplete()
@@ -122,10 +122,24 @@ namespace osu.Game.Screens.Select.Carousel
},
}
},
starCounter = new StarCounter
new FillFlowContainer
{
Current = (float)beatmap.StarDifficulty,
Scale = new Vector2(0.8f),
Direction = FillDirection.Horizontal,
Spacing = new Vector2(4, 0),
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new TopLocalRank(beatmap)
{
Scale = new Vector2(0.8f),
Size = new Vector2(40, 20)
},
starCounter = new StarCounter
{
Current = (float)beatmap.StarDifficulty,
Scale = new Vector2(0.8f),
}
}
}
}
}
@@ -0,0 +1,90 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets;
using osu.Game.Scoring;
namespace osu.Game.Screens.Select.Carousel
{
public class TopLocalRank : UpdateableRank
{
private readonly BeatmapInfo beatmap;
[Resolved]
private ScoreManager scores { get; set; }
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
public TopLocalRank(BeatmapInfo beatmap)
: base(null)
{
this.beatmap = beatmap;
}
[BackgroundDependencyLoader]
private void load()
{
scores.ItemAdded += scoreChanged;
scores.ItemRemoved += scoreChanged;
ruleset.ValueChanged += _ => fetchAndLoadTopScore();
fetchAndLoadTopScore();
}
private void scoreChanged(ScoreInfo score)
{
if (score.BeatmapInfoID == beatmap.ID)
fetchAndLoadTopScore();
}
private ScheduledDelegate scheduledRankUpdate;
private void fetchAndLoadTopScore()
{
var rank = fetchTopScore()?.Rank;
scheduledRankUpdate = Schedule(() =>
{
Rank = rank;
// Required since presence is changed via IsPresent override
Invalidate(Invalidation.Presence);
});
}
// We're present if a rank is set, or if there is a pending rank update (IsPresent = true is required for the scheduler to run).
public override bool IsPresent => base.IsPresent && (Rank != null || scheduledRankUpdate?.Completed == false);
private ScoreInfo fetchTopScore()
{
if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null)
return null;
return scores.QueryScores(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending)
.OrderByDescending(s => s.TotalScore)
.FirstOrDefault();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (scores != null)
{
scores.ItemAdded -= scoreChanged;
scores.ItemRemoved -= scoreChanged;
}
}
}
}
+4 -14
View File
@@ -27,18 +27,19 @@ namespace osu.Game.Screens.Select
}
protected readonly OsuSpriteText MultiplierText;
private readonly FooterModDisplay modDisplay;
private readonly ModDisplay modDisplay;
private Color4 lowMultiplierColour;
private Color4 highMultiplierColour;
public FooterButtonMods()
{
ButtonContentContainer.Add(modDisplay = new FooterModDisplay
ButtonContentContainer.Add(modDisplay = new ModDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
DisplayUnrankedText = false,
Scale = new Vector2(0.8f)
Scale = new Vector2(0.8f),
ExpansionMode = ExpansionMode.AlwaysContracted,
});
ButtonContentContainer.Add(MultiplierText = new OsuSpriteText
{
@@ -84,16 +85,5 @@ namespace osu.Game.Screens.Select
else
modDisplay.FadeOut();
}
private class FooterModDisplay : ModDisplay
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false;
public FooterModDisplay()
{
ExpansionMode = ExpansionMode.AlwaysContracted;
IconsContainer.Margin = new MarginPadding();
}
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ namespace osu.Game.Skinning
new Color4(242, 24, 57, 255)
);
Configuration.LegacyVersion = 2.0m;
Configuration.LegacyVersion = 2.7m;
}
public static SkinInfo Info { get; } = new SkinInfo
+15
View File
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
@@ -18,6 +19,20 @@ namespace osu.Game.Skinning
Configuration.AllowDefaultComboColoursFallback = false;
}
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version:
if (Configuration.LegacyVersion is decimal version)
return SkinUtils.As<TValue>(new Bindable<decimal>(version));
return null;
}
return base.GetConfig<TLookup, TValue>(lookup);
}
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
}
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.Formats;
using osuTK.Graphics;
@@ -24,6 +25,8 @@ namespace osu.Game.Skinning
public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>();
public Dictionary<string, string> ImageLookups = new Dictionary<string, string>();
public readonly float[] ColumnLineWidth;
public readonly float[] ColumnSpacing;
public readonly float[] ColumnWidth;
@@ -45,5 +48,13 @@ namespace osu.Game.Skinning
ColumnLineWidth.AsSpan().Fill(2);
ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE);
}
private float? minimumColumnWidth;
public float MinimumColumnWidth
{
get => minimumColumnWidth ?? ColumnWidth.Min();
set => minimumColumnWidth = value;
}
}
}
@@ -36,6 +36,10 @@ namespace osu.Game.Skinning
HoldNoteBodyImage,
ExplosionImage,
ExplosionScale,
ColumnLineColour
ColumnLineColour,
JudgementLineColour,
ColumnBackgroundColour,
ColumnLightColour,
MinimumColumnWidth
}
}
+20 -6
View File
@@ -71,12 +71,6 @@ namespace osu.Game.Skinning
{
var pair = SplitKeyVal(line);
if (pair.Key.StartsWith("Colour"))
{
HandleColours(currentConfig, line);
continue;
}
switch (pair.Key)
{
case "ColumnLineWidth":
@@ -106,8 +100,28 @@ namespace osu.Game.Skinning
case "LightingNWidth":
parseArrayValue(pair.Value, currentConfig.ExplosionWidth);
break;
case "WidthForNoteHeightScale":
float minWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
if (minWidth > 0)
currentConfig.MinimumColumnWidth = minWidth;
break;
case string _ when pair.Key.StartsWith("Colour"):
HandleColours(currentConfig, line);
break;
case string _ when pair.Key.StartsWith("NoteImage"):
currentConfig.ImageLookups[pair.Key] = pair.Value;
break;
case string _ when pair.Key.StartsWith("KeyImage"):
currentConfig.ImageLookups[pair.Key] = pair.Value;
break;
}
}
pendingLines.Clear();
}
private void parseArrayValue(string value, float[] output)
+67 -20
View File
@@ -71,7 +71,7 @@ namespace osu.Game.Skinning
}
}
else
Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION };
Configuration = new LegacySkinConfiguration();
}
if (storage != null)
@@ -88,7 +88,8 @@ namespace osu.Game.Skinning
// todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution.
hasKeyTexture = new Lazy<bool>(() => this.GetAnimation(
lookupForMania<string>(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null);
lookupForMania<string>(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true,
true) != null);
}
protected override void Dispose(bool isDisposing)
@@ -122,10 +123,7 @@ namespace osu.Game.Skinning
switch (legacy)
{
case LegacySkinConfiguration.LegacySetting.Version:
if (Configuration.LegacyVersion is decimal version)
return SkinUtils.As<TValue>(new Bindable<decimal>(version));
break;
return SkinUtils.As<TValue>(new Bindable<decimal>(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION));
}
break;
@@ -207,6 +205,44 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.ColumnLineColour:
return SkinUtils.As<TValue>(getCustomColour(existing, "ColourColumnLine"));
case LegacyManiaSkinConfigurationLookups.JudgementLineColour:
return SkinUtils.As<TValue>(getCustomColour(existing, "ColourJudgementLine"));
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn + 1}"));
case LegacyManiaSkinConfigurationLookups.ColumnLightColour:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn + 1}"));
case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth:
return SkinUtils.As<TValue>(new Bindable<float>(existing.MinimumColumnWidth));
case LegacyManiaSkinConfigurationLookups.NoteImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}"));
case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}H"));
case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}T"));
case LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L"));
case LegacyManiaSkinConfigurationLookups.KeyImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}"));
case LegacyManiaSkinConfigurationLookups.KeyImageDown:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As<TValue>(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}D"));
}
return null;
@@ -215,6 +251,9 @@ namespace osu.Game.Skinning
private IBindable<Color4> getCustomColour(IHasCustomColours source, string lookup)
=> source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable<Color4>(col) : null;
private IBindable<string> getManiaImage(LegacyManiaSkinConfiguration source, string lookup)
=> source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable<string>(image) : null;
public override Drawable GetDrawableComponent(ISkinComponent component)
{
switch (component)
@@ -243,21 +282,25 @@ namespace osu.Game.Skinning
public override Texture GetTexture(string componentName)
{
componentName = getFallbackName(componentName);
float ratio = 2;
var texture = Textures?.Get($"{componentName}@2x");
if (texture == null)
foreach (var name in getFallbackNames(componentName))
{
ratio = 1;
texture = Textures?.Get(componentName);
float ratio = 2;
var texture = Textures?.Get($"{name}@2x");
if (texture == null)
{
ratio = 1;
texture = Textures?.Get(name);
}
if (texture == null)
continue;
texture.ScaleAdjust = ratio;
return texture;
}
if (texture != null)
texture.ScaleAdjust = ratio;
return texture;
return null;
}
public override SampleChannel GetSample(ISampleInfo sampleInfo)
@@ -277,10 +320,14 @@ namespace osu.Game.Skinning
return null;
}
private string getFallbackName(string componentName)
private IEnumerable<string> getFallbackNames(string componentName)
{
// May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin.
yield return componentName;
// Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle").
string lastPiece = componentName.Split('/').Last();
return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece;
yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece;
}
}
}
+7
View File
@@ -52,5 +52,12 @@ namespace osu.Game.Skinning
base.ParseLine(skin, section, line);
}
protected override LegacySkinConfiguration CreateTemplateObject()
{
var config = base.CreateTemplateObject();
config.LegacyVersion = 1.0m;
return config;
}
}
}
+2 -2
View File
@@ -14,7 +14,7 @@ namespace osu.Game.Skinning
public static class LegacySkinExtensions
{
public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-",
bool startAtCurrentTime = false, double? frameLength = null)
bool startAtCurrentTime = true, double? frameLength = null)
{
Texture texture;
@@ -72,7 +72,7 @@ namespace osu.Game.Skinning
if (timeReference != null)
{
Clock = timeReference.Clock;
PlaybackPosition = timeReference.AnimationStartTime - timeReference.Clock.CurrentTime;
PlaybackPosition = timeReference.Clock.CurrentTime - timeReference.AnimationStartTime;
}
}
}
+2 -1
View File
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores;
using osu.Game.Database;
@@ -27,7 +28,7 @@ namespace osu.Game.Skinning
foreach (var filename in base.GetFilenames(name))
{
var path = getPathForFile(filename);
var path = getPathForFile(filename.ToStandardisedPath());
if (path != null)
yield return path;
}
@@ -46,7 +46,6 @@ namespace osu.Game.Storyboards.Drawables
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
PlaybackPosition = Video.StartTime
};
}
@@ -56,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables
if (video == null) return;
video.PlaybackPosition = Clock.CurrentTime - Video.StartTime;
using (video.BeginAbsoluteSequence(0))
video.FadeIn(500);
}
+6 -2
View File
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -31,10 +32,13 @@ namespace osu.Game.Tests.Visual
{
}
// Required to be part of the per-ruleset implementation to construct the newer version of the Ruleset.
protected abstract Ruleset CreateRulesetForSkinProvider();
[BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager)
{
var dllStore = new DllResourceStore(GetType().Assembly);
var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly);
metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), audio, true);
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
@@ -103,7 +107,7 @@ namespace osu.Game.Tests.Visual
{
new OutlineBox { Alpha = autoSize ? 1 : 0 },
mainProvider.WithChild(
new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap))
new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap))
{
Child = created,
RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None,
+2 -2
View File
@@ -22,10 +22,10 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.403.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.407.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
<PackageReference Include="Sentry" Version="2.1.1" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="SharpCompress" Version="0.25.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>
+3 -3
View File
@@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.403.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.407.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
</ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
@@ -79,8 +79,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.403.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.407.1" />
<PackageReference Include="SharpCompress" Version="0.25.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />