mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 19:42:55 +08:00
Merge branch 'master' into additions
This commit is contained in:
commit
42675be6cf
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.802.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<LegacyDefaultComboCounter>().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);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -32,30 +31,23 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case SkinComponentsContainerLookup containerLookup:
|
||||||
if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents)
|
// Only handle per ruleset defaults here.
|
||||||
|
if (containerLookup.Ruleset == null)
|
||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
// Modifications for global components.
|
|
||||||
if (containerLookup.Ruleset == null)
|
|
||||||
{
|
|
||||||
var components = base.GetDrawableComponent(lookup) as Container;
|
|
||||||
|
|
||||||
if (providesComboCounter && components != null)
|
|
||||||
{
|
|
||||||
// catch may provide its own combo counter; hide the default.
|
|
||||||
// todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
|
|
||||||
foreach (var legacyComboCounter in components.OfType<LegacyComboCounter>())
|
|
||||||
legacyComboCounter.HiddenByRulesetImplementation = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skin has configuration.
|
// Skin has configuration.
|
||||||
if (base.GetDrawableComponent(lookup) is Drawable d)
|
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||||
return d;
|
return d;
|
||||||
|
|
||||||
|
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||||
|
if (!IsProvidingLegacyResources)
|
||||||
|
return null;
|
||||||
|
|
||||||
// Our own ruleset components default.
|
// Our own ruleset components default.
|
||||||
|
switch (containerLookup.Target)
|
||||||
|
{
|
||||||
|
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||||
|
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||||
@ -76,6 +68,9 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
new LegacyKeyCounterDisplay(),
|
new LegacyKeyCounterDisplay(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponentLookup catchSkinComponent:
|
case CatchSkinComponentLookup catchSkinComponent:
|
||||||
switch (catchSkinComponent.Component)
|
switch (catchSkinComponent.Component)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
if (Catcher.Dashing || Catcher.HyperDashing)
|
if (Catcher.Dashing || Catcher.HyperDashing)
|
||||||
{
|
{
|
||||||
double generationInterval = Catcher.HyperDashing ? 25 : 50;
|
const double trail_generation_interval = 16;
|
||||||
|
|
||||||
if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval)
|
if (Time.Current - catcherTrails.LastDashTrailTime >= trail_generation_interval)
|
||||||
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Skinning.Argon;
|
||||||
|
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
|
{
|
||||||
|
public partial class TestSceneComboCounter : ManiaSkinnableTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("setup", () => SetContents(s =>
|
||||||
|
{
|
||||||
|
if (s is ArgonSkin)
|
||||||
|
return new ArgonManiaComboCounter();
|
||||||
|
|
||||||
|
if (s is LegacySkin)
|
||||||
|
return new LegacyManiaComboCounter();
|
||||||
|
|
||||||
|
return new LegacyManiaComboCounter();
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20);
|
||||||
|
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,13 +28,19 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
AddStep("Show " + result.GetDescription(), () =>
|
AddStep("Show " + result.GetDescription(), () =>
|
||||||
{
|
{
|
||||||
SetContents(_ =>
|
SetContents(_ =>
|
||||||
new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
|
||||||
{
|
{
|
||||||
Type = result
|
var drawableManiaJudgement = new DrawableManiaJudgement
|
||||||
}, null)
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
|
||||||
|
drawableManiaJudgement.Apply(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||||
|
{
|
||||||
|
Type = result
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
return drawableManiaJudgement;
|
||||||
});
|
});
|
||||||
|
|
||||||
// for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value
|
// for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value
|
||||||
|
@ -3,15 +3,22 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
{
|
{
|
||||||
public partial class TestScenePlayfield : ManiaSkinnableTestScene
|
public partial class TestScenePlayfield : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
|
||||||
|
|
||||||
private List<StageDefinition> stageDefinitions = new List<StageDefinition>();
|
private List<StageDefinition> stageDefinitions = new List<StageDefinition>();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -29,6 +36,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
Child = new ManiaPlayfield(stageDefinitions)
|
Child = new ManiaPlayfield(stageDefinitions)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
|
||||||
|
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
@ -54,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
|
||||||
|
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IBeatmap CreateBeatmapForSkinProvider()
|
protected override IBeatmap CreateBeatmapForSkinProvider()
|
||||||
|
@ -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]
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaPlayerLegacySkin : LegacySkinPlayerTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
// play with a converted beatmap to allow dual stages mod to work.
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(new RulesetInfo());
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleStage()
|
||||||
|
{
|
||||||
|
AddStep("Load single stage", LoadPlayer);
|
||||||
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDualStage()
|
||||||
|
{
|
||||||
|
AddStep("Load dual stage", () => LoadPlayer(new Mod[] { new ManiaModDualStages() }));
|
||||||
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -131,9 +131,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
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 _);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public partial class ArgonManiaComboCounter : ArgonComboCounter
|
||||||
|
{
|
||||||
|
protected override bool DisplayXSymbol => false;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
private IBindable<ScrollingDirection> direction = null!;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// the logic of flipping the position of the combo counter w.r.t. the direction does not work with "Closest" anchor,
|
||||||
|
// because it always forces the anchor to be top or bottom based on scrolling direction.
|
||||||
|
UsesFixedAnchor = true;
|
||||||
|
|
||||||
|
direction = scrollingInfo.Direction.GetBoundCopy();
|
||||||
|
direction.BindValueChanged(_ => updateAnchor());
|
||||||
|
|
||||||
|
// two schedules are required so that updateAnchor is executed in the next frame,
|
||||||
|
// which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer.
|
||||||
|
Schedule(() => Schedule(updateAnchor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAnchor()
|
||||||
|
{
|
||||||
|
// if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
|
||||||
|
if (!Anchor.HasFlag(Anchor.y1))
|
||||||
|
{
|
||||||
|
Anchor &= ~(Anchor.y0 | Anchor.y2);
|
||||||
|
Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// change the sign of the Y coordinate in line with the scrolling direction.
|
||||||
|
// i.e. if the user changes direction from down to up, the anchor is changed from top to bottom, and the Y is flipped from positive to negative here.
|
||||||
|
Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// 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.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -26,6 +28,37 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
|
case SkinComponentsContainerLookup containerLookup:
|
||||||
|
// Only handle per ruleset defaults here.
|
||||||
|
if (containerLookup.Ruleset == null)
|
||||||
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
|
// Skin has configuration.
|
||||||
|
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||||
|
return d;
|
||||||
|
|
||||||
|
switch (containerLookup.Target)
|
||||||
|
{
|
||||||
|
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||||
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
|
{
|
||||||
|
var combo = container.ChildrenOfType<ArgonManiaComboCounter>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (combo != null)
|
||||||
|
{
|
||||||
|
combo.ShowLabel.Value = false;
|
||||||
|
combo.Anchor = Anchor.TopCentre;
|
||||||
|
combo.Origin = Anchor.Centre;
|
||||||
|
combo.Y = 200;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
new ArgonManiaComboCounter(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
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.
|
||||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public partial class LegacyManiaComboCounter : LegacyComboCounter
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
DisplayedCountText.Anchor = Anchor.Centre;
|
||||||
|
DisplayedCountText.Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
PopOutCountText.Anchor = Anchor.Centre;
|
||||||
|
PopOutCountText.Origin = Anchor.Centre;
|
||||||
|
PopOutCountText.Colour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
private IBindable<ScrollingDirection> direction = null!;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
direction = scrollingInfo.Direction.GetBoundCopy();
|
||||||
|
direction.BindValueChanged(_ => updateAnchor());
|
||||||
|
|
||||||
|
// two schedules are required so that updateAnchor is executed in the next frame,
|
||||||
|
// which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer.
|
||||||
|
Schedule(() => Schedule(updateAnchor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAnchor()
|
||||||
|
{
|
||||||
|
// if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
|
||||||
|
if (!Anchor.HasFlag(Anchor.y1))
|
||||||
|
{
|
||||||
|
Anchor &= ~(Anchor.y0 | Anchor.y2);
|
||||||
|
Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// since we flip the vertical anchor when changing scroll direction,
|
||||||
|
// we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
|
||||||
|
if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up))
|
||||||
|
Y = -Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCountIncrement()
|
||||||
|
{
|
||||||
|
base.OnCountIncrement();
|
||||||
|
|
||||||
|
PopOutCountText.Hide();
|
||||||
|
DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f))
|
||||||
|
.ScaleTo(new Vector2(1f), 300, Easing.Out)
|
||||||
|
.FadeIn(120);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCountChange()
|
||||||
|
{
|
||||||
|
base.OnCountChange();
|
||||||
|
|
||||||
|
PopOutCountText.Hide();
|
||||||
|
DisplayedCountText.ScaleTo(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCountRolling()
|
||||||
|
{
|
||||||
|
if (DisplayedCount > 0)
|
||||||
|
{
|
||||||
|
PopOutCountText.Text = FormatCount(DisplayedCount);
|
||||||
|
PopOutCountText.FadeTo(0.8f).FadeOut(200)
|
||||||
|
.ScaleTo(1f).ScaleTo(4f, 200);
|
||||||
|
|
||||||
|
DisplayedCountText.FadeTo(0.5f, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnCountRolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,11 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
@ -78,6 +80,40 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
|
case SkinComponentsContainerLookup containerLookup:
|
||||||
|
// Modifications for global components.
|
||||||
|
if (containerLookup.Ruleset == null)
|
||||||
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
|
// Skin has configuration.
|
||||||
|
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||||
|
return d;
|
||||||
|
|
||||||
|
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||||
|
if (!IsProvidingLegacyResources)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
switch (containerLookup.Target)
|
||||||
|
{
|
||||||
|
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||||
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
|
{
|
||||||
|
var combo = container.ChildrenOfType<LegacyManiaComboCounter>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (combo != null)
|
||||||
|
{
|
||||||
|
combo.Anchor = Anchor.TopCentre;
|
||||||
|
combo.Origin = Anchor.Centre;
|
||||||
|
combo.Y = this.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
new LegacyManiaComboCounter(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||||
return getResult(resultComponent.Component);
|
return getResult(resultComponent.Component);
|
||||||
|
|
||||||
|
@ -5,22 +5,12 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
public partial class DrawableManiaJudgement : DrawableJudgement
|
public partial class DrawableManiaJudgement : DrawableJudgement
|
||||||
{
|
{
|
||||||
public DrawableManiaJudgement(JudgementResult result, DrawableHitObject judgedObject)
|
|
||||||
: base(result, judgedObject)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrawableManiaJudgement()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
||||||
|
|
||||||
private partial class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
private partial class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -299,6 +299,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
});
|
});
|
||||||
assertControlPointTypeDuringPlacement(0, PathType.BSpline(4));
|
assertControlPointTypeDuringPlacement(0, PathType.BSpline(4));
|
||||||
|
|
||||||
|
AddStep("press alt-2", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.Key(Key.Number2);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
assertControlPointTypeDuringPlacement(0, PathType.BEZIER);
|
||||||
|
|
||||||
AddStep("start new segment via S", () => InputManager.Key(Key.S));
|
AddStep("start new segment via S", () => InputManager.Key(Key.S));
|
||||||
assertControlPointTypeDuringPlacement(2, PathType.LINEAR);
|
assertControlPointTypeDuringPlacement(2, PathType.LINEAR);
|
||||||
|
|
||||||
@ -309,7 +317,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
addClickStep(MouseButton.Right);
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
assertFinalControlPointType(0, PathType.BEZIER);
|
||||||
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,8 +309,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (!e.AltPressed)
|
if (!e.AltPressed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// If no pieces are selected, we can't change the path type.
|
||||||
|
if (Pieces.All(p => !p.IsSelected.Value))
|
||||||
|
return false;
|
||||||
|
|
||||||
var type = path_types[e.Key - Key.Number1];
|
var type = path_types[e.Key - Key.Number1];
|
||||||
|
|
||||||
|
// The first control point can never be inherit type
|
||||||
if (Pieces[0].IsSelected.Value && type == null)
|
if (Pieces[0].IsSelected.Value && type == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -359,8 +359,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the cursor position.
|
// Update the cursor position.
|
||||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All);
|
cursor.Position = getCursorPosition();
|
||||||
cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
|
||||||
}
|
}
|
||||||
else if (cursor != null)
|
else if (cursor != null)
|
||||||
{
|
{
|
||||||
@ -374,6 +373,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Vector2 getCursorPosition()
|
||||||
|
{
|
||||||
|
var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All);
|
||||||
|
return ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether a new control point can be placed at the current mouse position.
|
/// Whether a new control point can be placed at the current mouse position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -386,7 +391,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last);
|
var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last);
|
||||||
|
|
||||||
lastPoint = last;
|
lastPoint = last;
|
||||||
return lastPiece.IsHovered != true;
|
// We may only place a new control point if the cursor is not overlapping with the last control point.
|
||||||
|
// If snapping is enabled, the cursor may not hover the last piece while still placing the control point at the same position.
|
||||||
|
return !lastPiece.IsHovered && (last is null || Vector2.DistanceSquared(last.Position, getCursorPosition()) > 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void placeNewControlPoint()
|
private void placeNewControlPoint()
|
||||||
|
54
osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs
Normal file
54
osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Components;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class GenerateToolboxGroup : EditorToolboxGroup
|
||||||
|
{
|
||||||
|
private readonly EditorToolButton polygonButton;
|
||||||
|
|
||||||
|
public GenerateToolboxGroup()
|
||||||
|
: base("Generate")
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
polygonButton = new EditorToolButton("Polygon",
|
||||||
|
() => new SpriteIcon { Icon = FontAwesome.Solid.Spinner },
|
||||||
|
() => new PolygonGenerationPopover()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Repeat) return false;
|
||||||
|
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case Key.D:
|
||||||
|
if (!e.ControlPressed || !e.ShiftPressed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
polygonButton.TriggerClick();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -65,13 +65,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private Bindable<HitObject> placementObject;
|
private Bindable<HitObject> placementObject;
|
||||||
|
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IDistanceSnapProvider))]
|
||||||
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
|
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
protected readonly FreehandSliderToolboxGroup FreehandSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -110,7 +110,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
||||||
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
|
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
|
||||||
},
|
},
|
||||||
FreehandlSliderToolboxGroup
|
new GenerateToolboxGroup(),
|
||||||
|
FreehandSliderToolboxGroup
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
193
osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs
Normal file
193
osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class PolygonGenerationPopover : OsuPopover
|
||||||
|
{
|
||||||
|
private SliderWithTextBoxInput<double> distanceSnapInput = null!;
|
||||||
|
private SliderWithTextBoxInput<int> offsetAngleInput = null!;
|
||||||
|
private SliderWithTextBoxInput<int> repeatCountInput = null!;
|
||||||
|
private SliderWithTextBoxInput<int> pointInput = null!;
|
||||||
|
private RoundedButton commitButton = null!;
|
||||||
|
|
||||||
|
private readonly List<HitCircle> insertedCircles = new List<HitCircle>();
|
||||||
|
private bool began;
|
||||||
|
private bool committed;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock editorClock { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IEditorChangeHandler? changeHandler { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private HitObjectComposer composer { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Width = 220,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(20),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
distanceSnapInput = new SliderWithTextBoxInput<double>("Distance snap:")
|
||||||
|
{
|
||||||
|
Current = new BindableNumber<double>(1)
|
||||||
|
{
|
||||||
|
MinValue = 0.1,
|
||||||
|
MaxValue = 6,
|
||||||
|
Precision = 0.1,
|
||||||
|
Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
|
||||||
|
},
|
||||||
|
Instantaneous = true
|
||||||
|
},
|
||||||
|
offsetAngleInput = new SliderWithTextBoxInput<int>("Offset angle:")
|
||||||
|
{
|
||||||
|
Current = new BindableNumber<int>
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 180,
|
||||||
|
Precision = 1
|
||||||
|
},
|
||||||
|
Instantaneous = true
|
||||||
|
},
|
||||||
|
repeatCountInput = new SliderWithTextBoxInput<int>("Repeats:")
|
||||||
|
{
|
||||||
|
Current = new BindableNumber<int>(1)
|
||||||
|
{
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 1
|
||||||
|
},
|
||||||
|
Instantaneous = true
|
||||||
|
},
|
||||||
|
pointInput = new SliderWithTextBoxInput<int>("Vertices:")
|
||||||
|
{
|
||||||
|
Current = new BindableNumber<int>(3)
|
||||||
|
{
|
||||||
|
MinValue = 3,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 1,
|
||||||
|
},
|
||||||
|
Instantaneous = true
|
||||||
|
},
|
||||||
|
commitButton = new RoundedButton
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Text = "Create",
|
||||||
|
Action = commit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
began = true;
|
||||||
|
|
||||||
|
distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon());
|
||||||
|
offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon());
|
||||||
|
repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon());
|
||||||
|
pointInput.Current.BindValueChanged(_ => tryCreatePolygon());
|
||||||
|
tryCreatePolygon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryCreatePolygon()
|
||||||
|
{
|
||||||
|
double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime);
|
||||||
|
TimingControlPoint timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(startTime);
|
||||||
|
double timeSpacing = timingPoint.BeatLength / editorBeatmap.BeatDivisor;
|
||||||
|
IHasSliderVelocity lastWithSliderVelocity = editorBeatmap.HitObjects.Where(ho => ho.GetEndTime() <= startTime).OfType<IHasSliderVelocity>().LastOrDefault() ?? new Slider();
|
||||||
|
double velocity = OsuHitObject.BASE_SCORING_DISTANCE * editorBeatmap.Difficulty.SliderMultiplier
|
||||||
|
/ LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(lastWithSliderVelocity, timingPoint, OsuRuleset.SHORT_NAME);
|
||||||
|
double length = distanceSnapInput.Current.Value * velocity * timeSpacing;
|
||||||
|
float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value)));
|
||||||
|
|
||||||
|
editorBeatmap.RemoveRange(insertedCircles);
|
||||||
|
insertedCircles.Clear();
|
||||||
|
|
||||||
|
var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler;
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i)
|
||||||
|
{
|
||||||
|
float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value);
|
||||||
|
var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle));
|
||||||
|
|
||||||
|
var circle = new HitCircle
|
||||||
|
{
|
||||||
|
Position = position,
|
||||||
|
StartTime = startTime,
|
||||||
|
NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True,
|
||||||
|
};
|
||||||
|
// TODO: probably ensure samples also follow current ternary status (not trivial)
|
||||||
|
circle.Samples.Add(circle.CreateHitSampleInfo());
|
||||||
|
|
||||||
|
if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y)
|
||||||
|
{
|
||||||
|
commitButton.Enabled.Value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertedCircles.Add(circle);
|
||||||
|
startTime = beatSnapProvider.SnapTime(startTime + timeSpacing);
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
editorBeatmap.AddRange(insertedCircles);
|
||||||
|
commitButton.Enabled.Value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commit()
|
||||||
|
{
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
committed = true;
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
base.PopOut();
|
||||||
|
|
||||||
|
if (began && !committed)
|
||||||
|
{
|
||||||
|
editorBeatmap.RemoveRange(insertedCircles);
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -50,9 +50,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
// Skin has configuration.
|
// Skin has configuration.
|
||||||
if (base.GetDrawableComponent(lookup) is Drawable d)
|
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||||
return d;
|
return d;
|
||||||
|
|
||||||
|
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||||
|
if (!IsProvidingLegacyResources)
|
||||||
|
return null;
|
||||||
|
|
||||||
// Our own ruleset components default.
|
// Our own ruleset components default.
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Target)
|
||||||
{
|
{
|
||||||
@ -70,10 +74,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
// 340px is the default height inherit from stable
|
// 340px is the default height inherit from stable
|
||||||
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
|
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var combo = container.OfType<LegacyDefaultComboCounter>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (combo != null)
|
||||||
|
{
|
||||||
|
combo.Anchor = Anchor.BottomLeft;
|
||||||
|
combo.Origin = Anchor.BottomLeft;
|
||||||
|
combo.Scale = new Vector2(1.28f);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new LegacyDefaultComboCounter(),
|
||||||
new LegacyKeyCounterDisplay(),
|
new LegacyKeyCounterDisplay(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -85,7 +99,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
switch (osuComponent.Component)
|
switch (osuComponent.Component)
|
||||||
{
|
{
|
||||||
case OsuSkinComponents.FollowPoint:
|
case OsuSkinComponents.FollowPoint:
|
||||||
return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS));
|
return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false,
|
||||||
|
maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS));
|
||||||
|
|
||||||
case OsuSkinComponents.SliderScorePoint:
|
case OsuSkinComponents.SliderScorePoint:
|
||||||
return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS);
|
return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS);
|
||||||
|
@ -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 })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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/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:
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -107,7 +107,7 @@ 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.Special1));
|
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 space", () => InputManager.PressKey(Key.Space));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
@ -174,7 +174,7 @@ 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.Special1));
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key4));
|
||||||
|
|
||||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
AddStep("pause", () => Player.Pause());
|
AddStep("pause", () => Player.Pause());
|
||||||
@ -237,7 +237,7 @@ 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.Special1));
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key4));
|
||||||
|
|
||||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
checkKey(() => counter, 1, true);
|
checkKey(() => counter, 1, true);
|
||||||
|
@ -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 LegacyDefaultComboCounter
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(2f),
|
||||||
|
}));
|
||||||
|
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||||
|
|
||||||
|
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||||
|
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||||
|
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||||
|
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||||
|
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Skin importSkinFromArchives(string filename)
|
||||||
|
{
|
||||||
|
var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||||
|
return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
|
||||||
|
}
|
||||||
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
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;
|
||||||
@ -19,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected override Drawable CreateArgonImplementation() => new ArgonComboCounter();
|
protected override Drawable CreateArgonImplementation() => new ArgonComboCounter();
|
||||||
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
|
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
|
||||||
protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter();
|
protected override Drawable CreateLegacyImplementation() => new LegacyDefaultComboCounter();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestComboCounterIncrementing()
|
public void TestComboCounterIncrementing()
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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", () =>
|
||||||
{
|
{
|
||||||
@ -271,10 +275,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||||
|
|
||||||
AddStep("delete beatmap files", () =>
|
AddStep("delete 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"))
|
||||||
Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath()));
|
Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath()));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
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,
|
||||||
|
@ -11,6 +11,7 @@ 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
|
||||||
@ -192,13 +193,26 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
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,
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
|
ProfileHue = hue2,
|
||||||
|
PlayMode = "osu",
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("show user different ruleset", () => profile.ShowUser(new APIUser { Id = 2 }, new TaikoRuleset().RulesetInfo));
|
||||||
|
AddWaitStep("wait some", 3);
|
||||||
|
|
||||||
|
AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser
|
||||||
|
{
|
||||||
|
Username = $"Colorful #{hue2}",
|
||||||
|
Id = 2,
|
||||||
CountryCode = CountryCode.JP,
|
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,
|
||||||
|
@ -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]
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||||
|
{
|
||||||
|
public abstract partial class SongSelectComponentsTestScene : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; } = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
};
|
||||||
|
|
||||||
|
private Container? resizeContainer;
|
||||||
|
private float relativeWidth;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
base.Content.Child = resizeContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
Width = relativeWidth,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourProvider.Background5,
|
||||||
|
},
|
||||||
|
Content
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AddSliderStep("change relative width", 0, 1f, 1f, v =>
|
||||||
|
{
|
||||||
|
if (resizeContainer != null)
|
||||||
|
resizeContainer.Width = v;
|
||||||
|
|
||||||
|
relativeWidth = v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public virtual void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("reset dependencies", () =>
|
||||||
|
{
|
||||||
|
Beatmap.SetDefault();
|
||||||
|
SelectedMods.SetDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,9 @@ using osu.Game.Rulesets.Objects.Types;
|
|||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||||
{
|
{
|
||||||
[TestFixture]
|
public partial class TestSceneBeatmapInfoWedge : SongSelectComponentsTestScene
|
||||||
public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene
|
|
||||||
{
|
{
|
||||||
private RulesetStore rulesets = null!;
|
private RulesetStore rulesets = null!;
|
||||||
private TestBeatmapInfoWedgeV2 infoWedge = null!;
|
private TestBeatmapInfoWedgeV2 infoWedge = null!;
|
||||||
@ -33,6 +32,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -107,12 +113,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
|
AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
|
||||||
public void SetUpSteps()
|
|
||||||
{
|
|
||||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestTruncation()
|
public void TestTruncation()
|
||||||
{
|
{
|
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Screens.SelectV2.Wedge;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||||
|
{
|
||||||
|
public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestScene
|
||||||
|
{
|
||||||
|
private DifficultyNameContent? difficultyNameContent;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocalBeatmap()
|
||||||
|
{
|
||||||
|
AddStep("set component", () => Child = difficultyNameContent = new LocalDifficultyNameContent());
|
||||||
|
|
||||||
|
AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<TruncatingSpriteText>().Single().Text));
|
||||||
|
AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<OsuHoverContainer>().Single().ChildrenOfType<OsuSpriteText>().Single().Text));
|
||||||
|
|
||||||
|
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
DifficultyName = "really long difficulty name that gets truncated",
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Author = { Username = "really long username that is autosized" },
|
||||||
|
},
|
||||||
|
OnlineID = 1,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<TruncatingSpriteText>().Single().Text));
|
||||||
|
AddAssert("author is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<OsuHoverContainer>().Single().ChildrenOfType<OsuSpriteText>().Single().Text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
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.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -24,9 +23,9 @@ using osu.Game.Tests.Resources;
|
|||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||||
{
|
{
|
||||||
public partial class TestSceneLeaderboardScoreV2 : OsuTestScene
|
public partial class TestSceneLeaderboardScore : SongSelectComponentsTestScene
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
@ -36,19 +35,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private FillFlowContainer? fillFlow;
|
private FillFlowContainer? fillFlow;
|
||||||
private OsuSpriteText? drawWidthText;
|
private OsuSpriteText? drawWidthText;
|
||||||
private float relativeWidth;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
// TODO: invalidation seems to be one-off when clicking slider to a certain value, so drag for now
|
|
||||||
// doesn't seem to happen in-game (when toggling window mode)
|
|
||||||
AddSliderStep("change relative width", 0, 1f, 0.6f, v =>
|
|
||||||
{
|
|
||||||
relativeWidth = v;
|
|
||||||
if (fillFlow != null) fillFlow.Width = v;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSheared()
|
public void TestSheared()
|
||||||
@ -59,7 +45,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
fillFlow = new FillFlowContainer
|
fillFlow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = relativeWidth,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -94,7 +79,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
fillFlow = new FillFlowContainer
|
fillFlow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = relativeWidth,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -118,8 +102,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
public override void SetUpSteps()
|
||||||
public void SetUpSteps()
|
|
||||||
{
|
{
|
||||||
AddToggleStep("toggle scoring mode", v => config.SetValue(OsuSetting.ScoreDisplayMode, v ? ScoringMode.Classic : ScoringMode.Standardised));
|
AddToggleStep("toggle scoring mode", v => config.SetValue(OsuSetting.ScoreDisplayMode, v ? ScoringMode.Classic : ScoringMode.Standardised));
|
||||||
}
|
}
|
@ -17,13 +17,12 @@ using osu.Game.Rulesets.Osu.Mods;
|
|||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Footer;
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.SelectV2;
|
|
||||||
using osu.Game.Screens.SelectV2.Footer;
|
using osu.Game.Screens.SelectV2.Footer;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||||
{
|
{
|
||||||
public partial class TestSceneSongSelectV2 : ScreenTestScene
|
public partial class TestSceneSongSelect : ScreenTestScene
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly ScreenFooter screenScreenFooter;
|
private readonly ScreenFooter screenScreenFooter;
|
||||||
@ -31,7 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OsuLogo logo;
|
private readonly OsuLogo logo;
|
||||||
|
|
||||||
public TestSceneSongSelectV2()
|
public TestSceneSongSelect()
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -63,8 +62,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
|
|
||||||
AddStep("load screen", () => Stack.Push(new SongSelectV2()));
|
AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SongSelectV2()));
|
||||||
AddUntilStep("wait for load", () => Stack.CurrentScreen is SongSelectV2 songSelect && songSelect.IsLoaded);
|
AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelectV2 songSelect && songSelect.IsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Footer
|
#region Footer
|
@ -5,19 +5,18 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.SelectV2;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||||
{
|
{
|
||||||
public partial class TestSceneSongSelectV2Navigation : OsuGameTestScene
|
public partial class TestSceneSongSelectNavigation : OsuGameTestScene
|
||||||
{
|
{
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||||
AddWaitStep("wait", 5);
|
AddWaitStep("wait", 5);
|
||||||
PushAndConfirm(() => new SongSelectV2());
|
PushAndConfirm(() => new Screens.SelectV2.SongSelectV2());
|
||||||
}
|
}
|
||||||
|
|
||||||
[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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
protected override Drawable IdleContent => idleBottomContent;
|
protected override Drawable IdleContent => idleBottomContent;
|
||||||
protected override Drawable DownloadInProgressContent => downloadProgressBar;
|
protected override Drawable DownloadInProgressContent => downloadProgressBar;
|
||||||
|
|
||||||
private const float height = 100;
|
public const float HEIGHT = 100;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly BeatmapCardContent content;
|
private readonly BeatmapCardContent content;
|
||||||
@ -42,14 +42,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
public BeatmapCardNormal(APIBeatmapSet beatmapSet, bool allowExpansion = true)
|
public BeatmapCardNormal(APIBeatmapSet beatmapSet, bool allowExpansion = true)
|
||||||
: base(beatmapSet, allowExpansion)
|
: base(beatmapSet, allowExpansion)
|
||||||
{
|
{
|
||||||
content = new BeatmapCardContent(height);
|
content = new BeatmapCardContent(HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Width = WIDTH;
|
Width = WIDTH;
|
||||||
Height = height;
|
Height = HEIGHT;
|
||||||
|
|
||||||
FillFlowContainer leftIconArea = null!;
|
FillFlowContainer leftIconArea = null!;
|
||||||
FillFlowContainer titleBadgeArea = null!;
|
FillFlowContainer titleBadgeArea = null!;
|
||||||
@ -65,7 +65,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet)
|
thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet)
|
||||||
{
|
{
|
||||||
Name = @"Left (icon) area",
|
Name = @"Left (icon) area",
|
||||||
Size = new Vector2(height),
|
Size = new Vector2(HEIGHT),
|
||||||
Padding = new MarginPadding { Right = CORNER_RADIUS },
|
Padding = new MarginPadding { Right = CORNER_RADIUS },
|
||||||
Child = leftIconArea = new FillFlowContainer
|
Child = leftIconArea = new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -77,8 +77,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
},
|
},
|
||||||
buttonContainer = new CollapsibleButtonContainer(BeatmapSet)
|
buttonContainer = new CollapsibleButtonContainer(BeatmapSet)
|
||||||
{
|
{
|
||||||
X = height - CORNER_RADIUS,
|
X = HEIGHT - CORNER_RADIUS,
|
||||||
Width = WIDTH - height + CORNER_RADIUS,
|
Width = WIDTH - HEIGHT + CORNER_RADIUS,
|
||||||
FavouriteState = { BindTarget = FavouriteState },
|
FavouriteState = { BindTarget = FavouriteState },
|
||||||
ButtonsCollapsedWidth = CORNER_RADIUS,
|
ButtonsCollapsedWidth = CORNER_RADIUS,
|
||||||
ButtonsExpandedWidth = 30,
|
ButtonsExpandedWidth = 30,
|
||||||
|
@ -90,10 +90,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
progress.Progress = playButton.Progress.Value;
|
|
||||||
|
|
||||||
playButton.Scale = new Vector2(DrawWidth / 100);
|
progress.Progress = playButton.Progress.Value;
|
||||||
progress.Size = new Vector2(50 * DrawWidth / 100);
|
progress.Size = new Vector2(50 * playButton.DrawWidth / (BeatmapCardNormal.HEIGHT - BeatmapCard.CORNER_RADIUS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
|
@ -79,6 +79,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
|
icon.Scale = new Vector2(DrawWidth / (BeatmapCardNormal.HEIGHT - BeatmapCard.CORNER_RADIUS));
|
||||||
|
|
||||||
if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded)
|
if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded)
|
||||||
progress.Value = previewTrack.CurrentTime / previewTrack.Length;
|
progress.Value = previewTrack.CurrentTime / previewTrack.Length;
|
||||||
else
|
else
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,15 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Development;
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer.Countdown;
|
using osu.Game.Online.Multiplayer.Countdown;
|
||||||
@ -22,7 +23,6 @@ using osu.Game.Overlays.Notifications;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osu.Game.Localisation;
|
|
||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer
|
namespace osu.Game.Online.Multiplayer
|
||||||
{
|
{
|
||||||
@ -777,12 +777,22 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
|
Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
|
||||||
|
|
||||||
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
|
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
|
||||||
|
|
||||||
APIRoom.Playlist.RemoveAt(existingIndex);
|
APIRoom.Playlist.RemoveAt(existingIndex);
|
||||||
APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item));
|
APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item));
|
||||||
}
|
}
|
||||||
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.
|
||||||
|
StringBuilder exceptionText = new StringBuilder();
|
||||||
|
|
||||||
|
exceptionText.AppendLine("MultiplayerClient test failure investigation");
|
||||||
|
exceptionText.AppendLine($"Exception : {ex.ToString()}");
|
||||||
|
exceptionText.AppendLine($"Lookup : {item.ID}");
|
||||||
|
exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}");
|
||||||
|
exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}");
|
||||||
|
|
||||||
|
throw new AggregateException(exceptionText.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemChanged?.Invoke(item);
|
ItemChanged?.Invoke(item);
|
||||||
|
@ -60,6 +60,8 @@ namespace osu.Game.Overlays
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private RealmAccess realm { get; set; } = null!;
|
private RealmAccess realm { get; set; } = null!;
|
||||||
|
|
||||||
|
private BindableNumber<double> sampleVolume = null!;
|
||||||
|
|
||||||
private readonly BindableDouble audioDuckVolume = new BindableDouble(1);
|
private readonly BindableDouble audioDuckVolume = new BindableDouble(1);
|
||||||
|
|
||||||
private AudioFilter audioDuckFilter = null!;
|
private AudioFilter audioDuckFilter = null!;
|
||||||
@ -69,6 +71,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer));
|
AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer));
|
||||||
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume);
|
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume);
|
||||||
|
sampleVolume = audio.VolumeSample.GetBoundCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -269,6 +272,10 @@ namespace osu.Game.Overlays
|
|||||||
/// <returns>A <see cref="IDisposable"/> which will restore the duck operation when disposed.</returns>
|
/// <returns>A <see cref="IDisposable"/> which will restore the duck operation when disposed.</returns>
|
||||||
public IDisposable Duck(DuckParameters? parameters = null)
|
public IDisposable Duck(DuckParameters? parameters = null)
|
||||||
{
|
{
|
||||||
|
// Don't duck if samples have no volume, it sounds weird.
|
||||||
|
if (sampleVolume.Value == 0)
|
||||||
|
return new InvokeOnDisposal(() => { });
|
||||||
|
|
||||||
parameters ??= new DuckParameters();
|
parameters ??= new DuckParameters();
|
||||||
|
|
||||||
duckOperations.Add(parameters);
|
duckOperations.Add(parameters);
|
||||||
@ -302,6 +309,10 @@ namespace osu.Game.Overlays
|
|||||||
/// <param name="parameters">Parameters defining the ducking operation.</param>
|
/// <param name="parameters">Parameters defining the ducking operation.</param>
|
||||||
public void DuckMomentarily(double delayUntilRestore, DuckParameters? parameters = null)
|
public void DuckMomentarily(double delayUntilRestore, DuckParameters? parameters = null)
|
||||||
{
|
{
|
||||||
|
// Don't duck if samples have no volume, it sounds weird.
|
||||||
|
if (sampleVolume.Value == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
parameters ??= new DuckParameters();
|
parameters ??= new DuckParameters();
|
||||||
|
|
||||||
IDisposable duckOperation = Duck(parameters);
|
IDisposable duckOperation = Duck(parameters);
|
||||||
|
@ -12,8 +12,8 @@ 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.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
{
|
{
|
||||||
@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
|
|
||||||
APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics;
|
APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics;
|
||||||
|
|
||||||
dailyPlayCount.Text = UsersStrings.ShowDailyChallengeUnitDay(stats.PlayCount.ToLocalisableString("N0"));
|
dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0"));
|
||||||
dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount));
|
dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount));
|
||||||
|
|
||||||
TooltipContent = new DailyChallengeTooltipData(colourProvider, stats);
|
TooltipContent = new DailyChallengeTooltipData(colourProvider, stats);
|
||||||
|
@ -9,15 +9,16 @@ using osu.Framework.Graphics.Colour;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using Box = osu.Framework.Graphics.Shapes.Box;
|
using osuTK.Graphics;
|
||||||
using Color4 = osuTK.Graphics.Color4;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
{
|
{
|
||||||
@ -112,16 +113,16 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
background.Colour = colourProvider.Background4;
|
background.Colour = colourProvider.Background4;
|
||||||
topBackground.Colour = colourProvider.Background5;
|
topBackground.Colour = colourProvider.Background5;
|
||||||
|
|
||||||
currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0"));
|
currentDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0"));
|
||||||
currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent));
|
currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent));
|
||||||
|
|
||||||
currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0"));
|
currentWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0"));
|
||||||
currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent));
|
currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent));
|
||||||
|
|
||||||
bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0"));
|
bestDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0"));
|
||||||
bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest));
|
bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest));
|
||||||
|
|
||||||
bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0"));
|
bestWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0"));
|
||||||
bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest));
|
bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest));
|
||||||
|
|
||||||
topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0");
|
topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0");
|
||||||
|
@ -96,7 +96,8 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
Debug.Assert(user != null);
|
Debug.Assert(user != null);
|
||||||
|
|
||||||
if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true)
|
bool sameUser = user.OnlineID == Header.User.Value?.User.Id;
|
||||||
|
if (sameUser && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (sectionsContainer != null)
|
if (sectionsContainer != null)
|
||||||
@ -118,7 +119,9 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
: Array.Empty<ProfileSection>();
|
: Array.Empty<ProfileSection>();
|
||||||
|
|
||||||
|
if (!sameUser)
|
||||||
changeOverlayColours(OverlayColourScheme.Pink.GetHue());
|
changeOverlayColours(OverlayColourScheme.Pink.GetHue());
|
||||||
|
|
||||||
recreateBaseContent();
|
recreateBaseContent();
|
||||||
|
|
||||||
if (API.State.Value != APIState.Offline)
|
if (API.State.Value != APIState.Offline)
|
||||||
|
54
osu.Game/Overlays/Volume/MasterVolumeMeter.cs
Normal file
54
osu.Game/Overlays/Volume/MasterVolumeMeter.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Volume
|
||||||
|
{
|
||||||
|
public partial class MasterVolumeMeter : VolumeMeter
|
||||||
|
{
|
||||||
|
private MuteButton muteButton = null!;
|
||||||
|
|
||||||
|
public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
|
private readonly BindableDouble muteAdjustment = new BindableDouble();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private VolumeOverlay volumeOverlay { get; set; } = null!;
|
||||||
|
|
||||||
|
public MasterVolumeMeter(string name, float circleSize, Color4 meterColour)
|
||||||
|
: base(name, circleSize, meterColour)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio)
|
||||||
|
{
|
||||||
|
IsMuted.BindValueChanged(muted =>
|
||||||
|
{
|
||||||
|
if (muted.NewValue)
|
||||||
|
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||||
|
else
|
||||||
|
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||||
|
});
|
||||||
|
|
||||||
|
Add(muteButton = new MuteButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
X = CircleSize / 2,
|
||||||
|
Y = CircleSize * 0.23f,
|
||||||
|
Current = { BindTarget = IsMuted }
|
||||||
|
});
|
||||||
|
|
||||||
|
muteButton.Current.ValueChanged += _ => volumeOverlay.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleMute() => muteButton.Current.Value = !muteButton.Current.Value;
|
||||||
|
}
|
||||||
|
}
|
@ -7,13 +7,13 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Volume
|
namespace osu.Game.Overlays.Volume
|
||||||
{
|
{
|
||||||
@ -33,18 +33,18 @@ namespace osu.Game.Overlays.Volume
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 hoveredColour, unhoveredColour;
|
private ColourInfo hoveredBorderColour;
|
||||||
|
private ColourInfo unhoveredBorderColour;
|
||||||
private const float width = 100;
|
private CompositeDrawable border = null!;
|
||||||
public const float HEIGHT = 35;
|
|
||||||
|
|
||||||
public MuteButton()
|
public MuteButton()
|
||||||
{
|
{
|
||||||
Content.BorderThickness = 3;
|
const float width = 30;
|
||||||
Content.CornerRadius = HEIGHT / 2;
|
const float height = 30;
|
||||||
Content.CornerExponent = 2;
|
|
||||||
|
|
||||||
Size = new Vector2(width, HEIGHT);
|
Size = new Vector2(width, height);
|
||||||
|
Content.CornerRadius = height / 2;
|
||||||
|
Content.CornerExponent = 2;
|
||||||
|
|
||||||
Action = () => Current.Value = !Current.Value;
|
Action = () => Current.Value = !Current.Value;
|
||||||
}
|
}
|
||||||
@ -52,10 +52,9 @@ namespace osu.Game.Overlays.Volume
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
hoveredColour = colours.YellowDark;
|
|
||||||
|
|
||||||
Content.BorderColour = unhoveredColour = colours.Gray1;
|
|
||||||
BackgroundColour = colours.Gray1;
|
BackgroundColour = colours.Gray1;
|
||||||
|
hoveredBorderColour = colours.PinkLight;
|
||||||
|
unhoveredBorderColour = colours.Gray1;
|
||||||
|
|
||||||
SpriteIcon icon;
|
SpriteIcon icon;
|
||||||
|
|
||||||
@ -65,26 +64,39 @@ namespace osu.Game.Overlays.Volume
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
border = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 3,
|
||||||
|
BorderColour = unhoveredBorderColour,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Current.BindValueChanged(muted =>
|
Current.BindValueChanged(muted =>
|
||||||
{
|
{
|
||||||
icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp;
|
icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp;
|
||||||
icon.Size = new Vector2(muted.NewValue ? 18 : 20);
|
icon.Size = new Vector2(muted.NewValue ? 12 : 16);
|
||||||
icon.Margin = new MarginPadding { Right = muted.NewValue ? 2 : 0 };
|
icon.Margin = new MarginPadding { Right = muted.NewValue ? 2 : 0 };
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
Content.TransformTo<Container<Drawable>, ColourInfo>("BorderColour", hoveredColour, 500, Easing.OutQuint);
|
border.TransformTo(nameof(BorderColour), hoveredBorderColour, 500, Easing.OutQuint);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
Content.TransformTo<Container<Drawable>, ColourInfo>("BorderColour", unhoveredColour, 500, Easing.OutQuint);
|
border.TransformTo(nameof(BorderColour), unhoveredBorderColour, 500, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
@ -35,8 +35,12 @@ namespace osu.Game.Overlays.Volume
|
|||||||
private CircularProgress volumeCircle;
|
private CircularProgress volumeCircle;
|
||||||
private CircularProgress volumeCircleGlow;
|
private CircularProgress volumeCircleGlow;
|
||||||
|
|
||||||
|
protected static readonly Vector2 LABEL_SIZE = new Vector2(120, 20);
|
||||||
|
|
||||||
public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 };
|
public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 };
|
||||||
private readonly float circleSize;
|
|
||||||
|
protected readonly float CircleSize;
|
||||||
|
|
||||||
private readonly Color4 meterColour;
|
private readonly Color4 meterColour;
|
||||||
private readonly string name;
|
private readonly string name;
|
||||||
|
|
||||||
@ -73,7 +77,7 @@ namespace osu.Game.Overlays.Volume
|
|||||||
|
|
||||||
public VolumeMeter(string name, float circleSize, Color4 meterColour)
|
public VolumeMeter(string name, float circleSize, Color4 meterColour)
|
||||||
{
|
{
|
||||||
this.circleSize = circleSize;
|
CircleSize = circleSize;
|
||||||
this.meterColour = meterColour;
|
this.meterColour = meterColour;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
@ -101,7 +105,7 @@ namespace osu.Game.Overlays.Volume
|
|||||||
{
|
{
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
Size = new Vector2(circleSize),
|
Size = new Vector2(CircleSize),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new BufferedContainer
|
new BufferedContainer
|
||||||
@ -199,7 +203,7 @@ namespace osu.Game.Overlays.Volume
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.Numeric.With(size: 0.16f * circleSize)
|
Font = OsuFont.Numeric.With(size: 0.16f * CircleSize)
|
||||||
}).WithEffect(new GlowEffect
|
}).WithEffect(new GlowEffect
|
||||||
{
|
{
|
||||||
Colour = Color4.Transparent,
|
Colour = Color4.Transparent,
|
||||||
@ -209,10 +213,10 @@ namespace osu.Game.Overlays.Volume
|
|||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
Size = new Vector2(120, 20),
|
Size = LABEL_SIZE,
|
||||||
CornerRadius = 10,
|
CornerRadius = 10,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Margin = new MarginPadding { Left = circleSize + 10 },
|
Margin = new MarginPadding { Left = CircleSize + 10 },
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -20,21 +21,19 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public partial class VolumeOverlay : VisibilityContainer
|
public partial class VolumeOverlay : VisibilityContainer
|
||||||
{
|
{
|
||||||
|
public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
private const float offset = 10;
|
private const float offset = 10;
|
||||||
|
|
||||||
private VolumeMeter volumeMeterMaster = null!;
|
private VolumeMeter volumeMeterMaster = null!;
|
||||||
private VolumeMeter volumeMeterEffect = null!;
|
private VolumeMeter volumeMeterEffect = null!;
|
||||||
private VolumeMeter volumeMeterMusic = null!;
|
private VolumeMeter volumeMeterMusic = null!;
|
||||||
private MuteButton muteButton = null!;
|
|
||||||
|
|
||||||
private SelectionCycleFillFlowContainer<VolumeMeter> volumeMeters = null!;
|
private SelectionCycleFillFlowContainer<VolumeMeter> volumeMeters = null!;
|
||||||
|
|
||||||
private readonly BindableDouble muteAdjustment = new BindableDouble();
|
|
||||||
|
|
||||||
public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, OsuColour colours)
|
private void load(AudioManager audio, OsuColour colours)
|
||||||
{
|
{
|
||||||
@ -49,14 +48,7 @@ namespace osu.Game.Overlays
|
|||||||
Width = 300,
|
Width = 300,
|
||||||
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0))
|
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0))
|
||||||
},
|
},
|
||||||
muteButton = new MuteButton
|
new FillFlowContainer
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
Margin = new MarginPadding(10),
|
|
||||||
Current = { BindTarget = IsMuted }
|
|
||||||
},
|
|
||||||
volumeMeters = new SelectionCycleFillFlowContainer<VolumeMeter>
|
|
||||||
{
|
{
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
@ -64,26 +56,29 @@ namespace osu.Game.Overlays
|
|||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Spacing = new Vector2(0, offset),
|
Spacing = new Vector2(0, offset),
|
||||||
Margin = new MarginPadding { Left = offset },
|
Margin = new MarginPadding { Left = offset },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
volumeMeters = new SelectionCycleFillFlowContainer<VolumeMeter>
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Spacing = new Vector2(0, offset),
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker),
|
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker),
|
||||||
volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker),
|
volumeMeterMaster = new MasterVolumeMeter("MASTER", 150, colours.PinkDarker) { IsMuted = { BindTarget = IsMuted }, },
|
||||||
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
|
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
volumeMeterMaster.Bindable.BindTo(audio.Volume);
|
volumeMeterMaster.Bindable.BindTo(audio.Volume);
|
||||||
volumeMeterEffect.Bindable.BindTo(audio.VolumeSample);
|
volumeMeterEffect.Bindable.BindTo(audio.VolumeSample);
|
||||||
volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack);
|
volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack);
|
||||||
|
|
||||||
IsMuted.BindValueChanged(muted =>
|
|
||||||
{
|
|
||||||
if (muted.NewValue)
|
|
||||||
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
|
||||||
else
|
|
||||||
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -92,8 +87,6 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
foreach (var volumeMeter in volumeMeters)
|
foreach (var volumeMeter in volumeMeters)
|
||||||
volumeMeter.Bindable.ValueChanged += _ => Show();
|
volumeMeter.Bindable.ValueChanged += _ => Show();
|
||||||
|
|
||||||
muteButton.Current.ValueChanged += _ => Show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false)
|
public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false)
|
||||||
@ -130,7 +123,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
case GlobalAction.ToggleMute:
|
case GlobalAction.ToggleMute:
|
||||||
Show();
|
Show();
|
||||||
muteButton.Current.Value = !muteButton.Current.Value;
|
volumeMeters.OfType<MasterVolumeMeter>().First().ToggleMute();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -10,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Localisation;
|
||||||
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;
|
||||||
@ -24,25 +23,37 @@ namespace osu.Game.Overlays
|
|||||||
public string CurrentPath => path.Value;
|
public string CurrentPath => path.Value;
|
||||||
|
|
||||||
private readonly Bindable<string> path = new Bindable<string>(INDEX_PATH);
|
private readonly Bindable<string> path = new Bindable<string>(INDEX_PATH);
|
||||||
|
private readonly Bindable<APIWikiPage?> wikiData = new Bindable<APIWikiPage?>();
|
||||||
private readonly Bindable<APIWikiPage> wikiData = new Bindable<APIWikiPage>();
|
private readonly IBindable<Language> language = new Bindable<Language>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
private GetWikiRequest request;
|
[Resolved]
|
||||||
|
private OsuGameBase game { get; set; } = null!;
|
||||||
|
|
||||||
private CancellationTokenSource cancellationToken;
|
private GetWikiRequest? request;
|
||||||
|
private CancellationTokenSource? cancellationToken;
|
||||||
|
private WikiArticlePage? articlePage;
|
||||||
|
|
||||||
private bool displayUpdateRequired = true;
|
private bool displayUpdateRequired = true;
|
||||||
|
|
||||||
private WikiArticlePage articlePage;
|
|
||||||
|
|
||||||
public WikiOverlay()
|
public WikiOverlay()
|
||||||
: base(OverlayColourScheme.Orange, false)
|
: base(OverlayColourScheme.Orange, false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
path.BindValueChanged(onPathChanged);
|
||||||
|
wikiData.BindTo(Header.WikiPageData);
|
||||||
|
|
||||||
|
language.BindTo(game.CurrentLanguage);
|
||||||
|
language.BindValueChanged(onLangChanged);
|
||||||
|
}
|
||||||
|
|
||||||
public void ShowPage(string pagePath = INDEX_PATH)
|
public void ShowPage(string pagePath = INDEX_PATH)
|
||||||
{
|
{
|
||||||
path.Value = pagePath.Trim('/');
|
path.Value = pagePath.Trim('/');
|
||||||
@ -55,13 +66,6 @@ namespace osu.Game.Overlays
|
|||||||
ShowParentPage = showParentPage,
|
ShowParentPage = showParentPage,
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
path.BindValueChanged(onPathChanged);
|
|
||||||
wikiData.BindTo(Header.WikiPageData);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
base.PopIn();
|
base.PopIn();
|
||||||
@ -100,25 +104,18 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPathChanged(ValueChangedEvent<string> e)
|
private void loadPage(string path, Language lang)
|
||||||
{
|
{
|
||||||
// the path could change as a result of redirecting to a newer location of the same page.
|
|
||||||
// we already have the correct wiki data, so we can safely return here.
|
|
||||||
if (e.NewValue == wikiData.Value?.Path)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (e.NewValue == "error")
|
|
||||||
return;
|
|
||||||
|
|
||||||
cancellationToken?.Cancel();
|
cancellationToken?.Cancel();
|
||||||
request?.Cancel();
|
request?.Cancel();
|
||||||
|
|
||||||
string[] values = e.NewValue.Split('/', 2);
|
// Language code + path, or just path1 + path2 in case
|
||||||
|
string[] values = path.Split('/', 2);
|
||||||
|
|
||||||
if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var language))
|
if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var parsedLang))
|
||||||
request = new GetWikiRequest(values[1], language);
|
request = new GetWikiRequest(values[1], parsedLang);
|
||||||
else
|
else
|
||||||
request = new GetWikiRequest(e.NewValue);
|
request = new GetWikiRequest(path, lang);
|
||||||
|
|
||||||
Loading.Show();
|
Loading.Show();
|
||||||
|
|
||||||
@ -132,6 +129,25 @@ namespace osu.Game.Overlays
|
|||||||
api.PerformAsync(request);
|
api.PerformAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onPathChanged(ValueChangedEvent<string> e)
|
||||||
|
{
|
||||||
|
// the path could change as a result of redirecting to a newer location of the same page.
|
||||||
|
// we already have the correct wiki data, so we can safely return here.
|
||||||
|
if (e.NewValue == wikiData.Value?.Path)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (e.NewValue == "error")
|
||||||
|
return;
|
||||||
|
|
||||||
|
loadPage(e.NewValue, language.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLangChanged(ValueChangedEvent<Language> e)
|
||||||
|
{
|
||||||
|
// Path unmodified, just reload the page with new language value.
|
||||||
|
loadPage(path.Value, e.NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
private void onSuccess(APIWikiPage response)
|
private void onSuccess(APIWikiPage response)
|
||||||
{
|
{
|
||||||
wikiData.Value = response;
|
wikiData.Value = response;
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -35,17 +36,6 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
private readonly Lazy<Drawable> proxiedAboveHitObjectsContent;
|
private readonly Lazy<Drawable> proxiedAboveHitObjectsContent;
|
||||||
public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value;
|
public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="result">The judgement to visualise.</param>
|
|
||||||
/// <param name="judgedObject">The object which was judged.</param>
|
|
||||||
public DrawableJudgement(JudgementResult result, DrawableHitObject judgedObject)
|
|
||||||
: this()
|
|
||||||
{
|
|
||||||
Apply(result, judgedObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrawableJudgement()
|
public DrawableJudgement()
|
||||||
{
|
{
|
||||||
Size = new Vector2(judgement_size);
|
Size = new Vector2(judgement_size);
|
||||||
@ -112,6 +102,9 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
{
|
{
|
||||||
base.PrepareForUse();
|
base.PrepareForUse();
|
||||||
|
|
||||||
|
if (!IsInPool)
|
||||||
|
Logger.Log($"{nameof(DrawableJudgement)} for judgement type {Result} was not retrieved from a pool. Consider adding to a JudgementPooler.");
|
||||||
|
|
||||||
Debug.Assert(Result != null);
|
Debug.Assert(Result != null);
|
||||||
|
|
||||||
runAnimation();
|
runAnimation();
|
||||||
@ -119,9 +112,6 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
|
|
||||||
private void runAnimation()
|
private void runAnimation()
|
||||||
{
|
{
|
||||||
// is a no-op if the drawables are already in a correct state.
|
|
||||||
prepareDrawables();
|
|
||||||
|
|
||||||
// undo any transforms applies in ApplyMissAnimations/ApplyHitAnimations to get a sane initial state.
|
// undo any transforms applies in ApplyMissAnimations/ApplyHitAnimations to get a sane initial state.
|
||||||
ApplyTransformsAt(double.MinValue, true);
|
ApplyTransformsAt(double.MinValue, true);
|
||||||
ClearTransforms(true);
|
ClearTransforms(true);
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
protected readonly double Offset;
|
protected readonly double Offset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The beatmap version.
|
/// The .osu format (beatmap) version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly int FormatVersion;
|
protected readonly int FormatVersion;
|
||||||
|
|
||||||
@ -48,7 +48,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
{
|
{
|
||||||
string[] split = text.Split(',');
|
string[] split = text.Split(',');
|
||||||
|
|
||||||
Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));
|
Vector2 pos =
|
||||||
|
FormatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION
|
||||||
|
? new Vector2(Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE))
|
||||||
|
: new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));
|
||||||
|
|
||||||
double startTime = Parsing.ParseDouble(split[2]) + Offset;
|
double startTime = Parsing.ParseDouble(split[2]) + Offset;
|
||||||
|
|
||||||
|
@ -280,8 +280,11 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In mania, mouseX encodes the pressed keys in the lower 20 bits
|
||||||
|
int mouseXParseLimit = currentRuleset.RulesetInfo.OnlineID == 3 ? (1 << 20) - 1 : Parsing.MAX_COORDINATE_VALUE;
|
||||||
|
|
||||||
float diff = Parsing.ParseFloat(split[0]);
|
float diff = Parsing.ParseFloat(split[0]);
|
||||||
float mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE);
|
float mouseX = Parsing.ParseFloat(split[1], mouseXParseLimit);
|
||||||
float mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE);
|
float mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE);
|
||||||
|
|
||||||
lastTime += diff;
|
lastTime += diff;
|
||||||
|
@ -17,13 +17,12 @@ namespace osu.Game.Screens
|
|||||||
|
|
||||||
private const float x_movement_amount = 50;
|
private const float x_movement_amount = 50;
|
||||||
|
|
||||||
private readonly bool animateOnEnter;
|
|
||||||
|
|
||||||
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
||||||
|
|
||||||
protected BackgroundScreen(bool animateOnEnter = true)
|
public bool AnimateEntry { get; set; } = true;
|
||||||
|
|
||||||
|
protected BackgroundScreen()
|
||||||
{
|
{
|
||||||
this.animateOnEnter = animateOnEnter;
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
}
|
}
|
||||||
@ -53,12 +52,11 @@ namespace osu.Game.Screens
|
|||||||
|
|
||||||
public override void OnEntering(ScreenTransitionEvent e)
|
public override void OnEntering(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
if (animateOnEnter)
|
if (AnimateEntry)
|
||||||
{
|
{
|
||||||
this.FadeOut();
|
this.FadeOut();
|
||||||
this.MoveToX(x_movement_amount);
|
|
||||||
|
|
||||||
this.FadeIn(TRANSITION_LENGTH, Easing.InOutQuart);
|
this.FadeIn(TRANSITION_LENGTH, Easing.InOutQuart);
|
||||||
|
this.MoveToX(x_movement_amount);
|
||||||
this.MoveToX(0, TRANSITION_LENGTH, Easing.InOutQuart);
|
this.MoveToX(0, TRANSITION_LENGTH, Easing.InOutQuart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,10 +27,14 @@ namespace osu.Game.Screens
|
|||||||
if (screen == null)
|
if (screen == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (EqualityComparer<BackgroundScreen>.Default.Equals((BackgroundScreen)CurrentScreen, screen))
|
bool isFirstScreen = CurrentScreen == null;
|
||||||
|
screen.AnimateEntry = !isFirstScreen;
|
||||||
|
|
||||||
|
if (EqualityComparer<BackgroundScreen>.Default.Equals((BackgroundScreen?)CurrentScreen, screen))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
base.Push(screen);
|
base.Push(screen);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,6 @@ namespace osu.Game.Screens.Backgrounds
|
|||||||
|
|
||||||
protected virtual bool AllowStoryboardBackground => true;
|
protected virtual bool AllowStoryboardBackground => true;
|
||||||
|
|
||||||
public BackgroundScreenDefault(bool animateOnEnter = true)
|
|
||||||
: base(animateOnEnter)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config)
|
private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -16,6 +17,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText();
|
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText();
|
||||||
|
EditorBeatmap.PlacementObject.BindValueChanged(_ => updateInspectorText());
|
||||||
EditorBeatmap.TransactionBegan += updateInspectorText;
|
EditorBeatmap.TransactionBegan += updateInspectorText;
|
||||||
EditorBeatmap.TransactionEnded += updateInspectorText;
|
EditorBeatmap.TransactionEnded += updateInspectorText;
|
||||||
updateInspectorText();
|
updateInspectorText();
|
||||||
@ -29,24 +31,33 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
rollingTextUpdate?.Cancel();
|
rollingTextUpdate?.Cancel();
|
||||||
rollingTextUpdate = null;
|
rollingTextUpdate = null;
|
||||||
|
|
||||||
AddInspectorValues();
|
HitObject[] objects;
|
||||||
|
|
||||||
|
if (EditorBeatmap.SelectedHitObjects.Count > 0)
|
||||||
|
objects = EditorBeatmap.SelectedHitObjects.ToArray();
|
||||||
|
else if (EditorBeatmap.PlacementObject.Value != null)
|
||||||
|
objects = new[] { EditorBeatmap.PlacementObject.Value };
|
||||||
|
else
|
||||||
|
objects = Array.Empty<HitObject>();
|
||||||
|
|
||||||
|
AddInspectorValues(objects);
|
||||||
|
|
||||||
// I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes.
|
// I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes.
|
||||||
// This is a good middle-ground for the time being.
|
// This is a good middle-ground for the time being.
|
||||||
if (EditorBeatmap.SelectedHitObjects.Count > 0)
|
if (objects.Length > 0)
|
||||||
rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250);
|
rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void AddInspectorValues()
|
protected virtual void AddInspectorValues(HitObject[] objects)
|
||||||
{
|
{
|
||||||
switch (EditorBeatmap.SelectedHitObjects.Count)
|
switch (objects.Length)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
AddValue("No selection");
|
AddValue("No selection");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
var selected = EditorBeatmap.SelectedHitObjects.Single();
|
var selected = objects.Single();
|
||||||
|
|
||||||
AddHeader("Type");
|
AddHeader("Type");
|
||||||
AddValue($"{selected.GetType().ReadableName()}");
|
AddValue($"{selected.GetType().ReadableName()}");
|
||||||
@ -105,13 +116,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
AddHeader("Selected Objects");
|
AddHeader("Selected Objects");
|
||||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}");
|
AddValue($"{objects.Length:#,0.##}");
|
||||||
|
|
||||||
AddHeader("Start Time");
|
AddHeader("Start Time");
|
||||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms");
|
AddValue($"{objects.Min(o => o.StartTime):#,0.##}ms");
|
||||||
|
|
||||||
AddHeader("End Time");
|
AddHeader("End Time");
|
||||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms");
|
AddValue($"{objects.Max(o => o.GetEndTime()):#,0.##}ms");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1286,10 +1286,23 @@ namespace osu.Game.Screens.Edit
|
|||||||
foreach (var beatmap in rulesetBeatmaps)
|
foreach (var beatmap in rulesetBeatmaps)
|
||||||
{
|
{
|
||||||
bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmap);
|
bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmap);
|
||||||
difficultyItems.Add(new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty));
|
var difficultyMenuItem = new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty);
|
||||||
|
difficultyItems.Add(difficultyMenuItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure difficulty names are updated when modified in the editor.
|
||||||
|
// Maybe we could trigger less often but this seems to work well enough.
|
||||||
|
editorBeatmap.SaveStateTriggered += () =>
|
||||||
|
{
|
||||||
|
foreach (var beatmapInfo in Beatmap.Value.BeatmapSetInfo.Beatmaps)
|
||||||
|
{
|
||||||
|
var menuItem = difficultyItems.OfType<DifficultyMenuItem>().FirstOrDefault(i => i.BeatmapInfo.Equals(beatmapInfo));
|
||||||
|
if (menuItem != null)
|
||||||
|
menuItem.Text.Value = string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? "(unnamed)" : beatmapInfo.DifficultyName;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return new EditorMenuItem(EditorStrings.ChangeDifficulty) { Items = difficultyItems };
|
return new EditorMenuItem(EditorStrings.ChangeDifficulty) { Items = difficultyItems };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
});
|
});
|
||||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, Key.M));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, Key.M));
|
||||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, Key.L));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, Key.L));
|
||||||
buttonsPlay.Add(new DailyChallengeButton(@"button-default-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D));
|
buttonsPlay.Add(new DailyChallengeButton(@"button-daily-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D));
|
||||||
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
||||||
|
|
||||||
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E)
|
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E)
|
||||||
|
@ -9,12 +9,10 @@ using osu.Framework.Extensions.ObjectExtensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -30,7 +28,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Menu
|
namespace osu.Game.Screens.Menu
|
||||||
{
|
{
|
||||||
public partial class DailyChallengeButton : MainMenuButton, IHasCustomTooltip<APIBeatmapSet?>
|
public partial class DailyChallengeButton : MainMenuButton
|
||||||
{
|
{
|
||||||
public Room? Room { get; private set; }
|
public Room? Room { get; private set; }
|
||||||
|
|
||||||
@ -104,8 +102,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true));
|
info.BindValueChanged(dailyChallengeChanged, true);
|
||||||
dailyChallengeChanged(postNotification: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -131,7 +128,9 @@ namespace osu.Game.Screens.Menu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dailyChallengeChanged(bool postNotification)
|
private long? lastNotifiedDailyChallengeRoomId;
|
||||||
|
|
||||||
|
private void dailyChallengeChanged(ValueChangedEvent<DailyChallengeInfo?> _)
|
||||||
{
|
{
|
||||||
UpdateState();
|
UpdateState();
|
||||||
|
|
||||||
@ -152,8 +151,14 @@ namespace osu.Game.Screens.Menu
|
|||||||
Room = room;
|
Room = room;
|
||||||
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
|
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
|
||||||
|
|
||||||
if (postNotification)
|
// We only want to notify the user if a new challenge recently went live.
|
||||||
|
if (room.StartDate.Value != null
|
||||||
|
&& Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800
|
||||||
|
&& room.RoomID.Value != lastNotifiedDailyChallengeRoomId)
|
||||||
|
{
|
||||||
|
lastNotifiedDailyChallengeRoomId = room.RoomID.Value;
|
||||||
notificationOverlay?.Post(new NewDailyChallengeNotification(room));
|
notificationOverlay?.Post(new NewDailyChallengeNotification(room));
|
||||||
|
}
|
||||||
|
|
||||||
updateCountdown();
|
updateCountdown();
|
||||||
Scheduler.AddDelayed(updateCountdown, 1000, true);
|
Scheduler.AddDelayed(updateCountdown, 1000, true);
|
||||||
@ -194,36 +199,6 @@ namespace osu.Game.Screens.Menu
|
|||||||
base.UpdateState();
|
base.UpdateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITooltip<APIBeatmapSet?> GetCustomTooltip() => new DailyChallengeTooltip();
|
|
||||||
|
|
||||||
public APIBeatmapSet? TooltipContent { get; private set; }
|
public APIBeatmapSet? TooltipContent { get; private set; }
|
||||||
|
|
||||||
internal partial class DailyChallengeTooltip : CompositeDrawable, ITooltip<APIBeatmapSet?>
|
|
||||||
{
|
|
||||||
[Cached]
|
|
||||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
|
||||||
|
|
||||||
private APIBeatmapSet? lastContent;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Move(Vector2 pos) => Position = pos;
|
|
||||||
|
|
||||||
public void SetContent(APIBeatmapSet? content)
|
|
||||||
{
|
|
||||||
if (content == lastContent)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lastContent = content;
|
|
||||||
|
|
||||||
ClearInternal();
|
|
||||||
if (content != null)
|
|
||||||
AddInternal(new BeatmapCardNano(content));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool UsingThemedIntro { get; private set; }
|
protected bool UsingThemedIntro { get; private set; }
|
||||||
|
|
||||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(false)
|
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault
|
||||||
{
|
{
|
||||||
Colour = Color4.Black
|
Colour = Color4.Black
|
||||||
};
|
};
|
||||||
|
@ -150,7 +150,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
OnPlaylists = () => this.Push(new Playlists()),
|
OnPlaylists = () => this.Push(new Playlists()),
|
||||||
OnDailyChallenge = room =>
|
OnDailyChallenge = room =>
|
||||||
{
|
{
|
||||||
this.Push(new DailyChallenge(room));
|
this.Push(new DailyChallengeIntro(room));
|
||||||
},
|
},
|
||||||
OnExit = () =>
|
OnExit = () =>
|
||||||
{
|
{
|
||||||
|
@ -3,10 +3,8 @@
|
|||||||
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
@ -20,17 +18,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
private CancellationTokenSource? cancellationSource;
|
private CancellationTokenSource? cancellationSource;
|
||||||
private PlaylistItemBackground? background;
|
private PlaylistItemBackground? background;
|
||||||
|
|
||||||
protected OnlinePlayBackgroundScreen()
|
|
||||||
: base(false)
|
|
||||||
{
|
|
||||||
AddInternal(new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Depth = float.MinValue,
|
|
||||||
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.9f), Color4.Black.Opacity(0.6f))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -84,6 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
newBackground.Depth = newDepth;
|
newBackground.Depth = newDepth;
|
||||||
|
newBackground.Colour = ColourInfo.GradientVertical(new Color4(0.1f, 0.1f, 0.1f, 1f), new Color4(0.4f, 0.4f, 0.4f, 1f));
|
||||||
newBackground.BlurTo(new Vector2(10));
|
newBackground.BlurTo(new Vector2(10));
|
||||||
|
|
||||||
AddInternal(background = newBackground);
|
AddInternal(background = newBackground);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
@ -76,6 +77,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGame? game { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmapManager { get; set; } = null!;
|
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||||
|
|
||||||
@ -386,7 +390,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
beatmapAvailabilityTracker.SelectedItem.Value = playlistItem;
|
beatmapAvailabilityTracker.SelectedItem.Value = playlistItem;
|
||||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true);
|
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true);
|
||||||
|
|
||||||
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay);
|
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay);
|
||||||
userModsSelectOverlay.SelectedItem.Value = playlistItem;
|
userModsSelectOverlay.SelectedItem.Value = playlistItem;
|
||||||
@ -398,15 +402,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
dailyChallengeInfo.BindValueChanged(dailyChallengeChanged);
|
dailyChallengeInfo.BindValueChanged(dailyChallengeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trySetDailyChallengeBeatmap()
|
|
||||||
{
|
|
||||||
var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID);
|
|
||||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally.
|
|
||||||
Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID);
|
|
||||||
|
|
||||||
applyLoopingToTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||||
{
|
{
|
||||||
if (state.NewValue != APIState.Online)
|
if (state.NewValue != APIState.Online)
|
||||||
@ -440,7 +435,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
|
|
||||||
waves.Show();
|
waves.Show();
|
||||||
roomManager.JoinRoom(room);
|
roomManager.JoinRoom(room);
|
||||||
applyLoopingToTrack();
|
startLoopingTrack(this, musicController);
|
||||||
|
|
||||||
metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t =>
|
metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
@ -452,6 +447,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
|
|
||||||
MultiplayerPlaylistItemStats[] stats = t.GetResultSafely();
|
MultiplayerPlaylistItemStats[] stats = t.GetResultSafely();
|
||||||
var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID);
|
var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID);
|
||||||
|
|
||||||
if (itemStats == null) return;
|
if (itemStats == null) return;
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
@ -459,17 +455,17 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
breakdown.SetInitialCounts(itemStats.TotalScoreDistribution);
|
breakdown.SetInitialCounts(itemStats.TotalScoreDistribution);
|
||||||
totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.CumulativeScore);
|
totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.CumulativeScore);
|
||||||
});
|
});
|
||||||
});
|
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||||
|
|
||||||
beatmapAvailabilityTracker.SelectedItem.Value = playlistItem;
|
|
||||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true);
|
|
||||||
userModsSelectOverlay.SelectedItem.Value = playlistItem;
|
userModsSelectOverlay.SelectedItem.Value = playlistItem;
|
||||||
|
|
||||||
|
TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnResuming(ScreenTransitionEvent e)
|
public override void OnResuming(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
base.OnResuming(e);
|
base.OnResuming(e);
|
||||||
applyLoopingToTrack();
|
startLoopingTrack(this, musicController);
|
||||||
// re-apply mods as they may have been changed by a child screen
|
// re-apply mods as they may have been changed by a child screen
|
||||||
// (one known instance of this is showing a replay).
|
// (one known instance of this is showing a replay).
|
||||||
updateMods();
|
updateMods();
|
||||||
@ -498,17 +494,30 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
return base.OnExiting(e);
|
return base.OnExiting(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyLoopingToTrack()
|
public static void TrySetDailyChallengeBeatmap(OsuScreen screen, BeatmapManager beatmaps, RulesetStore rulesets, MusicController music, PlaylistItem item)
|
||||||
{
|
{
|
||||||
if (!this.IsCurrentScreen())
|
if (!screen.IsCurrentScreen())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var track = Beatmap.Value?.Track;
|
var beatmap = beatmaps.QueryBeatmap(b => b.OnlineID == item.Beatmap.OnlineID);
|
||||||
|
|
||||||
|
screen.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally.
|
||||||
|
screen.Ruleset.Value = rulesets.GetRuleset(item.RulesetID);
|
||||||
|
|
||||||
|
startLoopingTrack(screen, music);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startLoopingTrack(OsuScreen screen, MusicController music)
|
||||||
|
{
|
||||||
|
if (!screen.IsCurrentScreen())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var track = screen.Beatmap.Value?.Track;
|
||||||
|
|
||||||
if (track != null)
|
if (track != null)
|
||||||
{
|
{
|
||||||
Beatmap.Value?.PrepareTrackForPreview(true);
|
screen.Beatmap.Value?.PrepareTrackForPreview(true);
|
||||||
musicController.EnsurePlayingSomething();
|
music.EnsurePlayingSomething();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,9 +556,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
metadataClient.MultiplayerRoomScoreSet -= onRoomScoreSet;
|
metadataClient.MultiplayerRoomScoreSet -= onRoomScoreSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuGame? game { get; set; }
|
|
||||||
|
|
||||||
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
|
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
|
||||||
{
|
{
|
||||||
if (!this.IsCurrentScreen())
|
if (!this.IsCurrentScreen())
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user