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

Compare commits

...

243 Commits

171 changed files with 2358 additions and 551 deletions
-2
View File
@@ -2,7 +2,5 @@ clone_depth: 1
version: '{branch}-{build}'
image: Previous Visual Studio 2017
test: off
install:
- cmd: git submodule update --init --recursive --depth=5
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
+10
View File
@@ -0,0 +1,10 @@
clone_depth: 1
version: '{build}'
image: Previous Visual Studio 2017
test: off
skip_non_tags: true
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
deploy:
- provider: Environment
name: nuget
+2 -2
View File
@@ -60,7 +60,7 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.830.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.830.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.904.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.905.0" />
</ItemGroup>
</Project>
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osuTK.Graphics;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -82,11 +83,11 @@ namespace osu.Game.Rulesets.Catch.Tests
remove { }
}
public Drawable GetDrawableComponent(string componentName)
public Drawable GetDrawableComponent(ISkinComponent component)
{
switch (componentName)
switch (component.LookupName)
{
case "Play/Catch/fruit-catcher-idle":
case "Gameplay/catch/fruit-catcher-idle":
return new CatcherCustomSkin();
}
@@ -99,8 +100,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public Texture GetTexture(string componentName) =>
throw new NotImplementedException();
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration =>
throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
}
}
}
@@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
+3 -1
View File
@@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Catch
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
public const string SHORT_NAME = "fruits";
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
@@ -117,7 +119,7 @@ namespace osu.Game.Rulesets.Catch
public override string Description => "osu!catch";
public override string ShortName => "fruits";
public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
@@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch
{
public class CatchSkinComponent : GameplaySkinComponent<CatchSkinComponents>
{
public CatchSkinComponent(CatchSkinComponents component)
: base(component)
{
}
protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME;
protected override string ComponentName => Component.ToString().ToLower();
}
}
@@ -0,0 +1,9 @@
// 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.
namespace osu.Game.Rulesets.Catch
{
public enum CatchSkinComponents
{
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new SkinnableSprite(@"Play/Catch/fruit-catcher-idle")
InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle")
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
@@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
@@ -11,7 +11,9 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mania.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty
{
@@ -32,12 +34,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (beatmap.HitObjects.Count == 0)
return new ManiaDifficultyAttributes { Mods = mods, Skills = skills };
HitWindows hitWindows = new ManiaHitWindows();
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
return new ManiaDifficultyAttributes
{
StarRating = difficultyValue(skills) * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
GreatHitWindow = (int)(hitWindows.Great / 2) / clockRate,
Skills = skills
};
}
+3 -1
View File
@@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Mania
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
public const string SHORT_NAME = "mania";
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
@@ -163,7 +165,7 @@ namespace osu.Game.Rulesets.Mania
public override string Description => "osu!mania";
public override string ShortName => "mania";
public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
@@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania
{
public class ManiaSkinComponent : GameplaySkinComponent<ManiaSkinComponents>
{
public ManiaSkinComponent(ManiaSkinComponents component)
: base(component)
{
}
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
protected override string ComponentName => Component.ToString().ToLower();
}
}
@@ -0,0 +1,9 @@
// 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.
namespace osu.Game.Rulesets.Mania
{
public enum ManiaSkinComponents
{
}
}
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -209,6 +210,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
// Factor in the release lenience
timeOffset /= release_window_lenience;
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -52,6 +53,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
@@ -5,6 +5,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Mania.Objects
@@ -99,5 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects
}
public override Judgement CreateJudgement() => new HoldNoteJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
@@ -3,6 +3,7 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Objects
{
@@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public class HoldNoteTick : ManiaHitObject
{
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
new DrawableOsuJudgement(new JudgementResult(null) { Type = result }, null)
new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
@@ -13,8 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(),
drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay);
return drawableHitObject;
}
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
@@ -119,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests
this.identifier = identifier;
}
public Drawable GetDrawableComponent(string componentName)
public Drawable GetDrawableComponent(ISkinComponent component)
{
if (!enabled) return null;
@@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => default;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
public event Action SourceChanged;
@@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
@@ -34,8 +35,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
HitWindows hitWindows = new OsuHitWindows();
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate;
double hitWindowGreat = (int)(hitWindows.Great / 2) / clockRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count;
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu.Judgements
{
@@ -9,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Judgements
{
public ComboResult ComboType;
public OsuJudgementResult(Judgement judgement)
: base(judgement)
public OsuJudgementResult(HitObject hitObject, Judgement judgement)
: base(hitObject, judgement)
{
}
}
+1 -1
View File
@@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Mods
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get("Play/osu/blinds-panel");
Texture = textures.Get("Gameplay/osu/blinds-panel");
}
}
}
+7 -1
View File
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
@@ -38,7 +39,12 @@ namespace osu.Game.Rulesets.Osu.Mods
if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit)
continue;
requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime);
if (osuHit is DrawableHitCircle && osuHit.IsHovered)
{
Debug.Assert(osuHit.HitObject.HitWindows != null);
requiresHit |= osuHit.HitObject.HitWindows.CanBeHit(relativetime);
}
requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner;
}
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
Origin = Anchor.Centre;
Child = new SkinnableDrawable("Play/osu/followpoint", _ => new Container
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container
{
Masking = true,
AutoSizeAxes = Axes.Both,
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
mainContent = new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
@@ -87,6 +88,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
@@ -119,6 +122,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
Debug.Assert(HitObject.HitWindows != null);
switch (state)
{
case ArmedState.Idle:
@@ -41,6 +41,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
}
}
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon
InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.Solid.ChevronRight,
@@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -166,12 +167,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.SkinChanged(skin, allowFallback);
Body.BorderSize = skin.GetValue<SkinConfiguration, float?>(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE;
sliderPathRadius = skin.GetValue<SkinConfiguration, float?>(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
Body.BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
sliderPathRadius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
updatePathRadius();
Body.AccentColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value;
Body.BorderColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White;
Body.AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
Body.BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
}
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Origin = Anchor.Centre;
InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/sliderscorepoint", _ => new CircularContainer
InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
{
Masking = true,
Origin = Anchor.Centre,
@@ -31,13 +31,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private class SkinnableApproachCircle : SkinnableSprite
{
public SkinnableApproachCircle()
: base("Play/osu/approachcircle")
: base("Gameplay/osu/approachcircle")
{
}
protected override Drawable CreateDefault(string name)
protected override Drawable CreateDefault(ISkinComponent component)
{
var drawable = base.CreateDefault(name);
var drawable = base.CreateDefault(component);
// account for the sprite being used for the default approach circle being taken from stable,
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Texture = textures.Get(@"Play/osu/disc"),
Texture = textures.Get(@"Gameplay/osu/disc"),
},
new TrianglesPiece
{
@@ -3,7 +3,6 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@@ -20,12 +19,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Blending = BlendingParameters.Additive;
Alpha = 0;
Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece
Child = new TrianglesPiece
{
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
Alpha = 0.2f,
}, s => s.GetTexture("Play/osu/hitcircle") == null);
};
}
}
}
@@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Shapes;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Blending = BlendingParameters.Additive;
Alpha = 0;
Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer
Child = new CircularContainer
{
Masking = true,
RelativeSizeAxes = Axes.Both,
@@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both
}
}, s => s.GetTexture("Play/osu/hitcircle") == null);
};
}
}
}
@@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -22,14 +21,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Child = new SkinnableDrawable("Play/osu/ring-glow", name => new Sprite
Child = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Texture = textures.Get(name),
Texture = textures.Get("Gameplay/osu/ring-glow"),
Blending = BlendingParameters.Additive,
Alpha = 0.5f
}, s => s.GetTexture("Play/osu/hitcircle") == null);
};
}
}
}
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new Drawable[]
{
new SkinnableDrawable("Play/osu/number-glow", name => new CircularContainer
new CircularContainer
{
Masking = true,
Origin = Anchor.Centre,
@@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Colour = Color4.White.Opacity(0.5f),
},
Child = new Box()
}, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
},
number = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
@@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
InternalChild = new SkinnableDrawable("Play/osu/hitcircleoverlay", _ => new Container
InternalChild = new Container
{
Masking = true,
CornerRadius = Size.X / 2,
@@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
RelativeSizeAxes = Axes.Both
}
}
});
};
}
}
}
@@ -11,6 +11,7 @@ using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Skinning;
using osuTK.Graphics;
using osu.Game.Skinning;
using osuTK;
@@ -43,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new DefaultFollowCircle()),
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
},
new CircularContainer
{
@@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Child = new SkinnableDrawable("Play/osu/sliderball", _ => new DefaultSliderBall()),
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()),
}
}
};
@@ -218,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both;
float radius = skin.GetValue<SkinConfiguration, float?>(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
float radius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
InternalChild = new CircularContainer
{
@@ -5,6 +5,7 @@ using System;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects
@@ -28,5 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
+2
View File
@@ -229,5 +229,7 @@ namespace osu.Game.Rulesets.Osu.Objects
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
@@ -23,5 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
@@ -4,6 +4,7 @@
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects
@@ -30,5 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
+3
View File
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects
@@ -31,5 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
+4 -2
View File
@@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Osu
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
public const string SHORT_NAME = "osu";
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
@@ -161,11 +163,11 @@ namespace osu.Game.Rulesets.Osu
public override string Description => "osu!";
public override string ShortName => "osu";
public override string ShortName => SHORT_NAME;
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkin(source);
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
public override int? LegacyID => 0;
+19
View File
@@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu
{
public class OsuSkinComponent : GameplaySkinComponent<OsuSkinComponents>
{
public OsuSkinComponent(OsuSkinComponents component)
: base(component)
{
}
protected override string RulesetPrefix => OsuRuleset.SHORT_NAME;
protected override string ComponentName => Component.ToString().ToLower();
}
}
@@ -0,0 +1,18 @@
// 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.
namespace osu.Game.Rulesets.Osu
{
public enum OsuSkinComponents
{
HitCircle,
FollowPoint,
Cursor,
SliderScorePoint,
ApproachCircle,
ReverseArrow,
HitCircleText,
SliderFollowCircle,
SliderBall
}
}
@@ -6,9 +6,11 @@ using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Scoring;
@@ -36,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Replays
/// </summary>
private readonly double reactionTime;
private readonly HitWindows defaultHitWindows;
/// <summary>
/// What easing to use when moving between hitobjects
/// </summary>
@@ -50,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Replays
{
// Already superhuman, but still somewhat realistic
reactionTime = ApplyModsToRate(100);
defaultHitWindows = new OsuHitWindows();
defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
}
#endregion
@@ -91,21 +98,49 @@ namespace osu.Game.Rulesets.Osu.Replays
{
double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime;
HitWindows hitWindows = null;
switch (h)
{
case HitCircle hitCircle:
hitWindows = hitCircle.HitWindows;
break;
case Slider slider:
hitWindows = slider.TailCircle.HitWindows;
break;
case Spinner _:
hitWindows = defaultHitWindows;
break;
}
Debug.Assert(hitWindows != null);
// Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Miss) > endTime + hitWindows.HalfWindowFor(HitResult.Meh) + 50)
{
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
if (!(prev is Spinner) && h.StartTime - endTime < 1000)
AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner))
AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
else if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Meh) > endTime + hitWindows.HalfWindowFor(HitResult.Meh) + 50)
{
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
if (!(prev is Spinner) && h.StartTime - endTime < 1000)
AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner))
AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good) > endTime + h.HitWindows.HalfWindowFor(HitResult.Good) + 50)
else if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Good) > endTime + hitWindows.HalfWindowFor(HitResult.Good) + 50)
{
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
if (!(prev is Spinner) && h.StartTime - endTime < 1000)
AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner))
AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
}
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
}
}
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
public override HitWindows CreateHitWindows() => new OsuHitWindows();
}
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, DrawableHitObject drawableObject)
{
animationContent.Colour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White;
animationContent.Colour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
InternalChildren = new[]
{
@@ -3,21 +3,19 @@
using System;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning
{
public class OsuLegacySkin : ISkin
public class OsuLegacySkinTransformer : ISkin
{
private readonly ISkin source;
private Lazy<SkinConfiguration> configuration;
private Lazy<bool> hasHitCircle;
/// <summary>
@@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
/// </summary>
private const float legacy_circle_radius = 64 - 5;
public OsuLegacySkin(ISkinSource source)
public OsuLegacySkinTransformer(ISkinSource source)
{
this.source = source;
@@ -37,32 +35,20 @@ namespace osu.Game.Rulesets.Osu.Skinning
private void sourceChanged()
{
// these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source.
configuration = new Lazy<SkinConfiguration>(() =>
{
var config = new SkinConfiguration();
if (hasHitCircle.Value)
config.SliderPathRadius = legacy_circle_radius;
// defaults should only be applied for non-beatmap skins (which are parsed via this constructor).
config.CustomColours["SliderBall"] =
source.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null)
?? new Color4(2, 170, 255, 255);
return config;
});
hasHitCircle = new Lazy<bool>(() => source.GetTexture("hitcircle") != null);
}
public Drawable GetDrawableComponent(string componentName)
public Drawable GetDrawableComponent(ISkinComponent component)
{
switch (componentName)
{
case "Play/osu/sliderfollowcircle":
return this.GetAnimation(componentName, true, true);
if (!(component is OsuSkinComponent osuComponent))
return null;
case "Play/osu/sliderball":
switch (osuComponent.Component)
{
case OsuSkinComponents.SliderFollowCircle:
return this.GetAnimation("sliderfollowcircle", true, true);
case OsuSkinComponents.SliderBall:
var sliderBallContent = this.GetAnimation("sliderb", true, true, "");
if (sliderBallContent != null)
@@ -80,26 +66,25 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
case "Play/osu/hitcircle":
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
return null;
case "Play/osu/cursor":
case OsuSkinComponents.Cursor:
if (source.GetTexture("cursor") != null)
return new LegacyCursor();
return null;
case "Play/osu/number-text":
string font = GetValue<SkinConfiguration, string>(config => config.HitCircleFont);
var overlap = GetValue<SkinConfiguration, float>(config => config.HitCircleOverlap);
case OsuSkinComponents.HitCircleText:
var font = GetConfig<OsuSkinConfiguration, string>(OsuSkinConfiguration.HitCircleFont)?.Value ?? "default";
var overlap = GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0;
return !hasFont(font)
? null
: new LegacySpriteText(this, font)
: new LegacySpriteText(source, font)
{
// Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size
Scale = new Vector2(0.96f),
@@ -110,13 +95,33 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
}
public Texture GetTexture(string componentName) => null;
public Texture GetTexture(string componentName) => source.GetTexture(componentName);
public SampleChannel GetSample(ISampleInfo sample) => null;
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration
=> configuration.Value is TConfiguration conf ? query.Invoke(conf) : default;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case OsuSkinColour colour:
return source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null;
case OsuSkinConfiguration osuLookup:
switch (osuLookup)
{
case OsuSkinConfiguration.SliderPathRadius:
if (hasHitCircle.Value)
return SkinUtils.As<TValue>(new BindableFloat(legacy_circle_radius));
break;
}
break;
}
return source.GetConfig<TLookup, TValue>(lookup);
}
private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null;
}
}
@@ -0,0 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Rulesets.Osu.Skinning
{
public enum OsuSkinColour
{
SliderTrackOverride,
SliderBorder,
SliderBall
}
}
@@ -0,0 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Rulesets.Osu.Skinning
{
public enum OsuSkinConfiguration
{
HitCircleFont,
HitCircleOverlap,
SliderBorderSize,
SliderPathRadius,
CursorExpand,
}
}
+3 -2
View File
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
cursorExpand = skin.GetValue<SkinConfiguration, bool>(s => s.CursorExpand ?? true);
cursorExpand = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorExpand)?.Value ?? true;
}
[BackgroundDependencyLoader]
@@ -49,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Child = scaleTarget = new SkinnableDrawable("Play/osu/cursor", _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
Child = scaleTarget = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@@ -0,0 +1,74 @@
// 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.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
public class TestSceneSwellJudgements : PlayerTestScene
{
protected new TestPlayer Player => (TestPlayer)base.Player;
public TestSceneSwellJudgements()
: base(new TaikoRuleset())
{
}
[Test]
public void TestZeroTickTimeOffsets()
{
AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted);
AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.Judgement is TaikoSwellTickJudgement).All(r => r.TimeOffset == 0));
}
protected override bool Autoplay => true;
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap<TaikoHitObject>
{
BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo },
HitObjects =
{
new Swell
{
StartTime = 1000,
Duration = 1000,
}
}
};
return beatmap;
}
protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer();
protected class TestPlayer : Player
{
public readonly List<JudgementResult> Results = new List<JudgementResult>();
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public TestPlayer()
: base(false, false)
{
}
[BackgroundDependencyLoader]
private void load()
{
ScoreProcessor.NewJudgement += r => Results.Add(r);
}
}
}
}
@@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
}
private void addStrongHitJudgement(bool kiai)
@@ -159,13 +159,13 @@ namespace osu.Game.Rulesets.Taiko.Tests
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great });
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
}
private void addMissJudgement()
{
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss });
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
}
private void addBarLine(bool major, double delay = scroll_time)
@@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
@@ -8,6 +8,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
using osu.Game.Rulesets.Taiko.Mods;
@@ -29,12 +30,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (beatmap.HitObjects.Count == 0)
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
HitWindows hitWindows = new TaikoHitWindows();
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
return new TaikoDifficultyAttributes
{
StarRating = skills.Single().DifficultyValue() * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
GreatHitWindow = (int)(hitWindows.Great / 2) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
Skills = skills
};
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
@@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
@@ -94,6 +97,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
Debug.Assert(HitObject.HitWindows != null);
switch (state)
{
case ArmedState.Idle:
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@@ -14,7 +15,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
}
public void TriggerResult(HitResult type) => ApplyResult(r => r.Type = type);
protected override void UpdateInitialTransforms() => this.FadeOut();
public void TriggerResult(HitResult type)
{
HitObject.StartTime = Time.Current;
ApplyResult(r => r.Type = type);
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum
protected override IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
protected override string SampleNamespace => "Taiko";
protected override string SampleNamespace => "taiko";
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
@@ -6,6 +6,7 @@ using System;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -86,5 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
}
public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -25,5 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public double HitWindow => TickSpacing / 2;
public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -9,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public class StrongHitObject : TaikoHitObject
{
public override Judgement CreateJudgement() => new TaikoStrongJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
+3
View File
@@ -4,6 +4,7 @@
using System;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -33,5 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
}
public override Judgement CreateJudgement() => new TaikoSwellJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -9,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public class SwellTick : TaikoHitObject
{
public override Judgement CreateJudgement() => new TaikoSwellTickJudgement();
protected override HitWindows CreateHitWindows() => null;
}
}
+3 -1
View File
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko
public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods) => new DrawableTaikoRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
public const string SHORT_NAME = "taiko";
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
@@ -116,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko
public override string Description => "osu!taiko";
public override string ShortName => "taiko";
public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
@@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko
{
public class TaikoSkinComponent : GameplaySkinComponent<TaikoSkinComponents>
{
public TaikoSkinComponent(TaikoSkinComponents component)
: base(component)
{
}
protected override string RulesetPrefix => TaikoRuleset.SHORT_NAME;
protected override string ComponentName => Component.ToString().ToLower();
}
}
@@ -0,0 +1,9 @@
// 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.
namespace osu.Game.Rulesets.Taiko
{
public enum TaikoSkinComponents
{
}
}
+4 -4
View File
@@ -132,10 +132,10 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load(TextureStore textures, OsuColour colours)
{
rim.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer");
rimHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer-hit");
centre.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner");
centreHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner-hit");
rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer");
rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit");
centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner");
centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit");
rimHit.Colour = colours.Blue;
centreHit.Colour = colours.Pink;
@@ -119,6 +119,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
[Test]
public void TestOldFormatLinkWithBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (tricky (one))[https://osu.ppy.sh]!" });
Assert.AreEqual("This is a tricky (one)!", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(12, result.Links[0].Length);
}
[Test]
public void TestOldFormatLinkWithEscapedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is (another loose bracket \\))[https://osu.ppy.sh]." });
Assert.AreEqual("This is another loose bracket ).", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(8, result.Links[0].Index);
Assert.AreEqual(23, result.Links[0].Length);
}
[Test]
public void TestOldFormatWithBackslashes()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This link (should end with a backslash \\)[https://osu.ppy.sh]." });
Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(29, result.Links[0].Length);
}
[Test]
public void TestOldFormatLinkWithEscapedAndBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (\\)super\\(\\( tricky (one))[https://osu.ppy.sh]!" });
Assert.AreEqual("This is a )super(( tricky (one)!", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(21, result.Links[0].Length);
}
[Test]
public void TestNewFormatLink()
{
@@ -131,6 +178,42 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
[Test]
public void TestNewFormatLinkWithEscapedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh nasty link with escaped brackets: \\] and \\[]" });
Assert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(41, result.Links[0].Length);
}
[Test]
public void TestNewFormatLinkWithBackslashesInside()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh link \\ with \\ backslashes \\]" });
Assert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(27, result.Links[0].Length);
}
[Test]
public void TestNewFormatLinkWithEscapedAndBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [link [with \\] too many brackets \\[ ]]]" });
Assert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(36, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLink()
{
@@ -143,6 +226,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [tricky [one]](https://osu.ppy.sh)!" });
Assert.AreEqual("This is a tricky [one]!", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(12, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithEscapedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is [another loose bracket \\]](https://osu.ppy.sh)." });
Assert.AreEqual("This is another loose bracket ].", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(8, result.Links[0].Index);
Assert.AreEqual(23, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatWithBackslashes()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This link [should end with a backslash \\](https://osu.ppy.sh)." });
Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(29, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithEscapedAndBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [\\]super\\[\\[ tricky [one]](https://osu.ppy.sh)!" });
Assert.AreEqual("This is a ]super[[ tricky [one]!", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(21, result.Links[0].Length);
}
[Test]
public void TestChannelLink()
{
+1
View File
@@ -1,5 +1,6 @@
[General]
Name: test skin
TestLookup: TestValue
[Colours]
Combo1 : 142,199,255
@@ -41,5 +41,20 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
[Test]
public void TestDecodeGeneral()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin.ini"))
using (var stream = new StreamReader(resStream))
{
var config = decoder.Decode(stream);
Assert.AreEqual("test skin", config.SkinInfo.Name);
Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]);
}
}
}
}
@@ -0,0 +1,156 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
namespace osu.Game.Tests.Skins
{
[TestFixture]
public class TestSceneSkinConfigurationLookup : OsuTestScene
{
private LegacySkin source1;
private LegacySkin source2;
private SkinRequester requester;
[SetUp]
public void SetUp() => Schedule(() =>
{
Add(new SkinProvidingContainer(source1 = new SkinSource())
.WithChild(new SkinProvidingContainer(source2 = new SkinSource())
.WithChild(requester = new SkinRequester())));
});
[Test]
public void TestBasicLookup()
{
AddStep("Add config values", () =>
{
source1.Configuration.ConfigDictionary["Lookup"] = "source1";
source2.Configuration.ConfigDictionary["Lookup"] = "source2";
});
AddAssert("Check lookup finds source2", () => requester.GetConfig<string, string>("Lookup")?.Value == "source2");
}
[Test]
public void TestFloatLookup()
{
AddStep("Add config values", () => source1.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");
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");
AddAssert("Check enum parse lookup", () => requester.GetConfig<LookupType, ValueType>(LookupType.Test)?.Value == ValueType.Test2);
}
[Test]
public void TestLookupFailure()
{
AddAssert("Check lookup failure", () => requester.GetConfig<string, float>("Lookup") == null);
}
[Test]
public void TestLookupNull()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null);
AddAssert("Check lookup null", () =>
{
var bindable = requester.GetConfig<string, string>("Lookup");
return bindable != null && bindable.Value == null;
});
}
[Test]
public void TestColourLookup()
{
AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
AddAssert("Check colour lookup", () => requester.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
}
[Test]
public void TestGlobalLookup()
{
AddAssert("Check combo colours", () => requester.GetConfig<GlobalSkinConfiguration, List<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
}
[Test]
public void TestWrongColourType()
{
AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
AddAssert("perform incorrect lookup", () =>
{
try
{
requester.GetConfig<SkinCustomColourLookup, int>(new SkinCustomColourLookup("Lookup"));
return false;
}
catch
{
return true;
}
});
}
public enum LookupType
{
Test
}
public enum ValueType
{
Test1,
Test2,
Test3
}
public class SkinSource : LegacySkin
{
public SkinSource()
: base(new SkinInfo(), null, null, string.Empty)
{
}
}
public class SkinRequester : Drawable, ISkin
{
private ISkinSource skin;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
this.skin = skin;
}
public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
public Texture GetTexture(string componentName) => skin.GetTexture(componentName);
public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => skin.GetConfig<TLookup, TValue>(lookup);
}
}
}
@@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void newJudgement(double offset = 0)
{
var judgement = new JudgementResult(new Judgement())
var judgement = new JudgementResult(new HitObject(), new Judgement())
{
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
Type = HitResult.Perfect,
@@ -3,12 +3,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Play;
using osuTK;
@@ -29,57 +30,118 @@ namespace osu.Game.Tests.Visual.Gameplay
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
Child = globalActionContainer = new GlobalActionContainer(game)
{
Children = new Drawable[]
{
pauseOverlay = new PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
},
failOverlay = new FailOverlay
Child = globalActionContainer = new GlobalActionContainer(game);
}
{
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
}
[SetUp]
public void SetUp() => Schedule(() =>
{
globalActionContainer.Children = new Drawable[]
{
pauseOverlay = new PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
},
failOverlay = new FailOverlay
{
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
}
};
InputManager.MoveMouseTo(Vector2.Zero);
});
[Test]
public void TestAdjustRetryCount()
{
showOverlay();
var retryCount = 0;
AddStep("Add retry", () =>
AddRepeatStep("Add retry", () =>
{
retryCount++;
pauseOverlay.Retries = failOverlay.Retries = retryCount;
});
}, 10);
}
AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility());
AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility());
/// <summary>
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
/// </summary>
[Test]
public void TestEnterWithoutSelection()
{
showOverlay();
testHideResets();
AddStep("Press select", () => press(GlobalAction.Select));
AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
}
testEnterWithoutSelection();
testKeyUpFromInitial();
testKeyDownFromInitial();
testKeyUpWrapping();
testKeyDownWrapping();
/// <summary>
/// Tests that pressing the up arrow from the initial state selects the last button.
/// </summary>
[Test]
public void TestKeyUpFromInitial()
{
showOverlay();
testMouseSelectionAfterKeySelection();
testKeySelectionAfterMouseSelection();
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
}
testMouseDeselectionResets();
/// <summary>
/// Tests that pressing the down arrow from the initial state selects the first button.
/// </summary>
[Test]
public void TestKeyDownFromInitial()
{
showOverlay();
testClickSelection();
testEnterKeySelection();
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
/// <summary>
/// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
[Test]
public void TestKeyUpWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
}
/// <summary>
/// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
[Test]
public void TestKeyDownWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
}
/// <summary>
/// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected.
/// </summary>
private void testHideResets()
[Test]
public void TestHideResets()
{
AddStep("Show overlay", () => failOverlay.Show());
@@ -90,141 +152,78 @@ namespace osu.Game.Tests.Visual.Gameplay
}
/// <summary>
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
/// Tests that entering menu with cursor initially on button doesn't selects it immediately.
/// This is to allow for stable keyboard navigation.
/// </summary>
private void testEnterWithoutSelection()
[Test]
public void TestInitialButtonHover()
{
AddStep("Show overlay", () => pauseOverlay.Show());
showOverlay();
AddStep("Press select", () => press(GlobalAction.Select));
AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
AddStep("Hover first button", () => InputManager.MoveMouseTo(getButton(0)));
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
showOverlay();
/// <summary>
/// Tests that pressing the up arrow from the initial state selects the last button.
/// </summary>
private void testKeyUpFromInitial()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the down arrow from the initial state selects the first button.
/// </summary>
private void testKeyDownFromInitial()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
private void testKeyUpWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Hide overlay", () => failOverlay.Hide());
}
/// <summary>
/// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
private void testKeyDownWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => failOverlay.Hide());
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
/// <summary>
/// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button.
/// </summary>
private void testMouseSelectionAfterKeySelection()
[Test]
public void TestMouseSelectionAfterKeySelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var secondButton = pauseOverlay.Buttons.Skip(1).First();
showOverlay();
AddStep("Down arrow", () => press(Key.Down));
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value);
AddAssert("Second button selected", () => secondButton.Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
AddAssert("Second button selected", () => getButton(1).Selected.Value);
}
/// <summary>
/// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button.
/// </summary>
private void testKeySelectionAfterMouseSelection()
[Test]
public void TestKeySelectionAfterMouseSelection()
{
AddStep("Show overlay", () =>
{
pauseOverlay.Show();
InputManager.MoveMouseTo(Vector2.Zero);
});
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Second button not selected", () => !secondButton.Selected.Value);
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
/// <summary>
/// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state.
/// </summary>
private void testMouseDeselectionResets()
[Test]
public void TestMouseDeselectionResets()
{
AddStep("Show overlay", () => pauseOverlay.Show());
showOverlay();
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition
AddStep("Hide overlay", () => pauseOverlay.Hide());
AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
}
/// <summary>
/// Tests that clicking on a button correctly causes a click event for that button.
/// </summary>
private void testClickSelection()
[Test]
public void TestClickSelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var retryButton = pauseOverlay.Buttons.Skip(1).First();
showOverlay();
bool triggered = false;
AddStep("Click retry button", () =>
@@ -232,7 +231,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
retryButton.Click();
getButton(1).Click();
pauseOverlay.OnRetry = lastAction;
});
@@ -243,9 +242,10 @@ namespace osu.Game.Tests.Visual.Gameplay
/// <summary>
/// Tests that pressing the enter key with a button selected correctly causes a click event for that button.
/// </summary>
private void testEnterKeySelection()
[Test]
public void TestEnterKeySelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
showOverlay();
AddStep("Select second button", () =>
{
@@ -275,6 +275,10 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
private void press(Key key)
{
InputManager.PressKey(key);
@@ -5,7 +5,9 @@ using System;
using System.Globalization;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -133,12 +135,55 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
[Test]
public void TestSwitchOff()
{
SkinConsumer consumer = null;
SwitchableSkinProvidingContainer target = null;
AddStep("setup layout", () =>
{
Child = new SkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = target = new SwitchableSkinProvidingContainer(new SecondarySource())
{
RelativeSizeAxes = Axes.Both,
}
};
});
AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddStep("disable", () => target.Disable());
AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox);
}
private class SwitchableSkinProvidingContainer : SkinProvidingContainer
{
private bool allow = true;
protected override bool AllowDrawableLookup(ISkinComponent component) => allow;
public void Disable()
{
allow = false;
TriggerSourceChanged();
}
public SwitchableSkinProvidingContainer(ISkin skin)
: base(skin)
{
}
}
private class ExposedSkinnableDrawable : SkinnableDrawable
{
public new Drawable Drawable => base.Drawable;
public ExposedSkinnableDrawable(string name, Func<string, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
: base(name, defaultImplementation, allowFallback, confineMode)
public ExposedSkinnableDrawable(string name, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null,
ConfineMode confineMode = ConfineMode.ScaleDownToFit)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
}
@@ -206,8 +251,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public new Drawable Drawable => base.Drawable;
public int SkinChangedCount { get; private set; }
public SkinConsumer(string name, Func<string, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null)
: base(name, defaultImplementation, allowFallback)
public SkinConsumer(string name, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback)
{
}
@@ -243,8 +288,8 @@ namespace osu.Game.Tests.Visual.Gameplay
this.size = size;
}
public Drawable GetDrawableComponent(string componentName) =>
componentName == "available"
public Drawable GetDrawableComponent(ISkinComponent componentName) =>
componentName.LookupName == "available"
? new DrawWidthBox
{
Colour = Color4.Yellow,
@@ -256,29 +301,46 @@ namespace osu.Game.Tests.Visual.Gameplay
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
}
private class SecondarySource : ISkin
{
public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
}
private class SkinSourceContainer : Container, ISkin
[Cached(typeof(ISkinSource))]
private class SkinSourceContainer : Container, ISkinSource
{
public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public event Action SourceChanged;
}
private class TestSkinComponent : ISkinComponent
{
private readonly string name;
public TestSkinComponent(string name)
{
this.name = name;
}
public string ComponentGroup => string.Empty;
public string LookupName => name;
}
}
}
@@ -10,14 +10,9 @@ namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneDisclaimer : ScreenTestScene
{
[Cached(typeof(IAPIProvider))]
private readonly DummyAPIAccess api = new DummyAPIAccess();
[BackgroundDependencyLoader]
private void load()
private void load(IAPIProvider api)
{
Add(api);
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
AddStep("toggle support", () =>
@@ -14,6 +14,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : MultiplayerTestScene
{
protected override bool RequiresAPIAccess => true;
public TestSceneMatchLeaderboard()
{
Room.RoomID.Value = 3;
@@ -27,11 +29,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
[Resolved]
private IAPIProvider api { get; set; }
[BackgroundDependencyLoader]
private void load()
private void load(IAPIProvider api)
{
var req = new GetRoomScoresRequest();
req.Success += v => { };
@@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestFixture]
public class TestSceneMultiScreen : ScreenTestScene
{
protected override bool RequiresAPIAccess => true;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Screens.Multi.Multiplayer),
@@ -25,17 +25,14 @@ namespace osu.Game.Tests.Visual.Online
typeof(AccountCreationScreen),
};
[Cached(typeof(IAPIProvider))]
private DummyAPIAccess api = new DummyAPIAccess();
private readonly Container userPanelArea;
public TestSceneAccountCreationOverlay()
{
Container userPanelArea;
AccountCreationOverlay accountCreation;
Children = new Drawable[]
{
api,
accountCreation = new AccountCreationOverlay(),
userPanelArea = new Container
{
@@ -46,11 +43,16 @@ namespace osu.Game.Tests.Visual.Online
},
};
AddStep("show", () => accountCreation.Show());
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
api.Logout();
api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
AddStep("show", () => accountCreation.Show());
AddStep("logout", () => api.Logout());
AddStep("logout", api.Logout);
}
}
}
@@ -42,6 +42,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(BeatmapAvailability),
};
protected override bool RequiresAPIAccess => true;
private RulesetInfo taikoRuleset;
private RulesetInfo maniaRuleset;
@@ -176,6 +178,8 @@ namespace osu.Game.Tests.Visual.Online
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Language = new BeatmapSetOnlineLanguage { Id = 3, Name = "English" },
Genre = new BeatmapSetOnlineGenre { Id = 4, Name = "Rock" },
},
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = new List<BeatmapInfo>
@@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(Comments),
};
protected override bool RequiresAPIAccess => true;
protected override void LoadComplete()
{
base.LoadComplete();
@@ -127,6 +127,9 @@ namespace osu.Game.Tests.Visual.Online
addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- <Version 0>]", 1, true, expectedActions: LinkAction.OpenBeatmap);
addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3,
expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External });
addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://osu.ppy.sh/home)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://osu.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External });
// note that there's 0 links here (they get removed if a channel is not found)
addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).");
addMessageWithChecks("I am important!", 0, false, true);
@@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online
{
private DirectOverlay direct;
protected override bool RequiresAPIAccess => true;
protected override void LoadComplete()
{
base.LoadComplete();
@@ -17,14 +17,15 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneHistoricalSection : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes =>
new[]
{
typeof(HistoricalSection),
typeof(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedBeatmap),
typeof(DrawableProfileRow)
};
protected override bool RequiresAPIAccess => true;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HistoricalSection),
typeof(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedBeatmap),
typeof(DrawableProfileRow)
};
public TestSceneHistoricalSection()
{
@@ -0,0 +1,246 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Overlays.Profile.Sections.Kudosu;
using System.Collections.Generic;
using System;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneKudosuHistory : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableKudosuHistoryItem),
};
private readonly Box background;
public TestSceneKudosuHistory()
{
FillFlowContainer<DrawableKudosuHistoryItem> content;
AddRange(new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
content = new FillFlowContainer<DrawableKudosuHistoryItem>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Width = 0.7f,
AutoSizeAxes = Axes.Y,
}
});
items.ForEach(t => content.Add(new DrawableKudosuHistoryItem(t)));
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
background.Colour = colours.GreySeafoam;
}
private readonly IEnumerable<APIKudosuHistory> items = new[]
{
new APIKudosuHistory
{
Amount = 10,
CreatedAt = new DateTimeOffset(new DateTime(2011, 11, 11)),
Source = KudosuSource.DenyKudosu,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 1",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username1",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 5,
CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)),
Source = KudosuSource.Forum,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 2",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username2",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 8,
CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)),
Source = KudosuSource.Forum,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 3",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username3",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 7,
CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)),
Source = KudosuSource.Forum,
Action = KudosuAction.Revoke,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 4",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username4",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 100,
CreatedAt = new DateTimeOffset(new DateTime(2015, 7, 11)),
Source = KudosuSource.Vote,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 5",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username5",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 20,
CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
Source = KudosuSource.Vote,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 6",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username6",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 11,
CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
Source = KudosuSource.AllowKudosu,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 7",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username7",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 24,
CreatedAt = new DateTimeOffset(new DateTime(2014, 6, 11)),
Source = KudosuSource.Delete,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 8",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username8",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 12,
CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
Source = KudosuSource.Restore,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 9",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username9",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 2,
CreatedAt = new DateTimeOffset(new DateTime(2012, 6, 11)),
Source = KudosuSource.Recalculate,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 10",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username10",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 32,
CreatedAt = new DateTimeOffset(new DateTime(2019, 8, 11)),
Source = KudosuSource.Recalculate,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 11",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username11",
Url = @"https://osu.ppy.sh/u/1234"
}
}
};
}
}
@@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneSocialOverlay : OsuTestScene
{
protected override bool RequiresAPIAccess => true;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(UserPanel),
@@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Online
{
public class TestSceneUserProfileHeader : OsuTestScene
{
protected override bool RequiresAPIAccess => true;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ProfileHeader),
@@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserProfileOverlay : OsuTestScene
{
protected override bool RequiresAPIAccess => true;
private readonly TestUserProfileOverlay profile;
[Resolved]
@@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserRanks : OsuTestScene
{
protected override bool RequiresAPIAccess => true;
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) };
public TestSceneUserRanks()
@@ -2,6 +2,7 @@
// 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.Audio.Track;
@@ -25,6 +26,11 @@ namespace osu.Game.Tests.Visual.UserInterface
{
private readonly NowPlayingOverlay np;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BeatSyncedContainer)
};
[Cached]
private MusicController musicController = new MusicController();
@@ -154,7 +160,9 @@ namespace osu.Game.Tests.Visual.UserInterface
if (timingPoints[timingPoints.Count - 1] == current)
return current;
return timingPoints[timingPoints.IndexOf(current) + 1];
int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat"
return index == -1 ? current : timingPoints[index + 1];
}
private int calculateBeatCount(TimingControlPoint current)
@@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene
{
protected override bool RequiresAPIAccess => true;
private BeatmapSetInfo testBeatmap;
private IAPIProvider api;
private RulesetStore rulesets;
+1 -1
View File
@@ -5,7 +5,7 @@
<PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
@@ -7,7 +7,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
+22
View File
@@ -75,6 +75,28 @@ namespace osu.Game.Beatmaps
/// The availability of this beatmap set.
/// </summary>
public BeatmapSetOnlineAvailability Availability { get; set; }
/// <summary>
/// The song genre of this beatmap set.
/// </summary>
public BeatmapSetOnlineGenre Genre { get; set; }
/// <summary>
/// The song language of this beatmap set.
/// </summary>
public BeatmapSetOnlineLanguage Language { get; set; }
}
public class BeatmapSetOnlineGenre
{
public int Id { get; set; }
public string Name { get; set; }
}
public class BeatmapSetOnlineLanguage
{
public int Id { get; set; }
public string Name { get; set; }
}
public class BeatmapSetOnlineCovers
@@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
Icon = ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
}
};
}
@@ -16,11 +16,11 @@ namespace osu.Game.Configuration
private readonly int? variant;
private readonly List<DatabasedSetting> databasedSettings;
private List<DatabasedSetting> databasedSettings;
private readonly RulesetInfo ruleset;
private readonly bool legacySettingsExist;
private bool legacySettingsExist;
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
{
@@ -28,21 +28,31 @@ namespace osu.Game.Configuration
this.ruleset = ruleset;
this.variant = variant;
databasedSettings = settings.Query(ruleset?.ID, variant);
legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
Load();
InitialiseDefaults();
}
protected override void PerformLoad()
{
databasedSettings = settings.Query(ruleset?.ID, variant);
legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
}
protected override bool PerformSave()
{
lock (dirtySettings)
{
foreach (var setting in dirtySettings)
settings.Update(setting);
dirtySettings.Clear();
}
return true;
}
private readonly List<DatabasedSetting> dirtySettings = new List<DatabasedSetting>();
protected override void AddBindable<TBindable>(T lookup, Bindable<TBindable> bindable)
{
base.AddBindable(lookup, bindable);
@@ -80,7 +90,12 @@ namespace osu.Game.Configuration
bindable.ValueChanged += b =>
{
setting.Value = b.NewValue;
settings.Update(setting);
lock (dirtySettings)
{
if (!dirtySettings.Contains(setting))
dirtySettings.Add(setting);
}
};
}
}

Some files were not shown because too many files have changed in this diff Show More