mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 20:22:55 +08:00
Merge branch 'master' into localisation-settings
This commit is contained in:
commit
e9ab465da6
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -4,6 +4,9 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
inspect-code:
|
inspect-code:
|
||||||
name: Code Quality
|
name: Code Quality
|
||||||
|
4
.github/workflows/report-nunit.yml
vendored
4
.github/workflows/report-nunit.yml
vendored
@ -8,8 +8,12 @@ on:
|
|||||||
workflows: ["Continuous Integration"]
|
workflows: ["Continuous Integration"]
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
permissions: {}
|
||||||
jobs:
|
jobs:
|
||||||
annotate:
|
annotate:
|
||||||
|
permissions:
|
||||||
|
checks: write # to create checks (dorny/test-reporter)
|
||||||
|
|
||||||
name: Annotate CI run with test results
|
name: Annotate CI run with test results
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
|
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
|
||||||
|
3
.github/workflows/sentry-release.yml
vendored
3
.github/workflows/sentry-release.yml
vendored
@ -5,6 +5,9 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sentry_release:
|
sentry_release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.916.1" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1005.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -137,12 +137,13 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
|
|
||||||
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
|
||||||
|
|
||||||
var desktopWindow = (SDL2DesktopWindow)host.Window;
|
var desktopWindow = (SDL2DesktopWindow)host.Window;
|
||||||
|
|
||||||
|
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
||||||
|
if (iconStream != null)
|
||||||
|
desktopWindow.SetIconFromStream(iconStream);
|
||||||
|
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.SetIconFromStream(iconStream);
|
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSkin : DefaultSkin
|
private class TestSkin : TrianglesSkin
|
||||||
{
|
{
|
||||||
public bool FlipCatcherPlate { get; set; }
|
public bool FlipCatcherPlate { get; set; }
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -106,20 +105,37 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
public void TestCatcherCatchWidth()
|
public void TestCatcherCatchWidth()
|
||||||
{
|
{
|
||||||
float halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
|
float halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
|
||||||
|
|
||||||
|
AddStep("move catcher to center", () => catcher.X = CatchPlayfield.CENTER_X);
|
||||||
|
|
||||||
|
float leftPlateBounds = CatchPlayfield.CENTER_X - halfWidth;
|
||||||
|
float rightPlateBounds = CatchPlayfield.CENTER_X + halfWidth;
|
||||||
|
|
||||||
AddStep("catch fruit", () =>
|
AddStep("catch fruit", () =>
|
||||||
{
|
{
|
||||||
attemptCatch(new Fruit { X = -halfWidth + 1 });
|
attemptCatch(new Fruit { X = leftPlateBounds + 1 });
|
||||||
attemptCatch(new Fruit { X = halfWidth - 1 });
|
attemptCatch(new Fruit { X = rightPlateBounds - 1 });
|
||||||
});
|
});
|
||||||
checkPlate(2);
|
checkPlate(2);
|
||||||
|
|
||||||
AddStep("miss fruit", () =>
|
AddStep("miss fruit", () =>
|
||||||
{
|
{
|
||||||
attemptCatch(new Fruit { X = -halfWidth - 1 });
|
attemptCatch(new Fruit { X = leftPlateBounds - 1 });
|
||||||
attemptCatch(new Fruit { X = halfWidth + 1 });
|
attemptCatch(new Fruit { X = rightPlateBounds + 1 });
|
||||||
});
|
});
|
||||||
checkPlate(2);
|
checkPlate(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFruitClampedToCatchableRegion()
|
||||||
|
{
|
||||||
|
AddStep("catch fruit left", () => attemptCatch(new Fruit { X = -CatchPlayfield.WIDTH }));
|
||||||
|
checkPlate(1);
|
||||||
|
AddStep("move catcher to right", () => catcher.X = CatchPlayfield.WIDTH);
|
||||||
|
AddStep("catch fruit right", () => attemptCatch(new Fruit { X = CatchPlayfield.WIDTH * 2 }));
|
||||||
|
checkPlate(2);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFruitChangesCatcherState()
|
public void TestFruitChangesCatcherState()
|
||||||
{
|
{
|
||||||
@ -233,11 +249,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestHitLightingColour()
|
public void TestHitLightingColour()
|
||||||
{
|
{
|
||||||
var fruitColour = SkinConfiguration.DefaultComboColours[1];
|
|
||||||
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", () =>
|
AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
|
||||||
catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == fruitColour);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -16,22 +15,14 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||||
|
|
||||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 0.5f,
|
MinValue = 0.5f,
|
||||||
MaxValue = 1.5f,
|
MaxValue = 1.5f,
|
||||||
Default = 1f,
|
|
||||||
Value = 1f,
|
|
||||||
Precision = 0.1f
|
Precision = 0.1f
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||||
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
|
||||||
{
|
|
||||||
Default = true,
|
|
||||||
Value = true
|
|
||||||
};
|
|
||||||
|
|
||||||
public override float DefaultFlashlightSize => 350;
|
public override float DefaultFlashlightSize => 350;
|
||||||
|
|
||||||
|
@ -6,8 +6,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
@ -17,15 +15,8 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public override LocalisableString Description => "Where's the catcher?";
|
public override LocalisableString Description => "Where's the catcher?";
|
||||||
|
|
||||||
[SettingSource(
|
public override BindableInt HiddenComboCount { get; } = new BindableInt(10)
|
||||||
"Hidden at combo",
|
|
||||||
"The combo count at which the catcher becomes completely hidden",
|
|
||||||
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
|
||||||
)]
|
|
||||||
public override BindableInt HiddenComboCount { get; } = new BindableInt
|
|
||||||
{
|
{
|
||||||
Default = 10,
|
|
||||||
Value = 10,
|
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 50,
|
MaxValue = 50,
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
|
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
|
||||||
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
|
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public float EffectiveX => OriginalX + XOffset;
|
public float EffectiveX => Math.Clamp(OriginalX + XOffset, 0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
public double TimePreempt { get; set; } = 1000;
|
public double TimePreempt { get; set; } = 1000;
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ using Newtonsoft.Json;
|
|||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -84,8 +85,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
AddNested(new TinyDroplet
|
AddNested(new TinyDroplet
|
||||||
{
|
{
|
||||||
StartTime = t + lastEvent.Value.Time,
|
StartTime = t + lastEvent.Value.Time,
|
||||||
X = OriginalX + Path.PositionAt(
|
X = ClampToPlayfield(EffectiveX + Path.PositionAt(
|
||||||
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
|
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
Samples = dropletSamples,
|
Samples = dropletSamples,
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
X = OriginalX + Path.PositionAt(e.PathProgress).X,
|
X = ClampToPlayfield(EffectiveX + Path.PositionAt(e.PathProgress).X),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -113,14 +114,16 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
Samples = this.GetNodeSamples(nodeIndex++),
|
Samples = this.GetNodeSamples(nodeIndex++),
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
X = OriginalX + Path.PositionAt(e.PathProgress).X,
|
X = ClampToPlayfield(EffectiveX + Path.PositionAt(e.PathProgress).X),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float EndX => OriginalX + this.CurvePositionAt(1).X;
|
public float EndX => ClampToPlayfield(EffectiveX + this.CurvePositionAt(1).X);
|
||||||
|
|
||||||
|
public float ClampToPlayfield(float value) => Math.Clamp(value, 0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double Duration
|
public double Duration
|
||||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(SkinManager skins)
|
private void load(SkinManager skins)
|
||||||
{
|
{
|
||||||
var defaultLegacySkin = skins.DefaultLegacySkin;
|
var defaultLegacySkin = skins.DefaultClassicSkin;
|
||||||
|
|
||||||
// sprite names intentionally swapped to match stable member naming / ease of cross-referencing
|
// sprite names intentionally swapped to match stable member naming / ease of cross-referencing
|
||||||
explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2");
|
explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2");
|
||||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDefaultSkin()
|
public void TestDefaultSkin()
|
||||||
{
|
{
|
||||||
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged());
|
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -17,22 +16,14 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
|
||||||
|
|
||||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 0.5f,
|
MinValue = 0.5f,
|
||||||
MaxValue = 3f,
|
MaxValue = 3f,
|
||||||
Default = 1f,
|
|
||||||
Value = 1f,
|
|
||||||
Precision = 0.1f
|
Precision = 0.1f
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
public override BindableBool ComboBasedSize { get; } = new BindableBool();
|
||||||
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
|
||||||
{
|
|
||||||
Default = false,
|
|
||||||
Value = false
|
|
||||||
};
|
|
||||||
|
|
||||||
public override float DefaultFlashlightSize => 50;
|
public override float DefaultFlashlightSize => 50;
|
||||||
|
|
||||||
|
@ -54,10 +54,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
|
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,14 +30,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
public bool UpdateResult() => base.UpdateResult(true);
|
public bool UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
|
||||||
{
|
|
||||||
base.UpdateInitialTransforms();
|
|
||||||
|
|
||||||
// This hitobject should never expire, so this is just a safe maximum.
|
|
||||||
LifetimeEnd = LifetimeStart + 30000;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
{
|
{
|
||||||
// suppress the base call explicitly.
|
// suppress the base call explicitly.
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
protected override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
|
public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
|
@ -23,10 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
// Leaving the default (10s) makes hitobjects not appear, as this offset is used for the initial state transforms.
|
|
||||||
// Calculated as DrawableManiaRuleset.MAX_TIME_RANGE + some additional allowance for velocity < 1.
|
|
||||||
protected override double InitialLifetimeOffset => 30000;
|
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private ManiaPlayfield playfield { get; set; }
|
private ManiaPlayfield playfield { get; set; }
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -88,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
if (!objects.Any())
|
if (!objects.Any())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType<ShakeContainer>().First().Children.OfType<Container>().Single().Scale.X, target));
|
return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType<Container>().First().Scale.X, target));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool checkSomeHit()
|
private bool checkSomeHit()
|
||||||
|
102
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs
Normal file
102
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModRandom : OsuModTestScene
|
||||||
|
{
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(7)]
|
||||||
|
[TestCase(10)]
|
||||||
|
public void TestDefaultBeatmap(float angleSharpness) => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModRandom
|
||||||
|
{
|
||||||
|
AngleSharpness = { Value = angleSharpness }
|
||||||
|
},
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(7)]
|
||||||
|
[TestCase(10)]
|
||||||
|
public void TestJumpBeatmap(float angleSharpness) => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModRandom
|
||||||
|
{
|
||||||
|
AngleSharpness = { Value = angleSharpness }
|
||||||
|
},
|
||||||
|
Beatmap = jumpBeatmap,
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(7)]
|
||||||
|
[TestCase(10)]
|
||||||
|
public void TestStreamBeatmap(float angleSharpness) => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModRandom
|
||||||
|
{
|
||||||
|
AngleSharpness = { Value = angleSharpness }
|
||||||
|
},
|
||||||
|
Beatmap = streamBeatmap,
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
private OsuBeatmap jumpBeatmap =>
|
||||||
|
createHitCircleBeatmap(new[] { 100, 200, 300, 400 }, 8, 300, 2 * 300);
|
||||||
|
|
||||||
|
private OsuBeatmap streamBeatmap =>
|
||||||
|
createHitCircleBeatmap(new[] { 10, 20, 30, 40, 50, 60, 70, 80 }, 16, 150, 4 * 150);
|
||||||
|
|
||||||
|
private OsuBeatmap createHitCircleBeatmap(IEnumerable<int> spacings, int objectsPerSpacing, int interval, int beatLength)
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint
|
||||||
|
{
|
||||||
|
Time = 0,
|
||||||
|
BeatLength = beatLength
|
||||||
|
});
|
||||||
|
|
||||||
|
var beatmap = new OsuBeatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
StackLeniency = 0,
|
||||||
|
Difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
ApproachRate = 8.5f
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (int spacing in spacings)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < objectsPerSpacing; i++)
|
||||||
|
{
|
||||||
|
beatmap.HitObjects.Add(new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = interval * beatmap.HitObjects.Count,
|
||||||
|
Position = beatmap.HitObjects.Count % 2 == 0 ? Vector2.Zero : new Vector2(spacing, 0),
|
||||||
|
NewCombo = i == 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
});
|
});
|
||||||
AddStep("setup default legacy skin", () =>
|
AddStep("setup default legacy skin", () =>
|
||||||
{
|
{
|
||||||
skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo;
|
skinManager.CurrentSkinInfo.Value = skinManager.DefaultClassicSkin.SkinInfo;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
||||||
{
|
{
|
||||||
var drawable = createSingle(circleSize, auto, timeOffset, positionOffset);
|
|
||||||
|
|
||||||
var playfield = new TestOsuPlayfield();
|
var playfield = new TestOsuPlayfield();
|
||||||
playfield.Add(drawable);
|
|
||||||
|
for (double t = timeOffset; t < timeOffset + 60000; t += 2000)
|
||||||
|
playfield.Add(createSingle(circleSize, auto, t, positionOffset));
|
||||||
|
|
||||||
return playfield;
|
return playfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
Child = new SkinProvidingContainer(new DefaultSkin(null))
|
Child = new SkinProvidingContainer(new TrianglesSkin(null))
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
|
Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
|
||||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -43,7 +42,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
|
||||||
private DrawableSpinner drawableSpinner = null!;
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
@ -77,7 +75,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
double finalCumulativeTrackerRotation = 0;
|
double finalCumulativeTrackerRotation = 0;
|
||||||
double finalTrackerRotation = 0, trackerRotationTolerance = 0;
|
double finalTrackerRotation = 0, trackerRotationTolerance = 0;
|
||||||
double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0;
|
|
||||||
|
|
||||||
addSeekStep(spinner_start_time + 5000);
|
addSeekStep(spinner_start_time + 5000);
|
||||||
AddStep("retrieve disc rotation", () =>
|
AddStep("retrieve disc rotation", () =>
|
||||||
@ -85,11 +82,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
finalTrackerRotation = drawableSpinner.RotationTracker.Rotation;
|
finalTrackerRotation = drawableSpinner.RotationTracker.Rotation;
|
||||||
trackerRotationTolerance = Math.Abs(finalTrackerRotation * 0.05f);
|
trackerRotationTolerance = Math.Abs(finalTrackerRotation * 0.05f);
|
||||||
});
|
});
|
||||||
AddStep("retrieve spinner symbol rotation", () =>
|
|
||||||
{
|
|
||||||
finalSpinnerSymbolRotation = spinnerSymbol.Rotation;
|
|
||||||
spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f);
|
|
||||||
});
|
|
||||||
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation);
|
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation);
|
||||||
|
|
||||||
addSeekStep(spinner_start_time + 2500);
|
addSeekStep(spinner_start_time + 2500);
|
||||||
@ -98,8 +90,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
||||||
// (5% relative to the final rotation value, but we're half-way through the spin).
|
// (5% relative to the final rotation value, but we're half-way through the spin).
|
||||||
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
|
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
|
||||||
AddAssert("symbol rotation rewound",
|
|
||||||
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
|
|
||||||
AddAssert("is cumulative rotation rewound",
|
AddAssert("is cumulative rotation rewound",
|
||||||
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
||||||
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
|
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
|
||||||
@ -107,8 +97,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addSeekStep(spinner_start_time + 5000);
|
addSeekStep(spinner_start_time + 5000);
|
||||||
AddAssert("is disc rotation almost same",
|
AddAssert("is disc rotation almost same",
|
||||||
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance));
|
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance));
|
||||||
AddAssert("is symbol rotation almost same",
|
|
||||||
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
|
|
||||||
AddAssert("is cumulative rotation almost same",
|
AddAssert("is cumulative rotation almost same",
|
||||||
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
|
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
|
||||||
}
|
}
|
||||||
@ -122,7 +110,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addSeekStep(5000);
|
addSeekStep(5000);
|
||||||
|
|
||||||
AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.RotationTracker.Rotation > 0 : drawableSpinner.RotationTracker.Rotation < 0);
|
AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.RotationTracker.Rotation > 0 : drawableSpinner.RotationTracker.Rotation < 0);
|
||||||
AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Replay flip(Replay scoreReplay) => new Replay
|
private Replay flip(Replay scoreReplay) => new Replay
|
||||||
|
149
osu.Game.Rulesets.Osu.Tests/TestSceneTrianglesSpinnerRotation.cs
Normal file
149
osu.Game.Rulesets.Osu.Tests/TestSceneTrianglesSpinnerRotation.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Replays;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneTrianglesSpinnerRotation : TestSceneOsuPlayer
|
||||||
|
{
|
||||||
|
private const double spinner_start_time = 100;
|
||||||
|
private const double spinner_duration = 6000;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skinManager { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private AudioManager audioManager { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
|
||||||
|
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
|
||||||
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
|
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("set triangles skin", () => skinManager.CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged());
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSymbolMiddleRewindingRotation()
|
||||||
|
{
|
||||||
|
double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0;
|
||||||
|
|
||||||
|
addSeekStep(spinner_start_time + 5000);
|
||||||
|
AddStep("retrieve spinner symbol rotation", () =>
|
||||||
|
{
|
||||||
|
finalSpinnerSymbolRotation = spinnerSymbol.Rotation;
|
||||||
|
spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f);
|
||||||
|
});
|
||||||
|
|
||||||
|
addSeekStep(spinner_start_time + 2500);
|
||||||
|
AddAssert("symbol rotation rewound",
|
||||||
|
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
|
||||||
|
|
||||||
|
addSeekStep(spinner_start_time + 5000);
|
||||||
|
AddAssert("is symbol rotation almost same",
|
||||||
|
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSymbolRotationDirection([Values(true, false)] bool clockwise)
|
||||||
|
{
|
||||||
|
if (clockwise)
|
||||||
|
transformReplay(flip);
|
||||||
|
|
||||||
|
addSeekStep(5000);
|
||||||
|
AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Replay flip(Replay scoreReplay) => new Replay
|
||||||
|
{
|
||||||
|
Frames = scoreReplay
|
||||||
|
.Frames
|
||||||
|
.Cast<OsuReplayFrame>()
|
||||||
|
.Select(replayFrame =>
|
||||||
|
{
|
||||||
|
var flippedPosition = new Vector2(OsuPlayfield.BASE_SIZE.X - replayFrame.Position.X, replayFrame.Position.Y);
|
||||||
|
return new OsuReplayFrame(replayFrame.Time, flippedPosition, replayFrame.Actions.ToArray());
|
||||||
|
})
|
||||||
|
.Cast<ReplayFrame>()
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
private void addSeekStep(double time)
|
||||||
|
{
|
||||||
|
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
||||||
|
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transformReplay(Func<Replay, Replay> replayTransformation) => AddStep("set replay", () =>
|
||||||
|
{
|
||||||
|
var drawableRuleset = this.ChildrenOfType<DrawableOsuRuleset>().Single();
|
||||||
|
var score = drawableRuleset.ReplayScore;
|
||||||
|
var transformedScore = new Score
|
||||||
|
{
|
||||||
|
ScoreInfo = score.ScoreInfo,
|
||||||
|
Replay = replayTransformation.Invoke(score.Replay)
|
||||||
|
};
|
||||||
|
drawableRuleset.SetReplayScore(transformedScore);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = spinner_start_time,
|
||||||
|
Duration = spinner_duration
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private class ScoreExposedPlayer : TestPlayer
|
||||||
|
{
|
||||||
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
|
public ScoreExposedPlayer()
|
||||||
|
: base(false, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
|
|
||||||
if (!(currentObj.BaseObject is Spinner))
|
if (!(currentObj.BaseObject is Spinner))
|
||||||
{
|
{
|
||||||
double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length;
|
double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length;
|
||||||
|
|
||||||
cumulativeStrainTime += lastObj.StrainTime;
|
cumulativeStrainTime += lastObj.StrainTime;
|
||||||
|
|
||||||
|
@ -44,6 +44,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
||||||
|
|
||||||
|
if (mods.Any(m => m is OsuModTouchDevice))
|
||||||
|
{
|
||||||
|
aimRating = Math.Pow(aimRating, 0.8);
|
||||||
|
flashlightRating = Math.Pow(flashlightRating, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
if (mods.Any(h => h is OsuModRelax))
|
if (mods.Any(h => h is OsuModRelax))
|
||||||
{
|
{
|
||||||
aimRating *= 0.9;
|
aimRating *= 0.9;
|
||||||
@ -127,6 +133,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||||
{
|
{
|
||||||
|
new OsuModTouchDevice(),
|
||||||
new OsuModDoubleTime(),
|
new OsuModDoubleTime(),
|
||||||
new OsuModHalfTime(),
|
new OsuModHalfTime(),
|
||||||
new OsuModEasy(),
|
new OsuModEasy(),
|
||||||
|
@ -88,12 +88,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
double rawAim = attributes.AimDifficulty;
|
double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is OsuModTouchDevice))
|
|
||||||
rawAim = Math.Pow(rawAim, 0.8);
|
|
||||||
|
|
||||||
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
|
|
||||||
|
|
||||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||||
@ -233,12 +228,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
|
||||||
double rawFlashlight = attributes.FlashlightDifficulty;
|
double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is OsuModTouchDevice))
|
|
||||||
rawFlashlight = Math.Pow(rawFlashlight, 0.8);
|
|
||||||
|
|
||||||
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
|
|
||||||
|
|
||||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
if (effectiveMissCount > 0)
|
if (effectiveMissCount > 0)
|
||||||
|
@ -303,11 +303,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition));
|
||||||
|
|
||||||
|
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position;
|
||||||
|
|
||||||
for (int i = 0; i < controlPoints.Count; ++i)
|
for (int i = 0; i < controlPoints.Count; ++i)
|
||||||
{
|
{
|
||||||
var controlPoint = controlPoints[i];
|
var controlPoint = controlPoints[i];
|
||||||
if (selectedControlPoints.Contains(controlPoint))
|
if (selectedControlPoints.Contains(controlPoint))
|
||||||
controlPoint.Position = dragStartPositions[i] + (e.MousePosition - e.MouseDownPosition);
|
controlPoint.Position = dragStartPositions[i] + movementDelta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +198,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the cursor position.
|
// Update the cursor position.
|
||||||
cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
var result = snapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
|
||||||
|
cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
}
|
}
|
||||||
else if (cursor != null)
|
else if (cursor != null)
|
||||||
{
|
{
|
||||||
|
@ -163,7 +163,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
protected override void OnDrag(DragEvent e)
|
protected override void OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
if (placementControlPoint != null)
|
if (placementControlPoint != null)
|
||||||
placementControlPoint.Position = e.MousePosition - HitObject.Position;
|
{
|
||||||
|
var result = snapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition));
|
||||||
|
placementControlPoint.Position = ToLocalSpace(result?.ScreenSpacePosition ?? ToScreenSpace(e.MousePosition)) - HitObject.Position;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -18,13 +17,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override LocalisableString Description => "Hit them at the right size!";
|
public override LocalisableString Description => "Hit them at the right size!";
|
||||||
|
|
||||||
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
public override BindableNumber<float> StartScale { get; } = new BindableFloat(2)
|
||||||
public override BindableNumber<float> StartScale { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 1f,
|
MinValue = 1f,
|
||||||
MaxValue = 25f,
|
MaxValue = 25f,
|
||||||
Default = 2f,
|
|
||||||
Value = 2f,
|
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -32,22 +32,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
Precision = default_follow_delay,
|
Precision = default_follow_delay,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 0.5f,
|
MinValue = 0.5f,
|
||||||
MaxValue = 2f,
|
MaxValue = 2f,
|
||||||
Default = 1f,
|
|
||||||
Value = 1f,
|
|
||||||
Precision = 0.1f
|
Precision = 0.1f
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||||
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
|
||||||
{
|
|
||||||
Default = true,
|
|
||||||
Value = true
|
|
||||||
};
|
|
||||||
|
|
||||||
public override float DefaultFlashlightSize => 180;
|
public override float DefaultFlashlightSize => 180;
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -18,13 +17,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override LocalisableString Description => "Hit them at the right size!";
|
public override LocalisableString Description => "Hit them at the right size!";
|
||||||
|
|
||||||
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
public override BindableNumber<float> StartScale { get; } = new BindableFloat(0.5f)
|
||||||
public override BindableNumber<float> StartScale { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 0f,
|
MinValue = 0f,
|
||||||
MaxValue = 0.99f,
|
MaxValue = 0.99f,
|
||||||
Default = 0.5f,
|
|
||||||
Value = 0.5f,
|
|
||||||
Precision = 0.01f,
|
Precision = 0.01f,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -22,15 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private PeriodTracker spinnerPeriods = null!;
|
private PeriodTracker spinnerPeriods = null!;
|
||||||
|
|
||||||
[SettingSource(
|
public override BindableInt HiddenComboCount { get; } = new BindableInt(10)
|
||||||
"Hidden at combo",
|
|
||||||
"The combo count at which the cursor becomes completely hidden",
|
|
||||||
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
|
||||||
)]
|
|
||||||
public override BindableInt HiddenComboCount { get; } = new BindableInt
|
|
||||||
{
|
{
|
||||||
Default = 10,
|
|
||||||
Value = 10,
|
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 50,
|
MaxValue = 50,
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -20,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
||||||
public abstract BindableNumber<float> StartScale { get; }
|
public abstract BindableNumber<float> StartScale { get; }
|
||||||
|
|
||||||
protected virtual float EndScale => 1;
|
protected virtual float EndScale => 1;
|
||||||
|
@ -4,9 +4,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
@ -25,6 +28,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
|
||||||
|
|
||||||
|
[SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider<float>))]
|
||||||
|
public BindableFloat AngleSharpness { get; } = new BindableFloat(7)
|
||||||
|
{
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f
|
||||||
|
};
|
||||||
|
|
||||||
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
||||||
|
|
||||||
private Random random = null!;
|
private Random random = null!;
|
||||||
@ -50,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
if (shouldStartNewSection(osuBeatmap, positionInfos, i))
|
if (shouldStartNewSection(osuBeatmap, positionInfos, i))
|
||||||
{
|
{
|
||||||
sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.0008f);
|
sectionOffset = getRandomOffset(0.0008f);
|
||||||
flowDirection = !flowDirection;
|
flowDirection = !flowDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,11 +76,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
float flowChangeOffset = 0;
|
float flowChangeOffset = 0;
|
||||||
|
|
||||||
// Offsets only the angle of the current hit object.
|
// Offsets only the angle of the current hit object.
|
||||||
float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f);
|
float oneTimeOffset = getRandomOffset(0.002f);
|
||||||
|
|
||||||
if (shouldApplyFlowChange(positionInfos, i))
|
if (shouldApplyFlowChange(positionInfos, i))
|
||||||
{
|
{
|
||||||
flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f);
|
flowChangeOffset = getRandomOffset(0.002f);
|
||||||
flowDirection = !flowDirection;
|
flowDirection = !flowDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,13 +97,36 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
|
osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float getRandomOffset(float stdDev)
|
||||||
|
{
|
||||||
|
// Range: [0.5, 2]
|
||||||
|
// Higher angle sharpness -> lower multiplier
|
||||||
|
float customMultiplier = (1.5f * AngleSharpness.MaxValue - AngleSharpness.Value) / (1.5f * AngleSharpness.MaxValue - AngleSharpness.Default);
|
||||||
|
|
||||||
|
return OsuHitObjectGenerationUtils.RandomGaussian(random, 0, stdDev * customMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
/// <param name="targetDistance">The target distance between the previous and the current <see cref="OsuHitObject"/>.</param>
|
/// <param name="targetDistance">The target distance between the previous and the current <see cref="OsuHitObject"/>.</param>
|
||||||
/// <param name="offset">The angle (in rad) by which the target angle should be offset.</param>
|
/// <param name="offset">The angle (in rad) by which the target angle should be offset.</param>
|
||||||
/// <param name="flowDirection">Whether the relative angle should be positive or negative.</param>
|
/// <param name="flowDirection">Whether the relative angle should be positive or negative.</param>
|
||||||
private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection)
|
private float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection)
|
||||||
{
|
{
|
||||||
float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310))) + 0.5 + offset);
|
// Range: [0.1, 1]
|
||||||
|
float angleSharpness = AngleSharpness.Value / AngleSharpness.MaxValue;
|
||||||
|
// Range: [0, 0.9]
|
||||||
|
float angleWideness = 1 - angleSharpness;
|
||||||
|
|
||||||
|
// Range: [-60, 30]
|
||||||
|
float customOffsetX = angleSharpness * 100 - 70;
|
||||||
|
// Range: [-0.075, 0.15]
|
||||||
|
float customOffsetY = angleWideness * 0.25f - 0.075f;
|
||||||
|
|
||||||
|
targetDistance += customOffsetX;
|
||||||
|
float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310 + customOffsetX))) + 0.5);
|
||||||
|
angle += offset + customOffsetY;
|
||||||
|
|
||||||
float relativeAngle = (float)Math.PI - angle;
|
float relativeAngle = (float)Math.PI - angle;
|
||||||
|
|
||||||
return flowDirection ? -relativeAngle : relativeAngle;
|
return flowDirection ? -relativeAngle : relativeAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,11 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
|
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
|
||||||
public Bindable<int?> Seed { get; } = new Bindable<int?>
|
public Bindable<int?> Seed { get; } = new Bindable<int?>();
|
||||||
{
|
|
||||||
Default = null,
|
|
||||||
Value = null
|
|
||||||
};
|
|
||||||
|
|
||||||
[SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")]
|
[SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")]
|
||||||
public Bindable<bool> Metronome { get; } = new BindableBool(true);
|
public Bindable<bool> Metronome { get; } = new BindableBool(true);
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
@ -47,12 +48,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ShakeContainer shakeContainer;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
scaleContainer = new Container
|
scaleContainer = new Container
|
||||||
{
|
{
|
||||||
@ -72,22 +75,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece())
|
shakeContainer = new ShakeContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
ShakeDuration = 30,
|
||||||
Origin = Anchor.Centre,
|
|
||||||
},
|
|
||||||
ApproachCircle = new ProxyableSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ApproachCircle), _ => new DefaultApproachCircle())
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0,
|
Children = new Drawable[]
|
||||||
Scale = new Vector2(4),
|
{
|
||||||
|
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
ApproachCircle = new ProxyableSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ApproachCircle), _ => new DefaultApproachCircle())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
Scale = new Vector2(4),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
Size = HitArea.DrawSize;
|
Size = HitArea.DrawSize;
|
||||||
|
|
||||||
@ -123,6 +134,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Shake() => shakeContainer.Shake();
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
Debug.Assert(HitObject.HitWindows != null);
|
Debug.Assert(HitObject.HitWindows != null);
|
||||||
@ -139,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
|
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
|
||||||
{
|
{
|
||||||
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
|
Shake();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,12 +204,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// todo: temporary / arbitrary, used for lifetime optimisation.
|
// todo: temporary / arbitrary, used for lifetime optimisation.
|
||||||
this.Delay(800).FadeOut();
|
this.Delay(800).FadeOut();
|
||||||
|
|
||||||
// in the case of an early state change, the fade should be expedited to the current point in time.
|
|
||||||
if (HitStateUpdateTime < HitObject.StartTime)
|
|
||||||
ApproachCircle.FadeOut(50);
|
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
default:
|
||||||
|
ApproachCircle.FadeOut();
|
||||||
|
break;
|
||||||
|
|
||||||
case ArmedState.Idle:
|
case ArmedState.Idle:
|
||||||
HitArea.HitAction = null;
|
HitArea.HitAction = null;
|
||||||
break;
|
break;
|
||||||
|
@ -6,17 +6,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
|
public abstract class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
|
||||||
{
|
{
|
||||||
public readonly IBindable<Vector2> PositionBindable = new Bindable<Vector2>();
|
public readonly IBindable<Vector2> PositionBindable = new Bindable<Vector2>();
|
||||||
public readonly IBindable<int> StackHeightBindable = new Bindable<int>();
|
public readonly IBindable<int> StackHeightBindable = new Bindable<int>();
|
||||||
@ -34,8 +35,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<DrawableHitObject, double, bool> CheckHittable;
|
public Func<DrawableHitObject, double, bool> CheckHittable;
|
||||||
|
|
||||||
private ShakeContainer shakeContainer;
|
|
||||||
|
|
||||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
@ -45,12 +44,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Alpha = 0;
|
Alpha = 0;
|
||||||
|
|
||||||
base.AddInternal(shakeContainer = new ShakeContainer
|
|
||||||
{
|
|
||||||
ShakeDuration = 30,
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
@ -73,18 +66,32 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward all internal management to shakeContainer.
|
protected override void UpdateInitialTransforms()
|
||||||
// This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690)
|
{
|
||||||
protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable);
|
base.UpdateInitialTransforms();
|
||||||
protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren);
|
|
||||||
protected override bool RemoveInternal(Drawable drawable, bool disposeImmediately) => shakeContainer.Remove(drawable, disposeImmediately);
|
// Dim should only be applied at a top level, as it will be implicitly applied to nested objects.
|
||||||
|
if (ParentHitObject == null)
|
||||||
|
{
|
||||||
|
// Of note, no one noticed this was missing for years, but it definitely feels like it should still exist.
|
||||||
|
// For now this is applied across all skins, and matches stable.
|
||||||
|
// For simplicity, dim colour is applied to the DrawableHitObject itself.
|
||||||
|
// We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod).
|
||||||
|
this.FadeColour(new Color4(195, 195, 195, 255));
|
||||||
|
using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
|
||||||
|
this.FadeColour(Color4.White, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
private OsuInputManager osuActionInputManager;
|
private OsuInputManager osuActionInputManager;
|
||||||
internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager;
|
internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager;
|
||||||
|
|
||||||
public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
/// <summary>
|
||||||
|
/// Shake the hit object in case it was clicked far too early or late (aka "note lock").
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Shake() { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
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.Osu.Skinning;
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public SkinnableDrawable Body { get; private set; }
|
public SkinnableDrawable Body { get; private set; }
|
||||||
|
|
||||||
|
private ShakeContainer shakeContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A target container which can be used to add top level elements to the slider's display.
|
/// A target container which can be used to add top level elements to the slider's display.
|
||||||
/// Intended to be used for proxy purposes only.
|
/// Intended to be used for proxy purposes only.
|
||||||
@ -74,17 +77,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
shakeContainer = new ShakeContainer
|
||||||
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
|
{
|
||||||
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
ShakeDuration = 30,
|
||||||
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
||||||
|
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
|
||||||
|
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||||
|
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// slider head is not included in shake as it handles hit detection, and handles its own shaking.
|
||||||
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
||||||
OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, },
|
OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, },
|
||||||
Ball,
|
Ball,
|
||||||
slidingSample = new PausableSkinnableSound { Looping = true }
|
slidingSample = new PausableSkinnableSound { Looping = true }
|
||||||
};
|
});
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||||
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||||
@ -109,6 +121,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
PathVersion.BindTo(HitObject.Path.Version);
|
PathVersion.BindTo(HitObject.Path.Version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Shake() => shakeContainer.Shake();
|
||||||
|
|
||||||
protected override void OnFree()
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree();
|
base.OnFree();
|
||||||
|
@ -63,7 +63,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
pathVersion.BindTo(DrawableSlider.PathVersion);
|
pathVersion.BindTo(DrawableSlider.PathVersion);
|
||||||
|
|
||||||
OnShake = DrawableSlider.Shake;
|
|
||||||
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +95,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss;
|
return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action<double> OnShake;
|
public override void Shake()
|
||||||
|
{
|
||||||
public override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
|
base.Shake();
|
||||||
|
DrawableSlider.Shake();
|
||||||
|
}
|
||||||
|
|
||||||
private void updatePosition()
|
private void updatePosition()
|
||||||
{
|
{
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
InternalChild = scaleContainer = new Container
|
AddInternal(scaleContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
},
|
},
|
||||||
Arrow = new ReverseArrowPiece(),
|
Arrow = new ReverseArrowPiece(),
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
scaleContainer = new Container
|
scaleContainer = new Container
|
||||||
{
|
{
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty())
|
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
|
AddInternal(scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
|
||||||
{
|
{
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
};
|
});
|
||||||
|
|
||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply a judgement result.
|
/// Apply a judgement result.
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
// This is so on repeats ticks don't appear too late to be visually processed by the player.
|
// This is so on repeats ticks don't appear too late to be visually processed by the player.
|
||||||
offset = 200;
|
offset = 200;
|
||||||
else
|
else
|
||||||
offset = TimeFadeIn * 0.66f;
|
offset = TimePreempt * 0.66f;
|
||||||
|
|
||||||
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
|
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ using osu.Game.Rulesets.Osu.Mods;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Argon;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Statistics;
|
using osu.Game.Rulesets.Osu.Statistics;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -237,6 +238,9 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
case LegacySkin:
|
case LegacySkin:
|
||||||
return new OsuLegacySkinTransformer(skin);
|
return new OsuLegacySkinTransformer(skin);
|
||||||
|
|
||||||
|
case ArgonSkin:
|
||||||
|
return new OsuArgonSkinTransformer(skin);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
// 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 osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Scoring
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
{
|
{
|
||||||
public class OsuHitWindows : HitWindows
|
public class OsuHitWindows : HitWindows
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// osu! ruleset has a fixed miss window regardless of difficulty settings.
|
||||||
|
/// </summary>
|
||||||
|
public const double MISS_WINDOW = 400;
|
||||||
|
|
||||||
private static readonly DifficultyRange[] osu_ranges =
|
private static readonly DifficultyRange[] osu_ranges =
|
||||||
{
|
{
|
||||||
new DifficultyRange(HitResult.Great, 80, 50, 20),
|
new DifficultyRange(HitResult.Great, 80, 50, 20),
|
||||||
new DifficultyRange(HitResult.Ok, 140, 100, 60),
|
new DifficultyRange(HitResult.Ok, 140, 100, 60),
|
||||||
new DifficultyRange(HitResult.Meh, 200, 150, 100),
|
new DifficultyRange(HitResult.Meh, 200, 150, 100),
|
||||||
new DifficultyRange(HitResult.Miss, 400, 400, 400),
|
new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW),
|
||||||
};
|
};
|
||||||
|
|
||||||
public override bool IsHitResultAllowed(HitResult result)
|
public override bool IsHitResultAllowed(HitResult result)
|
||||||
|
80
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs
Normal file
80
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonCursor : OsuCursorSprite
|
||||||
|
{
|
||||||
|
public ArgonCursor()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
ExpandTarget = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 6,
|
||||||
|
BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.4f,
|
||||||
|
Colour = Colour4.FromHex("FC618F").Darken(0.6f),
|
||||||
|
},
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
BorderColour = Color4.White.Opacity(0.8f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Scale = new Vector2(0.2f),
|
||||||
|
Colour = new Color4(255, 255, 255, 255),
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Radius = 20,
|
||||||
|
Colour = new Color4(171, 255, 255, 100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs
Normal file
29
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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.Textures;
|
||||||
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonCursorTrail : CursorTrail
|
||||||
|
{
|
||||||
|
protected override float IntervalMultiplier => 0.4f;
|
||||||
|
|
||||||
|
protected override float FadeExponent => 4;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore textures)
|
||||||
|
{
|
||||||
|
Texture = textures.Get(@"Cursor/cursortrail");
|
||||||
|
Scale = new Vector2(0.8f / Texture.ScaleAdjust);
|
||||||
|
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
Alpha = 0.8f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs
Normal file
71
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// 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.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonFollowCircle : FollowCircle
|
||||||
|
{
|
||||||
|
public ArgonFollowCircle()
|
||||||
|
{
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 4,
|
||||||
|
BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.3f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderPress()
|
||||||
|
{
|
||||||
|
const float duration = 300f;
|
||||||
|
|
||||||
|
if (Precision.AlmostEquals(0, Alpha))
|
||||||
|
this.ScaleTo(1);
|
||||||
|
|
||||||
|
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint)
|
||||||
|
.FadeIn(duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderRelease()
|
||||||
|
{
|
||||||
|
const float duration = 150;
|
||||||
|
|
||||||
|
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration, Easing.OutQuint)
|
||||||
|
.FadeTo(0, duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderEnd()
|
||||||
|
{
|
||||||
|
const float duration = 300;
|
||||||
|
|
||||||
|
this.ScaleTo(1, duration, Easing.OutQuint)
|
||||||
|
.FadeOut(duration / 2, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderTick()
|
||||||
|
{
|
||||||
|
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint)
|
||||||
|
.Then()
|
||||||
|
.ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderBreak()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs
Normal file
39
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 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.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonFollowPoint : CompositeDrawable
|
||||||
|
{
|
||||||
|
public ArgonFollowPoint()
|
||||||
|
{
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41"));
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Size = new Vector2(8),
|
||||||
|
Colour = OsuColour.Gray(0.2f),
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Size = new Vector2(8),
|
||||||
|
X = 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
Normal file
171
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// 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.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||||
|
{
|
||||||
|
protected readonly HitResult Result;
|
||||||
|
|
||||||
|
protected SpriteText JudgementText { get; private set; } = null!;
|
||||||
|
|
||||||
|
private RingExplosion? ringExplosion;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
public ArgonJudgementPiece(HitResult result)
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
JudgementText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = Result.GetDescription().ToUpperInvariant(),
|
||||||
|
Colour = colours.ForHitResult(Result),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Spacing = new Vector2(5, 0),
|
||||||
|
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Result.IsHit())
|
||||||
|
{
|
||||||
|
AddInternal(ringExplosion = new RingExplosion(Result)
|
||||||
|
{
|
||||||
|
Colour = colours.ForHitResult(Result),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plays the default animation for this judgement piece.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The base implementation only handles fade (for all result types) and misses.
|
||||||
|
/// Individual rulesets are recommended to implement their appropriate hit animations.
|
||||||
|
/// </remarks>
|
||||||
|
public virtual void PlayAnimation()
|
||||||
|
{
|
||||||
|
switch (Result)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
JudgementText
|
||||||
|
.ScaleTo(Vector2.One)
|
||||||
|
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Miss:
|
||||||
|
this.ScaleTo(1.6f);
|
||||||
|
this.ScaleTo(1, 100, Easing.In);
|
||||||
|
|
||||||
|
this.MoveTo(Vector2.Zero);
|
||||||
|
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||||
|
|
||||||
|
this.RotateTo(0);
|
||||||
|
this.RotateTo(40, 800, Easing.InQuint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FadeOutFromOne(800);
|
||||||
|
|
||||||
|
ringExplosion?.PlayAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable? GetAboveHitObjectsProxiedContent() => null;
|
||||||
|
|
||||||
|
private class RingExplosion : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly float travel = 52;
|
||||||
|
|
||||||
|
public RingExplosion(HitResult result)
|
||||||
|
{
|
||||||
|
const float thickness = 4;
|
||||||
|
|
||||||
|
const float small_size = 9;
|
||||||
|
const float large_size = 14;
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
int countSmall = 0;
|
||||||
|
int countLarge = 0;
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.Meh:
|
||||||
|
countSmall = 3;
|
||||||
|
travel *= 0.3f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Ok:
|
||||||
|
case HitResult.Good:
|
||||||
|
countSmall = 4;
|
||||||
|
travel *= 0.6f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Great:
|
||||||
|
case HitResult.Perfect:
|
||||||
|
countSmall = 4;
|
||||||
|
countLarge = 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < countSmall; i++)
|
||||||
|
AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
|
||||||
|
|
||||||
|
for (int i = 0; i < countLarge; i++)
|
||||||
|
AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayAnimation()
|
||||||
|
{
|
||||||
|
foreach (var c in InternalChildren)
|
||||||
|
{
|
||||||
|
const float start_position_ratio = 0.3f;
|
||||||
|
|
||||||
|
float direction = RNG.NextSingle(0, 360);
|
||||||
|
float distance = RNG.NextSingle(travel / 2, travel);
|
||||||
|
|
||||||
|
c.MoveTo(new Vector2(
|
||||||
|
MathF.Cos(direction) * distance * start_position_ratio,
|
||||||
|
MathF.Sin(direction) * distance * start_position_ratio
|
||||||
|
));
|
||||||
|
|
||||||
|
c.MoveTo(new Vector2(
|
||||||
|
MathF.Cos(direction) * distance,
|
||||||
|
MathF.Sin(direction) * distance
|
||||||
|
), 600, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FadeOutFromOne(1000, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
226
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
Normal file
226
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonMainCirclePiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public const float BORDER_THICKNESS = (OsuHitObject.OBJECT_RADIUS * 2) * (2f / 58);
|
||||||
|
|
||||||
|
public const float GRADIENT_THICKNESS = BORDER_THICKNESS * 2.5f;
|
||||||
|
|
||||||
|
public const float OUTER_GRADIENT_SIZE = (OsuHitObject.OBJECT_RADIUS * 2) - BORDER_THICKNESS * 4;
|
||||||
|
|
||||||
|
public const float INNER_GRADIENT_SIZE = OUTER_GRADIENT_SIZE - GRADIENT_THICKNESS * 2;
|
||||||
|
public const float INNER_FILL_SIZE = INNER_GRADIENT_SIZE - GRADIENT_THICKNESS * 2;
|
||||||
|
|
||||||
|
private readonly Circle outerFill;
|
||||||
|
private readonly Circle outerGradient;
|
||||||
|
private readonly Circle innerGradient;
|
||||||
|
private readonly Circle innerFill;
|
||||||
|
|
||||||
|
private readonly RingPiece border;
|
||||||
|
private readonly OsuSpriteText number;
|
||||||
|
|
||||||
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||||
|
private readonly FlashPiece flash;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
|
public ArgonMainCirclePiece(bool withOuterFill)
|
||||||
|
{
|
||||||
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
outerFill = new Circle // renders white outer border and dark fill
|
||||||
|
{
|
||||||
|
Size = Size,
|
||||||
|
Alpha = withOuterFill ? 1 : 0,
|
||||||
|
},
|
||||||
|
outerGradient = new Circle // renders the outer bright gradient
|
||||||
|
{
|
||||||
|
Size = new Vector2(OUTER_GRADIENT_SIZE),
|
||||||
|
Alpha = 1,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
innerGradient = new Circle // renders the inner bright gradient
|
||||||
|
{
|
||||||
|
Size = new Vector2(INNER_GRADIENT_SIZE),
|
||||||
|
Alpha = 1,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
innerFill = new Circle // renders the inner dark fill
|
||||||
|
{
|
||||||
|
Size = new Vector2(INNER_FILL_SIZE),
|
||||||
|
Alpha = 1,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
number = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.Default.With(size: 52, weight: FontWeight.Bold),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = -2,
|
||||||
|
Text = @"1",
|
||||||
|
},
|
||||||
|
flash = new FlashPiece(),
|
||||||
|
border = new RingPiece(BORDER_THICKNESS),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var drawableOsuObject = (DrawableOsuHitObject)drawableObject;
|
||||||
|
|
||||||
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
|
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
accentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
|
||||||
|
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
|
||||||
|
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
|
||||||
|
flash.Colour = colour.NewValue;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
||||||
|
|
||||||
|
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(drawableObject, drawableObject.State.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Hit:
|
||||||
|
// Fade out time is at a maximum of 800. Must match `DrawableHitCircle`'s arbitrary lifetime spec.
|
||||||
|
const double fade_out_time = 800;
|
||||||
|
|
||||||
|
const double flash_in_duration = 150;
|
||||||
|
const double resize_duration = 400;
|
||||||
|
|
||||||
|
const float shrink_size = 0.8f;
|
||||||
|
|
||||||
|
// Animating with the number present is distracting.
|
||||||
|
// The number disappearing is hidden by the bright flash.
|
||||||
|
number.FadeOut(flash_in_duration / 2);
|
||||||
|
|
||||||
|
// The fill layers add too much noise during the explosion animation.
|
||||||
|
// They will be hidden by the additive effects anyway.
|
||||||
|
outerFill.FadeOut(flash_in_duration, Easing.OutQuint);
|
||||||
|
innerFill.FadeOut(flash_in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
// The inner-most gradient should actually be resizing, but is only visible for
|
||||||
|
// a few milliseconds before it's hidden by the flash, so it's pointless overhead to bother with it.
|
||||||
|
innerGradient.FadeOut(flash_in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
// The border is always white, but after hit it gets coloured by the skin/beatmap's colouring.
|
||||||
|
// A gradient is applied to make the border less prominent over the course of the animation.
|
||||||
|
// Without this, the border dominates the visual presence of the explosion animation in a bad way.
|
||||||
|
border.TransformTo(nameof
|
||||||
|
(BorderColour), ColourInfo.GradientVertical(
|
||||||
|
accentColour.Value.Opacity(0.5f),
|
||||||
|
accentColour.Value.Opacity(0)), fade_out_time);
|
||||||
|
|
||||||
|
// The outer ring shrinks immediately, but accounts for its thickness so it doesn't overlap the inner
|
||||||
|
// gradient layers.
|
||||||
|
border.ResizeTo(Size * shrink_size + new Vector2(border.BorderThickness), resize_duration, Easing.OutElasticHalf);
|
||||||
|
|
||||||
|
// The outer gradient is resize with a slight delay from the border.
|
||||||
|
// This is to give it a bomb-like effect, with the border "triggering" its animation when getting close.
|
||||||
|
using (BeginDelayedSequence(flash_in_duration / 12))
|
||||||
|
{
|
||||||
|
outerGradient.ResizeTo(outerGradient.Size * shrink_size, resize_duration, Easing.OutElasticHalf);
|
||||||
|
outerGradient
|
||||||
|
.FadeColour(Color4.White, 80)
|
||||||
|
.Then()
|
||||||
|
.FadeOut(flash_in_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The flash layer starts white to give the wanted brightness, but is almost immediately
|
||||||
|
// recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash)
|
||||||
|
// but works well enough with the colour fade.
|
||||||
|
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
|
||||||
|
flash.FlashColour(accentColour.Value, fade_out_time, Easing.OutQuint);
|
||||||
|
|
||||||
|
this.FadeOut(fade_out_time, Easing.OutQuad);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableObject.IsNotNull())
|
||||||
|
drawableObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FlashPiece : Circle
|
||||||
|
{
|
||||||
|
public FlashPiece()
|
||||||
|
{
|
||||||
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS);
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Alpha = 0;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
// The edge effect provides the fill due to not being rendered hollow.
|
||||||
|
Child.Alpha = 0;
|
||||||
|
Child.AlwaysPresent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Colour,
|
||||||
|
Radius = OsuHitObject.OBJECT_RADIUS * 1.2f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs
Normal file
54
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.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.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonReverseArrow : CompositeDrawable
|
||||||
|
{
|
||||||
|
private Bindable<Color4> accentColour = null!;
|
||||||
|
|
||||||
|
private SpriteIcon icon = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Size = new Vector2(40, 20),
|
||||||
|
Colour = Color4.White,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.AngleDoubleRight,
|
||||||
|
Size = new Vector2(16),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
accentColour = hitObject.AccentColour.GetBoundCopy();
|
||||||
|
accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs
Normal file
109
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSliderBall : CircularContainer
|
||||||
|
{
|
||||||
|
private readonly Box fill;
|
||||||
|
private readonly SpriteIcon icon;
|
||||||
|
|
||||||
|
private readonly Vector2 defaultIconScale = new Vector2(0.6f, 0.8f);
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DrawableHitObject? parentObject { get; set; }
|
||||||
|
|
||||||
|
public ArgonSliderBall()
|
||||||
|
{
|
||||||
|
Size = new Vector2(ArgonMainCirclePiece.OUTER_GRADIENT_SIZE);
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
BorderThickness = ArgonMainCirclePiece.GRADIENT_THICKNESS;
|
||||||
|
BorderColour = Color4.White;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
fill = new Box
|
||||||
|
{
|
||||||
|
Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Size = new Vector2(48),
|
||||||
|
Scale = defaultIconScale,
|
||||||
|
Icon = FontAwesome.Solid.AngleRight,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
{
|
||||||
|
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(parentObject, parentObject.State.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState _)
|
||||||
|
{
|
||||||
|
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||||
|
// animations which in this case have no visual impact (due to
|
||||||
|
// instant fade) but may negatively affect performance
|
||||||
|
if (drawableObject is not DrawableSlider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const float duration = 200;
|
||||||
|
const float icon_scale = 0.9f;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
|
||||||
|
{
|
||||||
|
this.FadeInFromZero(duration, Easing.OutQuint);
|
||||||
|
icon.ScaleTo(0).Then().ScaleTo(defaultIconScale, duration, Easing.OutElasticHalf);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
{
|
||||||
|
this.FadeOut(duration, Easing.OutQuint);
|
||||||
|
icon.ScaleTo(defaultIconScale * icon_scale, duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
//undo rotation on layers which should not be rotated.
|
||||||
|
float appliedRotation = Parent.Rotation;
|
||||||
|
|
||||||
|
fill.Rotation = -appliedRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
parentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs
Normal file
40
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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.Extensions.Color4Extensions;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSliderBody : PlaySliderBody
|
||||||
|
{
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
const float path_radius = ArgonMainCirclePiece.OUTER_GRADIENT_SIZE / 2;
|
||||||
|
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AccentColourBindable.BindValueChanged(accent => BorderColour = accent.NewValue, true);
|
||||||
|
ScaleBindable.BindValueChanged(scale => PathRadius = path_radius * scale.NewValue, true);
|
||||||
|
|
||||||
|
// This border size thing is kind of weird, hey.
|
||||||
|
const float intended_thickness = ArgonMainCirclePiece.GRADIENT_THICKNESS / path_radius;
|
||||||
|
|
||||||
|
BorderSize = intended_thickness / Default.DrawableSliderPath.BORDER_PORTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Default.DrawableSliderPath CreateSliderPath() => new DrawableSliderPath();
|
||||||
|
|
||||||
|
private class DrawableSliderPath : Default.DrawableSliderPath
|
||||||
|
{
|
||||||
|
protected override Color4 ColourAt(float position)
|
||||||
|
{
|
||||||
|
if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion)
|
||||||
|
return BorderColour;
|
||||||
|
|
||||||
|
return AccentColour.Darken(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
// 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.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSliderScorePoint : CircularContainer
|
||||||
|
{
|
||||||
|
private Bindable<Color4> accentColour = null!;
|
||||||
|
|
||||||
|
private const float size = 12;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
Masking = true;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Size = new Vector2(size);
|
||||||
|
BorderThickness = 3;
|
||||||
|
BorderColour = Color4.White;
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
accentColour = hitObject.AccentColour.GetBoundCopy();
|
||||||
|
accentColour.BindValueChanged(accent => BorderColour = accent.NewValue, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs
Normal file
146
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// 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.Globalization;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSpinner : CompositeDrawable
|
||||||
|
{
|
||||||
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
|
|
||||||
|
private OsuSpriteText bonusCounter = null!;
|
||||||
|
|
||||||
|
private Container spmContainer = null!;
|
||||||
|
private OsuSpriteText spmCounter = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
drawableSpinner = (DrawableSpinner)drawableHitObject;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
bonusCounter = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Default.With(size: 24),
|
||||||
|
Y = -120,
|
||||||
|
},
|
||||||
|
new ArgonSpinnerDisc
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
bonusCounter = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Bold),
|
||||||
|
Y = -100,
|
||||||
|
},
|
||||||
|
spmContainer = new Container
|
||||||
|
{
|
||||||
|
Alpha = 0f,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 60,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
spmCounter = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = @"0",
|
||||||
|
Font = OsuFont.Default.With(size: 28, weight: FontWeight.SemiBold)
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = @"SPINS PER MINUTE",
|
||||||
|
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
|
Y = 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBindable<double> gainedBonus = null!;
|
||||||
|
private IBindable<double> spinsPerMinute = null!;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy();
|
||||||
|
gainedBonus.BindValueChanged(bonus =>
|
||||||
|
{
|
||||||
|
bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo);
|
||||||
|
bonusCounter.FadeOutFromOne(1500);
|
||||||
|
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
|
||||||
|
});
|
||||||
|
|
||||||
|
spinsPerMinute = drawableSpinner.SpinsPerMinute.GetBoundCopy();
|
||||||
|
spinsPerMinute.BindValueChanged(spm =>
|
||||||
|
{
|
||||||
|
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
||||||
|
fadeCounterOnTimeStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
if (!(drawableHitObject is DrawableSpinner))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fadeCounterOnTimeStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fadeCounterOnTimeStart()
|
||||||
|
{
|
||||||
|
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||||
|
{
|
||||||
|
using (BeginAbsoluteSequence(startTime))
|
||||||
|
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableSpinner.IsNotNull())
|
||||||
|
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
247
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs
Normal file
247
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSpinnerDisc : CompositeDrawable
|
||||||
|
{
|
||||||
|
private const float initial_scale = 1f;
|
||||||
|
private const float idle_alpha = 0.2f;
|
||||||
|
private const float tracking_alpha = 0.4f;
|
||||||
|
|
||||||
|
private const float idle_centre_size = 80f;
|
||||||
|
private const float tracking_centre_size = 40f;
|
||||||
|
|
||||||
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
|
|
||||||
|
private readonly BindableBool complete = new BindableBool();
|
||||||
|
|
||||||
|
private int wholeRotationCount;
|
||||||
|
|
||||||
|
private bool checkNewRotationCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int rotations = (int)(drawableSpinner.Result.RateAdjustedRotation / 360);
|
||||||
|
|
||||||
|
if (wholeRotationCount == rotations) return false;
|
||||||
|
|
||||||
|
wholeRotationCount = rotations;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Container disc = null!;
|
||||||
|
private Container centre = null!;
|
||||||
|
private CircularContainer fill = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
drawableSpinner = (DrawableSpinner)drawableHitObject;
|
||||||
|
|
||||||
|
// we are slightly bigger than our parent, to clip the top and bottom of the circle
|
||||||
|
// this should probably be revisited when scaled spinners are a thing.
|
||||||
|
Scale = new Vector2(initial_scale);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
disc = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
fill = new CircularContainer
|
||||||
|
{
|
||||||
|
Name = @"Fill",
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Colour4.FromHex("FC618F").Opacity(1f),
|
||||||
|
Radius = 40,
|
||||||
|
},
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0f,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
Name = @"Ring",
|
||||||
|
Masking = true,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
BorderThickness = 5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ArgonSpinnerTicks(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
centre = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(idle_centre_size),
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new RingPiece(10)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(0.8f),
|
||||||
|
},
|
||||||
|
new RingPiece(3)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(1f),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
|
||||||
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
complete.Value = Time.Current >= drawableSpinner.Result.TimeCompleted;
|
||||||
|
|
||||||
|
if (complete.Value)
|
||||||
|
{
|
||||||
|
if (checkNewRotationCount)
|
||||||
|
{
|
||||||
|
fill.FinishTransforms(false, nameof(Alpha));
|
||||||
|
fill
|
||||||
|
.FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo)
|
||||||
|
.Then()
|
||||||
|
.FadeTo(tracking_alpha, 250, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centre.Width == idle_centre_size && drawableSpinner.Result?.TimeStarted != null)
|
||||||
|
updateCentrePieceSize();
|
||||||
|
|
||||||
|
const float initial_fill_scale = 0.1f;
|
||||||
|
float targetScale = initial_fill_scale + (0.98f - initial_fill_scale) * drawableSpinner.Progress;
|
||||||
|
|
||||||
|
fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
|
||||||
|
disc.Rotation = drawableSpinner.RotationTracker.Rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
if (!(drawableHitObject is DrawableSpinner))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Spinner spinner = drawableSpinner.HitObject;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
|
{
|
||||||
|
this.ScaleTo(initial_scale);
|
||||||
|
this.RotateTo(0);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
|
{
|
||||||
|
// constant ambient rotation to give the spinner "spinning" character.
|
||||||
|
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Hit:
|
||||||
|
this.ScaleTo(initial_scale * 1.2f, 320, Easing.Out);
|
||||||
|
this.RotateTo(Rotation + 180, 320);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ArmedState.Miss:
|
||||||
|
this.ScaleTo(initial_scale * 0.8f, 320, Easing.In);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0);
|
||||||
|
disc.ScaleTo(0);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||||
|
disc.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0.8f, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
|
disc.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawableSpinner.Result?.TimeStarted != null)
|
||||||
|
updateCentrePieceSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCentrePieceSize()
|
||||||
|
{
|
||||||
|
Debug.Assert(drawableSpinner.Result?.TimeStarted != null);
|
||||||
|
|
||||||
|
Spinner spinner = drawableSpinner.HitObject;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableSpinner.Result.TimeStarted.Value))
|
||||||
|
centre.ResizeTo(new Vector2(tracking_centre_size), spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableSpinner.IsNotNull())
|
||||||
|
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerTicks.cs
Normal file
61
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerTicks.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSpinnerTicks : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
const float count = 25;
|
||||||
|
|
||||||
|
for (float i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
AddInternal(new CircularContainer
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
BorderThickness = 2f,
|
||||||
|
Size = new Vector2(30, 5),
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Position = new Vector2(
|
||||||
|
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.75f,
|
||||||
|
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.75f
|
||||||
|
),
|
||||||
|
Rotation = -i / count * 360 - 120,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Colour4.White.Opacity(0.2f),
|
||||||
|
Radius = 30,
|
||||||
|
},
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.Graphics;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class OsuArgonSkinTransformer : SkinTransformer
|
||||||
|
{
|
||||||
|
public OsuArgonSkinTransformer(ISkin skin)
|
||||||
|
: base(skin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
|
{
|
||||||
|
switch (component)
|
||||||
|
{
|
||||||
|
case GameplaySkinComponent<HitResult> resultComponent:
|
||||||
|
return new ArgonJudgementPiece(resultComponent.Component);
|
||||||
|
|
||||||
|
case OsuSkinComponent osuComponent:
|
||||||
|
switch (osuComponent.Component)
|
||||||
|
{
|
||||||
|
case OsuSkinComponents.HitCircle:
|
||||||
|
return new ArgonMainCirclePiece(true);
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderHeadHitCircle:
|
||||||
|
return new ArgonMainCirclePiece(false);
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderBody:
|
||||||
|
return new ArgonSliderBody();
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderBall:
|
||||||
|
return new ArgonSliderBall();
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderFollowCircle:
|
||||||
|
return new ArgonFollowCircle();
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderScorePoint:
|
||||||
|
return new ArgonSliderScorePoint();
|
||||||
|
|
||||||
|
case OsuSkinComponents.SpinnerBody:
|
||||||
|
return new ArgonSpinner();
|
||||||
|
|
||||||
|
case OsuSkinComponents.ReverseArrow:
|
||||||
|
return new ArgonReverseArrow();
|
||||||
|
|
||||||
|
case OsuSkinComponents.FollowPoint:
|
||||||
|
return new ArgonFollowPoint();
|
||||||
|
|
||||||
|
case OsuSkinComponents.Cursor:
|
||||||
|
return new ArgonCursor();
|
||||||
|
|
||||||
|
case OsuSkinComponents.CursorTrail:
|
||||||
|
return new ArgonCursorTrail();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDrawableComponent(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public abstract class DrawableSliderPath : SmoothPath
|
public abstract class DrawableSliderPath : SmoothPath
|
||||||
{
|
{
|
||||||
protected const float BORDER_PORTION = 0.128f;
|
public const float BORDER_PORTION = 0.128f;
|
||||||
protected const float GRADIENT_PORTION = 1 - BORDER_PORTION;
|
public const float GRADIENT_PORTION = 1 - BORDER_PORTION;
|
||||||
|
|
||||||
private const float border_max_size = 8f;
|
private const float border_max_size = 8f;
|
||||||
private const float border_min_size = 0f;
|
private const float border_min_size = 0f;
|
||||||
|
@ -16,9 +16,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public abstract class PlaySliderBody : SnakingSliderBody
|
public abstract class PlaySliderBody : SnakingSliderBody
|
||||||
{
|
{
|
||||||
private IBindable<float> scaleBindable;
|
protected IBindable<float> ScaleBindable { get; private set; } = null!;
|
||||||
|
|
||||||
|
protected IBindable<Color4> AccentColourBindable { get; private set; } = null!;
|
||||||
|
|
||||||
private IBindable<int> pathVersion;
|
private IBindable<int> pathVersion;
|
||||||
private IBindable<Color4> accentColour;
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private OsuRulesetConfigManager config { get; set; }
|
private OsuRulesetConfigManager config { get; set; }
|
||||||
@ -30,14 +32,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
var drawableSlider = (DrawableSlider)drawableObject;
|
var drawableSlider = (DrawableSlider)drawableObject;
|
||||||
|
|
||||||
scaleBindable = drawableSlider.ScaleBindable.GetBoundCopy();
|
ScaleBindable = drawableSlider.ScaleBindable.GetBoundCopy();
|
||||||
scaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true);
|
ScaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true);
|
||||||
|
|
||||||
pathVersion = drawableSlider.PathVersion.GetBoundCopy();
|
pathVersion = drawableSlider.PathVersion.GetBoundCopy();
|
||||||
pathVersion.BindValueChanged(_ => Refresh());
|
pathVersion.BindValueChanged(_ => Refresh());
|
||||||
|
|
||||||
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
AccentColourBindable = drawableObject.AccentColour.GetBoundCopy();
|
||||||
accentColour.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true);
|
AccentColourBindable.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true);
|
||||||
|
|
||||||
config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
|
config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
|
||||||
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut);
|
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut);
|
||||||
|
@ -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 osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -14,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public class RingPiece : CircularContainer
|
public class RingPiece : CircularContainer
|
||||||
{
|
{
|
||||||
public RingPiece()
|
public RingPiece(float thickness = 9)
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
@ -22,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
BorderThickness = 9; // roughly matches slider borders and makes stacked circles distinctly visible from each other.
|
BorderThickness = thickness;
|
||||||
BorderColour = Color4.White;
|
BorderColour = Color4.White;
|
||||||
|
|
||||||
Child = new Box
|
Child = new Box
|
||||||
|
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null);
|
var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null);
|
||||||
|
|
||||||
if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin))
|
if (topProvider is ISkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin))
|
||||||
{
|
{
|
||||||
AddInternal(ApproachCircle = new Sprite
|
AddInternal(ApproachCircle = new Sprite
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
@ -129,5 +130,32 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
|||||||
AssertResult<Hit>(0, HitResult.Miss);
|
AssertResult<Hit>(0, HitResult.Miss);
|
||||||
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHighVelocityHit()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
var beatmap = CreateBeatmap(new Hit
|
||||||
|
{
|
||||||
|
Type = HitType.Centre,
|
||||||
|
StartTime = hit_time,
|
||||||
|
});
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 6 });
|
||||||
|
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 10 });
|
||||||
|
|
||||||
|
var hitWindows = new HitWindows();
|
||||||
|
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
||||||
|
|
||||||
|
PerformTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
new TaikoReplayFrame(hit_time - hitWindows.WindowFor(HitResult.Great), TaikoAction.LeftCentre),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
AssertJudgementCount(1);
|
||||||
|
AssertResult<Hit>(0, HitResult.Ok);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
private int countOk;
|
private int countOk;
|
||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
private double accuracy;
|
||||||
|
|
||||||
private double effectiveMissCount;
|
private double effectiveMissCount;
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
|
accuracy = customAccuracy;
|
||||||
|
|
||||||
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
|
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
|
||||||
if (totalSuccessfulHits > 0)
|
if (totalSuccessfulHits > 0)
|
||||||
@ -87,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
||||||
difficultyValue *= 1.050 * lengthBonus;
|
difficultyValue *= 1.050 * lengthBonus;
|
||||||
|
|
||||||
return difficultyValue * Math.Pow(score.Accuracy, 2.0);
|
return difficultyValue * Math.Pow(accuracy, 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
|
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
|
||||||
@ -95,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
if (attributes.GreatHitWindow <= 0)
|
if (attributes.GreatHitWindow <= 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
|
double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
|
||||||
|
|
||||||
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
||||||
accuracyValue *= lengthBonus;
|
accuracyValue *= lengthBonus;
|
||||||
@ -110,5 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||||
|
|
||||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
||||||
|
|
||||||
|
private double customAccuracy => totalHits > 0 ? (countGreat * 300 + countOk * 150) / (totalHits * 300.0) : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
@ -17,22 +16,14 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||||
|
|
||||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 0.5f,
|
MinValue = 0.5f,
|
||||||
MaxValue = 1.5f,
|
MaxValue = 1.5f,
|
||||||
Default = 1f,
|
|
||||||
Value = 1f,
|
|
||||||
Precision = 0.1f
|
Precision = 0.1f
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||||
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
|
||||||
{
|
|
||||||
Default = true,
|
|
||||||
Value = true
|
|
||||||
};
|
|
||||||
|
|
||||||
public override float DefaultFlashlightSize => 250;
|
public override float DefaultFlashlightSize => 250;
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
Filled = HitObject.FirstTick
|
Filled = HitObject.FirstTick
|
||||||
});
|
});
|
||||||
|
|
||||||
protected override double MaximumJudgementOffset => HitObject.HitWindow;
|
public override double MaximumJudgementOffset => HitObject.HitWindow;
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
|
@ -306,7 +306,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
new Color4(128, 255, 128, 255),
|
new Color4(128, 255, 128, 255),
|
||||||
new Color4(255, 187, 255, 255),
|
new Color4(255, 187, 255, 255),
|
||||||
new Color4(255, 177, 140, 255),
|
new Color4(255, 177, 140, 255),
|
||||||
new Color4(100, 100, 100, 100),
|
new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored.
|
||||||
};
|
};
|
||||||
Assert.AreEqual(expectedColors.Length, comboColors.Count);
|
Assert.AreEqual(expectedColors.Length, comboColors.Count);
|
||||||
for (int i = 0; i < expectedColors.Length; i++)
|
for (int i = 0; i < expectedColors.Length; i++)
|
||||||
|
@ -204,31 +204,23 @@ namespace osu.Game.Tests.Online
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||||
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
|
public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1.5)
|
||||||
{
|
{
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
MaxValue = 2,
|
MaxValue = 2,
|
||||||
Default = 1.5,
|
|
||||||
Value = 1.5,
|
|
||||||
Precision = 0.01,
|
Precision = 0.01,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Final rate", "The speed increase to ramp towards")]
|
[SettingSource("Final rate", "The speed increase to ramp towards")]
|
||||||
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
|
public override BindableNumber<double> FinalRate { get; } = new BindableDouble(0.5)
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 1,
|
MaxValue = 1,
|
||||||
Default = 0.5,
|
|
||||||
Value = 0.5,
|
|
||||||
Precision = 0.01,
|
Precision = 0.01,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
||||||
public override BindableBool AdjustPitch { get; } = new BindableBool
|
public override BindableBool AdjustPitch { get; } = new BindableBool(true);
|
||||||
{
|
|
||||||
Default = true,
|
|
||||||
Value = true
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestModDifficultyAdjust : ModDifficultyAdjust
|
private class TestModDifficultyAdjust : ModDifficultyAdjust
|
||||||
|
@ -124,31 +124,23 @@ namespace osu.Game.Tests.Online
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||||
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
|
public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1.5)
|
||||||
{
|
{
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
MaxValue = 2,
|
MaxValue = 2,
|
||||||
Default = 1.5,
|
|
||||||
Value = 1.5,
|
|
||||||
Precision = 0.01,
|
Precision = 0.01,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Final rate", "The speed increase to ramp towards")]
|
[SettingSource("Final rate", "The speed increase to ramp towards")]
|
||||||
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
|
public override BindableNumber<double> FinalRate { get; } = new BindableDouble(0.5)
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 1,
|
MaxValue = 1,
|
||||||
Default = 0.5,
|
|
||||||
Value = 0.5,
|
|
||||||
Precision = 0.01,
|
Precision = 0.01,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
||||||
public override BindableBool AdjustPitch { get; } = new BindableBool
|
public override BindableBool AdjustPitch { get; } = new BindableBool(true);
|
||||||
{
|
|
||||||
Default = true,
|
|
||||||
Value = true
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestModEnum : Mod
|
private class TestModEnum : Mod
|
||||||
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -78,7 +77,7 @@ namespace osu.Game.Tests.Online
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
beatmaps.AllowImport = new TaskCompletionSource<bool>();
|
beatmaps.AllowImport.Reset();
|
||||||
|
|
||||||
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
|
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
|
||||||
|
|
||||||
@ -132,7 +131,7 @@ namespace osu.Game.Tests.Online
|
|||||||
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile));
|
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile));
|
||||||
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
||||||
|
|
||||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
AddStep("allow importing", () => beatmaps.AllowImport.Set());
|
||||||
AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
|
AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
|
||||||
AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet));
|
AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet));
|
||||||
addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable);
|
addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
@ -141,7 +140,7 @@ namespace osu.Game.Tests.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestTrackerRespectsSoftDeleting()
|
public void TestTrackerRespectsSoftDeleting()
|
||||||
{
|
{
|
||||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
AddStep("allow importing", () => beatmaps.AllowImport.Set());
|
||||||
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
|
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
|
||||||
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
|
|
||||||
@ -155,7 +154,7 @@ namespace osu.Game.Tests.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestTrackerRespectsChecksum()
|
public void TestTrackerRespectsChecksum()
|
||||||
{
|
{
|
||||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
AddStep("allow importing", () => beatmaps.AllowImport.Set());
|
||||||
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
|
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
|
||||||
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
|
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
|
|
||||||
@ -202,7 +201,7 @@ namespace osu.Game.Tests.Online
|
|||||||
|
|
||||||
private class TestBeatmapManager : BeatmapManager
|
private class TestBeatmapManager : BeatmapManager
|
||||||
{
|
{
|
||||||
public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>();
|
public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim();
|
||||||
|
|
||||||
public Live<BeatmapSetInfo> CurrentImport { get; private set; }
|
public Live<BeatmapSetInfo> CurrentImport { get; private set; }
|
||||||
|
|
||||||
@ -229,7 +228,9 @@ namespace osu.Game.Tests.Online
|
|||||||
|
|
||||||
public override Live<BeatmapSetInfo> ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
|
public override Live<BeatmapSetInfo> ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
testBeatmapManager.AllowImport.Task.WaitSafely();
|
if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken))
|
||||||
|
throw new TimeoutException("Timeout waiting for import to be allowed.");
|
||||||
|
|
||||||
return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken));
|
return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
skinManager.CurrentSkinInfo.Value.PerformRead(s =>
|
skinManager.CurrentSkinInfo.Value.PerformRead(s =>
|
||||||
{
|
{
|
||||||
Assert.IsFalse(s.Protected);
|
Assert.IsFalse(s.Protected);
|
||||||
Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType());
|
Assert.AreEqual(typeof(ArgonSkin), s.CreateInstance(skinManager).GetType());
|
||||||
|
|
||||||
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
{
|
{
|
||||||
Assert.IsFalse(s.Protected);
|
Assert.IsFalse(s.Protected);
|
||||||
Assert.AreNotEqual(originalSkinId, s.ID);
|
Assert.AreNotEqual(originalSkinId, s.ID);
|
||||||
Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType());
|
Assert.AreEqual(typeof(ArgonSkin), s.CreateInstance(skinManager).GetType());
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@ -226,7 +226,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
{
|
{
|
||||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||||
|
|
||||||
skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo;
|
skinManager.CurrentSkinInfo.Value = skinManager.DefaultClassicSkin.SkinInfo;
|
||||||
|
|
||||||
skinManager.EnsureMutableSkin();
|
skinManager.EnsureMutableSkin();
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Tests.Skins
|
|||||||
new Color4(142, 199, 255, 255),
|
new Color4(142, 199, 255, 255),
|
||||||
new Color4(255, 128, 128, 255),
|
new Color4(255, 128, 128, 255),
|
||||||
new Color4(128, 255, 255, 255),
|
new Color4(128, 255, 255, 255),
|
||||||
new Color4(100, 100, 100, 100),
|
new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored.
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.AreEqual(expectedColors.Count, comboColors.Count);
|
Assert.AreEqual(expectedColors.Count, comboColors.Count);
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -19,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
SetContents(skin =>
|
SetContents(skin =>
|
||||||
{
|
{
|
||||||
var implementation = skin != null
|
var implementation = skin is LegacySkin
|
||||||
? CreateLegacyImplementation()
|
? CreateLegacyImplementation()
|
||||||
: CreateDefaultImplementation();
|
: CreateDefaultImplementation();
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||||
{
|
{
|
||||||
CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
|
CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
|
||||||
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
||||||
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||||
}
|
}
|
||||||
|
117
osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs
Normal file
117
osu.Game.Tests/Visual/Gameplay/TestSceneColourHitErrorMeter.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneColourHitErrorMeter : OsuTestScene
|
||||||
|
{
|
||||||
|
private DependencyProvidingContainer dependencyContainer = null!;
|
||||||
|
|
||||||
|
private readonly Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
|
||||||
|
private ScoreProcessor scoreProcessor = null!;
|
||||||
|
|
||||||
|
private int iteration;
|
||||||
|
|
||||||
|
private ColourHitErrorMeter colourHitErrorMeter = null!;
|
||||||
|
|
||||||
|
public TestSceneColourHitErrorMeter()
|
||||||
|
{
|
||||||
|
AddSliderStep("Judgement spacing", 0, 10, 2, spacing =>
|
||||||
|
{
|
||||||
|
if (colourHitErrorMeter.IsNotNull())
|
||||||
|
colourHitErrorMeter.JudgementSpacing.Value = spacing;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddSliderStep("Judgement count", 1, 50, 5, spacing =>
|
||||||
|
{
|
||||||
|
if (colourHitErrorMeter.IsNotNull())
|
||||||
|
colourHitErrorMeter.JudgementCount.Value = spacing;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetupSteps() => AddStep("Create components", () =>
|
||||||
|
{
|
||||||
|
var ruleset = CreateRuleset();
|
||||||
|
|
||||||
|
Debug.Assert(ruleset != null);
|
||||||
|
|
||||||
|
scoreProcessor = new ScoreProcessor(ruleset);
|
||||||
|
Child = dependencyContainer = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[]
|
||||||
|
{
|
||||||
|
(typeof(ScoreProcessor), scoreProcessor)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dependencyContainer.Child = colourHitErrorMeter = new ColourHitErrorMeter
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = 100
|
||||||
|
},
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Scale = new Vector2(2),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpacingChange()
|
||||||
|
{
|
||||||
|
AddRepeatStep("Add judgement", applyOneJudgement, 5);
|
||||||
|
AddStep("Change spacing", () => colourHitErrorMeter.JudgementSpacing.Value = 10);
|
||||||
|
AddRepeatStep("Add judgement", applyOneJudgement, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestJudgementAmountChange()
|
||||||
|
{
|
||||||
|
AddRepeatStep("Add judgement", applyOneJudgement, 10);
|
||||||
|
AddStep("Judgement count change to 4", () => colourHitErrorMeter.JudgementCount.Value = 4);
|
||||||
|
AddRepeatStep("Add judgement", applyOneJudgement, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitErrorShapeChange()
|
||||||
|
{
|
||||||
|
AddRepeatStep("Add judgement", applyOneJudgement, 8);
|
||||||
|
AddStep("Change shape square", () => colourHitErrorMeter.JudgementShape.Value = ColourHitErrorMeter.ShapeStyle.Square);
|
||||||
|
AddRepeatStep("Add judgement", applyOneJudgement, 10);
|
||||||
|
AddStep("Change shape circle", () => colourHitErrorMeter.JudgementShape.Value = ColourHitErrorMeter.ShapeStyle.Circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyOneJudgement()
|
||||||
|
{
|
||||||
|
lastJudgementResult.Value = new OsuJudgementResult(new HitObject
|
||||||
|
{
|
||||||
|
StartTime = iteration * 10000,
|
||||||
|
}, new OsuJudgement())
|
||||||
|
{
|
||||||
|
Type = HitResult.Great,
|
||||||
|
};
|
||||||
|
scoreProcessor.ApplyResult(lastJudgementResult.Value);
|
||||||
|
|
||||||
|
iteration++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,9 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.PolygonExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -18,37 +20,62 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneGameplayLeaderboard : OsuTestScene
|
public class TestSceneGameplayLeaderboard : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly TestGameplayLeaderboard leaderboard;
|
private TestGameplayLeaderboard leaderboard;
|
||||||
|
|
||||||
private readonly BindableDouble playerScore = new BindableDouble();
|
private readonly BindableDouble playerScore = new BindableDouble();
|
||||||
|
|
||||||
public TestSceneGameplayLeaderboard()
|
public TestSceneGameplayLeaderboard()
|
||||||
{
|
{
|
||||||
Add(leaderboard = new TestGameplayLeaderboard
|
AddStep("toggle expanded", () =>
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
if (leaderboard != null)
|
||||||
Origin = Anchor.Centre,
|
leaderboard.Expanded.Value = !leaderboard.Expanded.Value;
|
||||||
Scale = new Vector2(2),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[Test]
|
||||||
public void SetUpSteps()
|
public void TestLayoutWithManyScores()
|
||||||
{
|
{
|
||||||
AddStep("reset leaderboard", () =>
|
createLeaderboard();
|
||||||
|
|
||||||
|
AddStep("add many scores in one go", () =>
|
||||||
{
|
{
|
||||||
leaderboard.Clear();
|
for (int i = 0; i < 32; i++)
|
||||||
playerScore.Value = 1222333;
|
createRandomScore(new APIUser { Username = $"Player {i + 1}" });
|
||||||
|
|
||||||
|
// Add player at end to force an animation down the whole list.
|
||||||
|
playerScore.Value = 0;
|
||||||
|
createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add local player", () => createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true));
|
// Gameplay leaderboard has custom scroll logic, which when coupled with LayoutDuration
|
||||||
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
|
// has caused layout to not work in the past.
|
||||||
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
|
||||||
|
AddUntilStep("wait for fill flow layout",
|
||||||
|
() => leaderboard.ChildrenOfType<FillFlowContainer<GameplayLeaderboardScore>>().First().ScreenSpaceDrawQuad.Intersects(leaderboard.ScreenSpaceDrawQuad));
|
||||||
|
|
||||||
|
AddUntilStep("wait for some scores not masked away",
|
||||||
|
() => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre)));
|
||||||
|
|
||||||
|
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
|
||||||
|
|
||||||
|
AddStep("change score to middle", () => playerScore.Value = 1000000);
|
||||||
|
AddWaitStep("wait for movement", 5);
|
||||||
|
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
|
||||||
|
|
||||||
|
AddStep("change score to first", () => playerScore.Value = 5000000);
|
||||||
|
AddWaitStep("wait for movement", 5);
|
||||||
|
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPlayerScore()
|
public void TestPlayerScore()
|
||||||
{
|
{
|
||||||
|
createLeaderboard();
|
||||||
|
addLocalPlayer();
|
||||||
|
|
||||||
var player2Score = new BindableDouble(1234567);
|
var player2Score = new BindableDouble(1234567);
|
||||||
var player3Score = new BindableDouble(1111111);
|
var player3Score = new BindableDouble(1111111);
|
||||||
|
|
||||||
@ -73,6 +100,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRandomScores()
|
public void TestRandomScores()
|
||||||
{
|
{
|
||||||
|
createLeaderboard();
|
||||||
|
addLocalPlayer();
|
||||||
|
|
||||||
int playerNumber = 1;
|
int playerNumber = 1;
|
||||||
AddRepeatStep("add player with random score", () => createRandomScore(new APIUser { Username = $"Player {playerNumber++}" }), 10);
|
AddRepeatStep("add player with random score", () => createRandomScore(new APIUser { Username = $"Player {playerNumber++}" }), 10);
|
||||||
}
|
}
|
||||||
@ -80,6 +110,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExistingUsers()
|
public void TestExistingUsers()
|
||||||
{
|
{
|
||||||
|
createLeaderboard();
|
||||||
|
addLocalPlayer();
|
||||||
|
|
||||||
AddStep("add peppy", () => createRandomScore(new APIUser { Username = "peppy", Id = 2 }));
|
AddStep("add peppy", () => createRandomScore(new APIUser { Username = "peppy", Id = 2 }));
|
||||||
AddStep("add smoogipoo", () => createRandomScore(new APIUser { Username = "smoogipoo", Id = 1040328 }));
|
AddStep("add smoogipoo", () => createRandomScore(new APIUser { Username = "smoogipoo", Id = 1040328 }));
|
||||||
AddStep("add flyte", () => createRandomScore(new APIUser { Username = "flyte", Id = 3103765 }));
|
AddStep("add flyte", () => createRandomScore(new APIUser { Username = "flyte", Id = 3103765 }));
|
||||||
@ -89,6 +122,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMaxHeight()
|
public void TestMaxHeight()
|
||||||
{
|
{
|
||||||
|
createLeaderboard();
|
||||||
|
addLocalPlayer();
|
||||||
|
|
||||||
int playerNumber = 1;
|
int playerNumber = 1;
|
||||||
AddRepeatStep("add 3 other players", () => createRandomScore(new APIUser { Username = $"Player {playerNumber++}" }), 3);
|
AddRepeatStep("add 3 other players", () => createRandomScore(new APIUser { Username = $"Player {playerNumber++}" }), 3);
|
||||||
checkHeight(4);
|
checkHeight(4);
|
||||||
@ -103,6 +139,28 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
=> AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
|
=> AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addLocalPlayer()
|
||||||
|
{
|
||||||
|
AddStep("add local player", () =>
|
||||||
|
{
|
||||||
|
playerScore.Value = 1222333;
|
||||||
|
createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createLeaderboard()
|
||||||
|
{
|
||||||
|
AddStep("create leaderboard", () =>
|
||||||
|
{
|
||||||
|
Child = leaderboard = new TestGameplayLeaderboard
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(2),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void createRandomScore(APIUser user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
|
private void createRandomScore(APIUser user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
|
||||||
|
|
||||||
private void createLeaderboardScore(BindableDouble score, APIUser user, bool isTracked = false)
|
private void createLeaderboardScore(BindableDouble score, APIUser user, bool isTracked = false)
|
||||||
|
@ -107,13 +107,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddAssert("circle added", () =>
|
AddAssert("circle added", () =>
|
||||||
this.ChildrenOfType<ColourHitErrorMeter>().All(
|
this.ChildrenOfType<ColourHitErrorMeter>().All(
|
||||||
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() == 1));
|
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() == 1));
|
||||||
|
|
||||||
AddStep("miss", () => newJudgement(50, HitResult.Miss));
|
AddStep("miss", () => newJudgement(50, HitResult.Miss));
|
||||||
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddAssert("circle added", () =>
|
AddAssert("circle added", () =>
|
||||||
this.ChildrenOfType<ColourHitErrorMeter>().All(
|
this.ChildrenOfType<ColourHitErrorMeter>().All(
|
||||||
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() == 2));
|
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() == 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -123,11 +123,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("small bonus", () => newJudgement(result: HitResult.SmallBonus));
|
AddStep("small bonus", () => newJudgement(result: HitResult.SmallBonus));
|
||||||
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
|
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
|
||||||
|
|
||||||
AddStep("large bonus", () => newJudgement(result: HitResult.LargeBonus));
|
AddStep("large bonus", () => newJudgement(result: HitResult.LargeBonus));
|
||||||
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
|
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -137,16 +137,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("ignore hit", () => newJudgement(result: HitResult.IgnoreHit));
|
AddStep("ignore hit", () => newJudgement(result: HitResult.IgnoreHit));
|
||||||
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
|
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
|
||||||
|
|
||||||
AddStep("ignore miss", () => newJudgement(result: HitResult.IgnoreMiss));
|
AddStep("ignore miss", () => newJudgement(result: HitResult.IgnoreMiss));
|
||||||
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
|
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestProcessingWhileHidden()
|
public void TestProcessingWhileHidden()
|
||||||
{
|
{
|
||||||
|
const int max_displayed_judgements = 20;
|
||||||
AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1));
|
AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1));
|
||||||
|
|
||||||
AddStep("hide displays", () =>
|
AddStep("hide displays", () =>
|
||||||
@ -155,16 +156,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
hitErrorMeter.Hide();
|
hitErrorMeter.Hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddRepeatStep("hit", () => newJudgement(), ColourHitErrorMeter.MAX_DISPLAYED_JUDGEMENTS * 2);
|
AddRepeatStep("hit", () => newJudgement(), max_displayed_judgements * 2);
|
||||||
|
|
||||||
AddAssert("bars added", () => this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddAssert("bars added", () => this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddAssert("circle added", () => this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
|
AddAssert("circle added", () => this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
|
||||||
|
|
||||||
AddUntilStep("wait for bars to disappear", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddUntilStep("wait for bars to disappear", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddUntilStep("ensure max circles not exceeded", () =>
|
AddUntilStep("ensure max circles not exceeded", () =>
|
||||||
{
|
{
|
||||||
return this.ChildrenOfType<ColourHitErrorMeter>()
|
return this.ChildrenOfType<ColourHitErrorMeter>()
|
||||||
.All(m => m.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() <= ColourHitErrorMeter.MAX_DISPLAYED_JUDGEMENTS);
|
.All(m => m.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() <= max_displayed_judgements);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("show displays", () =>
|
AddStep("show displays", () =>
|
||||||
@ -183,12 +184,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("bar added", () => this.ChildrenOfType<BarHitErrorMeter>().All(
|
AddAssert("bar added", () => this.ChildrenOfType<BarHitErrorMeter>().All(
|
||||||
meter => meter.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Count() == 1));
|
meter => meter.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Count() == 1));
|
||||||
AddAssert("circle added", () => this.ChildrenOfType<ColourHitErrorMeter>().All(
|
AddAssert("circle added", () => this.ChildrenOfType<ColourHitErrorMeter>().All(
|
||||||
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() == 1));
|
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() == 1));
|
||||||
|
|
||||||
AddStep("clear", () => this.ChildrenOfType<HitErrorMeter>().ForEach(meter => meter.Clear()));
|
AddStep("clear", () => this.ChildrenOfType<HitErrorMeter>().ForEach(meter => meter.Clear()));
|
||||||
|
|
||||||
AddAssert("bar cleared", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
AddAssert("bar cleared", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
|
||||||
AddAssert("colour cleared", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
|
AddAssert("colour cleared", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
|
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
|
||||||
|
@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TestParticleSpewer createSpewer() =>
|
private TestParticleSpewer createSpewer() =>
|
||||||
new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2"))
|
new TestParticleSpewer(skinManager.DefaultClassicSkin.GetTexture("star2"))
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativePositionAxes = Axes.Both,
|
RelativePositionAxes = Axes.Both,
|
||||||
|
@ -264,13 +264,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMutedNotificationMasterVolume()
|
public void TestMutedNotificationMasterVolume()
|
||||||
{
|
{
|
||||||
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.IsDefault);
|
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.Value == 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMutedNotificationTrackVolume()
|
public void TestMutedNotificationTrackVolume()
|
||||||
{
|
{
|
||||||
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.IsDefault);
|
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.Value == 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -15,8 +15,10 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -101,6 +103,37 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for last played to update", () => getLastPlayed() != null);
|
AddUntilStep("wait for last played to update", () => getLastPlayed() != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModReferenceNotRetained()
|
||||||
|
{
|
||||||
|
AddStep("allow fail", () => allowFail = false);
|
||||||
|
|
||||||
|
Mod[] originalMods = { new OsuModDaycore { SpeedChange = { Value = 0.8 } } };
|
||||||
|
Mod[] playerMods = null!;
|
||||||
|
|
||||||
|
AddStep("load player with mods", () => LoadPlayer(originalMods));
|
||||||
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
|
|
||||||
|
AddStep("get mods at start of gameplay", () => playerMods = Player.Score.ScoreInfo.Mods.ToArray());
|
||||||
|
|
||||||
|
// Player creates new instance of mods during load.
|
||||||
|
AddAssert("player score has copied mods", () => playerMods.First(), () => Is.Not.SameAs(originalMods.First()));
|
||||||
|
AddAssert("player score has matching mods", () => playerMods.First(), () => Is.EqualTo(originalMods.First()));
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
|
||||||
|
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
|
||||||
|
|
||||||
|
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
|
||||||
|
|
||||||
|
// Player creates new instance of mods after gameplay to ensure any runtime references to drawables etc. are not retained.
|
||||||
|
AddAssert("results screen score has copied mods", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.Not.SameAs(playerMods.First()));
|
||||||
|
AddAssert("results screen score has matching", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.EqualTo(playerMods.First()));
|
||||||
|
|
||||||
|
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
|
||||||
|
AddUntilStep("databased score has correct mods", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID)).Mods.First(), () => Is.EqualTo(playerMods.First()));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScoreStoredLocally()
|
public void TestScoreStoredLocally()
|
||||||
{
|
{
|
||||||
|
@ -11,8 +11,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -167,14 +169,39 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addHitObject(double time)
|
[Test]
|
||||||
|
public void TestVeryFlowScroll()
|
||||||
|
{
|
||||||
|
const double long_time_range = 100000;
|
||||||
|
var manualClock = new ManualClock();
|
||||||
|
|
||||||
|
AddStep("set manual clock", () =>
|
||||||
|
{
|
||||||
|
manualClock.CurrentTime = 0;
|
||||||
|
scrollContainers.ForEach(c => c.Clock = new FramedClock(manualClock));
|
||||||
|
|
||||||
|
setScrollAlgorithm(ScrollVisualisationMethod.Constant);
|
||||||
|
scrollContainers.ForEach(c => c.TimeRange = long_time_range);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add hit objects", () =>
|
||||||
|
{
|
||||||
|
addHitObject(long_time_range);
|
||||||
|
addHitObject(long_time_range + 100, 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("hit objects are alive", () => playfields.All(p => p.HitObjectContainer.AliveObjects.Count() == 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHitObject(double time, float size = 75)
|
||||||
{
|
{
|
||||||
playfields.ForEach(p =>
|
playfields.ForEach(p =>
|
||||||
{
|
{
|
||||||
var hitObject = new TestDrawableHitObject(time);
|
var hitObject = new TestHitObject(size) { StartTime = time };
|
||||||
setAnchor(hitObject, p);
|
var drawable = new TestDrawableHitObject(hitObject);
|
||||||
|
|
||||||
p.Add(hitObject);
|
setAnchor(drawable, p);
|
||||||
|
p.Add(drawable);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +275,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new TestScrollingHitObjectContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
|
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
|
||||||
@ -281,22 +310,41 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableHitObject : DrawableHitObject<HitObject>
|
private class TestHitObject : HitObject
|
||||||
{
|
{
|
||||||
public TestDrawableHitObject(double time)
|
public readonly float Size;
|
||||||
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
|
|
||||||
{
|
|
||||||
Origin = Anchor.Custom;
|
|
||||||
OriginPosition = new Vector2(75 / 4.0f);
|
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
public TestHitObject(float size)
|
||||||
|
{
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableHitObject : DrawableHitObject<TestHitObject>
|
||||||
|
{
|
||||||
|
public TestDrawableHitObject(TestHitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Size = new Vector2(hitObject.Size);
|
||||||
|
|
||||||
AddInternal(new Box
|
AddInternal(new Box
|
||||||
{
|
{
|
||||||
Size = new Vector2(75),
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
|
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestScrollingHitObjectContainer : ScrollingHitObjectContainer
|
||||||
|
{
|
||||||
|
protected override RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry)
|
||||||
|
{
|
||||||
|
if (entry.HitObject is TestHitObject testObject)
|
||||||
|
return new RectangleF().Inflate(testObject.Size / 2);
|
||||||
|
|
||||||
|
return base.GetConservativeBoundingBox(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneSoloGameplayLeaderboard : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||||
|
|
||||||
|
private readonly BindableList<ScoreInfo> scores = new BindableList<ScoreInfo>();
|
||||||
|
|
||||||
|
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
|
||||||
|
|
||||||
|
private SoloGameplayLeaderboard leaderboard = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("clear scores", () => scores.Clear());
|
||||||
|
|
||||||
|
AddStep("create component", () =>
|
||||||
|
{
|
||||||
|
var trackingUser = new APIUser
|
||||||
|
{
|
||||||
|
Username = "local user",
|
||||||
|
Id = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
Child = leaderboard = new SoloGameplayLeaderboard(trackingUser)
|
||||||
|
{
|
||||||
|
Scores = { BindTarget = scores },
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AlwaysVisible = { Value = false },
|
||||||
|
Expanded = { Value = true },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add scores", () => scores.AddRange(createSampleScores()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocalUser()
|
||||||
|
{
|
||||||
|
AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v);
|
||||||
|
AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v);
|
||||||
|
AddSliderStep("combo", 0, 10000, 0, v => scoreProcessor.HighestCombo.Value = v);
|
||||||
|
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVisibility()
|
||||||
|
{
|
||||||
|
AddStep("set config visible true", () => configVisibility.Value = true);
|
||||||
|
AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1);
|
||||||
|
|
||||||
|
AddStep("set config visible false", () => configVisibility.Value = false);
|
||||||
|
AddUntilStep("leaderboard not visible", () => leaderboard.Alpha == 0);
|
||||||
|
|
||||||
|
AddStep("set always visible", () => leaderboard.AlwaysVisible.Value = true);
|
||||||
|
AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1);
|
||||||
|
|
||||||
|
AddStep("set config visible true", () => configVisibility.Value = true);
|
||||||
|
AddAssert("leaderboard still visible", () => leaderboard.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ScoreInfo> createSampleScores()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new ScoreInfo { User = new APIUser { Username = @"peppy" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||||
|
new ScoreInfo { User = new APIUser { Username = @"smoogipoo" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||||
|
new ScoreInfo { User = new APIUser { Username = @"spaceman_atlas" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||||
|
new ScoreInfo { User = new APIUser { Username = @"frenzibyte" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||||
|
new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||||
|
}.Concat(Enumerable.Range(0, 50).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 1000000 - i * 10000 })).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
// 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 System.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -51,13 +51,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestToggleSeeking()
|
public void TestToggleSeeking()
|
||||||
{
|
{
|
||||||
DefaultSongProgress getDefaultProgress() => this.ChildrenOfType<DefaultSongProgress>().Single();
|
void applyToDefaultProgress(Action<DefaultSongProgress> action) =>
|
||||||
|
this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
|
||||||
|
|
||||||
AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true);
|
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
|
||||||
AddStep("hide graph", () => getDefaultProgress().ShowGraph.Value = false);
|
AddStep("hide graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = false));
|
||||||
AddStep("disallow seeking", () => getDefaultProgress().AllowSeeking.Value = false);
|
AddStep("disallow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = false));
|
||||||
AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true);
|
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
|
||||||
AddStep("show graph", () => getDefaultProgress().ShowGraph.Value = true);
|
AddStep("show graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setHitObjects()
|
private void setHitObjects()
|
||||||
|
@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected new OutroPlayer Player => (OutroPlayer)base.Player;
|
protected new OutroPlayer Player => (OutroPlayer)base.Player;
|
||||||
|
|
||||||
|
private double currentBeatmapDuration;
|
||||||
private double currentStoryboardDuration;
|
private double currentStoryboardDuration;
|
||||||
|
|
||||||
private bool showResults = true;
|
private bool showResults = true;
|
||||||
@ -45,7 +46,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
|
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
|
||||||
AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0));
|
AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0));
|
||||||
AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false);
|
AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false);
|
||||||
AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000);
|
AddStep("set beatmap duration to 0s", () => currentBeatmapDuration = 0);
|
||||||
|
AddStep("set storyboard duration to 8s", () => currentStoryboardDuration = 8000);
|
||||||
AddStep("set ShowResults = true", () => showResults = true);
|
AddStep("set ShowResults = true", () => showResults = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +153,24 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("player exited", () => Stack.CurrentScreen == null);
|
AddAssert("player exited", () => Stack.CurrentScreen == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPerformExitAfterOutro()
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("set beatmap duration to 4s", () => currentBeatmapDuration = 4000);
|
||||||
|
AddStep("set storyboard duration to 1s", () => currentStoryboardDuration = 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddStep("exit via pause", () => Player.ExitViaPause());
|
||||||
|
AddAssert("player paused", () => !Player.IsResuming);
|
||||||
|
|
||||||
|
AddStep("resume player", () => Player.Resume());
|
||||||
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool AllowFail => true;
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
@ -160,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap();
|
var beatmap = new Beatmap();
|
||||||
beatmap.HitObjects.Add(new HitCircle());
|
beatmap.HitObjects.Add(new HitCircle { StartTime = currentBeatmapDuration });
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +209,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private event Func<HealthProcessor, JudgementResult, bool> failConditions;
|
private event Func<HealthProcessor, JudgementResult, bool> failConditions;
|
||||||
|
|
||||||
public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true)
|
public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true)
|
||||||
: base(false, showResults)
|
: base(showResults: showResults)
|
||||||
{
|
{
|
||||||
this.failConditions = failConditions;
|
this.failConditions = failConditions;
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
=> AddStep($"set user {userId} time {time}", () => clocks[userId].CurrentTime = time);
|
=> AddStep($"set user {userId} time {time}", () => clocks[userId].CurrentTime = time);
|
||||||
|
|
||||||
private void assertCombo(int userId, int expectedCombo)
|
private void assertCombo(int userId, int expectedCombo)
|
||||||
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo);
|
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.OnlineID == userId).Combo.Value == expectedCombo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -522,7 +522,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType<PlayerArea>().Single(p => p.UserId == userId);
|
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType<PlayerArea>().Single(p => p.UserId == userId);
|
||||||
|
|
||||||
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId);
|
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.OnlineID == userId);
|
||||||
|
|
||||||
private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray();
|
private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray();
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEditDefaultSkin()
|
public void TestEditDefaultSkin()
|
||||||
{
|
{
|
||||||
AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.DEFAULT_SKIN);
|
AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.ARGON_SKIN);
|
||||||
|
|
||||||
AddStep("open settings", () => { Game.Settings.Show(); });
|
AddStep("open settings", () => { Game.Settings.Show(); });
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddStep("open skin editor", () => skinEditor.Show());
|
AddStep("open skin editor", () => skinEditor.Show());
|
||||||
|
|
||||||
// Until step required as the skin editor may take time to load (and an extra scheduled frame for the mutable part).
|
// Until step required as the skin editor may take time to load (and an extra scheduled frame for the mutable part).
|
||||||
AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.DEFAULT_SKIN);
|
AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.ARGON_SKIN);
|
||||||
AddAssert("is not protected", () => skinManager.CurrentSkinInfo.Value.PerformRead(s => !s.Protected));
|
AddAssert("is not protected", () => skinManager.CurrentSkinInfo.Value.PerformRead(s => !s.Protected));
|
||||||
|
|
||||||
AddUntilStep("export button enabled", () => Game.Settings.ChildrenOfType<SkinSection.ExportSkinButton>().SingleOrDefault()?.Enabled.Value == true);
|
AddUntilStep("export button enabled", () => Game.Settings.ChildrenOfType<SkinSection.ExportSkinButton>().SingleOrDefault()?.Enabled.Value == true);
|
||||||
|
@ -9,8 +9,10 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Catch;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -92,6 +94,31 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
returnToMenu();
|
returnToMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFromSongSelectWithFilter([Values] ScorePresentType type)
|
||||||
|
{
|
||||||
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
||||||
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
|
AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq");
|
||||||
|
AddUntilStep("wait for no results", () => Beatmap.IsDefault);
|
||||||
|
|
||||||
|
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
|
||||||
|
presentAndConfirm(firstImport, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type)
|
||||||
|
{
|
||||||
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
||||||
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
|
AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
|
||||||
|
|
||||||
|
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
|
||||||
|
presentAndConfirm(firstImport, type);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFromSongSelect([Values] ScorePresentType type)
|
public void TestFromSongSelect([Values] ScorePresentType type)
|
||||||
{
|
{
|
||||||
|
@ -29,11 +29,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
{
|
{
|
||||||
Child = textBox = new SettingsTextBox
|
Child = textBox = new SettingsTextBox
|
||||||
{
|
{
|
||||||
Current = new Bindable<string>
|
Current = new Bindable<string>("test")
|
||||||
{
|
|
||||||
Default = "test",
|
|
||||||
Value = "test"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
AddUntilStep("wait for loaded", () => textBox.IsLoaded);
|
AddUntilStep("wait for loaded", () => textBox.IsLoaded);
|
||||||
@ -59,11 +55,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
{
|
{
|
||||||
Child = textBox = new SettingsTextBox
|
Child = textBox = new SettingsTextBox
|
||||||
{
|
{
|
||||||
Current = new Bindable<string>
|
Current = new Bindable<string>("test")
|
||||||
{
|
|
||||||
Default = "test",
|
|
||||||
Value = "test"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
AddUntilStep("wait for loaded", () => textBox.IsLoaded);
|
AddUntilStep("wait for loaded", () => textBox.IsLoaded);
|
||||||
|
@ -67,11 +67,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))]
|
[SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))]
|
||||||
public Bindable<int?> IntTextBoxBindable { get; } = new Bindable<int?>
|
public Bindable<int?> IntTextBoxBindable { get; } = new Bindable<int?>();
|
||||||
{
|
|
||||||
Default = null,
|
|
||||||
Value = null
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestEnum
|
private enum TestEnum
|
||||||
|
@ -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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -36,10 +34,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[Cached(typeof(IDialogOverlay))]
|
[Cached(typeof(IDialogOverlay))]
|
||||||
private readonly DialogOverlay dialogOverlay;
|
private readonly DialogOverlay dialogOverlay;
|
||||||
|
|
||||||
private ScoreManager scoreManager;
|
private ScoreManager scoreManager = null!;
|
||||||
|
private RulesetStore rulesetStore = null!;
|
||||||
private RulesetStore rulesetStore;
|
private BeatmapManager beatmapManager = null!;
|
||||||
private BeatmapManager beatmapManager;
|
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
{
|
{
|
||||||
@ -74,7 +71,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLocalScoresDisplay()
|
public void TestLocalScoresDisplay()
|
||||||
{
|
{
|
||||||
BeatmapInfo beatmapInfo = null;
|
BeatmapInfo beatmapInfo = null!;
|
||||||
|
|
||||||
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
||||||
|
|
||||||
@ -387,7 +384,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
private class FailableLeaderboard : BeatmapLeaderboard
|
private class FailableLeaderboard : BeatmapLeaderboard
|
||||||
{
|
{
|
||||||
public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state);
|
public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state);
|
||||||
public new void SetScores(IEnumerable<ScoreInfo> scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore);
|
public new void SetScores(IEnumerable<ScoreInfo>? scores, ScoreInfo? userScore = null) => base.SetScores(scores, userScore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
InputManager.PressButton(MouseButton.Left);
|
InputManager.PressButton(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
|
AddUntilStep("wait for fetch", () => leaderboard.Scores.Any());
|
||||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID));
|
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID));
|
||||||
|
|
||||||
// "Clean up"
|
// "Clean up"
|
||||||
@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public void TestDeleteViaDatabase()
|
public void TestDeleteViaDatabase()
|
||||||
{
|
{
|
||||||
AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
|
AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
|
||||||
AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
|
AddUntilStep("wait for fetch", () => leaderboard.Scores.Any());
|
||||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID));
|
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -10,6 +11,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
@ -32,6 +34,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
|
InputManager.MoveMouseTo(Vector2.Zero);
|
||||||
|
|
||||||
TimeToCompleteProgress = 2000;
|
TimeToCompleteProgress = 2000;
|
||||||
progressingNotifications.Clear();
|
progressingNotifications.Clear();
|
||||||
|
|
||||||
@ -103,9 +107,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
AddStep("start drag", () =>
|
AddStep("start drag", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(notification.ChildrenOfType<Notification>().Single());
|
InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<Notification>().Single());
|
||||||
InputManager.PressButton(MouseButton.Left);
|
InputManager.PressButton(MouseButton.Left);
|
||||||
InputManager.MoveMouseTo(notification.ChildrenOfType<Notification>().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0));
|
InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<Notification>().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("fling away", () =>
|
AddStep("fling away", () =>
|
||||||
@ -119,6 +123,45 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0);
|
AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestProgressNotificationCantBeFlung()
|
||||||
|
{
|
||||||
|
bool activated = false;
|
||||||
|
ProgressNotification notification = null!;
|
||||||
|
|
||||||
|
AddStep("post", () =>
|
||||||
|
{
|
||||||
|
activated = false;
|
||||||
|
notificationOverlay.Post(notification = new ProgressNotification
|
||||||
|
{
|
||||||
|
Text = @"Uploading to BSS...",
|
||||||
|
CompletionText = "Uploaded to BSS!",
|
||||||
|
Activated = () => activated = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
progressingNotifications.Add(notification);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("start drag", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<Notification>().Single());
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<Notification>().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("attempt fling", () =>
|
||||||
|
{
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("was not closed", () => !notification.WasClosed);
|
||||||
|
AddUntilStep("was not cancelled", () => notification.State == ProgressNotificationState.Active);
|
||||||
|
AddAssert("was not activated", () => !activated);
|
||||||
|
AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero));
|
||||||
|
|
||||||
|
AddUntilStep("was completed", () => notification.State == ProgressNotificationState.Completed);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDismissWithoutActivationCloseButton()
|
public void TestDismissWithoutActivationCloseButton()
|
||||||
{
|
{
|
||||||
@ -228,6 +271,31 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddUntilStep("wait overlay not present", () => !notificationOverlay.IsPresent);
|
AddUntilStep("wait overlay not present", () => !notificationOverlay.IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestProgressClick()
|
||||||
|
{
|
||||||
|
ProgressNotification notification = null!;
|
||||||
|
|
||||||
|
AddStep("add progress notification", () =>
|
||||||
|
{
|
||||||
|
notification = new ProgressNotification
|
||||||
|
{
|
||||||
|
Text = @"Uploading to BSS...",
|
||||||
|
CompletionText = "Uploaded to BSS!",
|
||||||
|
};
|
||||||
|
notificationOverlay.Post(notification);
|
||||||
|
progressingNotifications.Add(notification);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("hover over notification", () => InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<ProgressNotification>().Single()));
|
||||||
|
|
||||||
|
AddStep("left click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("not cancelled", () => notification.State == ProgressNotificationState.Active);
|
||||||
|
|
||||||
|
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||||
|
AddAssert("cancelled", () => notification.State == ProgressNotificationState.Cancelled);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCompleteProgress()
|
public void TestCompleteProgress()
|
||||||
{
|
{
|
||||||
@ -299,7 +367,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
SimpleNotification notification = null!;
|
SimpleNotification notification = null!;
|
||||||
AddStep(@"post", () => notificationOverlay.Post(notification = new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" }));
|
AddStep(@"post", () => notificationOverlay.Post(notification = new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" }));
|
||||||
AddUntilStep("check is toast", () => !notification.IsInToastTray);
|
AddUntilStep("check is toast", () => notification.IsInToastTray);
|
||||||
AddAssert("light is not visible", () => notification.ChildrenOfType<Notification.NotificationLight>().Single().Alpha == 0);
|
AddAssert("light is not visible", () => notification.ChildrenOfType<Notification.NotificationLight>().Single().Alpha == 0);
|
||||||
|
|
||||||
AddUntilStep("wait for forward to overlay", () => !notification.IsInToastTray);
|
AddUntilStep("wait for forward to overlay", () => !notification.IsInToastTray);
|
||||||
@ -424,11 +492,19 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddRepeatStep("send barrage", sendBarrage, 10);
|
AddRepeatStep("send barrage", sendBarrage, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestServerShuttingDownNotification()
|
||||||
|
{
|
||||||
|
AddStep("post with 5 seconds", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromSeconds(5))));
|
||||||
|
AddStep("post with 30 seconds", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromSeconds(30))));
|
||||||
|
AddStep("post with 6 hours", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromHours(6))));
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed);
|
progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed && n.WasClosed);
|
||||||
|
|
||||||
if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
|
if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1)
|
public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1)
|
||||||
{
|
{
|
||||||
Precision = 0.01,
|
Precision = 0.01,
|
||||||
Default = 1,
|
|
||||||
MinValue = 0.1,
|
MinValue = 0.1,
|
||||||
MaxValue = 10
|
MaxValue = 10
|
||||||
};
|
};
|
||||||
|
@ -28,7 +28,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1)
|
public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1)
|
||||||
{
|
{
|
||||||
Precision = 0.01,
|
Precision = 0.01,
|
||||||
Default = 1,
|
|
||||||
MinValue = 0.01,
|
MinValue = 0.01,
|
||||||
MaxValue = 10
|
MaxValue = 10
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user