mirror of
https://github.com/ppy/osu.git
synced 2025-02-23 20:42:57 +08:00
Merge branch 'fix-applause-sound-stop' of https://github.com/Drison64/osu into fix-applause-sound-stop
This commit is contained in:
commit
d5e6178466
@ -21,7 +21,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2024.517.0",
|
"version": "2024.802.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -121,9 +121,7 @@ jobs:
|
|||||||
|
|
||||||
build-only-ios:
|
build-only-ios:
|
||||||
name: Build only (iOS)
|
name: Build only (iOS)
|
||||||
# `macos-13` is required, because the newest Microsoft.iOS.Sdk versions require Xcode 14.3.
|
runs-on: macos-latest
|
||||||
# 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
|
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -137,8 +135,5 @@ jobs:
|
|||||||
- name: Install .NET Workloads
|
- name: Install .NET Workloads
|
||||||
run: dotnet workload install maui-ios
|
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
|
- name: Build
|
||||||
run: dotnet build -c Debug osu.iOS
|
run: dotnet build -c Debug osu.iOS
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.720.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.809.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
|
public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin)
|
||||||
{
|
{
|
||||||
if (withModifiedSkin)
|
if (withModifiedSkin)
|
||||||
{
|
{
|
||||||
@ -29,10 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
CreateTest();
|
CreateTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
AddAssert("legacy HUD combo counter hidden", () =>
|
AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType<LegacyComboCounter>().Any());
|
||||||
{
|
|
||||||
return Player.ChildrenOfType<LegacyComboCounter>().All(c => c.ChildrenOfType<Container>().Single().Alpha == 0f);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
||||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
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]
|
[Test]
|
||||||
@ -259,6 +260,16 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
|
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 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);
|
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<Color4> AccentColour { get; } = new Bindable<Color4>();
|
||||||
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||||
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
||||||
|
public Vector2 DisplayPosition => DrawPosition;
|
||||||
public Vector2 DisplaySize => Size * Scale;
|
public Vector2 DisplaySize => Size * Scale;
|
||||||
|
|
||||||
public float DisplayRotation => Rotation;
|
public float DisplayRotation => Rotation;
|
||||||
|
|
||||||
public double DisplayStartTime => HitObject.StartTime;
|
public double DisplayStartTime => HitObject.StartTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -44,19 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
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()
|
protected override void FreeAfterUse()
|
||||||
{
|
{
|
||||||
ClearTransforms();
|
ClearTransforms();
|
||||||
@ -64,5 +49,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
base.FreeAfterUse();
|
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>
|
/// </summary>
|
||||||
protected readonly Container ScalingContainer;
|
protected readonly Container ScalingContainer;
|
||||||
|
|
||||||
|
public Vector2 DisplayPosition => DrawPosition;
|
||||||
|
|
||||||
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
|
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
|
||||||
|
|
||||||
public float DisplayRotation => ScalingContainer.Rotation;
|
public float DisplayRotation => ScalingContainer.Rotation;
|
||||||
@ -95,5 +97,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
base.OnFree();
|
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
|
public interface IHasCatchObjectState
|
||||||
{
|
{
|
||||||
PalpableCatchHitObject HitObject { get; }
|
PalpableCatchHitObject HitObject { get; }
|
||||||
|
|
||||||
double DisplayStartTime { get; }
|
|
||||||
|
|
||||||
Bindable<Color4> AccentColour { get; }
|
Bindable<Color4> AccentColour { get; }
|
||||||
|
|
||||||
Bindable<bool> HyperDash { get; }
|
Bindable<bool> HyperDash { get; }
|
||||||
|
|
||||||
Bindable<int> IndexInBeatmap { get; }
|
Bindable<int> IndexInBeatmap { get; }
|
||||||
|
double DisplayStartTime { get; }
|
||||||
|
Vector2 DisplayPosition { get; }
|
||||||
Vector2 DisplaySize { get; }
|
Vector2 DisplaySize { get; }
|
||||||
|
|
||||||
float DisplayRotation { 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);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
@ -28,27 +29,44 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is SkinComponentsContainerLookup containerLookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
switch (containerLookup.Target)
|
case SkinComponentsContainerLookup containerLookup:
|
||||||
{
|
if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents)
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
return base.GetDrawableComponent(lookup);
|
||||||
var components = base.GetDrawableComponent(lookup) as Container;
|
|
||||||
|
|
||||||
if (providesComboCounter && components != null)
|
// Modifications for global components.
|
||||||
{
|
if (containerLookup.Ruleset == null)
|
||||||
// catch may provide its own combo counter; hide the default.
|
return base.GetDrawableComponent(lookup) as Container;
|
||||||
// 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;
|
// Skin has configuration.
|
||||||
}
|
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||||
}
|
return d;
|
||||||
|
|
||||||
if (lookup is CatchSkinComponentLookup catchSkinComponent)
|
// Our own ruleset components default.
|
||||||
|
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
||||||
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
|
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||||
|
|
||||||
|
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 CatchSkinComponentLookup catchSkinComponent:
|
||||||
switch (catchSkinComponent.Component)
|
switch (catchSkinComponent.Component)
|
||||||
{
|
{
|
||||||
case CatchSkinComponents.Fruit:
|
case CatchSkinComponents.Fruit:
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -362,7 +363,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
if (caughtObject == null) return;
|
if (caughtObject == null) return;
|
||||||
|
|
||||||
caughtObject.CopyStateFrom(drawableObject);
|
caughtObject.RestoreState(drawableObject.SaveState());
|
||||||
caughtObject.Anchor = Anchor.TopCentre;
|
caughtObject.Anchor = Anchor.TopCentre;
|
||||||
caughtObject.Position = position;
|
caughtObject.Position = position;
|
||||||
caughtObject.Scale *= caught_fruit_scale_adjust;
|
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);
|
Debug.Assert(droppedObject != null);
|
||||||
|
|
||||||
droppedObject.CopyStateFrom(caughtObject);
|
droppedObject.RestoreState(state);
|
||||||
droppedObject.Anchor = Anchor.TopLeft;
|
droppedObject.Anchor = Anchor.TopLeft;
|
||||||
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget);
|
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(state.DisplayPosition, droppedObjectTarget);
|
||||||
|
|
||||||
return droppedObject;
|
return droppedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearPlate(DroppedObjectAnimation animation)
|
private void clearPlate(DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var caughtObjects = caughtObjectContainer.Children.ToArray();
|
int caughtCount = caughtObjectContainer.Children.Count;
|
||||||
|
CatchObjectState[] states = ArrayPool<CatchObjectState>.Shared.Rent(caughtCount);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i = 0; i < caughtCount; i++)
|
||||||
|
states[i] = caughtObjectContainer.Children[i].SaveState();
|
||||||
|
|
||||||
caughtObjectContainer.Clear(false);
|
caughtObjectContainer.Clear(false);
|
||||||
|
|
||||||
// Use the already returned PoolableDrawables for new objects
|
for (int i = 0; i < caughtCount; i++)
|
||||||
var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray();
|
{
|
||||||
|
CaughtObject obj = getDroppedObject(states[i]);
|
||||||
droppedObjectTarget.AddRange(droppedObjects);
|
droppedObjectTarget.Add(obj);
|
||||||
|
applyDropAnimation(obj, animation);
|
||||||
foreach (var droppedObject in droppedObjects)
|
}
|
||||||
applyDropAnimation(droppedObject, animation);
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<CatchObjectState>.Shared.Return(states);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
|
CatchObjectState state = caughtObject.SaveState();
|
||||||
caughtObjectContainer.Remove(caughtObject, false);
|
caughtObjectContainer.Remove(caughtObject, false);
|
||||||
|
|
||||||
var droppedObject = getDroppedObject(caughtObject);
|
var droppedObject = getDroppedObject(state);
|
||||||
|
|
||||||
droppedObjectTarget.Add(droppedObject);
|
droppedObjectTarget.Add(droppedObject);
|
||||||
|
|
||||||
applyDropAnimation(droppedObject, animation);
|
applyDropAnimation(droppedObject, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
[TestCase(ManiaAction.Key1)]
|
[TestCase(ManiaAction.Key1)]
|
||||||
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||||
[TestCase(ManiaAction.Special1)]
|
[TestCase(ManiaAction.Key5)]
|
||||||
[TestCase(ManiaAction.Key8)]
|
[TestCase(ManiaAction.Key9)]
|
||||||
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
|
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
|
||||||
{
|
{
|
||||||
var beatmap = new ManiaBeatmap(new StageDefinition(9));
|
var beatmap = new ManiaBeatmap(new StageDefinition(9));
|
||||||
@ -29,11 +29,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
[TestCase(ManiaAction.Key1)]
|
[TestCase(ManiaAction.Key1)]
|
||||||
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||||
[TestCase(ManiaAction.Special1)]
|
[TestCase(ManiaAction.Key3)]
|
||||||
[TestCase(ManiaAction.Special2)]
|
|
||||||
[TestCase(ManiaAction.Special1, ManiaAction.Special2)]
|
|
||||||
[TestCase(ManiaAction.Special1, ManiaAction.Key5)]
|
|
||||||
[TestCase(ManiaAction.Key8)]
|
[TestCase(ManiaAction.Key8)]
|
||||||
|
[TestCase(ManiaAction.Key3, ManiaAction.Key8)]
|
||||||
|
[TestCase(ManiaAction.Key3, ManiaAction.Key6)]
|
||||||
|
[TestCase(ManiaAction.Key10)]
|
||||||
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
|
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
|
||||||
{
|
{
|
||||||
var beatmap = new ManiaBeatmap(new StageDefinition(5));
|
var beatmap = new ManiaBeatmap(new StageDefinition(5));
|
||||||
|
@ -14,12 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
SetContents(_ =>
|
SetContents(_ =>
|
||||||
{
|
{
|
||||||
ManiaAction normalAction = ManiaAction.Key1;
|
ManiaAction action = ManiaAction.Key1;
|
||||||
ManiaAction specialAction = ManiaAction.Special1;
|
|
||||||
|
|
||||||
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
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(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
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.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.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[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(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release 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.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -131,9 +131,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
||||||
{
|
{
|
||||||
var specialAction = ManiaAction.Special1;
|
var stage = new Stage(0, new StageDefinition(2), ref action);
|
||||||
|
|
||||||
var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction);
|
|
||||||
stages.Add(stage);
|
stages.Add(stage);
|
||||||
|
|
||||||
return new ScrollingTestContainer(direction)
|
return new ScrollingTestContainer(direction)
|
||||||
|
@ -45,18 +45,15 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
LeftKeys = stage1LeftKeys,
|
LeftKeys = stage1LeftKeys,
|
||||||
RightKeys = stage1RightKeys,
|
RightKeys = stage1RightKeys,
|
||||||
SpecialKey = InputKey.V,
|
SpecialKey = InputKey.V,
|
||||||
SpecialAction = ManiaAction.Special1,
|
}.GenerateKeyBindingsFor(singleStageVariant);
|
||||||
NormalActionStart = ManiaAction.Key1
|
|
||||||
}.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal);
|
|
||||||
|
|
||||||
var stage2Bindings = new VariantMappingGenerator
|
var stage2Bindings = new VariantMappingGenerator
|
||||||
{
|
{
|
||||||
LeftKeys = stage2LeftKeys,
|
LeftKeys = stage2LeftKeys,
|
||||||
RightKeys = stage2RightKeys,
|
RightKeys = stage2RightKeys,
|
||||||
SpecialKey = InputKey.B,
|
SpecialKey = InputKey.B,
|
||||||
SpecialAction = ManiaAction.Special2,
|
ActionStart = (ManiaAction)singleStageVariant,
|
||||||
NormalActionStart = nextNormal
|
}.GenerateKeyBindingsFor(singleStageVariant);
|
||||||
}.GenerateKeyBindingsFor(singleStageVariant, out _);
|
|
||||||
|
|
||||||
return stage1Bindings.Concat(stage2Bindings);
|
return stage1Bindings.Concat(stage2Bindings);
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public enum ManiaAction
|
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")]
|
[Description("Key 1")]
|
||||||
Key1 = 10,
|
Key1,
|
||||||
|
|
||||||
[Description("Key 2")]
|
[Description("Key 2")]
|
||||||
Key2,
|
Key2,
|
||||||
|
@ -17,28 +17,9 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
|
public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
|
||||||
|
|
||||||
private readonly ManiaAction[] columnActions;
|
|
||||||
|
|
||||||
public ManiaAutoGenerator(ManiaBeatmap beatmap)
|
public ManiaAutoGenerator(ManiaBeatmap beatmap)
|
||||||
: base(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()
|
protected override void GenerateFrames()
|
||||||
@ -57,11 +38,11 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
switch (point)
|
switch (point)
|
||||||
{
|
{
|
||||||
case HitPoint:
|
case HitPoint:
|
||||||
actions.Add(columnActions[point.Column]);
|
actions.Add(ManiaAction.Key1 + point.Column);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ReleasePoint:
|
case ReleasePoint:
|
||||||
actions.Remove(columnActions[point.Column]);
|
actions.Remove(ManiaAction.Key1 + point.Column);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
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)
|
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
||||||
{
|
{
|
||||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
var action = ManiaAction.Key1;
|
||||||
|
|
||||||
var normalAction = ManiaAction.Key1;
|
|
||||||
var specialAction = ManiaAction.Special1;
|
|
||||||
|
|
||||||
int activeColumns = (int)(legacyFrame.MouseX ?? 0);
|
int activeColumns = (int)(legacyFrame.MouseX ?? 0);
|
||||||
int counter = 0;
|
|
||||||
|
|
||||||
while (activeColumns > 0)
|
while (activeColumns > 0)
|
||||||
{
|
{
|
||||||
bool isSpecial = isColumnAtIndexSpecial(maniaBeatmap, counter);
|
|
||||||
|
|
||||||
if ((activeColumns & 1) > 0)
|
if ((activeColumns & 1) > 0)
|
||||||
Actions.Add(isSpecial ? specialAction : normalAction);
|
Actions.Add(action);
|
||||||
|
|
||||||
if (isSpecial)
|
action++;
|
||||||
specialAction++;
|
|
||||||
else
|
|
||||||
normalAction++;
|
|
||||||
|
|
||||||
counter++;
|
|
||||||
activeColumns >>= 1;
|
activeColumns >>= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
|
||||||
|
|
||||||
int keys = 0;
|
int keys = 0;
|
||||||
|
|
||||||
foreach (var action in Actions)
|
foreach (var action in Actions)
|
||||||
{
|
keys |= 1 << (int)action;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
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,
|
LeftKeys = leftKeys,
|
||||||
RightKeys = rightKeys,
|
RightKeys = rightKeys,
|
||||||
SpecialKey = InputKey.Space,
|
SpecialKey = InputKey.Space,
|
||||||
SpecialAction = ManiaAction.Special1,
|
}.GenerateKeyBindingsFor(variant);
|
||||||
NormalActionStart = ManiaAction.Key1,
|
|
||||||
}.GenerateKeyBindingsFor(variant, out _);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,13 +66,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Content = new[] { new Drawable[stageDefinitions.Count] }
|
Content = new[] { new Drawable[stageDefinitions.Count] }
|
||||||
});
|
});
|
||||||
|
|
||||||
var normalColumnAction = ManiaAction.Key1;
|
var columnAction = ManiaAction.Key1;
|
||||||
var specialColumnAction = ManiaAction.Special1;
|
|
||||||
int firstColumnIndex = 0;
|
int firstColumnIndex = 0;
|
||||||
|
|
||||||
for (int i = 0; i < stageDefinitions.Count; i++)
|
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;
|
playfieldGrid.Content[0][i] = newStage;
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
private ISkinSource currentSkin = null!;
|
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;
|
this.firstColumnIndex = firstColumnIndex;
|
||||||
Definition = definition;
|
Definition = definition;
|
||||||
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 1,
|
Width = 1,
|
||||||
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
|
Action = { Value = columnStartAction++ }
|
||||||
};
|
};
|
||||||
|
|
||||||
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
|
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
|
||||||
|
@ -26,37 +26,30 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
public InputKey SpecialKey;
|
public InputKey SpecialKey;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
|
/// The <see cref="ManiaAction"/> at which the columns should begin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ManiaAction NormalActionStart;
|
public ManiaAction ActionStart;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="ManiaAction"/> for the special column.
|
|
||||||
/// </summary>
|
|
||||||
public ManiaAction SpecialAction;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="columns">The number of columns that need to be bound.</param>
|
/// <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>
|
/// <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>();
|
var bindings = new List<KeyBinding>();
|
||||||
|
|
||||||
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
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)
|
if (columns % 2 == 1)
|
||||||
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
bindings.Add(new KeyBinding(SpecialKey, currentAction++));
|
||||||
|
|
||||||
for (int i = 0; i < columns / 2; i++)
|
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;
|
return bindings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
});
|
});
|
||||||
|
|
||||||
moveMouseToHitObject(1);
|
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();
|
mergeSelection();
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
});
|
});
|
||||||
|
|
||||||
moveMouseToHitObject(1);
|
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();
|
mergeSelection();
|
||||||
AddAssert("circles not merged", () => circle1 is not null && circle2 is not null
|
AddAssert("circles not merged", () => circle1 is not null && circle2 is not null
|
||||||
&& EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2));
|
&& EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2));
|
||||||
@ -222,7 +222,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
});
|
});
|
||||||
|
|
||||||
moveMouseToHitObject(1);
|
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();
|
mergeSelection();
|
||||||
|
|
||||||
|
@ -42,7 +42,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
{
|
{
|
||||||
base.PostProcess();
|
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)
|
if (hitObjects.Count > 0)
|
||||||
{
|
{
|
||||||
@ -50,14 +55,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
h.StackHeight = 0;
|
h.StackHeight = 0;
|
||||||
|
|
||||||
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||||
applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||||
else
|
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.ThrowIfGreaterThan(startIndex, endIndex);
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
|
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++)
|
for (int i = 0; i < hitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
flashlightRating *= 0.7;
|
flashlightRating *= 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating);
|
||||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating);
|
||||||
double baseFlashlightPerformance = 0.0;
|
double baseFlashlightPerformance = 0.0;
|
||||||
|
|
||||||
if (mods.Any(h => h is OsuModFlashlight))
|
if (mods.Any(h => h is OsuModFlashlight))
|
||||||
baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0;
|
baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
|
||||||
|
|
||||||
double basePerformance =
|
double basePerformance =
|
||||||
Math.Pow(
|
Math.Pow(
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
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) +
|
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.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))
|
if (score.Mods.Any(h => h is OsuModRelax))
|
||||||
return 0.0;
|
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) +
|
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.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))
|
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
||||||
return 0.0;
|
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.
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
if (effectiveMissCount > 0)
|
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 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;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,6 +295,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
if (Vector2.Distance(closestSnapPosition, screenSpacePosition) < snapRadius)
|
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
|
// only return distance portion, since time is not really valid
|
||||||
snapResult = new SnapResult(closestSnapPosition, null, playfield);
|
snapResult = new SnapResult(closestSnapPosition, null, playfield);
|
||||||
return true;
|
return true;
|
||||||
|
@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public partial class OsuHitObjectInspector : HitObjectInspector
|
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 firstInSelection = (OsuHitObject)objects.MinBy(ho => ho.StartTime)!;
|
||||||
var lastInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MaxBy(ho => ho.GetEndTime())!;
|
var lastInSelection = (OsuHitObject)objects.MaxBy(ho => ho.GetEndTime())!;
|
||||||
|
|
||||||
Debug.Assert(firstInSelection != null && lastInSelection != null);
|
Debug.Assert(firstInSelection != null && lastInSelection != null);
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
@ -50,12 +51,33 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
var hitObjects = selectedMovableObjects;
|
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...
|
// this will potentially move the selection out of bounds...
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
h.Position += localDelta;
|
||||||
|
|
||||||
// but this will be corrected.
|
// but this will be corrected.
|
||||||
moveSelectionInBounds();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -41,8 +42,47 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is OsuSkinComponentLookup osuComponent)
|
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;
|
||||||
|
|
||||||
|
// Our own ruleset components default.
|
||||||
|
switch (containerLookup.Target)
|
||||||
|
{
|
||||||
|
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||||
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
|
{
|
||||||
|
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||||
|
|
||||||
|
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 LegacyComboCounter(),
|
||||||
|
new LegacyKeyCounterDisplay(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponentLookup osuComponent:
|
||||||
switch (osuComponent.Component)
|
switch (osuComponent.Component)
|
||||||
{
|
{
|
||||||
case OsuSkinComponents.FollowPoint:
|
case OsuSkinComponents.FollowPoint:
|
||||||
@ -171,10 +211,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
default:
|
default:
|
||||||
throw new UnsupportedSkinComponentException(lookup);
|
throw new UnsupportedSkinComponentException(lookup);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
default:
|
||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup 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.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Layout;
|
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
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
|
// -1 signals that the part is unusable, and should not be drawn
|
||||||
parts[i].InvalidationID = -1;
|
parts[i].InvalidationID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddLayout(partSizeCache);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[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>
|
/// <summary>
|
||||||
/// The amount of time to fade the cursor trail pieces.
|
/// The amount of time to fade the cursor trail pieces.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -156,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
protected void AddTrail(Vector2 position)
|
protected void AddTrail(Vector2 position)
|
||||||
{
|
{
|
||||||
|
position = ToLocalSpace(position);
|
||||||
|
|
||||||
if (InterpolateMovements)
|
if (InterpolateMovements)
|
||||||
{
|
{
|
||||||
if (!lastPosition.HasValue)
|
if (!lastPosition.HasValue)
|
||||||
@ -174,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
float distance = diff.Length;
|
float distance = diff.Length;
|
||||||
Vector2 direction = diff / distance;
|
Vector2 direction = diff / distance;
|
||||||
|
|
||||||
float interval = partSize.X / 2.5f * IntervalMultiplier;
|
float interval = Texture.DisplayWidth / 2.5f * IntervalMultiplier;
|
||||||
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
|
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
|
||||||
|
|
||||||
for (float d = interval; d < stopAt; d += interval)
|
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].Time = time + 1;
|
||||||
++parts[currentIndex].InvalidationID;
|
++parts[currentIndex].InvalidationID;
|
||||||
|
|
||||||
|
@ -206,6 +206,15 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
|
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
|
private partial class ProxyContainer : LifetimeManagementContainer
|
||||||
{
|
{
|
||||||
public void Add(Drawable proxy) => AddInternal(proxy);
|
public void Add(Drawable proxy) => AddInternal(proxy);
|
||||||
|
@ -33,9 +33,30 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
OsuResumeOverlayInputBlocker? inputBlocker = null;
|
||||||
|
|
||||||
|
if (drawableRuleset != null)
|
||||||
|
{
|
||||||
|
var osuPlayfield = (OsuPlayfield)drawableRuleset.Playfield;
|
||||||
|
osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker());
|
||||||
|
}
|
||||||
|
|
||||||
Add(cursorScaleContainer = new Container
|
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;
|
return false;
|
||||||
|
|
||||||
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
||||||
|
ResumeRequested?.Invoke();
|
||||||
// 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());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,5 +161,27 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint);
|
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:
|
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||||
// This should eventually be moved to a skin setting, when supported.
|
// This should eventually be moved to a skin setting, when supported.
|
||||||
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
break;
|
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]
|
[Test]
|
||||||
public void TestDecodeBeatmapHitObjects()
|
public void TestDecodeBeatmapHitObjects()
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ using osu.Game.Replays;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Catch;
|
using osu.Game.Rulesets.Catch;
|
||||||
using osu.Game.Rulesets.Mania;
|
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.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
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(829_931, score.ScoreInfo.LegacyTotalScore);
|
||||||
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
||||||
|
|
||||||
Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic));
|
Assert.That(score.ScoreInfo.APIMods.Select(m => m.Acronym), Is.EquivalentTo(new[] { "CL", "9K", "DS" }));
|
||||||
Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL"));
|
|
||||||
Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL"));
|
|
||||||
|
|
||||||
Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001));
|
Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001));
|
||||||
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
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]
|
[Test]
|
||||||
public void TestNoChanges()
|
public void TestNoChanges()
|
||||||
{
|
{
|
||||||
@ -272,21 +310,25 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
|
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
|
||||||
|
|
||||||
|
var dateBefore = importBeforeUpdate!.Value.DateAdded;
|
||||||
|
|
||||||
Assert.That(importBeforeUpdate, Is.Not.Null);
|
Assert.That(importBeforeUpdate, Is.Not.Null);
|
||||||
Debug.Assert(importBeforeUpdate != null);
|
Debug.Assert(importBeforeUpdate != null);
|
||||||
|
|
||||||
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
|
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
Assert.That(importAfterUpdate, Is.Not.Null);
|
Assert.That(importAfterUpdate, Is.Not.Null);
|
||||||
Debug.Assert(importAfterUpdate != null);
|
Debug.Assert(importAfterUpdate != null);
|
||||||
|
|
||||||
realm.Run(r => r.Refresh());
|
|
||||||
|
|
||||||
checkCount<BeatmapSetInfo>(realm, 1);
|
checkCount<BeatmapSetInfo>(realm, 1);
|
||||||
checkCount<BeatmapInfo>(realm, count_beatmaps);
|
checkCount<BeatmapInfo>(realm, count_beatmaps);
|
||||||
checkCount<BeatmapMetadata>(realm, count_beatmaps);
|
checkCount<BeatmapMetadata>(realm, count_beatmaps);
|
||||||
|
|
||||||
Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1));
|
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));
|
Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Child = composer = new TestHitObjectComposer();
|
||||||
{
|
|
||||||
composer = new TestHitObjectComposer()
|
|
||||||
};
|
|
||||||
|
|
||||||
BeatDivisor.Value = 1;
|
BeatDivisor.Value = 1;
|
||||||
|
|
||||||
|
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 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>
|
/// <summary>
|
||||||
/// Create a test beatmap set model.
|
/// Create a test beatmap set model.
|
||||||
@ -88,7 +93,7 @@ namespace osu.Game.Tests.Resources
|
|||||||
|
|
||||||
RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length];
|
RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length];
|
||||||
|
|
||||||
int setId = Interlocked.Increment(ref importId);
|
int setId = GetNextTestID();
|
||||||
|
|
||||||
var metadata = new BeatmapMetadata
|
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
|
// Covers default rank display
|
||||||
"Archives/modified-default-20230809.osk",
|
"Archives/modified-default-20230809.osk",
|
||||||
// Covers legacy rank display
|
// Covers legacy rank display
|
||||||
"Archives/modified-classic-20230809.osk"
|
"Archives/modified-classic-20230809.osk",
|
||||||
|
// Covers legacy key counter
|
||||||
|
"Archives/modified-classic-20240724.osk"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -304,11 +304,6 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
{
|
{
|
||||||
private bool? lastLoadTriggerCausedChange;
|
private bool? lastLoadTriggerCausedChange;
|
||||||
|
|
||||||
public TestBackgroundScreenDefault()
|
|
||||||
: base(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Next()
|
public override bool Next()
|
||||||
{
|
{
|
||||||
bool didChange = base.Next();
|
bool didChange = base.Next();
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,74 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
|
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]
|
[Test]
|
||||||
public void TestCommitPlacementViaGlobalAction()
|
public void TestCommitPlacementViaGlobalAction()
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
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()
|
public TestSceneHUDOverlay()
|
||||||
{
|
{
|
||||||
@ -79,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
|
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
|
||||||
|
|
||||||
AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
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);
|
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);
|
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.
|
// 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]
|
[Test]
|
||||||
@ -150,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||||
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
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);
|
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||||
AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||||
AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
AddUntilStep("key counters still hidden", () => !keyCounterContent.IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -56,6 +57,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Scale = new Vector2(1, -1)
|
Scale = new Vector2(1, -1)
|
||||||
},
|
},
|
||||||
|
new LegacyKeyCounterDisplay
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
@ -89,6 +95,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Rotation = 90,
|
Rotation = 90,
|
||||||
},
|
},
|
||||||
|
new LegacyKeyCounterDisplay
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Rotation = 90,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -13,6 +14,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -32,6 +34,23 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private AudioManager audioManager { get; set; } = null!;
|
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) =>
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) =>
|
||||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
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("resume", () => Player.Resume());
|
||||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
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);
|
checkKey(() => counter, 2, true);
|
||||||
|
|
||||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
checkKey(() => counter, 2, false);
|
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]
|
[Test]
|
||||||
@ -90,30 +107,29 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
KeyCounter counter = null!;
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
loadPlayer(() => new ManiaRuleset());
|
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);
|
checkKey(() => counter, 0, false);
|
||||||
|
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
checkKey(() => counter, 1, true);
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 1, false);
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
AddStep("pause", () => Player.Pause());
|
AddStep("pause", () => Player.Pause());
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
checkKey(() => counter, 1, false);
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 1, false);
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
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);
|
checkKey(() => counter, 2, true);
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 2, false);
|
checkKey(() => counter, 2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,8 +161,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
checkKey(() => counterZ, 1, false);
|
checkKey(() => counterZ, 2, true);
|
||||||
checkKey(() => counterX, 1, false);
|
checkKey(() => counterX, 1, false);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counterZ, 2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -155,12 +174,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
KeyCounter counter = null!;
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
loadPlayer(() => new ManiaRuleset());
|
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("pause", () => Player.Pause());
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 1, true);
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
@ -202,12 +221,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
checkKey(() => counterZ, 1, false);
|
checkKey(() => counterZ, 2, true);
|
||||||
checkKey(() => counterX, 1, true);
|
checkKey(() => counterX, 1, true);
|
||||||
|
|
||||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||||
checkKey(() => counterZ, 1, false);
|
|
||||||
checkKey(() => counterX, 1, false);
|
checkKey(() => counterX, 1, false);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counterZ, 2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -216,24 +237,50 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
KeyCounter counter = null!;
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
loadPlayer(() => new ManiaRuleset());
|
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);
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
AddStep("pause", () => Player.Pause());
|
AddStep("pause", () => Player.Pause());
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
|
|
||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||||
checkKey(() => counter, 1, true);
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 1, false);
|
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)
|
private void loadPlayer(Func<Ruleset> createRuleset)
|
||||||
{
|
{
|
||||||
AddStep("set ruleset", () => currentRuleset = 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("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
||||||
|
|
||||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000));
|
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0));
|
||||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500));
|
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500));
|
||||||
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
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)
|
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
||||||
|
@ -7,21 +7,25 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Overlays.SkinEditor;
|
using osu.Game.Overlays.SkinEditor;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Skinning.Components;
|
using osu.Game.Skinning.Components;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -39,6 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Cached]
|
[Cached]
|
||||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skins { get; set; } = null!;
|
||||||
|
|
||||||
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
|
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
@ -46,6 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||||
AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
|
AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
|
||||||
|
|
||||||
AddStep("reload skin editor", () =>
|
AddStep("reload skin editor", () =>
|
||||||
@ -369,6 +377,93 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
() => Is.EqualTo(3));
|
() => 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 LegacyComboCounter
|
||||||
|
{
|
||||||
|
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();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler
|
private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
@ -28,17 +27,5 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
|
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;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
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.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Gameplay;
|
using osu.Game.Tests.Gameplay;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -91,10 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
SetContents(_ =>
|
SetContents(_ =>
|
||||||
{
|
{
|
||||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
hudOverlay = new HUDOverlay(new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()), Array.Empty<Mod>());
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
|
||||||
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
|
||||||
|
|
||||||
action?.Invoke(hudOverlay);
|
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.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Metadata;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osuTK.Input;
|
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);
|
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]
|
[Test]
|
||||||
public void TestOnlineMenuBannerTrusted()
|
public void TestOnlineMenuBannerTrusted()
|
||||||
{
|
{
|
||||||
|
@ -61,7 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() });
|
AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() });
|
||||||
AddWaitStep("wait some", 3);
|
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]
|
[Test]
|
||||||
|
@ -139,8 +139,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void addRandomPlayer()
|
private void addRandomPlayer()
|
||||||
{
|
{
|
||||||
int randomUser = RNG.Next(200000, 500000);
|
int id = TestResources.GetNextTestID();
|
||||||
multiplayerClient.AddUser(new APIUser { Id = randomUser, Username = $"user {randomUser}" });
|
multiplayerClient.AddUser(new APIUser { Id = id, Username = $"user {id}" });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeLastUser()
|
private void removeLastUser()
|
||||||
|
@ -47,6 +47,7 @@ using osu.Game.Screens.Select.Carousel;
|
|||||||
using osu.Game.Screens.Select.Leaderboards;
|
using osu.Game.Screens.Select.Leaderboards;
|
||||||
using osu.Game.Screens.Select.Options;
|
using osu.Game.Screens.Select.Options;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
using SharpCompress;
|
using SharpCompress;
|
||||||
@ -239,6 +240,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||||
|
|
||||||
AddStep("change beatmap files", () =>
|
AddStep("change beatmap files", () =>
|
||||||
|
{
|
||||||
|
FileUtils.AttemptOperation(() =>
|
||||||
{
|
{
|
||||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||||
{
|
{
|
||||||
@ -246,6 +249,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
stream.WriteByte(0);
|
stream.WriteByte(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("invalidate cache", () =>
|
AddStep("invalidate cache", () =>
|
||||||
{
|
{
|
||||||
|
@ -9,13 +9,13 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Chat.ChannelList;
|
using osu.Game.Overlays.Chat.ChannelList;
|
||||||
using osu.Game.Overlays.Chat.Listing;
|
using osu.Game.Overlays.Chat.Listing;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private Channel createRandomPublicChannel()
|
private Channel createRandomPublicChannel()
|
||||||
{
|
{
|
||||||
int id = RNG.Next(0, 10000);
|
int id = TestResources.GetNextTestID();
|
||||||
return new Channel
|
return new Channel
|
||||||
{
|
{
|
||||||
Name = $"#channel-{id}",
|
Name = $"#channel-{id}",
|
||||||
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private Channel createRandomPrivateChannel()
|
private Channel createRandomPrivateChannel()
|
||||||
{
|
{
|
||||||
int id = RNG.Next(0, 10000);
|
int id = TestResources.GetNextTestID();
|
||||||
return new Channel(new APIUser
|
return new Channel(new APIUser
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private Channel createRandomAnnounceChannel()
|
private Channel createRandomAnnounceChannel()
|
||||||
{
|
{
|
||||||
int id = RNG.Next(0, 10000);
|
int id = TestResources.GetNextTestID();
|
||||||
return new Channel
|
return new Channel
|
||||||
{
|
{
|
||||||
Name = $"Announce {id}",
|
Name = $"Announce {id}",
|
||||||
|
@ -19,7 +19,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -33,6 +32,7 @@ using osu.Game.Overlays.Chat.ChannelList;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case PostMessageRequest postMessage:
|
case PostMessageRequest postMessage:
|
||||||
postMessage.TriggerSuccess(new Message(RNG.Next(0, 10000000))
|
postMessage.TriggerSuccess(new Message(TestResources.GetNextTestID())
|
||||||
{
|
{
|
||||||
Content = postMessage.Message.Content,
|
Content = postMessage.Message.Content,
|
||||||
ChannelId = postMessage.Message.ChannelId,
|
ChannelId = postMessage.Message.ChannelId,
|
||||||
@ -719,7 +719,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private Channel createPrivateChannel()
|
private Channel createPrivateChannel()
|
||||||
{
|
{
|
||||||
int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1);
|
int id = TestResources.GetNextTestID();
|
||||||
|
|
||||||
return new Channel(new APIUser
|
return new Channel(new APIUser
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
|
@ -33,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]
|
[Test]
|
||||||
public void TestDaySeparators()
|
public void TestDaySeparators()
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Taiko;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
@ -24,7 +26,17 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUp()
|
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]
|
[Test]
|
||||||
@ -131,6 +143,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
CountryCode = CountryCode.JP,
|
CountryCode = CountryCode.JP,
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
ProfileHue = hue,
|
ProfileHue = hue,
|
||||||
|
PlayMode = "osu",
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -174,21 +187,36 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
CountryCode = CountryCode.JP,
|
CountryCode = CountryCode.JP,
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
ProfileHue = hue,
|
ProfileHue = hue,
|
||||||
|
PlayMode = "osu",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
int hue2 = 0;
|
int hue2 = 0;
|
||||||
|
|
||||||
AddSliderStep("hue 2", 0, 360, 50, h => hue2 = h);
|
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);
|
AddWaitStep("wait some", 3);
|
||||||
|
|
||||||
AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser
|
AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser
|
||||||
{
|
{
|
||||||
Username = $"Colorful #{hue2}",
|
Username = $"Colorful #{hue2}",
|
||||||
Id = 1,
|
Id = 2,
|
||||||
CountryCode = CountryCode.JP,
|
CountryCode = CountryCode.JP,
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
ProfileHue = hue2,
|
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",
|
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",
|
Title = "osu!volunteer",
|
||||||
Colour = "ff0000",
|
Colour = "ff0000",
|
||||||
Achievements = Array.Empty<APIUserAchievement>(),
|
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);
|
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||||
waitForDisplay();
|
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);
|
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);
|
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||||
waitForDisplay();
|
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);
|
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
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);
|
AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible);
|
||||||
waitForDisplay();
|
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);
|
AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
base.SetUpSteps();
|
|
||||||
|
|
||||||
AddStep("populate ruleset statistics", () =>
|
AddStep("populate ruleset statistics", () =>
|
||||||
{
|
{
|
||||||
Dictionary<string, UserStatistics> rulesetStatistics = new Dictionary<string, UserStatistics>();
|
Dictionary<string, UserStatistics> rulesetStatistics = new Dictionary<string, UserStatistics>();
|
||||||
@ -68,6 +66,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.SetUpSteps();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -58,6 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
new PlaylistItem(beatmap)
|
new PlaylistItem(beatmap)
|
||||||
},
|
},
|
||||||
|
StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) },
|
||||||
EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
|
EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
@ -95,8 +96,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
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));
|
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
|
||||||
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||||
|
|
||||||
@ -105,7 +111,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
RoomID = 1234,
|
RoomID = 1234,
|
||||||
}));
|
}));
|
||||||
AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -10,6 +11,8 @@ using osu.Game.Overlays;
|
|||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
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 readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
private ModCustomisationPanel panel = null!;
|
private ModCustomisationPanel panel = null!;
|
||||||
|
private ModCustomisationHeader header = null!;
|
||||||
|
private Container content = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
|
SelectedMods.Value = Array.Empty<Mod>();
|
||||||
|
InputManager.MoveMouseTo(Vector2.One);
|
||||||
|
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -36,6 +44,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
SelectedMods = { BindTarget = SelectedMods },
|
SelectedMods = { BindTarget = SelectedMods },
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
header = panel.Children.OfType<ModCustomisationHeader>().First();
|
||||||
|
content = panel.Children.OfType<Container>().First();
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -44,23 +55,112 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("set DT", () =>
|
AddStep("set DT", () =>
|
||||||
{
|
{
|
||||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
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", () =>
|
AddStep("set DA", () =>
|
||||||
{
|
{
|
||||||
SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() };
|
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", () =>
|
AddStep("set FL+WU+DA+AD", () =>
|
||||||
{
|
{
|
||||||
SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() };
|
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", () =>
|
AddStep("set empty", () =>
|
||||||
{
|
{
|
||||||
SelectedMods.Value = Array.Empty<Mod>();
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0));
|
AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0));
|
||||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||||
AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true));
|
AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true));
|
||||||
|
AddStep("reset mouse", () => InputManager.MoveMouseTo(Vector2.One));
|
||||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo));
|
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo));
|
||||||
AddStep("set up presets", () =>
|
AddStep("set up presets", () =>
|
||||||
{
|
{
|
||||||
@ -998,7 +999,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left));
|
AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||||
AddAssert("search still not focused", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
AddAssert("search still not focused", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||||
AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
AddAssert("customisation panel closed by click", () => !this.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value);
|
AddAssert("customisation panel closed by click",
|
||||||
|
() => this.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||||
|
() => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||||
|
|
||||||
if (textSearchStartsActive)
|
if (textSearchStartsActive)
|
||||||
AddAssert("search focused", () => this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
AddAssert("search focused", () => this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||||
@ -1021,7 +1024,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private void assertCustomisationToggleState(bool disabled, bool active)
|
private void assertCustomisationToggleState(bool disabled, bool active)
|
||||||
{
|
{
|
||||||
AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Enabled.Value == !disabled);
|
AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Enabled.Value == !disabled);
|
||||||
AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value == active);
|
AddAssert($"customisation panel is {(active ? "" : "not ")}active",
|
||||||
|
() => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||||
|
() => active ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
private T getSelectedMod<T>() where T : Mod => SelectedMods.Value.OfType<T>().Single();
|
private T getSelectedMod<T>() where T : Mod => SelectedMods.Value.OfType<T>().Single();
|
||||||
|
86
osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs
Normal file
86
osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneOsuTooltip : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private TestTooltipContainer container = null!;
|
||||||
|
|
||||||
|
private static readonly string[] test_case_tooltip_string =
|
||||||
|
[
|
||||||
|
"Hello!!",
|
||||||
|
string.Concat(Enumerable.Repeat("Hello ", 100)),
|
||||||
|
|
||||||
|
//TODO: o!f issue: https://github.com/ppy/osu-framework/issues/5007
|
||||||
|
//Enable after o!f fixed
|
||||||
|
// $"H{new string('e', 500)}llo",
|
||||||
|
];
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(100),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Colour4.Red.Opacity(0.5f),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
container = new TestTooltipContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Hover me!",
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.GetFont(size: 50)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(test_case_tooltip_string))]
|
||||||
|
public void TestTooltipBasic(string text)
|
||||||
|
{
|
||||||
|
AddStep("Set tooltip content", () => container.TooltipText = text);
|
||||||
|
|
||||||
|
AddStep("Move mouse to container", () => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.Centre.Y)));
|
||||||
|
|
||||||
|
OsuTooltipContainer.OsuTooltip? tooltip = null!;
|
||||||
|
|
||||||
|
AddUntilStep("Wait for the tooltip shown", () =>
|
||||||
|
{
|
||||||
|
tooltip = container.FindClosestParent<OsuTooltipContainer>().ChildrenOfType<OsuTooltipContainer.OsuTooltip>().FirstOrDefault();
|
||||||
|
return tooltip != null && tooltip.Alpha == 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Check tooltip is under width limit", () => tooltip != null && tooltip.Width <= 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed partial class TestTooltipContainer : Container, IHasTooltip
|
||||||
|
{
|
||||||
|
public LocalisableString TooltipText { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public override async Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original)
|
public override async Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original)
|
||||||
{
|
{
|
||||||
|
var originalDateAdded = original.DateAdded;
|
||||||
|
|
||||||
Guid originalId = original.ID;
|
Guid originalId = original.ID;
|
||||||
|
|
||||||
var imported = await Import(notification, new[] { importTask }).ConfigureAwait(false);
|
var imported = await Import(notification, new[] { importTask }).ConfigureAwait(false);
|
||||||
@ -57,8 +59,11 @@ namespace osu.Game.Beatmaps
|
|||||||
// If there were no changes, ensure we don't accidentally nuke ourselves.
|
// If there were no changes, ensure we don't accidentally nuke ourselves.
|
||||||
if (first.ID == originalId)
|
if (first.ID == originalId)
|
||||||
{
|
{
|
||||||
first.PerformRead(s =>
|
first.PerformWrite(s =>
|
||||||
{
|
{
|
||||||
|
// Transfer local values which should be persisted across a beatmap update.
|
||||||
|
s.DateAdded = originalDateAdded;
|
||||||
|
|
||||||
// Re-run processing even in this case. We might have outdated metadata.
|
// Re-run processing even in this case. We might have outdated metadata.
|
||||||
ProcessBeatmap?.Invoke(s, MetadataLookupScope.OnlineFirst);
|
ProcessBeatmap?.Invoke(s, MetadataLookupScope.OnlineFirst);
|
||||||
});
|
});
|
||||||
@ -79,7 +84,7 @@ namespace osu.Game.Beatmaps
|
|||||||
original.DeletePending = true;
|
original.DeletePending = true;
|
||||||
|
|
||||||
// Transfer local values which should be persisted across a beatmap update.
|
// Transfer local values which should be persisted across a beatmap update.
|
||||||
updated.DateAdded = original.DateAdded;
|
updated.DateAdded = originalDateAdded;
|
||||||
|
|
||||||
transferCollectionReferences(realm, original, updated);
|
transferCollectionReferences(realm, original, updated);
|
||||||
|
|
||||||
@ -278,6 +283,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override void UndeleteForReuse(BeatmapSetInfo existing)
|
protected override void UndeleteForReuse(BeatmapSetInfo existing)
|
||||||
{
|
{
|
||||||
|
if (!existing.DeletePending)
|
||||||
|
return;
|
||||||
|
|
||||||
base.UndeleteForReuse(existing);
|
base.UndeleteForReuse(existing);
|
||||||
existing.DateAdded = DateTimeOffset.UtcNow;
|
existing.DateAdded = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
@ -563,7 +563,7 @@ namespace osu.Game.Beatmaps
|
|||||||
remove => workingBeatmapCache.OnInvalidated -= value;
|
remove => workingBeatmapCache.OnInvalidated -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID));
|
public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID && !s.DeletePending));
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -18,6 +18,12 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
public const int LATEST_VERSION = 14;
|
public const int LATEST_VERSION = 14;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The .osu format (beatmap) version.
|
||||||
|
///
|
||||||
|
/// osu!stable's versions end at <see cref="LATEST_VERSION"/>.
|
||||||
|
/// osu!lazer's versions starts at <see cref="LegacyBeatmapEncoder.FIRST_LAZER_VERSION"/>.
|
||||||
|
/// </summary>
|
||||||
protected readonly int FormatVersion;
|
protected readonly int FormatVersion;
|
||||||
|
|
||||||
protected LegacyDecoder(int version)
|
protected LegacyDecoder(int version)
|
||||||
@ -93,14 +99,8 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void HandleColours<TModel>(TModel output, string line, bool allowAlpha)
|
private Color4 convertSettingStringToColor4(string[] split, bool allowAlpha, KeyValuePair<string, string> pair)
|
||||||
{
|
{
|
||||||
var pair = SplitKeyVal(line);
|
|
||||||
|
|
||||||
bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal);
|
|
||||||
|
|
||||||
string[] split = pair.Value.Split(',');
|
|
||||||
|
|
||||||
if (split.Length != 3 && split.Length != 4)
|
if (split.Length != 3 && split.Length != 4)
|
||||||
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}");
|
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}");
|
||||||
|
|
||||||
@ -116,6 +116,18 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
|
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void HandleColours<TModel>(TModel output, string line, bool allowAlpha)
|
||||||
|
{
|
||||||
|
var pair = SplitKeyVal(line);
|
||||||
|
|
||||||
|
string[] split = pair.Value.Split(',');
|
||||||
|
Color4 colour = convertSettingStringToColor4(split, allowAlpha, pair);
|
||||||
|
|
||||||
|
bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal);
|
||||||
|
|
||||||
if (isCombo)
|
if (isCombo)
|
||||||
{
|
{
|
||||||
if (!(output is IHasComboColours tHasComboColours)) return;
|
if (!(output is IHasComboColours tHasComboColours)) return;
|
||||||
|
@ -43,6 +43,9 @@ namespace osu.Game.Collections
|
|||||||
//
|
//
|
||||||
// if we want to support user sorting (but changes will need to be made to realm to persist).
|
// if we want to support user sorting (but changes will need to be made to realm to persist).
|
||||||
ShowDragHandle.Value = false;
|
ShowDragHandle.Value = false;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = item_height / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateContent() => new ItemContent(Model);
|
protected override Drawable CreateContent() => new ItemContent(Model);
|
||||||
@ -50,7 +53,7 @@ namespace osu.Game.Collections
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main content of the <see cref="DrawableCollectionListItem"/>.
|
/// The main content of the <see cref="DrawableCollectionListItem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private partial class ItemContent : CircularContainer
|
private partial class ItemContent : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly Live<BeatmapCollection> collection;
|
private readonly Live<BeatmapCollection> collection;
|
||||||
|
|
||||||
@ -65,13 +68,12 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = item_height;
|
Height = item_height;
|
||||||
Masking = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Children = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
collection.IsManaged
|
collection.IsManaged
|
||||||
? new DeleteButton(collection)
|
? new DeleteButton(collection)
|
||||||
@ -132,7 +134,7 @@ namespace osu.Game.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class DeleteButton : CompositeDrawable
|
public partial class DeleteButton : OsuClickableContainer
|
||||||
{
|
{
|
||||||
public Func<Vector2, bool> IsTextBoxHovered = null!;
|
public Func<Vector2, bool> IsTextBoxHovered = null!;
|
||||||
|
|
||||||
@ -155,7 +157,7 @@ namespace osu.Game.Collections
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
InternalChild = fadeContainer = new Container
|
Child = fadeContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0.1f,
|
Alpha = 0.1f,
|
||||||
@ -176,6 +178,14 @@ namespace osu.Game.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0)
|
||||||
|
deleteCollection();
|
||||||
|
else
|
||||||
|
dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
|
||||||
@ -195,12 +205,7 @@ namespace osu.Game.Collections
|
|||||||
{
|
{
|
||||||
background.FlashColour(Color4.White, 150);
|
background.FlashColour(Color4.White, 150);
|
||||||
|
|
||||||
if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0)
|
return base.OnClick(e);
|
||||||
deleteCollection();
|
|
||||||
else
|
|
||||||
dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));
|
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));
|
||||||
|
@ -67,13 +67,7 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.Username, string.Empty);
|
SetDefault(OsuSetting.Username, string.Empty);
|
||||||
SetDefault(OsuSetting.Token, string.Empty);
|
SetDefault(OsuSetting.Token, string.Empty);
|
||||||
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, true);
|
||||||
// this default set MUST remain despite the setting being deprecated, because `SetDefault()` calls are implicitly used to declare the type returned for the lookup.
|
|
||||||
// if this is removed, the setting will be interpreted as a string, and `Migrate()` will fail due to cast failure.
|
|
||||||
// can be removed 20240618
|
|
||||||
SetDefault(OsuSetting.AutomaticallyDownloadWhenSpectating, false);
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||||
SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, false);
|
|
||||||
|
|
||||||
SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled =>
|
SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled =>
|
||||||
{
|
{
|
||||||
@ -244,12 +238,6 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
// migrations can be added here using a condition like:
|
// migrations can be added here using a condition like:
|
||||||
// if (combined < 20220103) { performMigration() }
|
// if (combined < 20220103) { performMigration() }
|
||||||
if (combined < 20230918)
|
|
||||||
{
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||||
SetValue(OsuSetting.AutomaticallyDownloadMissingBeatmaps, Get<bool>(OsuSetting.AutomaticallyDownloadWhenSpectating)); // can be removed 20240618
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override TrackedSettings CreateTrackedSettings()
|
public override TrackedSettings CreateTrackedSettings()
|
||||||
@ -424,9 +412,6 @@ namespace osu.Game.Configuration
|
|||||||
EditorAutoSeekOnPlacement,
|
EditorAutoSeekOnPlacement,
|
||||||
DiscordRichPresence,
|
DiscordRichPresence,
|
||||||
|
|
||||||
[Obsolete($"Use {nameof(AutomaticallyDownloadMissingBeatmaps)} instead.")] // can be removed 20240318
|
|
||||||
AutomaticallyDownloadWhenSpectating,
|
|
||||||
|
|
||||||
ShowOnlineExplicitContent,
|
ShowOnlineExplicitContent,
|
||||||
LastProcessedMetadataId,
|
LastProcessedMetadataId,
|
||||||
SafeAreaConsiderations,
|
SafeAreaConsiderations,
|
||||||
|
@ -92,8 +92,9 @@ namespace osu.Game.Database
|
|||||||
/// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values.
|
/// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values.
|
||||||
/// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on.
|
/// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on.
|
||||||
/// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances.
|
/// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances.
|
||||||
|
/// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 41;
|
private const int schema_version = 42;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
@ -1145,6 +1146,51 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 42:
|
||||||
|
for (int columns = 1; columns <= 10; columns++)
|
||||||
|
{
|
||||||
|
remapKeyBindingsForVariant(columns, false);
|
||||||
|
remapKeyBindingsForVariant(columns, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace existing key bindings with new ones reflecting changes to ManiaAction:
|
||||||
|
// - "Special#" actions are removed and "Key#" actions are inserted in their place.
|
||||||
|
// - All actions are renumbered to remove the old offsets.
|
||||||
|
void remapKeyBindingsForVariant(int columns, bool dual)
|
||||||
|
{
|
||||||
|
// https://github.com/ppy/osu/blob/8773c2f7ebc226942d6124eb95c07a83934272ea/osu.Game.Rulesets.Mania/ManiaRuleset.cs#L327-L336
|
||||||
|
int variant = dual ? 1000 + (columns * 2) : columns;
|
||||||
|
|
||||||
|
var oldKeyBindingsQuery = migration.NewRealm
|
||||||
|
.All<RealmKeyBinding>()
|
||||||
|
.Where(kb => kb.RulesetName == @"mania" && kb.Variant == variant);
|
||||||
|
var oldKeyBindings = oldKeyBindingsQuery.Detach();
|
||||||
|
|
||||||
|
migration.NewRealm.RemoveRange(oldKeyBindingsQuery);
|
||||||
|
|
||||||
|
// https://github.com/ppy/osu/blob/8773c2f7ebc226942d6124eb95c07a83934272ea/osu.Game.Rulesets.Mania/ManiaInputManager.cs#L22-L31
|
||||||
|
int oldNormalAction = 10; // Old Key1 offset
|
||||||
|
int oldSpecialAction = 1; // Old Special1 offset
|
||||||
|
|
||||||
|
for (int column = 0; column < columns * (dual ? 2 : 1); column++)
|
||||||
|
{
|
||||||
|
if (columns % 2 == 1 && column % columns == columns / 2)
|
||||||
|
remapKeyBinding(oldSpecialAction++, column);
|
||||||
|
else
|
||||||
|
remapKeyBinding(oldNormalAction++, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remapKeyBinding(int oldAction, int newAction)
|
||||||
|
{
|
||||||
|
var oldKeyBinding = oldKeyBindings.Find(kb => kb.ActionInt == oldAction);
|
||||||
|
|
||||||
|
if (oldKeyBinding != null)
|
||||||
|
migration.NewRealm.Add(new RealmKeyBinding(newAction, oldKeyBinding.KeyCombination, @"mania", variant));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new HoverClickSounds(),
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -92,7 +93,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
||||||
},
|
},
|
||||||
new HoverClickSounds()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,16 +8,24 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
|
|
||||||
namespace osu.Game.Graphics.Cursor
|
namespace osu.Game.Graphics.Cursor
|
||||||
{
|
{
|
||||||
|
[Cached(typeof(OsuContextMenuContainer))]
|
||||||
public partial class OsuContextMenuContainer : ContextMenuContainer
|
public partial class OsuContextMenuContainer : ContextMenuContainer
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
private OsuContextMenuSamples samples = new OsuContextMenuSamples();
|
private OsuContextMenuSamples samples = new OsuContextMenuSamples();
|
||||||
|
|
||||||
|
private OsuContextMenu menu = null!;
|
||||||
|
|
||||||
public OsuContextMenuContainer()
|
public OsuContextMenuContainer()
|
||||||
{
|
{
|
||||||
AddInternal(samples);
|
AddInternal(samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Menu CreateMenu() => new OsuContextMenu(true);
|
protected override Menu CreateMenu() => menu = new OsuContextMenu(true);
|
||||||
|
|
||||||
|
public void CloseMenu()
|
||||||
|
{
|
||||||
|
menu.Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Framework.Graphics.Cursor;
|
|||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Cursor
|
namespace osu.Game.Graphics.Cursor
|
||||||
{
|
{
|
||||||
@ -27,15 +27,20 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
|
|
||||||
public partial class OsuTooltip : Tooltip
|
public partial class OsuTooltip : Tooltip
|
||||||
{
|
{
|
||||||
|
private const float max_width = 500;
|
||||||
|
|
||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
private readonly OsuSpriteText text;
|
private readonly TextFlowContainer text;
|
||||||
private bool instantMovement = true;
|
private bool instantMovement = true;
|
||||||
|
|
||||||
public override void SetContent(LocalisableString contentString)
|
private LocalisableString lastContent;
|
||||||
{
|
|
||||||
if (contentString == text.Text) return;
|
|
||||||
|
|
||||||
text.Text = contentString;
|
public override void SetContent(LocalisableString content)
|
||||||
|
{
|
||||||
|
if (content.Equals(lastContent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
text.Text = content;
|
||||||
|
|
||||||
if (IsPresent)
|
if (IsPresent)
|
||||||
{
|
{
|
||||||
@ -44,6 +49,8 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
AutoSizeDuration = 0;
|
AutoSizeDuration = 0;
|
||||||
|
|
||||||
|
lastContent = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OsuTooltip()
|
public OsuTooltip()
|
||||||
@ -65,10 +72,14 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0.9f,
|
Alpha = 0.9f,
|
||||||
},
|
},
|
||||||
text = new OsuSpriteText
|
text = new TextFlowContainer(f =>
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding(5),
|
f.Font = OsuFont.GetFont(weight: FontWeight.Regular);
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Regular)
|
})
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding(5),
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
MaximumSize = new Vector2(max_width, float.PositiveInfinity),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention");
|
public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
/// <summary>
|
||||||
|
/// "press enter to chat..."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to chat...");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs
Normal file
24
osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class DailyChallengeStatsDisplayStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.DailyChallengeStatsDisplay";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "{0}d"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString UnitDay(LocalisableString count) => new TranslatableString(getKey(@"unit_day"), @"{0}d", count);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "{0}w"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString UnitWeek(LocalisableString count) => new TranslatableString(getKey(@"unit_week"), @"{0}w", count);
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,16 @@ namespace osu.Game.Localisation.HUD
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ShowGraphDescription => new TranslatableString(getKey(@"show_graph_description"), "Whether a graph displaying difficulty throughout the beatmap should be shown");
|
public static LocalisableString ShowGraphDescription => new TranslatableString(getKey(@"show_graph_description"), "Whether a graph displaying difficulty throughout the beatmap should be shown");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Show time"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ShowTime => new TranslatableString(getKey(@"show_time"), "Show time");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Whether the passed and remaining time should be shown"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ShowTimeDescription => new TranslatableString(getKey(@"show_time_description"), "Whether the passed and remaining time should be shown");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,6 +272,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty("groups")]
|
[JsonProperty("groups")]
|
||||||
public APIUserGroup[] Groups;
|
public APIUserGroup[] Groups;
|
||||||
|
|
||||||
|
[JsonProperty("daily_challenge_user_stats")]
|
||||||
|
public APIUserDailyChallengeStatistics DailyChallengeStatistics = new APIUserDailyChallengeStatistics();
|
||||||
|
|
||||||
public override string ToString() => Username;
|
public override string ToString() => Username;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
// 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 Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests.Responses
|
||||||
|
{
|
||||||
|
public class APIUserDailyChallengeStatistics
|
||||||
|
{
|
||||||
|
[JsonProperty("user_id")]
|
||||||
|
public int UserID;
|
||||||
|
|
||||||
|
[JsonProperty("daily_streak_best")]
|
||||||
|
public int DailyStreakBest;
|
||||||
|
|
||||||
|
[JsonProperty("daily_streak_current")]
|
||||||
|
public int DailyStreakCurrent;
|
||||||
|
|
||||||
|
[JsonProperty("weekly_streak_best")]
|
||||||
|
public int WeeklyStreakBest;
|
||||||
|
|
||||||
|
[JsonProperty("weekly_streak_current")]
|
||||||
|
public int WeeklyStreakCurrent;
|
||||||
|
|
||||||
|
[JsonProperty("top_10p_placements")]
|
||||||
|
public int Top10PercentPlacements;
|
||||||
|
|
||||||
|
[JsonProperty("top_50p_placements")]
|
||||||
|
public int Top50PercentPlacements;
|
||||||
|
|
||||||
|
[JsonProperty("playcount")]
|
||||||
|
public int PlayCount;
|
||||||
|
|
||||||
|
[JsonProperty("last_update")]
|
||||||
|
public DateTimeOffset? LastUpdate;
|
||||||
|
|
||||||
|
[JsonProperty("last_weekly_streak")]
|
||||||
|
public DateTimeOffset? LastWeeklyStreak;
|
||||||
|
}
|
||||||
|
}
|
@ -128,6 +128,9 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
public partial class ChatTextBox : HistoryTextBox
|
public partial class ChatTextBox : HistoryTextBox
|
||||||
{
|
{
|
||||||
|
public Action Focus;
|
||||||
|
public Action FocusLost;
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
// Chat text boxes are generally used in places where they retain focus, but shouldn't block interaction with other
|
// Chat text boxes are generally used in places where they retain focus, but shouldn't block interaction with other
|
||||||
@ -153,13 +156,17 @@ namespace osu.Game.Online.Chat
|
|||||||
BackgroundFocused = new Color4(10, 10, 10, 255);
|
BackgroundFocused = new Color4(10, 10, 10, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocus(e);
|
||||||
|
Focus?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnFocusLost(FocusLostEvent e)
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
{
|
{
|
||||||
base.OnFocusLost(e);
|
base.OnFocusLost(e);
|
||||||
FocusLost?.Invoke();
|
FocusLost?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action FocusLost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class StandAloneDrawableChannel : DrawableChannel
|
public partial class StandAloneDrawableChannel : DrawableChannel
|
||||||
|
@ -782,7 +782,21 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new AggregateException($"Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex);
|
// Temporary code to attempt to figure out long-term failing tests.
|
||||||
|
bool success = true;
|
||||||
|
int indexOf = -1234;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
indexOf = APIRoom!.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
|
||||||
|
Room.Playlist[indexOf] = item;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AggregateException($"Index: {indexOf} Length: {Room.Playlist.Count} Retry success: {success} Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemChanged?.Invoke(item);
|
ItemChanged?.Invoke(item);
|
||||||
|
@ -642,10 +642,10 @@ namespace osu.Game
|
|||||||
Live<BeatmapSetInfo> databasedSet = null;
|
Live<BeatmapSetInfo> databasedSet = null;
|
||||||
|
|
||||||
if (beatmap.OnlineID > 0)
|
if (beatmap.OnlineID > 0)
|
||||||
databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID);
|
databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID && !s.DeletePending);
|
||||||
|
|
||||||
if (beatmap is BeatmapSetInfo localBeatmap)
|
if (beatmap is BeatmapSetInfo localBeatmap)
|
||||||
databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash);
|
databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash && !s.DeletePending);
|
||||||
|
|
||||||
if (databasedSet == null)
|
if (databasedSet == null)
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -104,6 +105,8 @@ namespace osu.Game.Overlays.Chat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool isMention;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The colour used to paint the author's username.
|
/// The colour used to paint the author's username.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -255,12 +258,21 @@ namespace osu.Game.Overlays.Chat
|
|||||||
private void styleMessageContent(SpriteText text)
|
private void styleMessageContent(SpriteText text)
|
||||||
{
|
{
|
||||||
text.Shadow = false;
|
text.Shadow = false;
|
||||||
text.Font = text.Font.With(size: FontSize, italics: Message.IsAction);
|
text.Font = text.Font.With(size: FontSize, italics: Message.IsAction, weight: isMention ? FontWeight.SemiBold : FontWeight.Medium);
|
||||||
|
|
||||||
bool messageHasColour = Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour);
|
Color4 messageColour = colourProvider?.Content1 ?? Colour4.White;
|
||||||
text.Colour = messageHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : colourProvider?.Content1 ?? Colour4.White;
|
|
||||||
|
if (isMention)
|
||||||
|
messageColour = colourProvider?.Highlight1 ?? Color4.Orange;
|
||||||
|
else if (Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour))
|
||||||
|
messageColour = Color4Extensions.FromHex(message.Sender.Colour);
|
||||||
|
|
||||||
|
text.Colour = messageColour;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
private void updateMessageContent()
|
private void updateMessageContent()
|
||||||
{
|
{
|
||||||
this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint);
|
this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint);
|
||||||
@ -280,6 +292,8 @@ namespace osu.Game.Overlays.Chat
|
|||||||
// remove non-existent channels from the link list
|
// remove non-existent channels from the link list
|
||||||
message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true);
|
message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true);
|
||||||
|
|
||||||
|
isMention = MessageNotifier.CheckContainsUsername(message.DisplayContent, api.LocalUser.Value.Username);
|
||||||
|
|
||||||
drawableContentFlow.Clear();
|
drawableContentFlow.Clear();
|
||||||
drawableContentFlow.AddLinks(message.DisplayContent, message.Links);
|
drawableContentFlow.AddLinks(message.DisplayContent, message.Links);
|
||||||
}
|
}
|
||||||
|
@ -4,21 +4,26 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK;
|
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using static osu.Game.Overlays.Mods.ModCustomisationPanel;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public partial class ModCustomisationHeader : OsuHoverContainer
|
public partial class ModCustomisationHeader : OsuHoverContainer
|
||||||
{
|
{
|
||||||
private Box background = null!;
|
private Box background = null!;
|
||||||
|
private Box backgroundFlash = null!;
|
||||||
private SpriteIcon icon = null!;
|
private SpriteIcon icon = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -26,11 +31,13 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
protected override IEnumerable<Drawable> EffectTargets => new[] { background };
|
protected override IEnumerable<Drawable> EffectTargets => new[] { background };
|
||||||
|
|
||||||
public readonly BindableBool Expanded = new BindableBool();
|
public readonly Bindable<ModCustomisationPanelState> ExpandedState = new Bindable<ModCustomisationPanelState>(ModCustomisationPanelState.Collapsed);
|
||||||
|
|
||||||
public ModCustomisationHeader()
|
private readonly ModCustomisationPanel panel;
|
||||||
|
|
||||||
|
public ModCustomisationHeader(ModCustomisationPanel panel)
|
||||||
{
|
{
|
||||||
Action = Expanded.Toggle;
|
this.panel = panel;
|
||||||
Enabled.Value = false;
|
Enabled.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +53,13 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
backgroundFlash = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White.Opacity(0.4f),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
@ -84,12 +98,56 @@ namespace osu.Game.Overlays.Mods
|
|||||||
TooltipText = e.NewValue
|
TooltipText = e.NewValue
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: ModSelectOverlayStrings.CustomisationPanelDisabledReason;
|
: ModSelectOverlayStrings.CustomisationPanelDisabledReason;
|
||||||
|
|
||||||
|
if (e.NewValue)
|
||||||
|
{
|
||||||
|
backgroundFlash.FadeInFromZero(150, Easing.OutQuad).Then()
|
||||||
|
.FadeOutFromOne(350, Easing.OutQuad);
|
||||||
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Expanded.BindValueChanged(v =>
|
ExpandedState.BindValueChanged(v =>
|
||||||
{
|
{
|
||||||
icon.ScaleTo(v.NewValue ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint);
|
icon.ScaleTo(v.NewValue > ModCustomisationPanelState.Collapsed ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint);
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
if (Enabled.Value)
|
||||||
|
{
|
||||||
|
ExpandedState.Value = ExpandedState.Value switch
|
||||||
|
{
|
||||||
|
ModCustomisationPanelState.Collapsed => ModCustomisationPanelState.Expanded,
|
||||||
|
_ => ModCustomisationPanelState.Collapsed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnClick(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool touchedThisFrame;
|
||||||
|
|
||||||
|
protected override bool OnTouchDown(TouchDownEvent e)
|
||||||
|
{
|
||||||
|
if (Enabled.Value)
|
||||||
|
{
|
||||||
|
touchedThisFrame = true;
|
||||||
|
Schedule(() => touchedThisFrame = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnTouchDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
if (Enabled.Value)
|
||||||
|
{
|
||||||
|
if (!touchedThisFrame && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed)
|
||||||
|
panel.ExpandedState.Value = ModCustomisationPanelState.ExpandedByHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
public readonly BindableBool Enabled = new BindableBool();
|
public readonly BindableBool Enabled = new BindableBool();
|
||||||
|
|
||||||
public readonly BindableBool Expanded = new BindableBool();
|
public readonly Bindable<ModCustomisationPanelState> ExpandedState = new Bindable<ModCustomisationPanelState>();
|
||||||
|
|
||||||
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
@ -46,9 +46,9 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
// Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded.
|
// Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded.
|
||||||
// These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside
|
// These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside
|
||||||
// (returning Expanded.Value to OnHover or overriding Block{Non}PositionalInput doesn't work).
|
// (handling OnHover or overriding Block{Non}PositionalInput doesn't work).
|
||||||
public override bool HandlePositionalInput => Expanded.Value;
|
public override bool HandlePositionalInput => ExpandedState.Value != ModCustomisationPanelState.Collapsed;
|
||||||
public override bool HandleNonPositionalInput => Expanded.Value;
|
public override bool HandleNonPositionalInput => ExpandedState.Value != ModCustomisationPanelState.Collapsed;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -57,15 +57,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new ModCustomisationHeader
|
new ModCustomisationHeader(this)
|
||||||
{
|
{
|
||||||
Depth = float.MinValue,
|
Depth = float.MinValue,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = header_height,
|
Height = header_height,
|
||||||
Enabled = { BindTarget = Enabled },
|
Enabled = { BindTarget = Enabled },
|
||||||
Expanded = { BindTarget = Expanded },
|
ExpandedState = { BindTarget = ExpandedState },
|
||||||
},
|
},
|
||||||
content = new FocusGrabbingContainer
|
content = new FocusGrabbingContainer(this)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
BorderColour = colourProvider.Dark3,
|
BorderColour = colourProvider.Dark3,
|
||||||
@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Roundness = 5f,
|
Roundness = 5f,
|
||||||
Colour = Color4.Black.Opacity(0.25f),
|
Colour = Color4.Black.Opacity(0.25f),
|
||||||
},
|
},
|
||||||
Expanded = { BindTarget = Expanded },
|
ExpandedState = { BindTarget = ExpandedState },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
@ -122,7 +122,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
this.FadeColour(OsuColour.Gray(e.NewValue ? 1f : 0.6f), 300, Easing.OutQuint);
|
this.FadeColour(OsuColour.Gray(e.NewValue ? 1f : 0.6f), 300, Easing.OutQuint);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Expanded.BindValueChanged(_ => updateDisplay(), true);
|
ExpandedState.BindValueChanged(_ => updateDisplay(), true);
|
||||||
SelectedMods.BindValueChanged(_ => updateMods(), true);
|
SelectedMods.BindValueChanged(_ => updateMods(), true);
|
||||||
|
|
||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
Expanded.Value = false;
|
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case GlobalAction.Back:
|
case GlobalAction.Back:
|
||||||
Expanded.Value = false;
|
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
content.ClearTransforms();
|
content.ClearTransforms();
|
||||||
|
|
||||||
if (Expanded.Value)
|
if (ExpandedState.Value != ModCustomisationPanelState.Collapsed)
|
||||||
{
|
{
|
||||||
content.AutoSizeDuration = 400;
|
content.AutoSizeDuration = 400;
|
||||||
content.AutoSizeEasing = Easing.OutQuint;
|
content.AutoSizeEasing = Easing.OutQuint;
|
||||||
@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private void updateMods()
|
private void updateMods()
|
||||||
{
|
{
|
||||||
Expanded.Value = false;
|
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||||
sectionsFlow.Clear();
|
sectionsFlow.Clear();
|
||||||
|
|
||||||
// Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels).
|
// Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels).
|
||||||
@ -202,10 +202,35 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private partial class FocusGrabbingContainer : InputBlockingContainer
|
private partial class FocusGrabbingContainer : InputBlockingContainer
|
||||||
{
|
{
|
||||||
public IBindable<bool> Expanded { get; } = new BindableBool();
|
public readonly Bindable<ModCustomisationPanelState> ExpandedState = new Bindable<ModCustomisationPanelState>();
|
||||||
|
|
||||||
public override bool RequestsFocus => Expanded.Value;
|
public override bool RequestsFocus => panel.ExpandedState.Value != ModCustomisationPanelState.Collapsed;
|
||||||
public override bool AcceptsFocus => Expanded.Value;
|
public override bool AcceptsFocus => panel.ExpandedState.Value != ModCustomisationPanelState.Collapsed;
|
||||||
|
|
||||||
|
private readonly ModCustomisationPanel panel;
|
||||||
|
|
||||||
|
public FocusGrabbingContainer(ModCustomisationPanel panel)
|
||||||
|
{
|
||||||
|
this.panel = panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
if (ExpandedState.Value is ModCustomisationPanelState.ExpandedByHover
|
||||||
|
&& !ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
|
||||||
|
{
|
||||||
|
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ModCustomisationPanelState
|
||||||
|
{
|
||||||
|
Collapsed = 0,
|
||||||
|
ExpandedByHover = 1,
|
||||||
|
Expanded = 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,6 +138,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
},
|
},
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
|
Padding = new MarginPadding { Top = 1, Bottom = 3 },
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
|
@ -237,7 +237,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
ActiveMods.Value = ComputeActiveMods();
|
ActiveMods.Value = ComputeActiveMods();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
customisationPanel.ExpandedState.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||||
|
|
||||||
SearchTextBox.Current.BindValueChanged(query =>
|
SearchTextBox.Current.BindValueChanged(query =>
|
||||||
{
|
{
|
||||||
@ -368,18 +368,18 @@ namespace osu.Game.Overlays.Mods
|
|||||||
customisationPanel.Enabled.Value = true;
|
customisationPanel.Enabled.Value = true;
|
||||||
|
|
||||||
if (anyModPendingConfiguration)
|
if (anyModPendingConfiguration)
|
||||||
customisationPanel.Expanded.Value = true;
|
customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
customisationPanel.Expanded.Value = false;
|
customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed;
|
||||||
customisationPanel.Enabled.Value = false;
|
customisationPanel.Enabled.Value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCustomisationVisualState()
|
private void updateCustomisationVisualState()
|
||||||
{
|
{
|
||||||
if (customisationPanel.Expanded.Value)
|
if (customisationPanel.ExpandedState.Value != ModCustomisationPanel.ModCustomisationPanelState.Collapsed)
|
||||||
{
|
{
|
||||||
columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
||||||
SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
||||||
@ -544,7 +544,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
nonFilteredColumnCount += 1;
|
nonFilteredColumnCount += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
customisationPanel.Expanded.Value = false;
|
customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -571,7 +571,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
// wherein activating the binding will both change the contents of the search text box and deselect all mods.
|
// wherein activating the binding will both change the contents of the search text box and deselect all mods.
|
||||||
case GlobalAction.DeselectAllMods:
|
case GlobalAction.DeselectAllMods:
|
||||||
{
|
{
|
||||||
if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value)
|
if (!SearchTextBox.HasFocus && customisationPanel.ExpandedState.Value == ModCustomisationPanel.ModCustomisationPanelState.Collapsed)
|
||||||
{
|
{
|
||||||
DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick();
|
DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick();
|
||||||
return true;
|
return true;
|
||||||
@ -637,7 +637,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
if (e.Repeat || e.Key != Key.Tab)
|
if (e.Repeat || e.Key != Key.Tab)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (customisationPanel.Expanded.Value)
|
if (customisationPanel.ExpandedState.Value != ModCustomisationPanel.ModCustomisationPanelState.Collapsed)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`)
|
// TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`)
|
||||||
@ -668,6 +668,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
[Cached]
|
[Cached]
|
||||||
internal partial class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
|
internal partial class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
|
||||||
{
|
{
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
public ColumnScrollContainer()
|
public ColumnScrollContainer()
|
||||||
: base(Direction.Horizontal)
|
: base(Direction.Horizontal)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
// 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.Extensions.LocalisationExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
|
{
|
||||||
|
public partial class DailyChallengeStatsDisplay : CompositeDrawable, IHasCustomTooltip<DailyChallengeTooltipData>
|
||||||
|
{
|
||||||
|
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||||
|
|
||||||
|
public DailyChallengeTooltipData? TooltipContent { get; private set; }
|
||||||
|
|
||||||
|
private OsuSpriteText dailyPlayCount = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
CornerRadius = 5;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding(5f),
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
// can't use this because osu-web does weird stuff with \\n.
|
||||||
|
// Text = UsersStrings.ShowDailyChallengeTitle.,
|
||||||
|
Text = "Daily\nChallenge",
|
||||||
|
Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f },
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
CornerRadius = 5f,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background6,
|
||||||
|
},
|
||||||
|
dailyPlayCount = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
UseFullGlyphHeight = false,
|
||||||
|
Colour = colourProvider.Content2,
|
||||||
|
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
User.BindValueChanged(_ => updateDisplay(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplay()
|
||||||
|
{
|
||||||
|
if (User.Value == null || User.Value.Ruleset.OnlineID != 0)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics;
|
||||||
|
|
||||||
|
dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0"));
|
||||||
|
dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount));
|
||||||
|
|
||||||
|
TooltipContent = new DailyChallengeTooltipData(colourProvider, stats);
|
||||||
|
|
||||||
|
Show();
|
||||||
|
|
||||||
|
static RankingTier tierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily(playCount / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITooltip<DailyChallengeTooltipData> GetCustomTooltip() => new DailyChallengeStatsTooltip();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,242 @@
|
|||||||
|
// 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.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
|
{
|
||||||
|
public partial class DailyChallengeStatsTooltip : VisibilityContainer, ITooltip<DailyChallengeTooltipData>
|
||||||
|
{
|
||||||
|
private StreakPiece currentDaily = null!;
|
||||||
|
private StreakPiece currentWeekly = null!;
|
||||||
|
private StatisticsPiece bestDaily = null!;
|
||||||
|
private StatisticsPiece bestWeekly = null!;
|
||||||
|
private StatisticsPiece topTen = null!;
|
||||||
|
private StatisticsPiece topFifty = null!;
|
||||||
|
|
||||||
|
private Box topBackground = null!;
|
||||||
|
private Box background = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
CornerRadius = 20f;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Color4.Black.Opacity(0.25f),
|
||||||
|
Radius = 30f,
|
||||||
|
};
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
topBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Padding = new MarginPadding(15f),
|
||||||
|
Spacing = new Vector2(30f),
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent),
|
||||||
|
currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding(15f),
|
||||||
|
Spacing = new Vector2(10f),
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest),
|
||||||
|
bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest),
|
||||||
|
topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements),
|
||||||
|
topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContent(DailyChallengeTooltipData content)
|
||||||
|
{
|
||||||
|
var statistics = content.Statistics;
|
||||||
|
var colourProvider = content.ColourProvider;
|
||||||
|
|
||||||
|
background.Colour = colourProvider.Background4;
|
||||||
|
topBackground.Colour = colourProvider.Background5;
|
||||||
|
|
||||||
|
currentDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0"));
|
||||||
|
currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent));
|
||||||
|
|
||||||
|
currentWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0"));
|
||||||
|
currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent));
|
||||||
|
|
||||||
|
bestDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0"));
|
||||||
|
bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest));
|
||||||
|
|
||||||
|
bestWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0"));
|
||||||
|
bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest));
|
||||||
|
|
||||||
|
topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0");
|
||||||
|
topTen.ValueColour = colourProvider.Content2;
|
||||||
|
|
||||||
|
topFifty.Value = statistics.Top50PercentPlacements.ToLocalisableString(@"N0");
|
||||||
|
topFifty.ValueColour = colourProvider.Content2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reference: https://github.com/ppy/osu-web/blob/8206e0e91eeea80ccf92f0586561346dd40e085e/resources/js/profile-page/daily-challenge.tsx#L13-L43
|
||||||
|
public static RankingTier TierForDaily(int daily)
|
||||||
|
{
|
||||||
|
if (daily > 360)
|
||||||
|
return RankingTier.Lustrous;
|
||||||
|
|
||||||
|
if (daily > 240)
|
||||||
|
return RankingTier.Radiant;
|
||||||
|
|
||||||
|
if (daily > 120)
|
||||||
|
return RankingTier.Rhodium;
|
||||||
|
|
||||||
|
if (daily > 60)
|
||||||
|
return RankingTier.Platinum;
|
||||||
|
|
||||||
|
if (daily > 30)
|
||||||
|
return RankingTier.Gold;
|
||||||
|
|
||||||
|
if (daily > 10)
|
||||||
|
return RankingTier.Silver;
|
||||||
|
|
||||||
|
if (daily > 5)
|
||||||
|
return RankingTier.Bronze;
|
||||||
|
|
||||||
|
return RankingTier.Iron;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RankingTier TierForWeekly(int weekly) => TierForDaily((weekly - 1) * 7);
|
||||||
|
|
||||||
|
protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
|
||||||
|
|
||||||
|
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
|
||||||
|
|
||||||
|
public void Move(Vector2 pos) => Position = pos;
|
||||||
|
|
||||||
|
private partial class StreakPiece : FillFlowContainer
|
||||||
|
{
|
||||||
|
private readonly OsuSpriteText valueText;
|
||||||
|
|
||||||
|
public LocalisableString Value
|
||||||
|
{
|
||||||
|
set => valueText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColourInfo ValueColour
|
||||||
|
{
|
||||||
|
set => valueText.Colour = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreakPiece(LocalisableString title)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
Direction = FillDirection.Vertical;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.GetFont(size: 12),
|
||||||
|
Text = title,
|
||||||
|
},
|
||||||
|
valueText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class StatisticsPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly OsuSpriteText valueText;
|
||||||
|
|
||||||
|
public LocalisableString Value
|
||||||
|
{
|
||||||
|
set => valueText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColourInfo ValueColour
|
||||||
|
{
|
||||||
|
set => valueText.Colour = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatisticsPiece(LocalisableString title)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.GetFont(size: 12),
|
||||||
|
Text = title,
|
||||||
|
},
|
||||||
|
valueText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Font = OsuFont.GetFont(size: 12),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DailyChallengeTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics);
|
||||||
|
}
|
@ -44,22 +44,41 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
Spacing = new Vector2(0, 15),
|
Spacing = new Vector2(0, 15),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Horizontal,
|
ColumnDimensions = new[]
|
||||||
Spacing = new Vector2(20),
|
{
|
||||||
Children = new Drawable[]
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 20),
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
{
|
{
|
||||||
detailGlobalRank = new ProfileValueDisplay(true)
|
detailGlobalRank = new ProfileValueDisplay(true)
|
||||||
{
|
{
|
||||||
Title = UsersStrings.ShowRankGlobalSimple,
|
Title = UsersStrings.ShowRankGlobalSimple,
|
||||||
},
|
},
|
||||||
|
Empty(),
|
||||||
detailCountryRank = new ProfileValueDisplay(true)
|
detailCountryRank = new ProfileValueDisplay(true)
|
||||||
{
|
{
|
||||||
Title = UsersStrings.ShowRankCountrySimple,
|
Title = UsersStrings.ShowRankCountrySimple,
|
||||||
},
|
},
|
||||||
|
new DailyChallengeStatsDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
User = { BindTarget = User },
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
@ -10,6 +14,8 @@ namespace osu.Game.Overlays.Settings
|
|||||||
public partial class SettingsEnumDropdown<T> : SettingsDropdown<T>
|
public partial class SettingsEnumDropdown<T> : SettingsDropdown<T>
|
||||||
where T : struct, Enum
|
where T : struct, Enum
|
||||||
{
|
{
|
||||||
|
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.GetLocalisableDescription()));
|
||||||
|
|
||||||
protected override OsuDropdown<T> CreateDropdown() => new DropdownControl();
|
protected override OsuDropdown<T> CreateDropdown() => new DropdownControl();
|
||||||
|
|
||||||
protected new partial class DropdownControl : OsuEnumDropdown<T>
|
protected new partial class DropdownControl : OsuEnumDropdown<T>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user