mirror of
https://github.com/ppy/osu.git
synced 2024-09-22 00:47:24 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
7693d4c039
@ -21,7 +21,7 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2024.517.0",
|
||||
"version": "2024.802.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -121,9 +121,7 @@ jobs:
|
||||
|
||||
build-only-ios:
|
||||
name: Build only (iOS)
|
||||
# `macos-13` is required, because the newest Microsoft.iOS.Sdk versions require Xcode 14.3.
|
||||
# TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta: https://github.com/actions/runner-images/tree/main#available-images)
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -137,8 +135,5 @@ jobs:
|
||||
- name: Install .NET Workloads
|
||||
run: dotnet workload install maui-ios
|
||||
|
||||
- name: Select Xcode 15.2
|
||||
run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c Debug osu.iOS
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.720.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.809.2" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Skinning;
|
||||
@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
|
||||
public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin)
|
||||
{
|
||||
if (withModifiedSkin)
|
||||
{
|
||||
@ -29,10 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
CreateTest();
|
||||
}
|
||||
|
||||
AddAssert("legacy HUD combo counter hidden", () =>
|
||||
{
|
||||
return Player.ChildrenOfType<LegacyComboCounter>().All(c => c.ChildrenOfType<Container>().Single().Alpha == 0f);
|
||||
});
|
||||
AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType<LegacyDefaultComboCounter>().Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +248,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||
AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
|
||||
AddAssert("correct hit lighting colour",
|
||||
() => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -259,6 +260,16 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllExplodedObjectsAtUniquePositions()
|
||||
{
|
||||
AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
|
||||
AddStep("catch normal fruit", () => attemptCatch(new Fruit { IndexInBeatmap = 2, LastInCombo = true }));
|
||||
AddAssert("two fruit at distinct x coordinates",
|
||||
() => this.ChildrenOfType<CaughtFruit>().Select(f => f.DrawPosition.X).Distinct(),
|
||||
() => Has.Exactly(2).Items);
|
||||
}
|
||||
|
||||
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
||||
|
||||
private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
|
||||
|
@ -21,11 +21,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
||||
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
||||
|
||||
public Vector2 DisplayPosition => DrawPosition;
|
||||
public Vector2 DisplaySize => Size * Scale;
|
||||
|
||||
public float DisplayRotation => Rotation;
|
||||
|
||||
public double DisplayStartTime => HitObject.StartTime;
|
||||
|
||||
/// <summary>
|
||||
@ -44,19 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the hit object visual state from another <see cref="IHasCatchObjectState"/> object.
|
||||
/// </summary>
|
||||
public virtual void CopyStateFrom(IHasCatchObjectState objectState)
|
||||
{
|
||||
HitObject = objectState.HitObject;
|
||||
Scale = Vector2.Divide(objectState.DisplaySize, Size);
|
||||
Rotation = objectState.DisplayRotation;
|
||||
AccentColour.Value = objectState.AccentColour.Value;
|
||||
HyperDash.Value = objectState.HyperDash.Value;
|
||||
IndexInBeatmap.Value = objectState.IndexInBeatmap.Value;
|
||||
}
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
ClearTransforms();
|
||||
@ -64,5 +49,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
|
||||
base.FreeAfterUse();
|
||||
}
|
||||
|
||||
public void RestoreState(CatchObjectState state)
|
||||
{
|
||||
HitObject = state.HitObject;
|
||||
AccentColour.Value = state.AccentColour;
|
||||
HyperDash.Value = state.HyperDash;
|
||||
IndexInBeatmap.Value = state.IndexInBeatmap;
|
||||
Position = state.DisplayPosition;
|
||||
Scale = Vector2.Divide(state.DisplaySize, Size);
|
||||
Rotation = state.DisplayRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
/// </summary>
|
||||
protected readonly Container ScalingContainer;
|
||||
|
||||
public Vector2 DisplayPosition => DrawPosition;
|
||||
|
||||
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
|
||||
|
||||
public float DisplayRotation => ScalingContainer.Rotation;
|
||||
@ -95,5 +97,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
|
||||
base.OnFree();
|
||||
}
|
||||
|
||||
public void RestoreState(CatchObjectState state) => throw new NotSupportedException("Cannot restore state into a drawable catch hitobject.");
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,35 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
public interface IHasCatchObjectState
|
||||
{
|
||||
PalpableCatchHitObject HitObject { get; }
|
||||
|
||||
double DisplayStartTime { get; }
|
||||
|
||||
Bindable<Color4> AccentColour { get; }
|
||||
|
||||
Bindable<bool> HyperDash { get; }
|
||||
|
||||
Bindable<int> IndexInBeatmap { get; }
|
||||
|
||||
double DisplayStartTime { get; }
|
||||
Vector2 DisplayPosition { get; }
|
||||
Vector2 DisplaySize { get; }
|
||||
|
||||
float DisplayRotation { get; }
|
||||
|
||||
void RestoreState(CatchObjectState state);
|
||||
}
|
||||
|
||||
public static class HasCatchObjectStateExtensions
|
||||
{
|
||||
public static CatchObjectState SaveState(this IHasCatchObjectState target) => new CatchObjectState(
|
||||
target.HitObject,
|
||||
target.AccentColour.Value,
|
||||
target.HyperDash.Value,
|
||||
target.IndexInBeatmap.Value,
|
||||
target.DisplayPosition,
|
||||
target.DisplaySize,
|
||||
target.DisplayRotation);
|
||||
}
|
||||
|
||||
public readonly record struct CatchObjectState(
|
||||
PalpableCatchHitObject HitObject,
|
||||
Color4 AccentColour,
|
||||
bool HyperDash,
|
||||
int IndexInBeatmap,
|
||||
Vector2 DisplayPosition,
|
||||
Vector2 DisplaySize,
|
||||
float DisplayRotation);
|
||||
}
|
||||
|
@ -4,8 +4,8 @@
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
@ -28,76 +28,100 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
if (lookup is SkinComponentsContainerLookup containerLookup)
|
||||
switch (lookup)
|
||||
{
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
var components = base.GetDrawableComponent(lookup) as Container;
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Only handle per ruleset defaults here.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
if (providesComboCounter && components != null)
|
||||
{
|
||||
// catch may provide its own combo counter; hide the default.
|
||||
// todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
|
||||
foreach (var legacyComboCounter in components.OfType<LegacyComboCounter>())
|
||||
legacyComboCounter.HiddenByRulesetImplementation = false;
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
}
|
||||
|
||||
if (lookup is CatchSkinComponentLookup catchSkinComponent)
|
||||
{
|
||||
switch (catchSkinComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (hasPear)
|
||||
return new LegacyFruitPiece();
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||
if (!IsProvidingLegacyResources)
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
// Our own ruleset components default.
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
return null;
|
||||
if (keyCounter != null)
|
||||
{
|
||||
// set the anchor to top right so that it won't squash to the return button to the top
|
||||
keyCounter.Anchor = Anchor.CentreRight;
|
||||
keyCounter.Origin = Anchor.CentreRight;
|
||||
keyCounter.X = 0;
|
||||
// 340px is the default height inherit from stable
|
||||
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
|
||||
}
|
||||
})
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new LegacyKeyCounterDisplay(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
return null;
|
||||
|
||||
return null;
|
||||
case CatchSkinComponentLookup catchSkinComponent:
|
||||
switch (catchSkinComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (hasPear)
|
||||
return new LegacyFruitPiece();
|
||||
|
||||
case CatchSkinComponents.Catcher:
|
||||
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||
return null;
|
||||
|
||||
if (version < 2.3m)
|
||||
{
|
||||
if (hasOldStyleCatcherSprite())
|
||||
return new LegacyCatcherOld();
|
||||
}
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
|
||||
if (hasNewStyleCatcherSprite())
|
||||
return new LegacyCatcherNew();
|
||||
return null;
|
||||
|
||||
return null;
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter();
|
||||
return null;
|
||||
|
||||
return null;
|
||||
case CatchSkinComponents.Catcher:
|
||||
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||
|
||||
case CatchSkinComponents.HitExplosion:
|
||||
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
||||
return new LegacyHitExplosion();
|
||||
if (version < 2.3m)
|
||||
{
|
||||
if (hasOldStyleCatcherSprite())
|
||||
return new LegacyCatcherOld();
|
||||
}
|
||||
|
||||
return null;
|
||||
if (hasNewStyleCatcherSprite())
|
||||
return new LegacyCatcherNew();
|
||||
|
||||
default:
|
||||
throw new UnsupportedSkinComponentException(lookup);
|
||||
}
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter();
|
||||
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.HitExplosion:
|
||||
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
||||
return new LegacyHitExplosion();
|
||||
|
||||
return null;
|
||||
|
||||
default:
|
||||
throw new UnsupportedSkinComponentException(lookup);
|
||||
}
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@ -362,7 +363,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (caughtObject == null) return;
|
||||
|
||||
caughtObject.CopyStateFrom(drawableObject);
|
||||
caughtObject.RestoreState(drawableObject.SaveState());
|
||||
caughtObject.Anchor = Anchor.TopCentre;
|
||||
caughtObject.Position = position;
|
||||
caughtObject.Scale *= caught_fruit_scale_adjust;
|
||||
@ -411,41 +412,50 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
private CaughtObject getDroppedObject(CaughtObject caughtObject)
|
||||
private CaughtObject getDroppedObject(CatchObjectState state)
|
||||
{
|
||||
var droppedObject = getCaughtObject(caughtObject.HitObject);
|
||||
var droppedObject = getCaughtObject(state.HitObject);
|
||||
Debug.Assert(droppedObject != null);
|
||||
|
||||
droppedObject.CopyStateFrom(caughtObject);
|
||||
droppedObject.RestoreState(state);
|
||||
droppedObject.Anchor = Anchor.TopLeft;
|
||||
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget);
|
||||
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(state.DisplayPosition, droppedObjectTarget);
|
||||
|
||||
return droppedObject;
|
||||
}
|
||||
|
||||
private void clearPlate(DroppedObjectAnimation animation)
|
||||
{
|
||||
var caughtObjects = caughtObjectContainer.Children.ToArray();
|
||||
int caughtCount = caughtObjectContainer.Children.Count;
|
||||
CatchObjectState[] states = ArrayPool<CatchObjectState>.Shared.Rent(caughtCount);
|
||||
|
||||
caughtObjectContainer.Clear(false);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < caughtCount; i++)
|
||||
states[i] = caughtObjectContainer.Children[i].SaveState();
|
||||
|
||||
// Use the already returned PoolableDrawables for new objects
|
||||
var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray();
|
||||
caughtObjectContainer.Clear(false);
|
||||
|
||||
droppedObjectTarget.AddRange(droppedObjects);
|
||||
|
||||
foreach (var droppedObject in droppedObjects)
|
||||
applyDropAnimation(droppedObject, animation);
|
||||
for (int i = 0; i < caughtCount; i++)
|
||||
{
|
||||
CaughtObject obj = getDroppedObject(states[i]);
|
||||
droppedObjectTarget.Add(obj);
|
||||
applyDropAnimation(obj, animation);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<CatchObjectState>.Shared.Return(states);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
||||
{
|
||||
CatchObjectState state = caughtObject.SaveState();
|
||||
caughtObjectContainer.Remove(caughtObject, false);
|
||||
|
||||
var droppedObject = getDroppedObject(caughtObject);
|
||||
|
||||
var droppedObject = getDroppedObject(state);
|
||||
droppedObjectTarget.Add(droppedObject);
|
||||
|
||||
applyDropAnimation(droppedObject, animation);
|
||||
}
|
||||
|
||||
|
@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (Catcher.Dashing || Catcher.HyperDashing)
|
||||
{
|
||||
double generationInterval = Catcher.HyperDashing ? 25 : 50;
|
||||
const double trail_generation_interval = 16;
|
||||
|
||||
if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval)
|
||||
if (Time.Current - catcherTrails.LastDashTrailTime >= trail_generation_interval)
|
||||
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestCase(ManiaAction.Key1)]
|
||||
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||
[TestCase(ManiaAction.Special1)]
|
||||
[TestCase(ManiaAction.Key8)]
|
||||
[TestCase(ManiaAction.Key5)]
|
||||
[TestCase(ManiaAction.Key9)]
|
||||
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
|
||||
{
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(9));
|
||||
@ -29,11 +29,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
[TestCase(ManiaAction.Key1)]
|
||||
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||
[TestCase(ManiaAction.Special1)]
|
||||
[TestCase(ManiaAction.Special2)]
|
||||
[TestCase(ManiaAction.Special1, ManiaAction.Special2)]
|
||||
[TestCase(ManiaAction.Special1, ManiaAction.Key5)]
|
||||
[TestCase(ManiaAction.Key3)]
|
||||
[TestCase(ManiaAction.Key8)]
|
||||
[TestCase(ManiaAction.Key3, ManiaAction.Key8)]
|
||||
[TestCase(ManiaAction.Key3, ManiaAction.Key6)]
|
||||
[TestCase(ManiaAction.Key10)]
|
||||
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
|
||||
{
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(5));
|
||||
|
@ -0,0 +1,38 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Argon;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
public partial class TestSceneComboCounter : ManiaSkinnableTestScene
|
||||
{
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("setup", () => SetContents(s =>
|
||||
{
|
||||
if (s is ArgonSkin)
|
||||
return new ArgonManiaComboCounter();
|
||||
|
||||
if (s is LegacySkin)
|
||||
return new LegacyManiaComboCounter();
|
||||
|
||||
return new LegacyManiaComboCounter();
|
||||
}));
|
||||
|
||||
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20);
|
||||
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||
}
|
||||
}
|
||||
}
|
@ -28,14 +28,20 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
AddStep("Show " + result.GetDescription(), () =>
|
||||
{
|
||||
SetContents(_ =>
|
||||
new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||
{
|
||||
Type = result
|
||||
}, null)
|
||||
{
|
||||
var drawableManiaJudgement = new DrawableManiaJudgement
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
};
|
||||
|
||||
drawableManiaJudgement.Apply(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||
{
|
||||
Type = result
|
||||
}, null);
|
||||
|
||||
return drawableManiaJudgement;
|
||||
});
|
||||
|
||||
// for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value
|
||||
// (see `LegacyManiaJudgementPiece.load()`).
|
||||
|
@ -3,15 +3,22 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
public partial class TestScenePlayfield : ManiaSkinnableTestScene
|
||||
{
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
|
||||
|
||||
private List<StageDefinition> stageDefinitions = new List<StageDefinition>();
|
||||
|
||||
[Test]
|
||||
@ -29,6 +36,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
Child = new ManiaPlayfield(stageDefinitions)
|
||||
});
|
||||
});
|
||||
|
||||
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
|
||||
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||
}
|
||||
|
||||
[TestCase(2)]
|
||||
@ -54,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
|
||||
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmapForSkinProvider()
|
||||
|
@ -14,12 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
SetContents(_ =>
|
||||
{
|
||||
ManiaAction normalAction = ManiaAction.Key1;
|
||||
ManiaAction specialAction = ManiaAction.Special1;
|
||||
ManiaAction action = ManiaAction.Key1;
|
||||
|
||||
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||
{
|
||||
Child = new Stage(0, new StageDefinition(4), ref normalAction, ref specialAction)
|
||||
Child = new Stage(0, new StageDefinition(4), ref action)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -0,0 +1,36 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public partial class TestSceneManiaPlayerLegacySkin : LegacySkinPlayerTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
// play with a converted beatmap to allow dual stages mod to work.
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(new RulesetInfo());
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
[Test]
|
||||
public void TestSingleStage()
|
||||
{
|
||||
AddStep("Load single stage", LoadPlayer);
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDualStage()
|
||||
{
|
||||
AddStep("Load dual stage", () => LoadPlayer(new Mod[] { new ManiaModDualStages() }));
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -131,9 +131,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
||||
{
|
||||
var specialAction = ManiaAction.Special1;
|
||||
|
||||
var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction);
|
||||
var stage = new Stage(0, new StageDefinition(2), ref action);
|
||||
stages.Add(stage);
|
||||
|
||||
return new ScrollingTestContainer(direction)
|
||||
|
@ -45,18 +45,15 @@ namespace osu.Game.Rulesets.Mania
|
||||
LeftKeys = stage1LeftKeys,
|
||||
RightKeys = stage1RightKeys,
|
||||
SpecialKey = InputKey.V,
|
||||
SpecialAction = ManiaAction.Special1,
|
||||
NormalActionStart = ManiaAction.Key1
|
||||
}.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal);
|
||||
}.GenerateKeyBindingsFor(singleStageVariant);
|
||||
|
||||
var stage2Bindings = new VariantMappingGenerator
|
||||
{
|
||||
LeftKeys = stage2LeftKeys,
|
||||
RightKeys = stage2RightKeys,
|
||||
SpecialKey = InputKey.B,
|
||||
SpecialAction = ManiaAction.Special2,
|
||||
NormalActionStart = nextNormal
|
||||
}.GenerateKeyBindingsFor(singleStageVariant, out _);
|
||||
ActionStart = (ManiaAction)singleStageVariant,
|
||||
}.GenerateKeyBindingsFor(singleStageVariant);
|
||||
|
||||
return stage1Bindings.Concat(stage2Bindings);
|
||||
}
|
||||
|
@ -19,16 +19,8 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public enum ManiaAction
|
||||
{
|
||||
[Description("Special 1")]
|
||||
Special1 = 1,
|
||||
|
||||
[Description("Special 2")]
|
||||
Special2,
|
||||
|
||||
// This offsets the start value of normal keys in-case we add more special keys
|
||||
// above at a later time, without breaking replays/configs.
|
||||
[Description("Key 1")]
|
||||
Key1 = 10,
|
||||
Key1,
|
||||
|
||||
[Description("Key 2")]
|
||||
Key2,
|
||||
|
@ -17,28 +17,9 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
|
||||
|
||||
private readonly ManiaAction[] columnActions;
|
||||
|
||||
public ManiaAutoGenerator(ManiaBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
columnActions = new ManiaAction[Beatmap.TotalColumns];
|
||||
|
||||
var normalAction = ManiaAction.Key1;
|
||||
var specialAction = ManiaAction.Special1;
|
||||
int totalCounter = 0;
|
||||
|
||||
foreach (var stage in Beatmap.Stages)
|
||||
{
|
||||
for (int i = 0; i < stage.Columns; i++)
|
||||
{
|
||||
if (stage.IsSpecialColumn(i))
|
||||
columnActions[totalCounter] = specialAction++;
|
||||
else
|
||||
columnActions[totalCounter] = normalAction++;
|
||||
totalCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void GenerateFrames()
|
||||
@ -57,11 +38,11 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
switch (point)
|
||||
{
|
||||
case HitPoint:
|
||||
actions.Add(columnActions[point.Column]);
|
||||
actions.Add(ManiaAction.Key1 + point.Column);
|
||||
break;
|
||||
|
||||
case ReleasePoint:
|
||||
actions.Remove(columnActions[point.Column]);
|
||||
actions.Remove(ManiaAction.Key1 + point.Column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
|
||||
@ -27,118 +25,27 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
||||
{
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
|
||||
var normalAction = ManiaAction.Key1;
|
||||
var specialAction = ManiaAction.Special1;
|
||||
|
||||
var action = ManiaAction.Key1;
|
||||
int activeColumns = (int)(legacyFrame.MouseX ?? 0);
|
||||
int counter = 0;
|
||||
|
||||
while (activeColumns > 0)
|
||||
{
|
||||
bool isSpecial = isColumnAtIndexSpecial(maniaBeatmap, counter);
|
||||
|
||||
if ((activeColumns & 1) > 0)
|
||||
Actions.Add(isSpecial ? specialAction : normalAction);
|
||||
Actions.Add(action);
|
||||
|
||||
if (isSpecial)
|
||||
specialAction++;
|
||||
else
|
||||
normalAction++;
|
||||
|
||||
counter++;
|
||||
action++;
|
||||
activeColumns >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
||||
{
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
|
||||
int keys = 0;
|
||||
|
||||
foreach (var action in Actions)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case ManiaAction.Special1:
|
||||
keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 0);
|
||||
break;
|
||||
|
||||
case ManiaAction.Special2:
|
||||
keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
// the index in lazer, which doesn't include special keys.
|
||||
int nonSpecialKeyIndex = action - ManiaAction.Key1;
|
||||
|
||||
// the index inclusive of special keys.
|
||||
int overallIndex = 0;
|
||||
|
||||
// iterate to find the index including special keys.
|
||||
for (; overallIndex < maniaBeatmap.TotalColumns; overallIndex++)
|
||||
{
|
||||
// skip over special columns.
|
||||
if (isColumnAtIndexSpecial(maniaBeatmap, overallIndex))
|
||||
continue;
|
||||
// found a non-special column to use.
|
||||
if (nonSpecialKeyIndex == 0)
|
||||
break;
|
||||
// found a non-special column but not ours.
|
||||
nonSpecialKeyIndex--;
|
||||
}
|
||||
|
||||
keys |= 1 << overallIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
keys |= 1 << (int)action;
|
||||
|
||||
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the overall index (across all stages) for a specified special key.
|
||||
/// </summary>
|
||||
/// <param name="maniaBeatmap">The beatmap.</param>
|
||||
/// <param name="specialOffset">The special key offset (0 is S1).</param>
|
||||
/// <returns>The overall index for the special column.</returns>
|
||||
private int getSpecialColumnIndex(ManiaBeatmap maniaBeatmap, int specialOffset)
|
||||
{
|
||||
for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
|
||||
{
|
||||
if (isColumnAtIndexSpecial(maniaBeatmap, i))
|
||||
{
|
||||
if (specialOffset == 0)
|
||||
return i;
|
||||
|
||||
specialOffset--;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException("Special key index is too high.", nameof(specialOffset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the column at an overall index (across all stages) is a special column.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap.</param>
|
||||
/// <param name="index">The overall index to check.</param>
|
||||
private bool isColumnAtIndexSpecial(ManiaBeatmap beatmap, int index)
|
||||
{
|
||||
foreach (var stage in beatmap.Stages)
|
||||
{
|
||||
if (index >= stage.Columns)
|
||||
{
|
||||
index -= stage.Columns;
|
||||
continue;
|
||||
}
|
||||
|
||||
return stage.IsSpecialColumn(index);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Column index is too high.", nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Mania
|
||||
LeftKeys = leftKeys,
|
||||
RightKeys = rightKeys,
|
||||
SpecialKey = InputKey.Space,
|
||||
SpecialAction = ManiaAction.Special1,
|
||||
NormalActionStart = ManiaAction.Key1,
|
||||
}.GenerateKeyBindingsFor(variant, out _);
|
||||
}.GenerateKeyBindingsFor(variant);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonManiaComboCounter : ArgonComboCounter
|
||||
{
|
||||
protected override bool DisplayXSymbol => false;
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
private IBindable<ScrollingDirection> direction = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// the logic of flipping the position of the combo counter w.r.t. the direction does not work with "Closest" anchor,
|
||||
// because it always forces the anchor to be top or bottom based on scrolling direction.
|
||||
UsesFixedAnchor = true;
|
||||
|
||||
direction = scrollingInfo.Direction.GetBoundCopy();
|
||||
direction.BindValueChanged(_ => updateAnchor());
|
||||
|
||||
// two schedules are required so that updateAnchor is executed in the next frame,
|
||||
// which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer.
|
||||
Schedule(() => Schedule(updateAnchor));
|
||||
}
|
||||
|
||||
private void updateAnchor()
|
||||
{
|
||||
// if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
|
||||
if (!Anchor.HasFlag(Anchor.y1))
|
||||
{
|
||||
Anchor &= ~(Anchor.y0 | Anchor.y2);
|
||||
Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
|
||||
}
|
||||
|
||||
// change the sign of the Y coordinate in line with the scrolling direction.
|
||||
// i.e. if the user changes direction from down to up, the anchor is changed from top to bottom, and the Y is flipped from positive to negative here.
|
||||
Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -26,6 +28,37 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
switch (lookup)
|
||||
{
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Only handle per ruleset defaults here.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var combo = container.ChildrenOfType<ArgonManiaComboCounter>().FirstOrDefault();
|
||||
|
||||
if (combo != null)
|
||||
{
|
||||
combo.ShowLabel.Value = false;
|
||||
combo.Anchor = Anchor.TopCentre;
|
||||
combo.Origin = Anchor.Centre;
|
||||
combo.Y = 200;
|
||||
}
|
||||
})
|
||||
{
|
||||
new ArgonManiaComboCounter(),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||
|
@ -0,0 +1,91 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
public partial class LegacyManiaComboCounter : LegacyComboCounter
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
DisplayedCountText.Anchor = Anchor.Centre;
|
||||
DisplayedCountText.Origin = Anchor.Centre;
|
||||
|
||||
PopOutCountText.Anchor = Anchor.Centre;
|
||||
PopOutCountText.Origin = Anchor.Centre;
|
||||
PopOutCountText.Colour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
private IBindable<ScrollingDirection> direction = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
direction = scrollingInfo.Direction.GetBoundCopy();
|
||||
direction.BindValueChanged(_ => updateAnchor());
|
||||
|
||||
// two schedules are required so that updateAnchor is executed in the next frame,
|
||||
// which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer.
|
||||
Schedule(() => Schedule(updateAnchor));
|
||||
}
|
||||
|
||||
private void updateAnchor()
|
||||
{
|
||||
// if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
|
||||
if (!Anchor.HasFlag(Anchor.y1))
|
||||
{
|
||||
Anchor &= ~(Anchor.y0 | Anchor.y2);
|
||||
Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
|
||||
}
|
||||
|
||||
// since we flip the vertical anchor when changing scroll direction,
|
||||
// we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
|
||||
if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up))
|
||||
Y = -Y;
|
||||
}
|
||||
|
||||
protected override void OnCountIncrement()
|
||||
{
|
||||
base.OnCountIncrement();
|
||||
|
||||
PopOutCountText.Hide();
|
||||
DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f))
|
||||
.ScaleTo(new Vector2(1f), 300, Easing.Out)
|
||||
.FadeIn(120);
|
||||
}
|
||||
|
||||
protected override void OnCountChange()
|
||||
{
|
||||
base.OnCountChange();
|
||||
|
||||
PopOutCountText.Hide();
|
||||
DisplayedCountText.ScaleTo(1f);
|
||||
}
|
||||
|
||||
protected override void OnCountRolling()
|
||||
{
|
||||
if (DisplayedCount > 0)
|
||||
{
|
||||
PopOutCountText.Text = FormatCount(DisplayedCount);
|
||||
PopOutCountText.FadeTo(0.8f).FadeOut(200)
|
||||
.ScaleTo(1f).ScaleTo(4f, 200);
|
||||
|
||||
DisplayedCountText.FadeTo(0.5f, 300);
|
||||
}
|
||||
|
||||
base.OnCountRolling();
|
||||
}
|
||||
}
|
||||
}
|
@ -5,9 +5,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
@ -78,6 +80,40 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
switch (lookup)
|
||||
{
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Modifications for global components.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||
if (!IsProvidingLegacyResources)
|
||||
return null;
|
||||
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var combo = container.ChildrenOfType<LegacyManiaComboCounter>().FirstOrDefault();
|
||||
|
||||
if (combo != null)
|
||||
{
|
||||
combo.Anchor = Anchor.TopCentre;
|
||||
combo.Origin = Anchor.Centre;
|
||||
combo.Y = this.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0;
|
||||
}
|
||||
})
|
||||
{
|
||||
new LegacyManiaComboCounter(),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
return getResult(resultComponent.Component);
|
||||
|
||||
|
@ -5,22 +5,12 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public partial class DrawableManiaJudgement : DrawableJudgement
|
||||
{
|
||||
public DrawableManiaJudgement(JudgementResult result, DrawableHitObject judgedObject)
|
||||
: base(result, judgedObject)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableManiaJudgement()
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
||||
|
||||
private partial class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
||||
|
@ -66,13 +66,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
Content = new[] { new Drawable[stageDefinitions.Count] }
|
||||
});
|
||||
|
||||
var normalColumnAction = ManiaAction.Key1;
|
||||
var specialColumnAction = ManiaAction.Special1;
|
||||
var columnAction = ManiaAction.Key1;
|
||||
int firstColumnIndex = 0;
|
||||
|
||||
for (int i = 0; i < stageDefinitions.Count; i++)
|
||||
{
|
||||
var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
|
||||
var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref columnAction);
|
||||
|
||||
playfieldGrid.Content[0][i] = newStage;
|
||||
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private ISkinSource currentSkin = null!;
|
||||
|
||||
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
|
||||
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction columnStartAction)
|
||||
{
|
||||
this.firstColumnIndex = firstColumnIndex;
|
||||
Definition = definition;
|
||||
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 1,
|
||||
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
|
||||
Action = { Value = columnStartAction++ }
|
||||
};
|
||||
|
||||
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
|
||||
|
@ -26,37 +26,30 @@ namespace osu.Game.Rulesets.Mania
|
||||
public InputKey SpecialKey;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
|
||||
/// The <see cref="ManiaAction"/> at which the columns should begin.
|
||||
/// </summary>
|
||||
public ManiaAction NormalActionStart;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ManiaAction"/> for the special column.
|
||||
/// </summary>
|
||||
public ManiaAction SpecialAction;
|
||||
public ManiaAction ActionStart;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
||||
/// </summary>
|
||||
/// <param name="columns">The number of columns that need to be bound.</param>
|
||||
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
|
||||
/// <returns>The keybindings.</returns>
|
||||
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
|
||||
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns)
|
||||
{
|
||||
ManiaAction currentNormalAction = NormalActionStart;
|
||||
ManiaAction currentAction = ActionStart;
|
||||
|
||||
var bindings = new List<KeyBinding>();
|
||||
|
||||
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
||||
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
|
||||
bindings.Add(new KeyBinding(LeftKeys[i], currentAction++));
|
||||
|
||||
if (columns % 2 == 1)
|
||||
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
||||
bindings.Add(new KeyBinding(SpecialKey, currentAction++));
|
||||
|
||||
for (int i = 0; i < columns / 2; i++)
|
||||
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
|
||||
bindings.Add(new KeyBinding(RightKeys[i], currentAction++));
|
||||
|
||||
nextNormalAction = currentNormalAction;
|
||||
return bindings;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||
|
||||
mergeSelection();
|
||||
|
||||
@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems?.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||
mergeSelection();
|
||||
AddAssert("circles not merged", () => circle1 is not null && circle2 is not null
|
||||
&& EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2));
|
||||
@ -222,7 +222,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||
|
||||
mergeSelection();
|
||||
|
||||
|
@ -299,6 +299,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
assertControlPointTypeDuringPlacement(0, PathType.BSpline(4));
|
||||
|
||||
AddStep("press alt-2", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.AltLeft);
|
||||
InputManager.Key(Key.Number2);
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
});
|
||||
assertControlPointTypeDuringPlacement(0, PathType.BEZIER);
|
||||
|
||||
AddStep("start new segment via S", () => InputManager.Key(Key.S));
|
||||
assertControlPointTypeDuringPlacement(2, PathType.LINEAR);
|
||||
|
||||
@ -309,7 +317,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
base.PostProcess();
|
||||
|
||||
var hitObjects = Beatmap.HitObjects as List<OsuHitObject> ?? Beatmap.HitObjects.OfType<OsuHitObject>().ToList();
|
||||
ApplyStacking(Beatmap);
|
||||
}
|
||||
|
||||
internal static void ApplyStacking(IBeatmap beatmap)
|
||||
{
|
||||
var hitObjects = beatmap.HitObjects as List<OsuHitObject> ?? beatmap.HitObjects.OfType<OsuHitObject>().ToList();
|
||||
|
||||
if (hitObjects.Count > 0)
|
||||
{
|
||||
@ -50,14 +55,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
foreach (var h in hitObjects)
|
||||
h.StackHeight = 0;
|
||||
|
||||
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||
else
|
||||
applyStackingOld(Beatmap.BeatmapInfo, hitObjects);
|
||||
applyStackingOld(beatmap.BeatmapInfo, hitObjects);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
|
||||
private static void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
|
||||
@ -209,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
|
||||
private static void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
|
||||
{
|
||||
for (int i = 0; i < hitObjects.Count; i++)
|
||||
{
|
||||
|
@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
flashlightRating *= 0.7;
|
||||
}
|
||||
|
||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating);
|
||||
double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating);
|
||||
double baseFlashlightPerformance = 0.0;
|
||||
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0;
|
||||
baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
|
||||
|
||||
double basePerformance =
|
||||
Math.Pow(
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty);
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
@ -139,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (score.Mods.Any(h => h is OsuModRelax))
|
||||
return 0.0;
|
||||
|
||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
@ -226,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
||||
return 0.0;
|
||||
|
||||
double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0;
|
||||
double flashlightValue = Flashlight.DifficultyToPerformance(attributes.FlashlightDifficulty);
|
||||
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (effectiveMissCount > 0)
|
||||
|
@ -42,5 +42,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
}
|
||||
|
||||
public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER;
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => 25 * Math.Pow(difficulty, 2);
|
||||
}
|
||||
}
|
||||
|
@ -67,5 +67,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
return difficulty * DifficultyMultiplier;
|
||||
}
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
}
|
||||
}
|
||||
|
@ -309,8 +309,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
if (!e.AltPressed)
|
||||
return false;
|
||||
|
||||
// If no pieces are selected, we can't change the path type.
|
||||
if (Pieces.All(p => !p.IsSelected.Value))
|
||||
return false;
|
||||
|
||||
var type = path_types[e.Key - Key.Number1];
|
||||
|
||||
// The first control point can never be inherit type
|
||||
if (Pieces[0].IsSelected.Value && type == null)
|
||||
return false;
|
||||
|
||||
|
@ -359,8 +359,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
|
||||
// Update the cursor position.
|
||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All);
|
||||
cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||
cursor.Position = getCursorPosition();
|
||||
}
|
||||
else if (cursor != null)
|
||||
{
|
||||
@ -374,6 +373,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 getCursorPosition()
|
||||
{
|
||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All);
|
||||
return ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a new control point can be placed at the current mouse position.
|
||||
/// </summary>
|
||||
@ -386,7 +391,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last);
|
||||
|
||||
lastPoint = last;
|
||||
return lastPiece.IsHovered != true;
|
||||
// We may only place a new control point if the cursor is not overlapping with the last control point.
|
||||
// If snapping is enabled, the cursor may not hover the last piece while still placing the control point at the same position.
|
||||
return !lastPiece.IsHovered && (last is null || Vector2.DistanceSquared(last.Position, getCursorPosition()) > 1f);
|
||||
}
|
||||
|
||||
private void placeNewControlPoint()
|
||||
|
54
osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs
Normal file
54
osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class GenerateToolboxGroup : EditorToolboxGroup
|
||||
{
|
||||
private readonly EditorToolButton polygonButton;
|
||||
|
||||
public GenerateToolboxGroup()
|
||||
: base("Generate")
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
polygonButton = new EditorToolButton("Polygon",
|
||||
() => new SpriteIcon { Icon = FontAwesome.Solid.Spinner },
|
||||
() => new PolygonGenerationPopover()),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Repeat) return false;
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.D:
|
||||
if (!e.ControlPressed || !e.ShiftPressed)
|
||||
return false;
|
||||
|
||||
polygonButton.TriggerClick();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -65,13 +65,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private Bindable<HitObject> placementObject;
|
||||
|
||||
[Cached(typeof(IDistanceSnapProvider))]
|
||||
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||
public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||
|
||||
[Cached]
|
||||
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
|
||||
|
||||
[Cached]
|
||||
protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
||||
protected readonly FreehandSliderToolboxGroup FreehandSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -110,7 +110,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
||||
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
|
||||
},
|
||||
FreehandlSliderToolboxGroup
|
||||
new GenerateToolboxGroup(),
|
||||
FreehandSliderToolboxGroup
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -295,6 +296,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
if (Vector2.Distance(closestSnapPosition, screenSpacePosition) < snapRadius)
|
||||
{
|
||||
// if the snap target is a stacked object, snap to its unstacked position rather than its stacked position.
|
||||
// this is intended to make working with stacks easier (because thanks to this, you can drag an object to any
|
||||
// of the items on the stack to add an object to it, rather than having to drag to the position of the *first* object on it at all times).
|
||||
if (b.Item is OsuHitObject osuObject && osuObject.StackOffset != Vector2.Zero)
|
||||
closestSnapPosition = b.ToScreenSpace(b.ToLocalSpace(closestSnapPosition) - osuObject.StackOffset);
|
||||
|
||||
// only return distance portion, since time is not really valid
|
||||
snapResult = new SnapResult(closestSnapPosition, null, playfield);
|
||||
return true;
|
||||
|
@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class OsuHitObjectInspector : HitObjectInspector
|
||||
{
|
||||
protected override void AddInspectorValues()
|
||||
protected override void AddInspectorValues(HitObject[] objects)
|
||||
{
|
||||
base.AddInspectorValues();
|
||||
base.AddInspectorValues(objects);
|
||||
|
||||
if (EditorBeatmap.SelectedHitObjects.Count > 0)
|
||||
if (objects.Length > 0)
|
||||
{
|
||||
var firstInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MinBy(ho => ho.StartTime)!;
|
||||
var lastInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MaxBy(ho => ho.GetEndTime())!;
|
||||
var firstInSelection = (OsuHitObject)objects.MinBy(ho => ho.StartTime)!;
|
||||
var lastInSelection = (OsuHitObject)objects.MaxBy(ho => ho.GetEndTime())!;
|
||||
|
||||
Debug.Assert(firstInSelection != null && lastInSelection != null);
|
||||
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@ -50,12 +51,33 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
var hitObjects = selectedMovableObjects;
|
||||
|
||||
var localDelta = this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
||||
|
||||
// this conditional is a rather ugly special case for stacks.
|
||||
// as it turns out, adding the `EditorBeatmap.Update()` call at the end of this would cause stacked objects to jitter when moved around
|
||||
// (they would stack and then unstack every frame).
|
||||
// the reason for that is that the selection handling abstractions are not aware of the distinction between "displayed" and "actual" position
|
||||
// which is unique to osu! due to stacking being applied as a post-processing step.
|
||||
// therefore, the following loop would occur:
|
||||
// - on frame 1 the blueprint is snapped to the stack's baseline position. `EditorBeatmap.Update()` applies stacking successfully,
|
||||
// the blueprint moves up the stack from its original drag position.
|
||||
// - on frame 2 the blueprint's position is now the *stacked* position, which is interpreted higher up as *manually performing an unstack*
|
||||
// to the blueprint's unstacked position (as the machinery higher up only cares about differences in screen space position).
|
||||
if (hitObjects.Any(h => Precision.AlmostEquals(localDelta, -h.StackOffset)))
|
||||
return true;
|
||||
|
||||
// this will potentially move the selection out of bounds...
|
||||
foreach (var h in hitObjects)
|
||||
h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
||||
h.Position += localDelta;
|
||||
|
||||
// but this will be corrected.
|
||||
moveSelectionInBounds();
|
||||
|
||||
// manually update stacking.
|
||||
// this intentionally bypasses the editor `UpdateState()` / beatmap processor flow for performance reasons,
|
||||
// as the entire flow is too expensive to run on every movement.
|
||||
Scheduler.AddOnce(OsuBeatmapProcessor.ApplyStacking, EditorBeatmap);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
193
osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs
Normal file
193
osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs
Normal file
@ -0,0 +1,193 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class PolygonGenerationPopover : OsuPopover
|
||||
{
|
||||
private SliderWithTextBoxInput<double> distanceSnapInput = null!;
|
||||
private SliderWithTextBoxInput<int> offsetAngleInput = null!;
|
||||
private SliderWithTextBoxInput<int> repeatCountInput = null!;
|
||||
private SliderWithTextBoxInput<int> pointInput = null!;
|
||||
private RoundedButton commitButton = null!;
|
||||
|
||||
private readonly List<HitCircle> insertedCircles = new List<HitCircle>();
|
||||
private bool began;
|
||||
private bool committed;
|
||||
|
||||
[Resolved]
|
||||
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private HitObjectComposer composer { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
distanceSnapInput = new SliderWithTextBoxInput<double>("Distance snap:")
|
||||
{
|
||||
Current = new BindableNumber<double>(1)
|
||||
{
|
||||
MinValue = 0.1,
|
||||
MaxValue = 6,
|
||||
Precision = 0.1,
|
||||
Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
|
||||
},
|
||||
Instantaneous = true
|
||||
},
|
||||
offsetAngleInput = new SliderWithTextBoxInput<int>("Offset angle:")
|
||||
{
|
||||
Current = new BindableNumber<int>
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 180,
|
||||
Precision = 1
|
||||
},
|
||||
Instantaneous = true
|
||||
},
|
||||
repeatCountInput = new SliderWithTextBoxInput<int>("Repeats:")
|
||||
{
|
||||
Current = new BindableNumber<int>(1)
|
||||
{
|
||||
MinValue = 1,
|
||||
MaxValue = 10,
|
||||
Precision = 1
|
||||
},
|
||||
Instantaneous = true
|
||||
},
|
||||
pointInput = new SliderWithTextBoxInput<int>("Vertices:")
|
||||
{
|
||||
Current = new BindableNumber<int>(3)
|
||||
{
|
||||
MinValue = 3,
|
||||
MaxValue = 10,
|
||||
Precision = 1,
|
||||
},
|
||||
Instantaneous = true
|
||||
},
|
||||
commitButton = new RoundedButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Text = "Create",
|
||||
Action = commit
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
changeHandler?.BeginChange();
|
||||
began = true;
|
||||
|
||||
distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon());
|
||||
offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon());
|
||||
repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon());
|
||||
pointInput.Current.BindValueChanged(_ => tryCreatePolygon());
|
||||
tryCreatePolygon();
|
||||
}
|
||||
|
||||
private void tryCreatePolygon()
|
||||
{
|
||||
double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime);
|
||||
TimingControlPoint timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(startTime);
|
||||
double timeSpacing = timingPoint.BeatLength / editorBeatmap.BeatDivisor;
|
||||
IHasSliderVelocity lastWithSliderVelocity = editorBeatmap.HitObjects.Where(ho => ho.GetEndTime() <= startTime).OfType<IHasSliderVelocity>().LastOrDefault() ?? new Slider();
|
||||
double velocity = OsuHitObject.BASE_SCORING_DISTANCE * editorBeatmap.Difficulty.SliderMultiplier
|
||||
/ LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(lastWithSliderVelocity, timingPoint, OsuRuleset.SHORT_NAME);
|
||||
double length = distanceSnapInput.Current.Value * velocity * timeSpacing;
|
||||
float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value)));
|
||||
|
||||
editorBeatmap.RemoveRange(insertedCircles);
|
||||
insertedCircles.Clear();
|
||||
|
||||
var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler;
|
||||
bool first = true;
|
||||
|
||||
for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i)
|
||||
{
|
||||
float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value);
|
||||
var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle));
|
||||
|
||||
var circle = new HitCircle
|
||||
{
|
||||
Position = position,
|
||||
StartTime = startTime,
|
||||
NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True,
|
||||
};
|
||||
// TODO: probably ensure samples also follow current ternary status (not trivial)
|
||||
circle.Samples.Add(circle.CreateHitSampleInfo());
|
||||
|
||||
if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y)
|
||||
{
|
||||
commitButton.Enabled.Value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
insertedCircles.Add(circle);
|
||||
startTime = beatSnapProvider.SnapTime(startTime + timeSpacing);
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
editorBeatmap.AddRange(insertedCircles);
|
||||
commitButton.Enabled.Value = true;
|
||||
}
|
||||
|
||||
private void commit()
|
||||
{
|
||||
changeHandler?.EndChange();
|
||||
committed = true;
|
||||
Hide();
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
base.PopOut();
|
||||
|
||||
if (began && !committed)
|
||||
{
|
||||
editorBeatmap.RemoveRange(insertedCircles);
|
||||
changeHandler?.EndChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -41,139 +42,193 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
if (lookup is OsuSkinComponentLookup osuComponent)
|
||||
switch (lookup)
|
||||
{
|
||||
switch (osuComponent.Component)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS));
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Only handle per ruleset defaults here.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
case OsuSkinComponents.SliderScorePoint:
|
||||
return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS);
|
||||
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: MAX_FOLLOW_CIRCLE_AREA_SIZE);
|
||||
if (followCircleContent != null)
|
||||
return new LegacyFollowCircle(followCircleContent);
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||
if (!IsProvidingLegacyResources)
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderBall:
|
||||
if (GetTexture("sliderb") != null || GetTexture("sliderb0") != null)
|
||||
return new LegacySliderBall(this);
|
||||
// Our own ruleset components default.
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
return null;
|
||||
if (keyCounter != null)
|
||||
{
|
||||
// set the anchor to top right so that it won't squash to the return button to the top
|
||||
keyCounter.Anchor = Anchor.CentreRight;
|
||||
keyCounter.Origin = Anchor.CentreRight;
|
||||
keyCounter.X = 0;
|
||||
// 340px is the default height inherit from stable
|
||||
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
|
||||
}
|
||||
|
||||
case OsuSkinComponents.SliderBody:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderBody();
|
||||
var combo = container.OfType<LegacyDefaultComboCounter>().FirstOrDefault();
|
||||
|
||||
return null;
|
||||
if (combo != null)
|
||||
{
|
||||
combo.Anchor = Anchor.BottomLeft;
|
||||
combo.Origin = Anchor.BottomLeft;
|
||||
combo.Scale = new Vector2(1.28f);
|
||||
}
|
||||
})
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new LegacyDefaultComboCounter(),
|
||||
new LegacyKeyCounterDisplay(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
case OsuSkinComponents.SliderTailHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderendcircle", false);
|
||||
return null;
|
||||
|
||||
return null;
|
||||
case OsuSkinComponentLookup osuComponent:
|
||||
switch (osuComponent.Component)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false,
|
||||
maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS));
|
||||
|
||||
case OsuSkinComponents.SliderHeadHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderHeadHitCircle();
|
||||
case OsuSkinComponents.SliderScorePoint:
|
||||
return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS);
|
||||
|
||||
return null;
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: MAX_FOLLOW_CIRCLE_AREA_SIZE);
|
||||
if (followCircleContent != null)
|
||||
return new LegacyFollowCircle(followCircleContent);
|
||||
|
||||
case OsuSkinComponents.ReverseArrow:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyReverseArrow();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.Cursor:
|
||||
if (GetTexture("cursor") != null)
|
||||
return new LegacyCursor(this);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorTrail:
|
||||
if (GetTexture("cursortrail") != null)
|
||||
return new LegacyCursorTrail(this);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorRipple:
|
||||
if (GetTexture("cursor-ripple") != null)
|
||||
{
|
||||
var ripple = this.GetAnimation("cursor-ripple", false, false);
|
||||
|
||||
// In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible.
|
||||
// If anyone complains about these not being applied, this can be uncommented.
|
||||
//
|
||||
// But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size,
|
||||
// so we might be okay.
|
||||
//
|
||||
// if (ripple != null)
|
||||
// {
|
||||
// ripple.Scale = new Vector2(0.5f);
|
||||
// ripple.Alpha = 0.2f;
|
||||
// }
|
||||
|
||||
return ripple;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorParticles:
|
||||
if (GetTexture("star2") != null)
|
||||
return new LegacyCursorParticles();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorSmoke:
|
||||
if (GetTexture("cursor-smoke") != null)
|
||||
return new LegacySmokeSegment();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircleText:
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
const float hitcircle_text_scale = 0.8f;
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(hitcircle_text_scale),
|
||||
MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale,
|
||||
};
|
||||
case OsuSkinComponents.SliderBall:
|
||||
if (GetTexture("sliderb") != null || GetTexture("sliderb0") != null)
|
||||
return new LegacySliderBall(this);
|
||||
|
||||
case OsuSkinComponents.SpinnerBody:
|
||||
bool hasBackground = GetTexture("spinner-background") != null;
|
||||
return null;
|
||||
|
||||
if (GetTexture("spinner-top") != null && !hasBackground)
|
||||
return new LegacyNewStyleSpinner();
|
||||
else if (hasBackground)
|
||||
return new LegacyOldStyleSpinner();
|
||||
case OsuSkinComponents.SliderBody:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderBody();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.ApproachCircle:
|
||||
if (GetTexture(@"approachcircle") != null)
|
||||
return new LegacyApproachCircle();
|
||||
case OsuSkinComponents.SliderTailHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderendcircle", false);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
default:
|
||||
throw new UnsupportedSkinComponentException(lookup);
|
||||
}
|
||||
case OsuSkinComponents.SliderHeadHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderHeadHitCircle();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.ReverseArrow:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyReverseArrow();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.Cursor:
|
||||
if (GetTexture("cursor") != null)
|
||||
return new LegacyCursor(this);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorTrail:
|
||||
if (GetTexture("cursortrail") != null)
|
||||
return new LegacyCursorTrail(this);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorRipple:
|
||||
if (GetTexture("cursor-ripple") != null)
|
||||
{
|
||||
var ripple = this.GetAnimation("cursor-ripple", false, false);
|
||||
|
||||
// In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible.
|
||||
// If anyone complains about these not being applied, this can be uncommented.
|
||||
//
|
||||
// But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size,
|
||||
// so we might be okay.
|
||||
//
|
||||
// if (ripple != null)
|
||||
// {
|
||||
// ripple.Scale = new Vector2(0.5f);
|
||||
// ripple.Alpha = 0.2f;
|
||||
// }
|
||||
|
||||
return ripple;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorParticles:
|
||||
if (GetTexture("star2") != null)
|
||||
return new LegacyCursorParticles();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorSmoke:
|
||||
if (GetTexture("cursor-smoke") != null)
|
||||
return new LegacySmokeSegment();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircleText:
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
const float hitcircle_text_scale = 0.8f;
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(hitcircle_text_scale),
|
||||
MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale,
|
||||
};
|
||||
|
||||
case OsuSkinComponents.SpinnerBody:
|
||||
bool hasBackground = GetTexture("spinner-background") != null;
|
||||
|
||||
if (GetTexture("spinner-top") != null && !hasBackground)
|
||||
return new LegacyNewStyleSpinner();
|
||||
else if (hasBackground)
|
||||
return new LegacyOldStyleSpinner();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.ApproachCircle:
|
||||
if (GetTexture(@"approachcircle") != null)
|
||||
return new LegacyApproachCircle();
|
||||
|
||||
return null;
|
||||
|
||||
default:
|
||||
throw new UnsupportedSkinComponentException(lookup);
|
||||
}
|
||||
|
||||
default:
|
||||
return base.GetDrawableComponent(lookup);
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(lookup);
|
||||
}
|
||||
|
||||
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
|
@ -16,7 +16,6 @@ using osu.Framework.Graphics.Shaders.Types;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Timing;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -63,8 +62,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
// -1 signals that the part is unusable, and should not be drawn
|
||||
parts[i].InvalidationID = -1;
|
||||
}
|
||||
|
||||
AddLayout(partSizeCache);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -95,12 +92,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
}
|
||||
}
|
||||
|
||||
private readonly LayoutValue<Vector2> partSizeCache = new LayoutValue<Vector2>(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence);
|
||||
|
||||
private Vector2 partSize => partSizeCache.IsValid
|
||||
? partSizeCache.Value
|
||||
: (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time to fade the cursor trail pieces.
|
||||
/// </summary>
|
||||
@ -156,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
protected void AddTrail(Vector2 position)
|
||||
{
|
||||
position = ToLocalSpace(position);
|
||||
|
||||
if (InterpolateMovements)
|
||||
{
|
||||
if (!lastPosition.HasValue)
|
||||
@ -174,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
float distance = diff.Length;
|
||||
Vector2 direction = diff / distance;
|
||||
|
||||
float interval = partSize.X / 2.5f * IntervalMultiplier;
|
||||
float interval = Texture.DisplayWidth / 2.5f * IntervalMultiplier;
|
||||
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
|
||||
|
||||
for (float d = interval; d < stopAt; d += interval)
|
||||
@ -191,9 +184,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
}
|
||||
}
|
||||
|
||||
private void addPart(Vector2 screenSpacePosition)
|
||||
private void addPart(Vector2 localSpacePosition)
|
||||
{
|
||||
parts[currentIndex].Position = ToLocalSpace(screenSpacePosition);
|
||||
parts[currentIndex].Position = localSpacePosition;
|
||||
parts[currentIndex].Time = time + 1;
|
||||
++parts[currentIndex].InvalidationID;
|
||||
|
||||
@ -220,7 +213,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
private float fadeExponent;
|
||||
|
||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||
private Vector2 size;
|
||||
private Vector2 originPosition;
|
||||
|
||||
private IVertexBatch<TexturedTrailVertex> vertexBatch;
|
||||
@ -236,7 +228,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
shader = Source.shader;
|
||||
texture = Source.texture;
|
||||
size = Source.partSize;
|
||||
time = Source.time;
|
||||
fadeExponent = Source.FadeExponent;
|
||||
|
||||
@ -277,6 +268,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
RectangleF textureRect = texture.GetTextureRect();
|
||||
|
||||
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (part.InvalidationID == -1)
|
||||
@ -285,11 +278,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
if (time - part.Time >= 1)
|
||||
continue;
|
||||
|
||||
Vector2 screenSpacePos = Source.ToScreenSpace(part.Position);
|
||||
|
||||
vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y + size.Y * (1 - originPosition.Y)),
|
||||
Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)),
|
||||
TexturePosition = textureRect.BottomLeft,
|
||||
TextureRect = new Vector4(0, 0, 1, 1),
|
||||
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
||||
@ -298,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y + size.Y * (1 - originPosition.Y)),
|
||||
Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)),
|
||||
TexturePosition = textureRect.BottomRight,
|
||||
TextureRect = new Vector4(0, 0, 1, 1),
|
||||
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
||||
@ -307,7 +298,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y - size.Y * originPosition.Y),
|
||||
Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y - texture.DisplayHeight * originPosition.Y),
|
||||
TexturePosition = textureRect.TopRight,
|
||||
TextureRect = new Vector4(0, 0, 1, 1),
|
||||
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
||||
@ -316,7 +307,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y - size.Y * originPosition.Y),
|
||||
Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y - texture.DisplayHeight * originPosition.Y),
|
||||
TexturePosition = textureRect.TopLeft,
|
||||
TextureRect = new Vector4(0, 0, 1, 1),
|
||||
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
||||
@ -324,6 +315,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
});
|
||||
}
|
||||
|
||||
renderer.PopLocalMatrix();
|
||||
|
||||
vertexBatch.Draw();
|
||||
shader.Unbind();
|
||||
}
|
||||
|
@ -206,6 +206,15 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
private OsuResumeOverlay.OsuResumeOverlayInputBlocker? resumeInputBlocker;
|
||||
|
||||
public void AttachResumeOverlayInputBlocker(OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker)
|
||||
{
|
||||
Debug.Assert(this.resumeInputBlocker == null);
|
||||
this.resumeInputBlocker = resumeInputBlocker;
|
||||
AddInternal(resumeInputBlocker);
|
||||
}
|
||||
|
||||
private partial class ProxyContainer : LifetimeManagementContainer
|
||||
{
|
||||
public void Add(Drawable proxy) => AddInternal(proxy);
|
||||
|
@ -33,9 +33,30 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
OsuResumeOverlayInputBlocker? inputBlocker = null;
|
||||
|
||||
if (drawableRuleset != null)
|
||||
{
|
||||
var osuPlayfield = (OsuPlayfield)drawableRuleset.Playfield;
|
||||
osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker());
|
||||
}
|
||||
|
||||
Add(cursorScaleContainer = new Container
|
||||
{
|
||||
Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }
|
||||
Child = clickToResumeCursor = new OsuClickToResumeCursor
|
||||
{
|
||||
ResumeRequested = () =>
|
||||
{
|
||||
// since the user had to press a button to tap the resume cursor,
|
||||
// block that press event from potentially reaching a hit circle that's behind the cursor.
|
||||
// we cannot do this from OsuClickToResumeCursor directly since we're in a different input manager tree than the gameplay one,
|
||||
// so we rely on a dedicated input blocking component that's implanted in there to do that for us.
|
||||
if (inputBlocker != null)
|
||||
inputBlocker.BlockNextPress = true;
|
||||
|
||||
Resume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -115,10 +136,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
return false;
|
||||
|
||||
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
||||
|
||||
// When resuming with a button, we do not want the osu! input manager to see this button press and include it in the score.
|
||||
// To ensure that this works correctly, schedule the resume operation one frame forward, since the resume operation enables the input manager to see input events.
|
||||
Schedule(() => ResumeRequested?.Invoke());
|
||||
ResumeRequested?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -143,5 +161,27 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class OsuResumeOverlayInputBlocker : Drawable, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
public bool BlockNextPress;
|
||||
|
||||
public OsuResumeOverlayInputBlocker()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Depth = float.MinValue;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
{
|
||||
bool block = BlockNextPress;
|
||||
BlockNextPress = false;
|
||||
return block;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup component)
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
switch (component)
|
||||
switch (lookup)
|
||||
{
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
break;
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(component);
|
||||
return base.GetDrawableComponent(lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -468,6 +468,40 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapHitObjectCoordinatesLegacy()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("hitobject-coordinates-legacy.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
var positionData = hitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(256, 256), positionData!.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapHitObjectCoordinatesLazer()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder(LegacyBeatmapEncoder.FIRST_LAZER_VERSION);
|
||||
|
||||
using (var resStream = TestResources.OpenResource("hitobject-coordinates-lazer.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
var positionData = hitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(256.99853f, 256.001f), positionData!.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapHitObjects()
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ using osu.Game.Replays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
@ -65,14 +65,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore);
|
||||
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
||||
|
||||
Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic));
|
||||
Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL"));
|
||||
Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL"));
|
||||
Assert.That(score.ScoreInfo.APIMods.Select(m => m.Acronym), Is.EquivalentTo(new[] { "CL", "9K", "DS" }));
|
||||
|
||||
Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001));
|
||||
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
||||
|
||||
Assert.That(score.Replay.Frames, Is.Not.Empty);
|
||||
Assert.That(score.Replay.Frames, Has.One.Matches<ManiaReplayFrame>(frame =>
|
||||
frame.Time == 414 && frame.Actions.SequenceEqual(new[] { ManiaAction.Key1, ManiaAction.Key18 })));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,6 +259,44 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoChangesAfterDelete()
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||
|
||||
using var __ = getBeatmapArchive(out string pathOriginal);
|
||||
using var _ = getBeatmapArchive(out string pathOriginalSecond);
|
||||
|
||||
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
|
||||
|
||||
importBeforeUpdate!.PerformWrite(s => s.DeletePending = true);
|
||||
|
||||
var dateBefore = importBeforeUpdate.Value.DateAdded;
|
||||
|
||||
Assert.That(importBeforeUpdate, Is.Not.Null);
|
||||
Debug.Assert(importBeforeUpdate != null);
|
||||
|
||||
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
|
||||
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
Assert.That(importAfterUpdate, Is.Not.Null);
|
||||
Debug.Assert(importAfterUpdate != null);
|
||||
|
||||
checkCount<BeatmapSetInfo>(realm, 1);
|
||||
checkCount<BeatmapInfo>(realm, count_beatmaps);
|
||||
checkCount<BeatmapMetadata>(realm, count_beatmaps);
|
||||
|
||||
Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1));
|
||||
Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(dateBefore));
|
||||
Assert.That(importAfterUpdate.Value.DateAdded, Is.EqualTo(dateBefore));
|
||||
Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoChanges()
|
||||
{
|
||||
@ -272,21 +310,25 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
|
||||
|
||||
var dateBefore = importBeforeUpdate!.Value.DateAdded;
|
||||
|
||||
Assert.That(importBeforeUpdate, Is.Not.Null);
|
||||
Debug.Assert(importBeforeUpdate != null);
|
||||
|
||||
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
|
||||
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
Assert.That(importAfterUpdate, Is.Not.Null);
|
||||
Debug.Assert(importAfterUpdate != null);
|
||||
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
checkCount<BeatmapSetInfo>(realm, 1);
|
||||
checkCount<BeatmapInfo>(realm, count_beatmaps);
|
||||
checkCount<BeatmapMetadata>(realm, count_beatmaps);
|
||||
|
||||
Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1));
|
||||
Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(dateBefore));
|
||||
Assert.That(importAfterUpdate.Value.DateAdded, Is.EqualTo(dateBefore));
|
||||
Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID));
|
||||
});
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ namespace osu.Game.Tests.Editing
|
||||
new object?[] { "1:02:3000", false, null, null },
|
||||
new object?[] { "1:02:300 ()", false, null, null },
|
||||
new object?[] { "1:02:300 (1,2,3)", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||
new object?[] { "1:02:300 (1,2,3) - ", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||
new object?[] { "1:02:300 (1,2,3) - following mod", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||
new object?[] { "1:02:300 (1,2,3) - following mod\nwith newlines", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
|
@ -52,10 +52,7 @@ namespace osu.Game.Tests.Editing
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
composer = new TestHitObjectComposer()
|
||||
};
|
||||
Child = composer = new TestHitObjectComposer();
|
||||
|
||||
BeatDivisor.Value = 1;
|
||||
|
||||
|
@ -8,6 +8,8 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Tests.Mods
|
||||
@ -105,9 +107,6 @@ namespace osu.Game.Tests.Mods
|
||||
testMod.ResetSettingsToDefaults();
|
||||
|
||||
Assert.That(testMod.DrainRate.Value, Is.Null);
|
||||
|
||||
// ReSharper disable once HeuristicUnreachableCode
|
||||
// see https://youtrack.jetbrains.com/issue/RIDER-70159.
|
||||
Assert.That(testMod.OverallDifficulty.Value, Is.Null);
|
||||
|
||||
var applied = applyDifficulty(new BeatmapDifficulty
|
||||
@ -119,6 +118,48 @@ namespace osu.Game.Tests.Mods
|
||||
Assert.That(applied.OverallDifficulty, Is.EqualTo(10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeserializeIncorrectRange()
|
||||
{
|
||||
var apiMod = new APIMod
|
||||
{
|
||||
Acronym = @"DA",
|
||||
Settings = new Dictionary<string, object>
|
||||
{
|
||||
[@"circle_size"] = -727,
|
||||
[@"approach_rate"] = -727,
|
||||
}
|
||||
};
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
var mod = (OsuModDifficultyAdjust)apiMod.ToMod(ruleset);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(mod.CircleSize.Value, Is.GreaterThanOrEqualTo(0).And.LessThanOrEqualTo(11));
|
||||
Assert.That(mod.ApproachRate.Value, Is.GreaterThanOrEqualTo(-10).And.LessThanOrEqualTo(11));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeserializeNegativeApproachRate()
|
||||
{
|
||||
var apiMod = new APIMod
|
||||
{
|
||||
Acronym = @"DA",
|
||||
Settings = new Dictionary<string, object>
|
||||
{
|
||||
[@"approach_rate"] = -9,
|
||||
}
|
||||
};
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
var mod = (OsuModDifficultyAdjust)apiMod.ToMod(ruleset);
|
||||
|
||||
Assert.That(mod.ApproachRate.Value, Is.GreaterThanOrEqualTo(-10).And.LessThanOrEqualTo(11));
|
||||
Assert.That(mod.ApproachRate.Value, Is.EqualTo(-9));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a <see cref="BeatmapDifficulty"/> to the mod and returns a new <see cref="BeatmapDifficulty"/>
|
||||
/// representing the result if the mod were applied to a fresh <see cref="BeatmapDifficulty"/> instance.
|
||||
|
BIN
osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk
Normal file
Binary file not shown.
Binary file not shown.
@ -73,7 +73,12 @@ namespace osu.Game.Tests.Resources
|
||||
|
||||
private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz");
|
||||
|
||||
private static int importId;
|
||||
private static int testId = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Get a unique int value which is incremented each call.
|
||||
/// </summary>
|
||||
public static int GetNextTestID() => Interlocked.Increment(ref testId);
|
||||
|
||||
/// <summary>
|
||||
/// Create a test beatmap set model.
|
||||
@ -88,7 +93,7 @@ namespace osu.Game.Tests.Resources
|
||||
|
||||
RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length];
|
||||
|
||||
int setId = Interlocked.Increment(ref importId);
|
||||
int setId = GetNextTestID();
|
||||
|
||||
var metadata = new BeatmapMetadata
|
||||
{
|
||||
|
6
osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu
Normal file
6
osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu
Normal file
@ -0,0 +1,6 @@
|
||||
osu file format v128
|
||||
|
||||
[HitObjects]
|
||||
// Coordinates should be preserves in lazer beatmaps.
|
||||
|
||||
256.99853,256.001,1000,49,0,0:0:0:0:
|
@ -0,0 +1,5 @@
|
||||
osu file format v14
|
||||
|
||||
[HitObjects]
|
||||
// Coordinates should be truncated to int values in legacy beatmaps.
|
||||
256.99853,256.001,1000,49,0,0:0:0:0:
|
@ -65,7 +65,9 @@ namespace osu.Game.Tests.Skins
|
||||
// Covers default rank display
|
||||
"Archives/modified-default-20230809.osk",
|
||||
// Covers legacy rank display
|
||||
"Archives/modified-classic-20230809.osk"
|
||||
"Archives/modified-classic-20230809.osk",
|
||||
// Covers legacy key counter
|
||||
"Archives/modified-classic-20240724.osk"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -304,11 +304,6 @@ namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
private bool? lastLoadTriggerCausedChange;
|
||||
|
||||
public TestBackgroundScreenDefault()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Next()
|
||||
{
|
||||
bool didChange = base.Next();
|
||||
|
@ -4,16 +4,34 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.Metadata;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
|
||||
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
public partial class TestSceneDailyChallenge : OnlinePlayTestScene
|
||||
{
|
||||
[Cached(typeof(MetadataClient))]
|
||||
private TestMetadataClient metadataClient = new TestMetadataClient();
|
||||
|
||||
[Cached(typeof(INotificationOverlay))]
|
||||
private NotificationOverlay notificationOverlay = new NotificationOverlay();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
base.Content.Add(notificationOverlay);
|
||||
base.Content.Add(metadataClient);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDailyChallenge()
|
||||
{
|
||||
@ -36,5 +54,33 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotifications()
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = { Value = 1234 },
|
||||
Name = { Value = "Daily Challenge: June 4, 2024" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
|
||||
{
|
||||
RequiredMods = [new APIMod(new OsuModTraceable())],
|
||||
AllowedMods = [new APIMod(new OsuModDoubleTime())]
|
||||
}
|
||||
},
|
||||
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
|
||||
Category = { Value = RoomCategory.DailyChallenge }
|
||||
};
|
||||
|
||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });
|
||||
|
||||
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
|
||||
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||
AddUntilStep("wait for screen", () => screen.IsCurrentScreen());
|
||||
AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.Metadata;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
using CreateRoomRequest = osu.Game.Online.Rooms.CreateRoomRequest;
|
||||
|
||||
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
public partial class TestSceneDailyChallengeIntro : OnlinePlayTestScene
|
||||
{
|
||||
[Cached(typeof(MetadataClient))]
|
||||
private TestMetadataClient metadataClient = new TestMetadataClient();
|
||||
|
||||
[Cached(typeof(INotificationOverlay))]
|
||||
private NotificationOverlay notificationOverlay = new NotificationOverlay();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
base.Content.Add(notificationOverlay);
|
||||
base.Content.Add(metadataClient);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Solo]
|
||||
public void TestDailyChallenge()
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = { Value = 1234 },
|
||||
Name = { Value = "Daily Challenge: June 4, 2024" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First())
|
||||
{
|
||||
RequiredMods = [new APIMod(new OsuModTraceable())],
|
||||
AllowedMods = [new APIMod(new OsuModDoubleTime())]
|
||||
}
|
||||
},
|
||||
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
|
||||
Category = { Value = RoomCategory.DailyChallenge }
|
||||
};
|
||||
|
||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallengeIntro(room)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotifications()
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = { Value = 1234 },
|
||||
Name = { Value = "Daily Challenge: June 4, 2024" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
|
||||
{
|
||||
RequiredMods = [new APIMod(new OsuModTraceable())],
|
||||
AllowedMods = [new APIMod(new OsuModDoubleTime())]
|
||||
}
|
||||
},
|
||||
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
|
||||
Category = { Value = RoomCategory.DailyChallenge }
|
||||
};
|
||||
|
||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });
|
||||
|
||||
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
|
||||
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||
AddUntilStep("wait for screen", () => screen.IsCurrentScreen());
|
||||
AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
@ -20,11 +21,11 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
[Test]
|
||||
public void TestBasicAppearance()
|
||||
{
|
||||
DailyChallengeScoreBreakdown breakdown = null!;
|
||||
private DailyChallengeScoreBreakdown breakdown = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create content", () => Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -50,7 +51,14 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
breakdown.Height = height;
|
||||
});
|
||||
|
||||
AddToggleStep("toggle visible", v => breakdown.Alpha = v ? 1 : 0);
|
||||
|
||||
AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicAppearance()
|
||||
{
|
||||
AddStep("add new score", () =>
|
||||
{
|
||||
var ev = new NewScoreEvent(1, new APIUser
|
||||
@ -65,5 +73,24 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
AddStep("set user score", () => breakdown.UserBestScore.Value = new MultiplayerScore { TotalScore = RNG.Next(1_000_000) });
|
||||
AddStep("unset user score", () => breakdown.UserBestScore.Value = null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMassAdd()
|
||||
{
|
||||
AddStep("add 1000 scores at once", () =>
|
||||
{
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
var ev = new NewScoreEvent(1, new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
}, RNG.Next(1_000_000), null);
|
||||
|
||||
breakdown.AddNewScore(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
if (ring.IsNotNull())
|
||||
ring.Height = height;
|
||||
});
|
||||
AddToggleStep("toggle visible", v => ring.Alpha = v ? 1 : 0);
|
||||
|
||||
AddStep("just started", () =>
|
||||
{
|
||||
room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1);
|
||||
|
@ -49,6 +49,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
if (totals.IsNotNull())
|
||||
totals.Height = height;
|
||||
});
|
||||
AddToggleStep("toggle visible", v => totals.Alpha = v ? 1 : 0);
|
||||
|
||||
AddStep("set counts", () => totals.SetInitialCounts(totalPassCount: 9650, cumulativeTotalScore: 10_000_000_000));
|
||||
|
||||
|
@ -28,6 +28,74 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
|
||||
|
||||
[Test]
|
||||
public void TestDeleteUsingMiddleMouse()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddStep("delete with middle mouse", () => InputManager.Click(MouseButton.Middle));
|
||||
AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteUsingShiftRightClick()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddStep("delete with right mouse", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContextMenu()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddStep("delete with right mouse", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
AddAssert("circle not removed", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddAssert("circle selected", () => EditorBeatmap.SelectedHitObjects, () => Has.One.Items);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Solo]
|
||||
public void TestCommitPlacementViaRightClick()
|
||||
{
|
||||
Playfield playfield = null!;
|
||||
|
||||
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
|
||||
AddStep("move mouse to top left of playfield", () =>
|
||||
{
|
||||
playfield = this.ChildrenOfType<Playfield>().Single();
|
||||
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||
InputManager.MoveMouseTo(location);
|
||||
});
|
||||
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("move mouse to bottom right of playfield", () =>
|
||||
{
|
||||
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||
InputManager.MoveMouseTo(location);
|
||||
});
|
||||
AddStep("confirm via right click", () => InputManager.Click(MouseButton.Right));
|
||||
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCommitPlacementViaGlobalAction()
|
||||
{
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
// best way to check without exposing.
|
||||
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||
private Drawable keyCounterContent => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<Drawable>().Skip(1).First();
|
||||
|
||||
public TestSceneHUDOverlay()
|
||||
{
|
||||
@ -79,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
|
||||
|
||||
AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
|
||||
AddAssert("key counter flow is visible", () => keyCounterContent.IsPresent);
|
||||
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
}
|
||||
|
||||
@ -104,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
|
||||
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
|
||||
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
|
||||
AddAssert("key counter flow not affected", () => keyCounterContent.IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -150,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent);
|
||||
AddUntilStep("key counters hidden", () => !keyCounterContent.IsPresent);
|
||||
|
||||
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||
AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||
AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
||||
AddUntilStep("key counters still hidden", () => !keyCounterContent.IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -56,6 +57,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Anchor = Anchor.Centre,
|
||||
Scale = new Vector2(1, -1)
|
||||
},
|
||||
new LegacyKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@ -89,6 +95,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Anchor = Anchor.Centre,
|
||||
Rotation = 90,
|
||||
},
|
||||
new LegacyKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Rotation = 90,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -13,6 +14,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
@ -32,6 +34,23 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; } = null!;
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||
StartTime = 0,
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||
StartTime = 5000,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) =>
|
||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
@ -70,18 +89,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||
|
||||
// Z key was released before pause, resuming should not trigger it
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counter, 2, true);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counter, 2, false);
|
||||
|
||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counter, 3, true);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counter, 3, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -90,30 +107,29 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
KeyCounter counter = null!;
|
||||
|
||||
loadPlayer(() => new ManiaRuleset());
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key4));
|
||||
checkKey(() => counter, 0, false);
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
checkKey(() => counter, 2, true);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 2, false);
|
||||
}
|
||||
|
||||
@ -145,8 +161,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterZ, 2, true);
|
||||
checkKey(() => counterX, 1, false);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counterZ, 2, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -155,12 +174,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
KeyCounter counter = null!;
|
||||
|
||||
loadPlayer(() => new ManiaRuleset());
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key4));
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
AddStep("pause", () => Player.Pause());
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
@ -202,12 +221,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterZ, 2, true);
|
||||
checkKey(() => counterX, 1, true);
|
||||
|
||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterX, 1, false);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counterZ, 2, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -216,24 +237,50 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
KeyCounter counter = null!;
|
||||
|
||||
loadPlayer(() => new ManiaRuleset());
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key4));
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 1, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked()
|
||||
{
|
||||
KeyCounter counter = null!;
|
||||
|
||||
loadPlayer(() => new OsuRuleset());
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||
|
||||
// ensure the input manager receives the Z button press...
|
||||
checkKey(() => counter, 1, true);
|
||||
AddAssert("button is pressed in kbc", () => Player.DrawableRuleset.Playfield.FindClosestParent<OsuInputManager>()!.PressedActions.Single() == OsuAction.LeftButton);
|
||||
|
||||
// ...but also ensure the hit circle in front of the cursor isn't hit by checking max combo.
|
||||
AddAssert("circle not hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(0));
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
|
||||
checkKey(() => counter, 1, false);
|
||||
AddAssert("button is released in kbc", () => !Player.DrawableRuleset.Playfield.FindClosestParent<OsuInputManager>()!.PressedActions.Any());
|
||||
}
|
||||
|
||||
private void loadPlayer(Func<Ruleset> createRuleset)
|
||||
{
|
||||
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
||||
@ -241,9 +288,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
||||
|
||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000));
|
||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500));
|
||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0));
|
||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500));
|
||||
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
||||
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield));
|
||||
}
|
||||
|
||||
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
||||
|
@ -7,21 +7,25 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -39,6 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; } = null!;
|
||||
|
||||
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
|
||||
[SetUpSteps]
|
||||
@ -46,6 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
|
||||
|
||||
AddStep("reload skin editor", () =>
|
||||
@ -369,6 +377,93 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
() => Is.EqualTo(3));
|
||||
}
|
||||
|
||||
private SkinComponentsContainer globalHUDTarget => Player.ChildrenOfType<SkinComponentsContainer>()
|
||||
.Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null);
|
||||
|
||||
private SkinComponentsContainer rulesetHUDTarget => Player.ChildrenOfType<SkinComponentsContainer>()
|
||||
.Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null);
|
||||
|
||||
[Test]
|
||||
public void TestMigrationArgon()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old argon skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"argon-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<ArgonComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new ArgonComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigrationTriangles()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old triangles skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"triangles-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<DefaultComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new DefaultComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigrationLegacy()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old classic skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"classic-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<LegacyComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyDefaultComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
private Skin importSkinFromArchives(string filename)
|
||||
{
|
||||
var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
|
||||
}
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler
|
||||
|
@ -4,7 +4,6 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
@ -19,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected override Drawable CreateArgonImplementation() => new ArgonComboCounter();
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter();
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyDefaultComboCounter();
|
||||
|
||||
[Test]
|
||||
public void TestComboCounterIncrementing()
|
||||
@ -28,17 +27,5 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLegacyComboCounterHiddenByRulesetImplementation()
|
||||
{
|
||||
AddToggleStep("toggle legacy hidden by ruleset", visible =>
|
||||
{
|
||||
foreach (var legacyCounter in this.ChildrenOfType<LegacyComboCounter>())
|
||||
legacyCounter.HiddenByRulesetImplementation = visible;
|
||||
});
|
||||
|
||||
AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,13 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Gameplay;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
@ -91,10 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
SetContents(_ =>
|
||||
{
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
hudOverlay = new HUDOverlay(new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()), Array.Empty<Mod>());
|
||||
|
||||
action?.Invoke(hudOverlay);
|
||||
|
||||
|
@ -0,0 +1,42 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneSkinnableKeyCounter : SkinnableHUDComponentTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly InputCountController controller = new InputCountController();
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("create dependencies", () =>
|
||||
{
|
||||
Add(controller);
|
||||
controller.Add(new KeyCounterKeyboardTrigger(Key.Z));
|
||||
controller.Add(new KeyCounterKeyboardTrigger(Key.X));
|
||||
controller.Add(new KeyCounterKeyboardTrigger(Key.C));
|
||||
controller.Add(new KeyCounterKeyboardTrigger(Key.V));
|
||||
|
||||
foreach (var trigger in controller.Triggers)
|
||||
Add(trigger);
|
||||
});
|
||||
base.SetUpSteps();
|
||||
}
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
||||
protected override Drawable CreateDefaultImplementation() => new ArgonKeyCounterDisplay();
|
||||
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay();
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osuTK.Input;
|
||||
@ -23,6 +27,49 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("disable return to top on idle", () => Game.ChildrenOfType<ButtonSystem>().Single().ReturnToTopOnIdle = false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDailyChallenge()
|
||||
{
|
||||
AddStep("set up API", () => ((DummyAPIAccess)API).HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetRoomRequest getRoomRequest:
|
||||
if (getRoomRequest.RoomId != 1234)
|
||||
return false;
|
||||
|
||||
var beatmap = CreateAPIBeatmap();
|
||||
beatmap.OnlineID = 1001;
|
||||
getRoomRequest.TriggerSuccess(new Room
|
||||
{
|
||||
RoomID = { Value = 1234 },
|
||||
Name = { Value = "Aug 8, 2024" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem(beatmap)
|
||||
},
|
||||
StartDate = { Value = DateTimeOffset.Now.AddMinutes(-30) },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) }
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("beatmap of the day active", () => Game.ChildrenOfType<IMetadataClient>().Single().DailyChallengeUpdated(new DailyChallengeInfo
|
||||
{
|
||||
RoomID = 1234,
|
||||
}));
|
||||
|
||||
AddStep("enter menu", () => InputManager.Key(Key.P));
|
||||
AddStep("enter submenu", () => InputManager.Key(Key.P));
|
||||
AddStep("enter daily challenge", () => InputManager.Key(Key.D));
|
||||
|
||||
AddUntilStep("wait for daily challenge screen", () => Game.ScreenStack.CurrentScreen, Is.TypeOf<Screens.OnlinePlay.DailyChallenge.DailyChallenge>);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlineMenuBannerTrusted()
|
||||
{
|
||||
|
@ -61,7 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() });
|
||||
AddWaitStep("wait some", 3);
|
||||
AddAssert("customisation area not expanded", () => !this.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value);
|
||||
AddAssert("customisation area not expanded",
|
||||
() => this.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -139,8 +139,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private void addRandomPlayer()
|
||||
{
|
||||
int randomUser = RNG.Next(200000, 500000);
|
||||
multiplayerClient.AddUser(new APIUser { Id = randomUser, Username = $"user {randomUser}" });
|
||||
int id = TestResources.GetNextTestID();
|
||||
multiplayerClient.AddUser(new APIUser { Id = id, Username = $"user {id}" });
|
||||
}
|
||||
|
||||
private void removeLastUser()
|
||||
|
@ -47,6 +47,7 @@ using osu.Game.Screens.Select.Carousel;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Screens.Select.Options;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using SharpCompress;
|
||||
@ -240,11 +241,14 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("change beatmap files", () =>
|
||||
{
|
||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||
FileUtils.AttemptOperation(() =>
|
||||
{
|
||||
using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite))
|
||||
stream.WriteByte(0);
|
||||
}
|
||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||
{
|
||||
using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite))
|
||||
stream.WriteByte(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("invalidate cache", () =>
|
||||
@ -272,8 +276,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("delete beatmap files", () =>
|
||||
{
|
||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||
Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath()));
|
||||
FileUtils.AttemptOperation(() =>
|
||||
{
|
||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||
Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath()));
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("invalidate cache", () =>
|
||||
|
@ -9,13 +9,13 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Chat.ChannelList;
|
||||
using osu.Game.Overlays.Chat.Listing;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private Channel createRandomPublicChannel()
|
||||
{
|
||||
int id = RNG.Next(0, 10000);
|
||||
int id = TestResources.GetNextTestID();
|
||||
return new Channel
|
||||
{
|
||||
Name = $"#channel-{id}",
|
||||
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private Channel createRandomPrivateChannel()
|
||||
{
|
||||
int id = RNG.Next(0, 10000);
|
||||
int id = TestResources.GetNextTestID();
|
||||
return new Channel(new APIUser
|
||||
{
|
||||
Id = id,
|
||||
@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private Channel createRandomAnnounceChannel()
|
||||
{
|
||||
int id = RNG.Next(0, 10000);
|
||||
int id = TestResources.GetNextTestID();
|
||||
return new Channel
|
||||
{
|
||||
Name = $"Announce {id}",
|
||||
|
@ -19,7 +19,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
@ -33,6 +32,7 @@ using osu.Game.Overlays.Chat.ChannelList;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
return true;
|
||||
|
||||
case PostMessageRequest postMessage:
|
||||
postMessage.TriggerSuccess(new Message(RNG.Next(0, 10000000))
|
||||
postMessage.TriggerSuccess(new Message(TestResources.GetNextTestID())
|
||||
{
|
||||
Content = postMessage.Message.Content,
|
||||
ChannelId = postMessage.Message.ChannelId,
|
||||
@ -719,7 +719,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private Channel createPrivateChannel()
|
||||
{
|
||||
int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1);
|
||||
int id = TestResources.GetNextTestID();
|
||||
|
||||
return new Channel(new APIUser
|
||||
{
|
||||
Id = id,
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.Chat;
|
||||
@ -32,6 +33,34 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMention()
|
||||
{
|
||||
AddStep("add normal message", () => channel.AddNewMessages(
|
||||
new Message(1)
|
||||
{
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "TestUser2"
|
||||
},
|
||||
Content = "Hello how are you today?",
|
||||
Timestamp = new DateTimeOffset(2021, 12, 11, 13, 33, 24, TimeSpan.Zero)
|
||||
}));
|
||||
|
||||
AddStep("add mention", () => channel.AddNewMessages(
|
||||
new Message(2)
|
||||
{
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "TestUser2"
|
||||
},
|
||||
Content = $"Hello {API.LocalUser.Value.Username} how are you today?",
|
||||
Timestamp = new DateTimeOffset(2021, 12, 11, 13, 33, 25, TimeSpan.Zero)
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDaySeparators()
|
||||
{
|
||||
@ -40,6 +69,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 3,
|
||||
Username = "LocalUser"
|
||||
};
|
||||
|
||||
string uuid = Guid.NewGuid().ToString();
|
||||
AddStep("add local echo message", () => channel.AddLocalEcho(new LocalEchoMessage
|
||||
{
|
||||
@ -83,5 +113,38 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddUntilStep("three day separators present", () => drawableChannel.ChildrenOfType<DaySeparator>().Count() == 3);
|
||||
AddAssert("last day separator is from correct day", () => drawableChannel.ChildrenOfType<DaySeparator>().Last().Date.Date == new DateTime(2022, 11, 22));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBackgroundAlternating()
|
||||
{
|
||||
int messageCount = 1;
|
||||
|
||||
AddRepeatStep("add messages", () =>
|
||||
{
|
||||
channel.AddNewMessages(new Message(messageCount)
|
||||
{
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 3,
|
||||
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||
},
|
||||
Content = "Hi there all!",
|
||||
Timestamp = new DateTimeOffset(2022, 11, 21, 20, messageCount, 13, TimeSpan.Zero),
|
||||
Uuid = Guid.NewGuid().ToString(),
|
||||
});
|
||||
messageCount++;
|
||||
}, 10);
|
||||
|
||||
AddUntilStep("10 message present", () => drawableChannel.ChildrenOfType<ChatLine>().Count() == 10);
|
||||
|
||||
int checkCount = 0;
|
||||
|
||||
AddRepeatStep("check background", () =>
|
||||
{
|
||||
// +1 because the day separator take one index
|
||||
Assert.AreEqual((checkCount + 1) % 2 == 0, drawableChannel.ChildrenOfType<ChatLine>().ToList()[checkCount].AlternatingBackground);
|
||||
checkCount++;
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Profile;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public partial class TestSceneUserProfileDailyChallenge : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(new UserProfileData(new APIUser(), new OsuRuleset().RulesetInfo));
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
DailyChallengeStatsDisplay display = null!;
|
||||
|
||||
AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v));
|
||||
AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v));
|
||||
AddSliderStep("weekly", 0, 250, 1, v => update(s => s.WeeklyStreakCurrent = v));
|
||||
AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v));
|
||||
AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v));
|
||||
AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v));
|
||||
AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v));
|
||||
AddStep("create", () =>
|
||||
{
|
||||
Clear();
|
||||
Add(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background2,
|
||||
});
|
||||
Add(display = new DailyChallengeStatsDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(1f),
|
||||
User = { BindTarget = User },
|
||||
});
|
||||
});
|
||||
AddStep("hover", () => InputManager.MoveMouseTo(display));
|
||||
}
|
||||
|
||||
private void update(Action<APIUserDailyChallengeStatistics> change)
|
||||
{
|
||||
change.Invoke(User.Value!.User.DailyChallengeStatistics);
|
||||
User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,12 +4,14 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
@ -24,7 +26,17 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[SetUpSteps]
|
||||
public void SetUp()
|
||||
{
|
||||
AddStep("create profile overlay", () => Child = profile = new UserProfileOverlay());
|
||||
AddStep("create profile overlay", () =>
|
||||
{
|
||||
profile = new UserProfileOverlay();
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[] { (typeof(UserProfileOverlay), profile) },
|
||||
Child = profile,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -131,6 +143,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||
ProfileHue = hue,
|
||||
PlayMode = "osu",
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@ -174,21 +187,36 @@ namespace osu.Game.Tests.Visual.Online
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||
ProfileHue = hue,
|
||||
PlayMode = "osu",
|
||||
}));
|
||||
|
||||
int hue2 = 0;
|
||||
|
||||
AddSliderStep("hue 2", 0, 360, 50, h => hue2 = h);
|
||||
AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 }));
|
||||
AddStep("show user", () => profile.ShowUser(new APIUser { Id = 2 }));
|
||||
AddWaitStep("wait some", 3);
|
||||
|
||||
AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = $"Colorful #{hue2}",
|
||||
Id = 1,
|
||||
Id = 2,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||
ProfileHue = hue2,
|
||||
PlayMode = "osu",
|
||||
}));
|
||||
|
||||
AddStep("show user different ruleset", () => profile.ShowUser(new APIUser { Id = 2 }, new TaikoRuleset().RulesetInfo));
|
||||
AddWaitStep("wait some", 3);
|
||||
|
||||
AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = $"Colorful #{hue2}",
|
||||
Id = 2,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||
ProfileHue = hue2,
|
||||
PlayMode = "osu",
|
||||
}));
|
||||
}
|
||||
|
||||
@ -282,6 +310,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
ImageUrlLowRes = "https://assets.ppy.sh/profile-badges/contributor.png",
|
||||
},
|
||||
},
|
||||
DailyChallengeStatistics = new APIUserDailyChallengeStatistics
|
||||
{
|
||||
DailyStreakCurrent = 231,
|
||||
WeeklyStreakCurrent = 18,
|
||||
DailyStreakBest = 370,
|
||||
WeeklyStreakBest = 51,
|
||||
Top10PercentPlacements = 345,
|
||||
Top50PercentPlacements = 427,
|
||||
},
|
||||
Title = "osu!volunteer",
|
||||
Colour = "ff0000",
|
||||
Achievements = Array.Empty<APIUserAchievement>(),
|
||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() >= beforePanelCount + scores_per_result);
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
@ -156,7 +156,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() >= beforePanelCount + scores_per_result);
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() >= beforePanelCount + scores_per_result);
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("populate ruleset statistics", () =>
|
||||
{
|
||||
Dictionary<string, UserStatistics> rulesetStatistics = new Dictionary<string, UserStatistics>();
|
||||
@ -68,6 +66,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
base.SetUpSteps();
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -0,0 +1,67 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public abstract partial class SongSelectComponentsTestScene : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
protected override Container<Drawable> Content { get; } = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(10),
|
||||
};
|
||||
|
||||
private Container? resizeContainer;
|
||||
private float relativeWidth;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
base.Content.Child = resizeContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(10),
|
||||
Width = relativeWidth,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background5,
|
||||
},
|
||||
Content
|
||||
}
|
||||
};
|
||||
|
||||
AddSliderStep("change relative width", 0, 1f, 1f, v =>
|
||||
{
|
||||
if (resizeContainer != null)
|
||||
resizeContainer.Width = v;
|
||||
|
||||
relativeWidth = v;
|
||||
});
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public virtual void SetUpSteps()
|
||||
{
|
||||
AddStep("reset dependencies", () =>
|
||||
{
|
||||
Beatmap.SetDefault();
|
||||
SelectedMods.SetDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -18,10 +18,9 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene
|
||||
public partial class TestSceneBeatmapInfoWedge : SongSelectComponentsTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private TestBeatmapInfoWedgeV2 infoWedge = null!;
|
||||
@ -33,6 +32,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
this.rulesets = rulesets;
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -107,12 +113,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTruncation()
|
||||
{
|
@ -0,0 +1,44 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Screens.SelectV2.Wedge;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestScene
|
||||
{
|
||||
private DifficultyNameContent? difficultyNameContent;
|
||||
|
||||
[Test]
|
||||
public void TestLocalBeatmap()
|
||||
{
|
||||
AddStep("set component", () => Child = difficultyNameContent = new LocalDifficultyNameContent());
|
||||
|
||||
AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<TruncatingSpriteText>().Single().Text));
|
||||
AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<OsuHoverContainer>().Single().ChildrenOfType<OsuSpriteText>().Single().Text));
|
||||
|
||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
DifficultyName = "really long difficulty name that gets truncated",
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Author = { Username = "really long username that is autosized" },
|
||||
},
|
||||
OnlineID = 1,
|
||||
}
|
||||
}));
|
||||
|
||||
AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<TruncatingSpriteText>().Single().Text));
|
||||
AddAssert("author is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<OsuHoverContainer>().Single().ChildrenOfType<OsuSpriteText>().Single().Text));
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -24,9 +23,9 @@ using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneLeaderboardScoreV2 : OsuTestScene
|
||||
public partial class TestSceneLeaderboardScore : SongSelectComponentsTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
@ -36,19 +35,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private FillFlowContainer? fillFlow;
|
||||
private OsuSpriteText? drawWidthText;
|
||||
private float relativeWidth;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// TODO: invalidation seems to be one-off when clicking slider to a certain value, so drag for now
|
||||
// doesn't seem to happen in-game (when toggling window mode)
|
||||
AddSliderStep("change relative width", 0, 1f, 0.6f, v =>
|
||||
{
|
||||
relativeWidth = v;
|
||||
if (fillFlow != null) fillFlow.Width = v;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSheared()
|
||||
@ -59,7 +45,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
fillFlow = new FillFlowContainer
|
||||
{
|
||||
Width = relativeWidth,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -94,7 +79,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
fillFlow = new FillFlowContainer
|
||||
{
|
||||
Width = relativeWidth,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -118,8 +102,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
});
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddToggleStep("toggle scoring mode", v => config.SetValue(OsuSetting.ScoreDisplayMode, v ? ScoringMode.Classic : ScoringMode.Standardised));
|
||||
}
|
@ -17,13 +17,12 @@ using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneSongSelectV2 : ScreenTestScene
|
||||
public partial class TestSceneSongSelect : ScreenTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly ScreenFooter screenScreenFooter;
|
||||
@ -31,7 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[Cached]
|
||||
private readonly OsuLogo logo;
|
||||
|
||||
public TestSceneSongSelectV2()
|
||||
public TestSceneSongSelect()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -63,8 +62,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("load screen", () => Stack.Push(new SongSelectV2()));
|
||||
AddUntilStep("wait for load", () => Stack.CurrentScreen is SongSelectV2 songSelect && songSelect.IsLoaded);
|
||||
AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SongSelectV2()));
|
||||
AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelectV2 songSelect && songSelect.IsLoaded);
|
||||
}
|
||||
|
||||
#region Footer
|
@ -5,19 +5,18 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneSongSelectV2Navigation : OsuGameTestScene
|
||||
public partial class TestSceneSongSelectNavigation : OsuGameTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
AddWaitStep("wait", 5);
|
||||
PushAndConfirm(() => new SongSelectV2());
|
||||
PushAndConfirm(() => new Screens.SelectV2.SongSelectV2());
|
||||
}
|
||||
|
||||
[Test]
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -10,6 +11,7 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osuTK.Input;
|
||||
using Color4 = osuTK.Graphics.Color4;
|
||||
@ -39,8 +41,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestDailyChallengeButton()
|
||||
{
|
||||
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
|
||||
|
||||
AddStep("set up API", () => dummyAPI.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
@ -58,6 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
new PlaylistItem(beatmap)
|
||||
},
|
||||
StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
|
||||
});
|
||||
return true;
|
||||
@ -67,17 +68,50 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
ButtonSystemState = ButtonSystemState.TopLevel,
|
||||
});
|
||||
NotificationOverlay notificationOverlay = null!;
|
||||
DependencyProvidingContainer buttonContainer = null!;
|
||||
|
||||
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
|
||||
{
|
||||
RoomID = 1234,
|
||||
}));
|
||||
AddStep("add content", () =>
|
||||
{
|
||||
notificationOverlay = new NotificationOverlay();
|
||||
Children = new Drawable[]
|
||||
{
|
||||
notificationOverlay,
|
||||
buttonContainer = new DependencyProvidingContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)],
|
||||
Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
ButtonSystemState = ButtonSystemState.TopLevel,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
||||
|
||||
AddStep("clear notifications", () =>
|
||||
{
|
||||
foreach (var notification in notificationOverlay.AllNotifications)
|
||||
notification.Close(runFlingAnimation: false);
|
||||
});
|
||||
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
|
||||
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||
|
||||
AddStep("hide button's parent", () => buttonContainer.Hide());
|
||||
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
|
||||
{
|
||||
RoomID = 1234,
|
||||
}));
|
||||
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -10,6 +11,8 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
@ -19,10 +22,15 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
private ModCustomisationPanel panel = null!;
|
||||
private ModCustomisationHeader header = null!;
|
||||
private Container content = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
InputManager.MoveMouseTo(Vector2.One);
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -36,6 +44,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
SelectedMods = { BindTarget = SelectedMods },
|
||||
}
|
||||
};
|
||||
|
||||
header = panel.Children.OfType<ModCustomisationHeader>().First();
|
||||
content = panel.Children.OfType<Container>().First();
|
||||
});
|
||||
|
||||
[Test]
|
||||
@ -44,23 +55,112 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("set DT", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
});
|
||||
AddStep("set DA", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
});
|
||||
AddStep("set FL+WU+DA+AD", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
});
|
||||
AddStep("set empty", () =>
|
||||
{
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
panel.Enabled.Value = panel.Expanded.Value = false;
|
||||
panel.Enabled.Value = false;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoverDoesNotExpandWhenNoCustomisableMods()
|
||||
{
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
|
||||
checkExpanded(false);
|
||||
|
||||
AddStep("hover content", () => InputManager.MoveMouseTo(content));
|
||||
|
||||
checkExpanded(false);
|
||||
|
||||
AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoverExpandsWithCustomisableMods()
|
||||
{
|
||||
AddStep("add customisable mod", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = true;
|
||||
});
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move to content", () => InputManager.MoveMouseTo(content));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
checkExpanded(false);
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
checkExpanded(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpandedStatePersistsWhenClicked()
|
||||
{
|
||||
AddStep("add customisable mod", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = true;
|
||||
});
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(false);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoverExpandsAndCollapsesWhenHeaderClicked()
|
||||
{
|
||||
AddStep("add customisable mod", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = true;
|
||||
});
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(false);
|
||||
}
|
||||
|
||||
private void checkExpanded(bool expanded)
|
||||
{
|
||||
AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.ExpandedState.Value,
|
||||
() => expanded ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOutOfRangeValueStillApplied()
|
||||
public void TestValueAboveRangeStillApplied()
|
||||
{
|
||||
AddStep("set override cs to 11", () => modDifficultyAdjust.CircleSize.Value = 11);
|
||||
|
||||
@ -91,6 +91,28 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
checkBindableAtValue("Circle Size", 11);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestValueBelowRangeStillApplied()
|
||||
{
|
||||
AddStep("set override cs to -5", () => modDifficultyAdjust.ApproachRate.Value = -5);
|
||||
|
||||
checkSliderAtValue("Approach Rate", -5);
|
||||
checkBindableAtValue("Approach Rate", -5);
|
||||
|
||||
// this is a no-op, just showing that it won't reset the value during deserialisation.
|
||||
setExtendedLimits(false);
|
||||
|
||||
checkSliderAtValue("Approach Rate", -5);
|
||||
checkBindableAtValue("Approach Rate", -5);
|
||||
|
||||
// setting extended limits will reset the serialisation exception.
|
||||
// this should be fine as the goal is to allow, at most, the value of extended limits.
|
||||
setExtendedLimits(true);
|
||||
|
||||
checkSliderAtValue("Approach Rate", -5);
|
||||
checkBindableAtValue("Approach Rate", -5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExtendedLimits()
|
||||
{
|
||||
@ -109,6 +131,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
checkSliderAtValue("Circle Size", 11);
|
||||
checkBindableAtValue("Circle Size", 11);
|
||||
|
||||
setSliderValue("Approach Rate", -5);
|
||||
|
||||
checkSliderAtValue("Approach Rate", -5);
|
||||
checkBindableAtValue("Approach Rate", -5);
|
||||
|
||||
setExtendedLimits(false);
|
||||
|
||||
checkSliderAtValue("Circle Size", 10);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user