mirror of
https://github.com/ppy/osu.git
synced 2025-01-26 23:43:03 +08:00
Merge remote-tracking branch 'upstream/master' into chat-mention
This commit is contained in:
commit
39c3b08fc7
@ -10,14 +10,6 @@ trim_trailing_whitespace = true
|
||||
|
||||
#Roslyn naming styles
|
||||
|
||||
#PascalCase for public and protected members
|
||||
dotnet_naming_style.pascalcase.capitalization = pascal_case
|
||||
dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||
dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
|
||||
dotnet_naming_rule.public_members_pascalcase.severity = error
|
||||
dotnet_naming_rule.public_members_pascalcase.symbols = public_members
|
||||
dotnet_naming_rule.public_members_pascalcase.style = pascalcase
|
||||
|
||||
#camelCase for private members
|
||||
dotnet_naming_style.camelcase.capitalization = camel_case
|
||||
|
||||
@ -197,4 +189,4 @@ dotnet_diagnostic.IDE0069.severity = none
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Propose a feature you would like to see in the game!
|
||||
---
|
||||
**Describe the new feature:**
|
||||
|
||||
**Proposal designs of the feature:**
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,12 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Suggestions or feature request
|
||||
url: https://github.com/ppy/osu/discussions/categories/ideas
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||
- name: Help
|
||||
url: https://github.com/ppy/osu/discussions/categories/q-a
|
||||
about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
|
||||
- name: osu!stable issues
|
||||
url: https://github.com/ppy/osu-stable-issues
|
||||
about: For issues regarding osu!stable (not osu!lazer), open them here.
|
||||
about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports.
|
||||
|
||||
|
@ -97,7 +97,7 @@ Before committing your code, please run a code formatter. This can be achieved b
|
||||
|
||||
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
|
||||
|
||||
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`, which is [only supported on Windows](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
|
||||
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`. Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.EmptyFreeform
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0];
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.EmptyFreeform.Objects;
|
||||
using osu.Game.Rulesets.EmptyFreeform.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
@ -11,7 +10,7 @@ using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyFreeform.Mods
|
||||
{
|
||||
public class EmptyFreeformModAutoplay : ModAutoplay<EmptyFreeformHitObject>
|
||||
public class EmptyFreeformModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -4,14 +4,13 @@
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Pippidon.Objects;
|
||||
using osu.Game.Rulesets.Pippidon.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Mods
|
||||
{
|
||||
public class PippidonModAutoplay : ModAutoplay<PippidonHitObject>
|
||||
public class PippidonModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0];
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.EmptyScrolling
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0];
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.EmptyScrolling.Objects;
|
||||
using osu.Game.Rulesets.EmptyScrolling.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
@ -11,7 +10,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyScrolling.Mods
|
||||
{
|
||||
public class EmptyScrollingModAutoplay : ModAutoplay<EmptyScrollingHitObject>
|
||||
public class EmptyScrollingModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -4,14 +4,13 @@
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Pippidon.Objects;
|
||||
using osu.Game.Rulesets.Pippidon.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Mods
|
||||
{
|
||||
public class PippidonModAutoplay : ModAutoplay<PippidonHitObject>
|
||||
public class PippidonModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0];
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.524.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.601.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Desktop
|
||||
|
||||
private string getStableInstallPath()
|
||||
{
|
||||
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
|
||||
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")) || File.Exists(Path.Combine(p, "osu!.cfg"));
|
||||
|
||||
string stableInstallPath;
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
</ItemGroup>
|
||||
|
@ -1,8 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
@ -10,5 +18,22 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
|
||||
{
|
||||
if (withModifiedSkin)
|
||||
{
|
||||
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
||||
AddStep("update target", () => Player.ChildrenOfType<SkinnableTargetContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
||||
AddStep("exit player", () => Player.Exit());
|
||||
CreateTest(null);
|
||||
}
|
||||
|
||||
AddAssert("legacy HUD combo counter hidden", () =>
|
||||
{
|
||||
return Player.ChildrenOfType<LegacyComboCounter>().All(c => c.ChildrenOfType<Container>().Single().Alpha == 0f);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||
AddAssert("correct hit lighting colour", () =>
|
||||
catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour);
|
||||
catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == fruitColour);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
CircleSize = circleSize
|
||||
};
|
||||
|
||||
SetContents(() =>
|
||||
SetContents(_ =>
|
||||
{
|
||||
var droppedObjectContainer = new Container<CaughtObject>
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
scoreProcessor = new ScoreProcessor();
|
||||
|
||||
SetContents(() => new CatchComboDisplay
|
||||
SetContents(_ => new CatchComboDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -19,22 +19,22 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddStep("show pear", () => SetContents(() => createDrawableFruit(0)));
|
||||
AddStep("show grape", () => SetContents(() => createDrawableFruit(1)));
|
||||
AddStep("show pineapple / apple", () => SetContents(() => createDrawableFruit(2)));
|
||||
AddStep("show raspberry / orange", () => SetContents(() => createDrawableFruit(3)));
|
||||
AddStep("show pear", () => SetContents(_ => createDrawableFruit(0)));
|
||||
AddStep("show grape", () => SetContents(_ => createDrawableFruit(1)));
|
||||
AddStep("show pineapple / apple", () => SetContents(_ => createDrawableFruit(2)));
|
||||
AddStep("show raspberry / orange", () => SetContents(_ => createDrawableFruit(3)));
|
||||
|
||||
AddStep("show banana", () => SetContents(createDrawableBanana));
|
||||
AddStep("show banana", () => SetContents(_ => createDrawableBanana()));
|
||||
|
||||
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
||||
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
||||
AddStep("show droplet", () => SetContents(_ => createDrawableDroplet()));
|
||||
AddStep("show tiny droplet", () => SetContents(_ => createDrawableTinyDroplet()));
|
||||
|
||||
AddStep("show hyperdash pear", () => SetContents(() => createDrawableFruit(0, true)));
|
||||
AddStep("show hyperdash grape", () => SetContents(() => createDrawableFruit(1, true)));
|
||||
AddStep("show hyperdash pineapple / apple", () => SetContents(() => createDrawableFruit(2, true)));
|
||||
AddStep("show hyperdash raspberry / orange", () => SetContents(() => createDrawableFruit(3, true)));
|
||||
AddStep("show hyperdash pear", () => SetContents(_ => createDrawableFruit(0, true)));
|
||||
AddStep("show hyperdash grape", () => SetContents(_ => createDrawableFruit(1, true)));
|
||||
AddStep("show hyperdash pineapple / apple", () => SetContents(_ => createDrawableFruit(2, true)));
|
||||
AddStep("show hyperdash raspberry / orange", () => SetContents(_ => createDrawableFruit(3, true)));
|
||||
|
||||
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
||||
AddStep("show hyperdash droplet", () => SetContents(_ => createDrawableDroplet(true)));
|
||||
}
|
||||
|
||||
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
|
||||
|
@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
AddStep("fruit changes visual and hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
|
||||
AddStep("fruit changes visual and hyper", () => SetContents(_ => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
|
||||
{
|
||||
IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
|
||||
HyperDashBindable = { BindTarget = hyperDash },
|
||||
}))));
|
||||
|
||||
AddStep("droplet changes hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
|
||||
AddStep("droplet changes hyper", () => SetContents(_ => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
|
||||
{
|
||||
HyperDashBindable = { BindTarget = hyperDash },
|
||||
}))));
|
||||
|
@ -32,28 +32,28 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, true, userHasCustomColours);
|
||||
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, true);
|
||||
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, false);
|
||||
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||
}
|
||||
|
||||
@ -61,10 +61,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase(false, true)]
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
public void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
|
||||
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, false));
|
||||
ConfigureTest(useBeatmapSkin, useBeatmapColour, false);
|
||||
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||
}
|
||||
|
||||
@ -72,10 +72,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase(false, true)]
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
public void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
|
||||
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, false));
|
||||
ConfigureTest(useBeatmapSkin, useBeatmapColour, true);
|
||||
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase(false)]
|
||||
public void TestBeatmapHyperDashColours(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, true, true);
|
||||
AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestBeatmapSkin.HYPER_DASH_COLOUR);
|
||||
AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestBeatmapSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
|
||||
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase(false)]
|
||||
public void TestBeatmapHyperDashColoursOverride(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, true);
|
||||
AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestSkin.HYPER_DASH_COLOUR);
|
||||
AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -161,13 +161,13 @@ namespace osu.Game.Rulesets.Catch
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.LargeTickHit:
|
||||
return "large droplet";
|
||||
return "Large droplet";
|
||||
|
||||
case HitResult.SmallTickHit:
|
||||
return "small droplet";
|
||||
return "Small droplet";
|
||||
|
||||
case HitResult.LargeBonus:
|
||||
return "banana";
|
||||
return "Banana";
|
||||
}
|
||||
|
||||
return base.GetDisplayNameForHitResult(result);
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
}
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
|
||||
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
return new Skill[]
|
||||
{
|
||||
new Movement(mods, halfCatcherWidth),
|
||||
new Movement(mods, halfCatcherWidth, clockRate),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
/// </summary>
|
||||
public readonly double StrainTime;
|
||||
|
||||
public readonly double ClockRate;
|
||||
|
||||
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
@ -37,7 +35,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
|
||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(40, DeltaTime);
|
||||
ClockRate = clockRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,21 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
private float lastDistanceMoved;
|
||||
private double lastStrainTime;
|
||||
|
||||
public Movement(Mod[] mods, float halfCatcherWidth)
|
||||
/// <summary>
|
||||
/// The speed multiplier applied to the player's catcher.
|
||||
/// </summary>
|
||||
private readonly double catcherSpeedMultiplier;
|
||||
|
||||
public Movement(Mod[] mods, float halfCatcherWidth, double clockRate)
|
||||
: base(mods)
|
||||
{
|
||||
HalfCatcherWidth = halfCatcherWidth;
|
||||
|
||||
// In catch, clockrate adjustments do not only affect the timings of hitobjects,
|
||||
// but also the speed of the player's catcher, which has an impact on difficulty
|
||||
// TODO: Support variable clockrates caused by mods such as ModTimeRamp
|
||||
// (perhaps by using IApplicableToRate within the CatchDifficultyHitObject constructor to set a catcher speed for each object before processing)
|
||||
catcherSpeedMultiplier = clockRate;
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
@ -48,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
|
||||
float distanceMoved = playerPosition - lastPlayerPosition.Value;
|
||||
|
||||
double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate);
|
||||
double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catcherSpeedMultiplier);
|
||||
|
||||
double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
|
||||
double sqrtStrain = Math.Sqrt(weightedStrainTime);
|
||||
@ -81,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
playerPosition = catchCurrent.NormalizedPosition;
|
||||
}
|
||||
|
||||
distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
|
||||
distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catcherSpeedMultiplier, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
|
||||
}
|
||||
|
||||
lastPlayerPosition = playerPosition;
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
@ -11,7 +10,7 @@ using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModAutoplay : ModAutoplay<CatchHitObject>
|
||||
public class CatchModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -1,8 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -22,59 +24,68 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
if (component is HUDSkinComponent hudComponent)
|
||||
if (component is SkinnableTargetComponent targetComponent)
|
||||
{
|
||||
switch (hudComponent.Component)
|
||||
switch (targetComponent.Target)
|
||||
{
|
||||
case HUDSkinComponents.ComboCounter:
|
||||
// catch may provide its own combo counter; hide the default.
|
||||
return providesComboCounter ? Drawable.Empty() : null;
|
||||
case SkinnableTarget.MainHUDComponents:
|
||||
var components = Source.GetDrawableComponent(component) as SkinnableTargetComponentsContainer;
|
||||
|
||||
if (providesComboCounter && components != null)
|
||||
{
|
||||
// catch may provide its own combo counter; hide the default.
|
||||
// todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
|
||||
foreach (var legacyComboCounter in components.OfType<LegacyComboCounter>())
|
||||
legacyComboCounter.HiddenByRulesetImplementation = false;
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(component is CatchSkinComponent catchSkinComponent))
|
||||
return null;
|
||||
|
||||
switch (catchSkinComponent.Component)
|
||||
if (component is CatchSkinComponent catchSkinComponent)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (GetTexture("fruit-pear") != null)
|
||||
return new LegacyFruitPiece();
|
||||
switch (catchSkinComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (GetTexture("fruit-pear") != null)
|
||||
return new LegacyFruitPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.CatcherIdle:
|
||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherIdle:
|
||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatcherFail:
|
||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherFail:
|
||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatcherKiai:
|
||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherKiai:
|
||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter(Source);
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter(Source);
|
||||
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Source.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
|
@ -126,8 +126,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
private float hyperDashTargetPosition;
|
||||
private Bindable<bool> hitLighting;
|
||||
|
||||
private readonly DrawablePool<HitExplosion> hitExplosionPool;
|
||||
private readonly Container<HitExplosion> hitExplosionContainer;
|
||||
private readonly HitExplosionContainer hitExplosionContainer;
|
||||
|
||||
private readonly DrawablePool<CaughtFruit> caughtFruitPool;
|
||||
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
|
||||
@ -148,7 +147,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
||||
caughtFruitPool = new DrawablePool<CaughtFruit>(50),
|
||||
caughtBananaPool = new DrawablePool<CaughtBanana>(100),
|
||||
// less capacity is needed compared to fruit because droplet is not stacked
|
||||
@ -173,7 +171,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
Anchor = Anchor.TopCentre,
|
||||
Alpha = 0,
|
||||
},
|
||||
hitExplosionContainer = new Container<HitExplosion>
|
||||
hitExplosionContainer = new HitExplosionContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
@ -297,7 +295,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||
hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -508,15 +505,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
return position;
|
||||
}
|
||||
|
||||
private void addLighting(CatchHitObject hitObject, float x, Color4 colour)
|
||||
{
|
||||
HitExplosion hitExplosion = hitExplosionPool.Get();
|
||||
hitExplosion.HitObject = hitObject;
|
||||
hitExplosion.X = x;
|
||||
hitExplosion.Scale = new Vector2(hitObject.Scale);
|
||||
hitExplosion.ObjectColour = colour;
|
||||
hitExplosionContainer.Add(hitExplosion);
|
||||
}
|
||||
private void addLighting(CatchHitObject hitObject, float x, Color4 colour) =>
|
||||
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed));
|
||||
|
||||
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
||||
{
|
||||
|
@ -5,31 +5,16 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Objects.Pooling;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class HitExplosion : PoolableDrawable
|
||||
public class HitExplosion : PoolableDrawableWithLifetime<HitExplosionEntry>
|
||||
{
|
||||
private Color4 objectColour;
|
||||
public CatchHitObject HitObject;
|
||||
|
||||
public Color4 ObjectColour
|
||||
{
|
||||
get => objectColour;
|
||||
set
|
||||
{
|
||||
if (objectColour == value) return;
|
||||
|
||||
objectColour = value;
|
||||
onColourChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CircularContainer largeFaint;
|
||||
private readonly CircularContainer smallFaint;
|
||||
private readonly CircularContainer directionalGlow1;
|
||||
@ -83,9 +68,19 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
};
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
protected override void OnApply(HitExplosionEntry entry)
|
||||
{
|
||||
base.PrepareForUse();
|
||||
X = entry.Position;
|
||||
Scale = new Vector2(entry.Scale);
|
||||
setColour(entry.ObjectColour);
|
||||
|
||||
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||
applyTransforms(entry.RNGSeed);
|
||||
}
|
||||
|
||||
private void applyTransforms(int randomSeed)
|
||||
{
|
||||
ClearTransforms(true);
|
||||
|
||||
const double duration = 400;
|
||||
|
||||
@ -96,14 +91,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
.FadeOut(duration * 2);
|
||||
|
||||
const float angle_variangle = 15; // should be less than 45
|
||||
directionalGlow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
|
||||
directionalGlow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
|
||||
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
|
||||
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
|
||||
|
||||
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
|
||||
Expire(true);
|
||||
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire();
|
||||
}
|
||||
|
||||
private void onColourChanged()
|
||||
private void setColour(Color4 objectColour)
|
||||
{
|
||||
const float roundness = 100;
|
||||
|
||||
|
22
osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
Normal file
22
osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
Normal file
@ -0,0 +1,22 @@
|
||||
// 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.Pooling;
|
||||
using osu.Game.Rulesets.Objects.Pooling;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class HitExplosionContainer : PooledDrawableWithLifetimeContainer<HitExplosionEntry, HitExplosion>
|
||||
{
|
||||
protected override bool RemoveRewoundEntry => true;
|
||||
|
||||
private readonly DrawablePool<HitExplosion> pool;
|
||||
|
||||
public HitExplosionContainer()
|
||||
{
|
||||
AddInternal(pool = new DrawablePool<HitExplosion>(10));
|
||||
}
|
||||
|
||||
protected override HitExplosion GetDrawable(HitExplosionEntry entry) => pool.Get(d => d.Apply(entry));
|
||||
}
|
||||
}
|
25
osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
Normal file
25
osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// 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.Performance;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class HitExplosionEntry : LifetimeEntry
|
||||
{
|
||||
public readonly float Position;
|
||||
public readonly float Scale;
|
||||
public readonly Color4 ObjectColour;
|
||||
public readonly int RNGSeed;
|
||||
|
||||
public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed)
|
||||
{
|
||||
LifetimeStart = startTime;
|
||||
Position = position;
|
||||
Scale = scale;
|
||||
ObjectColour = objectColour;
|
||||
RNGSeed = rngSeed;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
SetContents(() => new FillFlowContainer
|
||||
SetContents(_ => new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new FillFlowContainer
|
||||
SetContents(_ => new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new FillFlowContainer
|
||||
SetContents(_ => new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
if (hitWindows.IsHitResultAllowed(result))
|
||||
{
|
||||
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
|
||||
AddStep("Show " + result.GetDescription(), () => SetContents(_ =>
|
||||
new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||
{
|
||||
Type = result
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() =>
|
||||
SetContents(_ =>
|
||||
{
|
||||
var pool = new DrawablePool<PoolableHitExplosion>(5);
|
||||
hitExplosionPools.Add(pool);
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new FillFlowContainer
|
||||
SetContents(_ => new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
new StageDefinition { Columns = 2 }
|
||||
};
|
||||
|
||||
SetContents(() => new ManiaPlayfield(stageDefinitions));
|
||||
SetContents(_ => new ManiaPlayfield(stageDefinitions));
|
||||
});
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
new StageDefinition { Columns = 2 }
|
||||
};
|
||||
|
||||
SetContents(() => new ManiaPlayfield(stageDefinitions));
|
||||
SetContents(_ => new ManiaPlayfield(stageDefinitions));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() =>
|
||||
SetContents(_ =>
|
||||
{
|
||||
ManiaAction normalAction = ManiaAction.Key1;
|
||||
ManiaAction specialAction = ManiaAction.Special1;
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }),
|
||||
SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }),
|
||||
_ => new DefaultStageBackground())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null)
|
||||
SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public class TestSceneManiaHitObjectSamples : HitObjectSampleTest
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
protected override IResourceStore<byte[]> Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneManiaHitObjectSamples)));
|
||||
protected override IResourceStore<byte[]> RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneManiaHitObjectSamples)));
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when a normal sample bank is used, the normal hitsound will be looked up.
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
|
||||
protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input;
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
|
||||
{
|
||||
new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns)
|
||||
};
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
@ -12,7 +11,7 @@ using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModAutoplay : ModAutoplay<ManiaHitObject>
|
||||
public class ManiaModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Utils;
|
||||
@ -17,8 +18,11 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
Seed.Value ??= RNG.Next();
|
||||
var rng = new Random((int)Seed.Value);
|
||||
|
||||
var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList();
|
||||
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => rng.Next()).ToList();
|
||||
|
||||
beatmap.HitObjects.OfType<ManiaHitObject>().ForEach(h => h.Column = shuffledColumns[h.Column]);
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
return Source.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
private Drawable getResult(HitResult result)
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
int poolIndex = 0;
|
||||
|
||||
SetContents(() =>
|
||||
SetContents(_ =>
|
||||
{
|
||||
DrawablePool<TestDrawableOsuJudgement> pool;
|
||||
|
||||
|
@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private void loadContent(bool automated = true, Func<SkinProvidingContainer> skinProvider = null)
|
||||
{
|
||||
SetContents(() =>
|
||||
SetContents(_ =>
|
||||
{
|
||||
var inputManager = automated ? (InputManager)new MovingCursorInputManager() : new OsuInputManager(new OsuRuleset().RulesetInfo);
|
||||
var skinContainer = skinProvider?.Invoke() ?? new SkinProvidingContainer(null);
|
||||
|
@ -23,18 +23,18 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestVariousHitCircles()
|
||||
{
|
||||
AddStep("Miss Big Single", () => SetContents(() => testSingle(2)));
|
||||
AddStep("Miss Medium Single", () => SetContents(() => testSingle(5)));
|
||||
AddStep("Miss Small Single", () => SetContents(() => testSingle(7)));
|
||||
AddStep("Hit Big Single", () => SetContents(() => testSingle(2, true)));
|
||||
AddStep("Hit Medium Single", () => SetContents(() => testSingle(5, true)));
|
||||
AddStep("Hit Small Single", () => SetContents(() => testSingle(7, true)));
|
||||
AddStep("Miss Big Stream", () => SetContents(() => testStream(2)));
|
||||
AddStep("Miss Medium Stream", () => SetContents(() => testStream(5)));
|
||||
AddStep("Miss Small Stream", () => SetContents(() => testStream(7)));
|
||||
AddStep("Hit Big Stream", () => SetContents(() => testStream(2, true)));
|
||||
AddStep("Hit Medium Stream", () => SetContents(() => testStream(5, true)));
|
||||
AddStep("Hit Small Stream", () => SetContents(() => testStream(7, true)));
|
||||
AddStep("Miss Big Single", () => SetContents(_ => testSingle(2)));
|
||||
AddStep("Miss Medium Single", () => SetContents(_ => testSingle(5)));
|
||||
AddStep("Miss Small Single", () => SetContents(_ => testSingle(7)));
|
||||
AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true)));
|
||||
AddStep("Hit Medium Single", () => SetContents(_ => testSingle(5, true)));
|
||||
AddStep("Hit Small Single", () => SetContents(_ => testSingle(7, true)));
|
||||
AddStep("Miss Big Stream", () => SetContents(_ => testStream(2)));
|
||||
AddStep("Miss Medium Stream", () => SetContents(_ => testStream(5)));
|
||||
AddStep("Miss Small Stream", () => SetContents(_ => testStream(7)));
|
||||
AddStep("Hit Big Stream", () => SetContents(_ => testStream(2, true)));
|
||||
AddStep("Hit Medium Stream", () => SetContents(_ => testStream(5, true)));
|
||||
AddStep("Hit Small Stream", () => SetContents(_ => testStream(7, true)));
|
||||
}
|
||||
|
||||
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
||||
|
30
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs
Normal file
30
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneHitCircleKiai : TestSceneHitCircle
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
|
||||
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPointInfo
|
||||
});
|
||||
|
||||
// track needs to be playing for BeatSyncedContainer to work.
|
||||
Beatmap.Value.Track.Start();
|
||||
});
|
||||
}
|
||||
}
|
@ -30,28 +30,28 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, true, userHasCustomColours);
|
||||
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, true);
|
||||
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, false);
|
||||
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||
}
|
||||
|
||||
@ -59,10 +59,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestCase(false, true)]
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
public void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
|
||||
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, false));
|
||||
ConfigureTest(useBeatmapSkin, useBeatmapColour, false);
|
||||
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||
}
|
||||
|
||||
@ -70,10 +70,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestCase(false, true)]
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
public void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
|
||||
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, false));
|
||||
ConfigureTest(useBeatmapSkin, useBeatmapColour, true);
|
||||
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||
}
|
||||
|
||||
|
@ -30,54 +30,54 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestVariousSliders()
|
||||
{
|
||||
AddStep("Big Single", () => SetContents(() => testSimpleBig()));
|
||||
AddStep("Medium Single", () => SetContents(() => testSimpleMedium()));
|
||||
AddStep("Small Single", () => SetContents(() => testSimpleSmall()));
|
||||
AddStep("Big 1 Repeat", () => SetContents(() => testSimpleBig(1)));
|
||||
AddStep("Medium 1 Repeat", () => SetContents(() => testSimpleMedium(1)));
|
||||
AddStep("Small 1 Repeat", () => SetContents(() => testSimpleSmall(1)));
|
||||
AddStep("Big 2 Repeats", () => SetContents(() => testSimpleBig(2)));
|
||||
AddStep("Medium 2 Repeats", () => SetContents(() => testSimpleMedium(2)));
|
||||
AddStep("Small 2 Repeats", () => SetContents(() => testSimpleSmall(2)));
|
||||
AddStep("Big Single", () => SetContents(_ => testSimpleBig()));
|
||||
AddStep("Medium Single", () => SetContents(_ => testSimpleMedium()));
|
||||
AddStep("Small Single", () => SetContents(_ => testSimpleSmall()));
|
||||
AddStep("Big 1 Repeat", () => SetContents(_ => testSimpleBig(1)));
|
||||
AddStep("Medium 1 Repeat", () => SetContents(_ => testSimpleMedium(1)));
|
||||
AddStep("Small 1 Repeat", () => SetContents(_ => testSimpleSmall(1)));
|
||||
AddStep("Big 2 Repeats", () => SetContents(_ => testSimpleBig(2)));
|
||||
AddStep("Medium 2 Repeats", () => SetContents(_ => testSimpleMedium(2)));
|
||||
AddStep("Small 2 Repeats", () => SetContents(_ => testSimpleSmall(2)));
|
||||
|
||||
AddStep("Slow Slider", () => SetContents(testSlowSpeed)); // slow long sliders take ages already so no repeat steps
|
||||
AddStep("Slow Short Slider", () => SetContents(() => testShortSlowSpeed()));
|
||||
AddStep("Slow Short Slider 1 Repeats", () => SetContents(() => testShortSlowSpeed(1)));
|
||||
AddStep("Slow Short Slider 2 Repeats", () => SetContents(() => testShortSlowSpeed(2)));
|
||||
AddStep("Slow Slider", () => SetContents(_ => testSlowSpeed())); // slow long sliders take ages already so no repeat steps
|
||||
AddStep("Slow Short Slider", () => SetContents(_ => testShortSlowSpeed()));
|
||||
AddStep("Slow Short Slider 1 Repeats", () => SetContents(_ => testShortSlowSpeed(1)));
|
||||
AddStep("Slow Short Slider 2 Repeats", () => SetContents(_ => testShortSlowSpeed(2)));
|
||||
|
||||
AddStep("Fast Slider", () => SetContents(() => testHighSpeed()));
|
||||
AddStep("Fast Slider 1 Repeat", () => SetContents(() => testHighSpeed(1)));
|
||||
AddStep("Fast Slider 2 Repeats", () => SetContents(() => testHighSpeed(2)));
|
||||
AddStep("Fast Short Slider", () => SetContents(() => testShortHighSpeed()));
|
||||
AddStep("Fast Short Slider 1 Repeat", () => SetContents(() => testShortHighSpeed(1)));
|
||||
AddStep("Fast Short Slider 2 Repeats", () => SetContents(() => testShortHighSpeed(2)));
|
||||
AddStep("Fast Short Slider 6 Repeats", () => SetContents(() => testShortHighSpeed(6)));
|
||||
AddStep("Fast Slider", () => SetContents(_ => testHighSpeed()));
|
||||
AddStep("Fast Slider 1 Repeat", () => SetContents(_ => testHighSpeed(1)));
|
||||
AddStep("Fast Slider 2 Repeats", () => SetContents(_ => testHighSpeed(2)));
|
||||
AddStep("Fast Short Slider", () => SetContents(_ => testShortHighSpeed()));
|
||||
AddStep("Fast Short Slider 1 Repeat", () => SetContents(_ => testShortHighSpeed(1)));
|
||||
AddStep("Fast Short Slider 2 Repeats", () => SetContents(_ => testShortHighSpeed(2)));
|
||||
AddStep("Fast Short Slider 6 Repeats", () => SetContents(_ => testShortHighSpeed(6)));
|
||||
|
||||
AddStep("Perfect Curve", () => SetContents(() => testPerfect()));
|
||||
AddStep("Perfect Curve 1 Repeat", () => SetContents(() => testPerfect(1)));
|
||||
AddStep("Perfect Curve 2 Repeats", () => SetContents(() => testPerfect(2)));
|
||||
AddStep("Perfect Curve", () => SetContents(_ => testPerfect()));
|
||||
AddStep("Perfect Curve 1 Repeat", () => SetContents(_ => testPerfect(1)));
|
||||
AddStep("Perfect Curve 2 Repeats", () => SetContents(_ => testPerfect(2)));
|
||||
|
||||
AddStep("Linear Slider", () => SetContents(() => testLinear()));
|
||||
AddStep("Linear Slider 1 Repeat", () => SetContents(() => testLinear(1)));
|
||||
AddStep("Linear Slider 2 Repeats", () => SetContents(() => testLinear(2)));
|
||||
AddStep("Linear Slider", () => SetContents(_ => testLinear()));
|
||||
AddStep("Linear Slider 1 Repeat", () => SetContents(_ => testLinear(1)));
|
||||
AddStep("Linear Slider 2 Repeats", () => SetContents(_ => testLinear(2)));
|
||||
|
||||
AddStep("Bezier Slider", () => SetContents(() => testBezier()));
|
||||
AddStep("Bezier Slider 1 Repeat", () => SetContents(() => testBezier(1)));
|
||||
AddStep("Bezier Slider 2 Repeats", () => SetContents(() => testBezier(2)));
|
||||
AddStep("Bezier Slider", () => SetContents(_ => testBezier()));
|
||||
AddStep("Bezier Slider 1 Repeat", () => SetContents(_ => testBezier(1)));
|
||||
AddStep("Bezier Slider 2 Repeats", () => SetContents(_ => testBezier(2)));
|
||||
|
||||
AddStep("Linear Overlapping", () => SetContents(() => testLinearOverlapping()));
|
||||
AddStep("Linear Overlapping 1 Repeat", () => SetContents(() => testLinearOverlapping(1)));
|
||||
AddStep("Linear Overlapping 2 Repeats", () => SetContents(() => testLinearOverlapping(2)));
|
||||
AddStep("Linear Overlapping", () => SetContents(_ => testLinearOverlapping()));
|
||||
AddStep("Linear Overlapping 1 Repeat", () => SetContents(_ => testLinearOverlapping(1)));
|
||||
AddStep("Linear Overlapping 2 Repeats", () => SetContents(_ => testLinearOverlapping(2)));
|
||||
|
||||
AddStep("Catmull Slider", () => SetContents(() => testCatmull()));
|
||||
AddStep("Catmull Slider 1 Repeat", () => SetContents(() => testCatmull(1)));
|
||||
AddStep("Catmull Slider 2 Repeats", () => SetContents(() => testCatmull(2)));
|
||||
AddStep("Catmull Slider", () => SetContents(_ => testCatmull()));
|
||||
AddStep("Catmull Slider 1 Repeat", () => SetContents(_ => testCatmull(1)));
|
||||
AddStep("Catmull Slider 2 Repeats", () => SetContents(_ => testCatmull(2)));
|
||||
|
||||
AddStep("Big Single, Large StackOffset", () => SetContents(() => testSimpleBigLargeStackOffset()));
|
||||
AddStep("Big 1 Repeat, Large StackOffset", () => SetContents(() => testSimpleBigLargeStackOffset(1)));
|
||||
AddStep("Big Single, Large StackOffset", () => SetContents(_ => testSimpleBigLargeStackOffset()));
|
||||
AddStep("Big 1 Repeat, Large StackOffset", () => SetContents(_ => testSimpleBigLargeStackOffset(1)));
|
||||
|
||||
AddStep("Distance Overflow", () => SetContents(() => testDistanceOverflow()));
|
||||
AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1)));
|
||||
AddStep("Distance Overflow", () => SetContents(_ => testDistanceOverflow()));
|
||||
AddStep("Distance Overflow 1 Repeat", () => SetContents(_ => testDistanceOverflow(1)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -29,15 +29,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public void TestVariousSpinners(bool autoplay)
|
||||
{
|
||||
string term = autoplay ? "Hit" : "Miss";
|
||||
AddStep($"{term} Big", () => SetContents(() => testSingle(2, autoplay)));
|
||||
AddStep($"{term} Medium", () => SetContents(() => testSingle(5, autoplay)));
|
||||
AddStep($"{term} Small", () => SetContents(() => testSingle(7, autoplay)));
|
||||
AddStep($"{term} Big", () => SetContents(_ => testSingle(2, autoplay)));
|
||||
AddStep($"{term} Medium", () => SetContents(_ => testSingle(5, autoplay)));
|
||||
AddStep($"{term} Small", () => SetContents(_ => testSingle(7, autoplay)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSpinningSamplePitchShift()
|
||||
{
|
||||
AddStep("Add spinner", () => SetContents(() => testSingle(5, true, 4000)));
|
||||
AddStep("Add spinner", () => SetContents(_ => testSingle(5, true, 4000)));
|
||||
AddUntilStep("Pitch starts low", () => getSpinningSample().Frequency.Value < 0.8);
|
||||
AddUntilStep("Pitch increases", () => getSpinningSample().Frequency.Value > 0.8);
|
||||
|
||||
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestCase(true)]
|
||||
public void TestLongSpinner(bool autoplay)
|
||||
{
|
||||
AddStep("Very long spinner", () => SetContents(() => testSingle(5, autoplay, 4000)));
|
||||
AddStep("Very long spinner", () => SetContents(_ => testSingle(5, autoplay, 4000)));
|
||||
AddUntilStep("Wait for completion", () => drawableSpinner.Result.HasResult);
|
||||
AddUntilStep("Check correct progress", () => drawableSpinner.Progress == (autoplay ? 1 : 0));
|
||||
}
|
||||
@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestCase(true)]
|
||||
public void TestSuperShortSpinner(bool autoplay)
|
||||
{
|
||||
AddStep("Very short spinner", () => SetContents(() => testSingle(5, autoplay, 200)));
|
||||
AddStep("Very short spinner", () => SetContents(_ => testSingle(5, autoplay, 200)));
|
||||
AddUntilStep("Wait for completion", () => drawableSpinner.Result.HasResult);
|
||||
AddUntilStep("Short spinner implicitly completes", () => drawableSpinner.Progress == 1);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
}
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
|
||||
{
|
||||
new Aim(mods),
|
||||
new Speed(mods)
|
||||
|
@ -6,14 +6,13 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAutoplay : ModAutoplay<OsuHitObject>
|
||||
public class OsuModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
|
||||
|
||||
|
280
osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
Normal file
280
osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
Normal file
@ -0,0 +1,280 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// Mod that randomises the positions of the <see cref="HitObject"/>s
|
||||
/// </summary>
|
||||
public class OsuModRandom : ModRandom, IApplicableToBeatmap
|
||||
{
|
||||
public override string Description => "It never gets boring!";
|
||||
public override bool Ranked => false;
|
||||
|
||||
// The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle.
|
||||
// The closer the hit objects draw to the border, the sharper the turn
|
||||
private const float playfield_edge_ratio = 0.375f;
|
||||
|
||||
private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio;
|
||||
private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio;
|
||||
|
||||
private static readonly Vector2 playfield_middle = Vector2.Divide(OsuPlayfield.BASE_SIZE, 2);
|
||||
|
||||
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
||||
|
||||
private Random rng;
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
if (!(beatmap is OsuBeatmap osuBeatmap))
|
||||
return;
|
||||
|
||||
var hitObjects = osuBeatmap.HitObjects;
|
||||
|
||||
Seed.Value ??= RNG.Next();
|
||||
|
||||
rng = new Random((int)Seed.Value);
|
||||
|
||||
RandomObjectInfo previous = null;
|
||||
|
||||
float rateOfChangeMultiplier = 0;
|
||||
|
||||
for (int i = 0; i < hitObjects.Count; i++)
|
||||
{
|
||||
var hitObject = hitObjects[i];
|
||||
|
||||
var current = new RandomObjectInfo(hitObject);
|
||||
|
||||
// rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams
|
||||
if (i % 3 == 0)
|
||||
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
|
||||
|
||||
if (hitObject is Spinner)
|
||||
{
|
||||
previous = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
applyRandomisation(rateOfChangeMultiplier, previous, current);
|
||||
|
||||
hitObject.Position = current.PositionRandomised;
|
||||
|
||||
// update end position as it may have changed as a result of the position update.
|
||||
current.EndPositionRandomised = current.PositionRandomised;
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
case Slider slider:
|
||||
// Shift nested objects the same distance as the slider got shifted in the randomisation process
|
||||
// so that moveSliderIntoPlayfield() can determine their relative distances to slider.Position and thus minMargin
|
||||
shiftNestedObjects(slider, Vector2.Subtract(slider.Position, current.PositionOriginal));
|
||||
|
||||
var oldPos = new Vector2(slider.Position.X, slider.Position.Y);
|
||||
|
||||
moveSliderIntoPlayfield(slider, current);
|
||||
|
||||
// Shift them again to move them to their final position after the slider got moved into the playfield
|
||||
shiftNestedObjects(slider, Vector2.Subtract(slider.Position, oldPos));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the final position of the hit object
|
||||
/// </summary>
|
||||
/// <returns>Final position of the hit object</returns>
|
||||
private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo previous, RandomObjectInfo current)
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
var playfieldSize = OsuPlayfield.BASE_SIZE;
|
||||
|
||||
current.AngleRad = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
|
||||
current.PositionRandomised = new Vector2((float)rng.NextDouble() * playfieldSize.X, (float)rng.NextDouble() * playfieldSize.Y);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float distanceToPrev = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal);
|
||||
|
||||
// The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
|
||||
// is proportional to the distance between the last and the current hit object
|
||||
// to allow jumps and prevent too sharp turns during streams.
|
||||
var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal;
|
||||
|
||||
current.AngleRad = (float)randomAngleRad + previous.AngleRad;
|
||||
if (current.AngleRad < 0)
|
||||
current.AngleRad += 2 * (float)Math.PI;
|
||||
|
||||
var posRelativeToPrev = new Vector2(
|
||||
distanceToPrev * (float)Math.Cos(current.AngleRad),
|
||||
distanceToPrev * (float)Math.Sin(current.AngleRad)
|
||||
);
|
||||
|
||||
posRelativeToPrev = getRotatedVector(previous.EndPositionRandomised, posRelativeToPrev);
|
||||
|
||||
current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
|
||||
|
||||
var position = Vector2.Add(previous.EndPositionRandomised, posRelativeToPrev);
|
||||
|
||||
// Move hit objects back into the playfield if they are outside of it,
|
||||
// which would sometimes happen during big jumps otherwise.
|
||||
position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X);
|
||||
position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y);
|
||||
|
||||
current.PositionRandomised = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
|
||||
/// </summary>
|
||||
private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo)
|
||||
{
|
||||
// Min. distances from the slider's position to the playfield border
|
||||
var minMargin = new MarginPadding();
|
||||
|
||||
foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderEndCircle))
|
||||
{
|
||||
if (!(hitObject is OsuHitObject osuHitObject))
|
||||
continue;
|
||||
|
||||
var relativePos = Vector2.Subtract(osuHitObject.Position, slider.Position);
|
||||
|
||||
minMargin.Left = Math.Max(minMargin.Left, -relativePos.X);
|
||||
minMargin.Right = Math.Max(minMargin.Right, relativePos.X);
|
||||
minMargin.Top = Math.Max(minMargin.Top, -relativePos.Y);
|
||||
minMargin.Bottom = Math.Max(minMargin.Bottom, relativePos.Y);
|
||||
}
|
||||
|
||||
if (slider.Position.X < minMargin.Left)
|
||||
slider.Position = new Vector2(minMargin.Left, slider.Position.Y);
|
||||
else if (slider.Position.X + minMargin.Right > OsuPlayfield.BASE_SIZE.X)
|
||||
slider.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - minMargin.Right, slider.Position.Y);
|
||||
|
||||
if (slider.Position.Y < minMargin.Top)
|
||||
slider.Position = new Vector2(slider.Position.X, minMargin.Top);
|
||||
else if (slider.Position.Y + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y)
|
||||
slider.Position = new Vector2(slider.Position.X, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom);
|
||||
|
||||
currentObjectInfo.PositionRandomised = slider.Position;
|
||||
currentObjectInfo.EndPositionRandomised = slider.EndPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts all nested <see cref="SliderTick"/>s and <see cref="SliderRepeat"/>s by the specified shift.
|
||||
/// </summary>
|
||||
/// <param name="slider"><see cref="Slider"/> whose nested <see cref="SliderTick"/>s and <see cref="SliderRepeat"/>s should be shifted</param>
|
||||
/// <param name="shift">The <see cref="Vector2"/> the <see cref="Slider"/>'s nested <see cref="SliderTick"/>s and <see cref="SliderRepeat"/>s should be shifted by</param>
|
||||
private void shiftNestedObjects(Slider slider, Vector2 shift)
|
||||
{
|
||||
foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat))
|
||||
{
|
||||
if (!(hitObject is OsuHitObject osuHitObject))
|
||||
continue;
|
||||
|
||||
osuHitObject.Position = Vector2.Add(osuHitObject.Position, shift);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the position of the current hit object relative to the previous one.
|
||||
/// </summary>
|
||||
/// <returns>The position of the current hit object relative to the previous one</returns>
|
||||
private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev)
|
||||
{
|
||||
var relativeRotationDistance = 0f;
|
||||
|
||||
if (prevPosChanged.X < playfield_middle.X)
|
||||
{
|
||||
relativeRotationDistance = Math.Max(
|
||||
(border_distance_x - prevPosChanged.X) / border_distance_x,
|
||||
relativeRotationDistance
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
relativeRotationDistance = Math.Max(
|
||||
(prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x,
|
||||
relativeRotationDistance
|
||||
);
|
||||
}
|
||||
|
||||
if (prevPosChanged.Y < playfield_middle.Y)
|
||||
{
|
||||
relativeRotationDistance = Math.Max(
|
||||
(border_distance_y - prevPosChanged.Y) / border_distance_y,
|
||||
relativeRotationDistance
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
relativeRotationDistance = Math.Max(
|
||||
(prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y,
|
||||
relativeRotationDistance
|
||||
);
|
||||
}
|
||||
|
||||
return rotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevPosChanged, relativeRotationDistance / 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates vector "initial" towards vector "destinantion"
|
||||
/// </summary>
|
||||
/// <param name="initial">Vector to rotate to "destination"</param>
|
||||
/// <param name="destination">Vector "initial" should be rotated to</param>
|
||||
/// <param name="relativeDistance">The angle the vector should be rotated relative to the difference between the angles of the the two vectors.</param>
|
||||
/// <returns>Resulting vector</returns>
|
||||
private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance)
|
||||
{
|
||||
var initialAngleRad = Math.Atan2(initial.Y, initial.X);
|
||||
var destAngleRad = Math.Atan2(destination.Y, destination.X);
|
||||
|
||||
var diff = destAngleRad - initialAngleRad;
|
||||
|
||||
while (diff < -Math.PI) diff += 2 * Math.PI;
|
||||
|
||||
while (diff > Math.PI) diff -= 2 * Math.PI;
|
||||
|
||||
var finalAngleRad = initialAngleRad + relativeDistance * diff;
|
||||
|
||||
return new Vector2(
|
||||
initial.Length * (float)Math.Cos(finalAngleRad),
|
||||
initial.Length * (float)Math.Sin(finalAngleRad)
|
||||
);
|
||||
}
|
||||
|
||||
private class RandomObjectInfo
|
||||
{
|
||||
public float AngleRad { get; set; }
|
||||
|
||||
public Vector2 PositionOriginal { get; }
|
||||
public Vector2 PositionRandomised { get; set; }
|
||||
|
||||
public Vector2 EndPositionOriginal { get; }
|
||||
public Vector2 EndPositionRandomised { get; set; }
|
||||
|
||||
public RandomObjectInfo(OsuHitObject hitObject)
|
||||
{
|
||||
PositionRandomised = PositionOriginal = hitObject.Position;
|
||||
EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition;
|
||||
AngleRad = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Pooling;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
@ -12,34 +13,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
/// <summary>
|
||||
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
|
||||
/// </summary>
|
||||
public class FollowPointConnection : PoolableDrawable
|
||||
public class FollowPointConnection : PoolableDrawableWithLifetime<FollowPointLifetimeEntry>
|
||||
{
|
||||
// Todo: These shouldn't be constants
|
||||
public const int SPACING = 32;
|
||||
public const double PREEMPT = 800;
|
||||
|
||||
public FollowPointLifetimeEntry Entry;
|
||||
public DrawablePool<FollowPoint> Pool;
|
||||
|
||||
protected override void PrepareForUse()
|
||||
protected override void OnApply(FollowPointLifetimeEntry entry)
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Entry.Invalidated += onEntryInvalidated;
|
||||
base.OnApply(entry);
|
||||
|
||||
entry.Invalidated += onEntryInvalidated;
|
||||
refreshPoints();
|
||||
}
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
protected override void OnFree(FollowPointLifetimeEntry entry)
|
||||
{
|
||||
base.FreeAfterUse();
|
||||
|
||||
Entry.Invalidated -= onEntryInvalidated;
|
||||
base.OnFree(entry);
|
||||
|
||||
entry.Invalidated -= onEntryInvalidated;
|
||||
// Return points to the pool.
|
||||
ClearInternal(false);
|
||||
|
||||
Entry = null;
|
||||
}
|
||||
|
||||
private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints);
|
||||
@ -48,8 +44,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
{
|
||||
ClearInternal(false);
|
||||
|
||||
OsuHitObject start = Entry.Start;
|
||||
OsuHitObject end = Entry.End;
|
||||
var entry = Entry;
|
||||
if (entry?.End == null) return;
|
||||
|
||||
OsuHitObject start = entry.Start;
|
||||
OsuHitObject end = entry.End;
|
||||
|
||||
double startTime = start.GetEndTime();
|
||||
|
||||
@ -87,14 +86,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
fp.FadeIn(end.TimeFadeIn);
|
||||
fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out);
|
||||
fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out);
|
||||
fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn);
|
||||
fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn).Expire();
|
||||
|
||||
finalTransformEndTime = fadeOutTime + end.TimeFadeIn;
|
||||
finalTransformEndTime = fp.LifetimeEnd;
|
||||
}
|
||||
}
|
||||
|
||||
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
|
||||
Entry.LifetimeEnd = finalTransformEndTime;
|
||||
entry.LifetimeEnd = finalTransformEndTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
@ -11,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
{
|
||||
public class FollowPointLifetimeEntry : LifetimeEntry
|
||||
{
|
||||
public event Action Invalidated;
|
||||
public event Action? Invalidated;
|
||||
public readonly OsuHitObject Start;
|
||||
|
||||
public FollowPointLifetimeEntry(OsuHitObject start)
|
||||
@ -22,9 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
bindEvents();
|
||||
}
|
||||
|
||||
private OsuHitObject end;
|
||||
private OsuHitObject? end;
|
||||
|
||||
public OsuHitObject End
|
||||
public OsuHitObject? End
|
||||
{
|
||||
get => end;
|
||||
set
|
||||
@ -56,11 +58,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
|
||||
public void UnbindEvents()
|
||||
{
|
||||
if (Start != null)
|
||||
{
|
||||
Start.DefaultsApplied -= onDefaultsApplied;
|
||||
Start.PositionBindable.ValueChanged -= onPositionChanged;
|
||||
}
|
||||
Start.DefaultsApplied -= onDefaultsApplied;
|
||||
Start.PositionBindable.ValueChanged -= onPositionChanged;
|
||||
|
||||
if (End != null)
|
||||
{
|
||||
|
@ -6,43 +6,32 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Pooling;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
|
||||
/// </summary>
|
||||
public class FollowPointRenderer : CompositeDrawable
|
||||
public class FollowPointRenderer : PooledDrawableWithLifetimeContainer<FollowPointLifetimeEntry, FollowPointConnection>
|
||||
{
|
||||
public override bool RemoveCompletedTransforms => false;
|
||||
|
||||
public IReadOnlyList<FollowPointLifetimeEntry> Entries => lifetimeEntries;
|
||||
public new IReadOnlyList<FollowPointLifetimeEntry> Entries => lifetimeEntries;
|
||||
|
||||
private DrawablePool<FollowPointConnection> connectionPool;
|
||||
private DrawablePool<FollowPoint> pointPool;
|
||||
|
||||
private readonly List<FollowPointLifetimeEntry> lifetimeEntries = new List<FollowPointLifetimeEntry>();
|
||||
private readonly Dictionary<LifetimeEntry, FollowPointConnection> connectionsInUse = new Dictionary<LifetimeEntry, FollowPointConnection>();
|
||||
private readonly Dictionary<HitObject, IBindable> startTimeMap = new Dictionary<HitObject, IBindable>();
|
||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||
|
||||
public FollowPointRenderer()
|
||||
{
|
||||
lifetimeManager.EntryBecameAlive += onEntryBecameAlive;
|
||||
lifetimeManager.EntryBecameDead += onEntryBecameDead;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
connectionPool = new DrawablePoolNoLifetime<FollowPointConnection>(1, 200),
|
||||
pointPool = new DrawablePoolNoLifetime<FollowPoint>(50, 1000)
|
||||
connectionPool = new DrawablePool<FollowPointConnection>(1, 200),
|
||||
pointPool = new DrawablePool<FollowPoint>(50, 1000)
|
||||
};
|
||||
}
|
||||
|
||||
@ -107,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
previousEntry.End = newEntry.Start;
|
||||
}
|
||||
|
||||
lifetimeManager.AddEntry(newEntry);
|
||||
Add(newEntry);
|
||||
}
|
||||
|
||||
private void removeEntry(OsuHitObject hitObject)
|
||||
@ -118,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
entry.UnbindEvents();
|
||||
|
||||
lifetimeEntries.RemoveAt(index);
|
||||
lifetimeManager.RemoveEntry(entry);
|
||||
Remove(entry);
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
@ -131,30 +120,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckChildrenLife()
|
||||
protected override FollowPointConnection GetDrawable(FollowPointLifetimeEntry entry)
|
||||
{
|
||||
bool anyAliveChanged = base.CheckChildrenLife();
|
||||
anyAliveChanged |= lifetimeManager.Update(Time.Current);
|
||||
return anyAliveChanged;
|
||||
}
|
||||
|
||||
private void onEntryBecameAlive(LifetimeEntry entry)
|
||||
{
|
||||
var connection = connectionPool.Get(c =>
|
||||
{
|
||||
c.Entry = (FollowPointLifetimeEntry)entry;
|
||||
c.Pool = pointPool;
|
||||
});
|
||||
|
||||
connectionsInUse[entry] = connection;
|
||||
|
||||
AddInternal(connection);
|
||||
}
|
||||
|
||||
private void onEntryBecameDead(LifetimeEntry entry)
|
||||
{
|
||||
RemoveInternal(connectionsInUse[entry]);
|
||||
connectionsInUse.Remove(entry);
|
||||
var connection = connectionPool.Get();
|
||||
connection.Pool = pointPool;
|
||||
connection.Apply(entry);
|
||||
return connection;
|
||||
}
|
||||
|
||||
private void onStartTimeChanged(OsuHitObject hitObject)
|
||||
@ -171,16 +142,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
entry.UnbindEvents();
|
||||
lifetimeEntries.Clear();
|
||||
}
|
||||
|
||||
private class DrawablePoolNoLifetime<T> : DrawablePool<T>
|
||||
where T : PoolableDrawable, new()
|
||||
{
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null)
|
||||
: base(initialSize, maximumSize)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,8 +182,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
// todo: temporary / arbitrary, used for lifetime optimisation.
|
||||
this.Delay(800).FadeOut();
|
||||
|
||||
(CirclePiece.Drawable as IMainCirclePiece)?.Animate(state);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
|
@ -97,8 +97,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.UpdateHitStateTransforms(state);
|
||||
|
||||
(CirclePiece.Drawable as IMainCirclePiece)?.Animate(state);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
|
@ -7,13 +7,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece
|
||||
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, IHasMainCirclePiece
|
||||
{
|
||||
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
||||
|
||||
@ -86,8 +87,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
Debug.Assert(HitObject.HitWindows != null);
|
||||
|
||||
(CirclePiece.Drawable as IMainCirclePiece)?.Animate(state);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
@ -111,7 +110,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
public void UpdateSnakingPosition(Vector2 start, Vector2 end) =>
|
||||
Position = HitObject.RepeatIndex % 2 == 0 ? end : start;
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
|
||||
if (Slider != null)
|
||||
Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +164,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
new OsuModTarget(),
|
||||
new OsuModDifficultyAdjust(),
|
||||
new OsuModClassic()
|
||||
new OsuModClassic(),
|
||||
new OsuModRandom(),
|
||||
};
|
||||
|
||||
case ModType.Automation:
|
||||
|
@ -42,6 +42,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
Origin = Anchor.Centre,
|
||||
Texture = textures.Get(@"Gameplay/osu/disc"),
|
||||
},
|
||||
new KiaiFlash
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
triangles = new TrianglesPiece
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -1,17 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public interface IMainCirclePiece
|
||||
{
|
||||
/// <summary>
|
||||
/// Begins animating this <see cref="IMainCirclePiece"/>.
|
||||
/// </summary>
|
||||
/// <param name="state">The <see cref="ArmedState"/> of the related <see cref="DrawableHitCircle"/>.</param>
|
||||
void Animate(ArmedState state);
|
||||
}
|
||||
}
|
43
osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs
Normal file
43
osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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.Audio.Track;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public class KiaiFlash : BeatSyncedContainer
|
||||
{
|
||||
private const double fade_length = 80;
|
||||
|
||||
private const float flash_opacity = 0.25f;
|
||||
|
||||
public KiaiFlash()
|
||||
{
|
||||
EarlyActivationMilliseconds = 80;
|
||||
Blending = BlendingParameters.Additive;
|
||||
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.White,
|
||||
Alpha = 0f,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
{
|
||||
if (!effectPoint.KiaiMode)
|
||||
return;
|
||||
|
||||
Child
|
||||
.FadeTo(flash_opacity, EarlyActivationMilliseconds, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeOut(timingPoint.BeatLength - fade_length, Easing.OutSine);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public class MainCirclePiece : CompositeDrawable, IMainCirclePiece
|
||||
public class MainCirclePiece : CompositeDrawable
|
||||
{
|
||||
private readonly CirclePiece circle;
|
||||
private readonly RingPiece ring;
|
||||
@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||
private readonly IBindable<ArmedState> armedState = new Bindable<ArmedState>();
|
||||
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableObject { get; set; }
|
||||
@ -53,6 +54,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||
armedState.BindTo(drawableObject.State);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -67,16 +69,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
}, true);
|
||||
|
||||
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
||||
|
||||
armedState.BindValueChanged(animate, true);
|
||||
}
|
||||
|
||||
public void Animate(ArmedState state)
|
||||
private void animate(ValueChangedEvent<ArmedState> state)
|
||||
{
|
||||
ClearTransforms(true);
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
|
||||
glow.FadeOut(400);
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
{
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
const double flash_in = 40;
|
||||
@ -89,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
explode.FadeIn(flash_in);
|
||||
this.ScaleTo(1.5f, 400, Easing.OutQuad);
|
||||
|
||||
using (BeginDelayedSequence(flash_in, true))
|
||||
using (BeginDelayedSequence(flash_in))
|
||||
{
|
||||
// after the flash, we can hide some elements that were behind it
|
||||
ring.FadeOut();
|
||||
|
61
osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs
Normal file
61
osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.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 osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
internal class KiaiFlashingSprite : BeatSyncedContainer
|
||||
{
|
||||
private readonly Sprite mainSprite;
|
||||
private readonly Sprite flashingSprite;
|
||||
|
||||
public Texture Texture
|
||||
{
|
||||
set
|
||||
{
|
||||
mainSprite.Texture = value;
|
||||
flashingSprite.Texture = value;
|
||||
}
|
||||
}
|
||||
|
||||
private const float flash_opacity = 0.3f;
|
||||
|
||||
public KiaiFlashingSprite()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
mainSprite = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
flashingSprite = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
Blending = BlendingParameters.Additive,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
{
|
||||
if (!effectPoint.KiaiMode)
|
||||
return;
|
||||
|
||||
flashingSprite
|
||||
.FadeTo(flash_opacity)
|
||||
.Then()
|
||||
.FadeOut(timingPoint.BeatLength * 0.75f);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,14 +5,12 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
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 osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -20,7 +18,7 @@ using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
public class LegacyMainCirclePiece : CompositeDrawable, IMainCirclePiece
|
||||
public class LegacyMainCirclePiece : CompositeDrawable
|
||||
{
|
||||
private readonly string priorityLookup;
|
||||
private readonly bool hasNumber;
|
||||
@ -33,14 +31,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
}
|
||||
|
||||
private Container<Sprite> circleSprites;
|
||||
private Sprite hitCircleSprite;
|
||||
private Sprite hitCircleOverlay;
|
||||
private Container circleSprites;
|
||||
private Drawable hitCircleSprite;
|
||||
private Drawable hitCircleOverlay;
|
||||
|
||||
private SkinnableSpriteText hitCircleText;
|
||||
|
||||
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
||||
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||
private readonly IBindable<ArmedState> armedState = new Bindable<ArmedState>();
|
||||
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableObject { get; set; }
|
||||
@ -72,20 +71,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
circleSprites = new Container<Sprite>
|
||||
circleSprites = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
hitCircleSprite = new Sprite
|
||||
hitCircleSprite = new KiaiFlashingSprite
|
||||
{
|
||||
Texture = baseTexture,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
hitCircleOverlay = new Sprite
|
||||
hitCircleOverlay = new KiaiFlashingSprite
|
||||
{
|
||||
Texture = overlayTexture,
|
||||
Anchor = Anchor.Centre,
|
||||
@ -115,6 +114,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||
armedState.BindTo(drawableObject.State);
|
||||
|
||||
Texture getTextureWithFallback(string name)
|
||||
{
|
||||
@ -139,15 +139,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||
if (hasNumber)
|
||||
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
|
||||
|
||||
armedState.BindValueChanged(animate, true);
|
||||
}
|
||||
|
||||
public void Animate(ArmedState state)
|
||||
private void animate(ValueChangedEvent<ArmedState> state)
|
||||
{
|
||||
const double legacy_fade_duration = 240;
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true))
|
||||
ClearTransforms(true);
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
{
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
circleSprites.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
|
@ -34,90 +34,90 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
if (!(component is OsuSkinComponent osuComponent))
|
||||
return null;
|
||||
|
||||
switch (osuComponent.Component)
|
||||
if (component is OsuSkinComponent osuComponent)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
||||
switch (osuComponent.Component)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
||||
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||
if (followCircle != null)
|
||||
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||
followCircle.Scale *= 0.5f;
|
||||
return followCircle;
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||
if (followCircle != null)
|
||||
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||
followCircle.Scale *= 0.5f;
|
||||
return followCircle;
|
||||
|
||||
case OsuSkinComponents.SliderBall:
|
||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||
case OsuSkinComponents.SliderBall:
|
||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||
|
||||
// todo: slider ball has a custom frame delay based on velocity
|
||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
||||
// todo: slider ball has a custom frame delay based on velocity
|
||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
||||
|
||||
if (sliderBallContent != null)
|
||||
return new LegacySliderBall(sliderBallContent);
|
||||
if (sliderBallContent != null)
|
||||
return new LegacySliderBall(sliderBallContent);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderBody:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderBody();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderTailHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderendcircle", false);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderHeadHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderstartcircle");
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.Cursor:
|
||||
if (Source.GetTexture("cursor") != null)
|
||||
return new LegacyCursor();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorTrail:
|
||||
if (Source.GetTexture("cursortrail") != null)
|
||||
return new LegacyCursorTrail();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircleText:
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(0.8f),
|
||||
};
|
||||
case OsuSkinComponents.SliderBody:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderBody();
|
||||
|
||||
case OsuSkinComponents.SpinnerBody:
|
||||
bool hasBackground = Source.GetTexture("spinner-background") != null;
|
||||
return null;
|
||||
|
||||
if (Source.GetTexture("spinner-top") != null && !hasBackground)
|
||||
return new LegacyNewStyleSpinner();
|
||||
else if (hasBackground)
|
||||
return new LegacyOldStyleSpinner();
|
||||
case OsuSkinComponents.SliderTailHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderendcircle", false);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderHeadHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderstartcircle");
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.Cursor:
|
||||
if (Source.GetTexture("cursor") != null)
|
||||
return new LegacyCursor();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorTrail:
|
||||
if (Source.GetTexture("cursortrail") != null)
|
||||
return new LegacyCursorTrail();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircleText:
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(0.8f),
|
||||
};
|
||||
|
||||
case OsuSkinComponents.SpinnerBody:
|
||||
bool hasBackground = Source.GetTexture("spinner-background") != null;
|
||||
|
||||
if (Source.GetTexture("spinner-top") != null && !hasBackground)
|
||||
return new LegacyNewStyleSpinner();
|
||||
else if (hasBackground)
|
||||
return new LegacyOldStyleSpinner();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Source.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddStep("Bar line", () => SetContents(() =>
|
||||
AddStep("Bar line", () => SetContents(_ =>
|
||||
{
|
||||
ScrollingHitObjectContainer hoc;
|
||||
|
||||
@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
return cont;
|
||||
}));
|
||||
|
||||
AddStep("Bar line (major)", () => SetContents(() =>
|
||||
AddStep("Bar line (major)", () => SetContents(_ =>
|
||||
{
|
||||
ScrollingHitObjectContainer hoc;
|
||||
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddStep("Drum roll", () => SetContents(() =>
|
||||
AddStep("Drum roll", () => SetContents(_ =>
|
||||
{
|
||||
var hoc = new ScrollingHitObjectContainer();
|
||||
|
||||
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
return hoc;
|
||||
}));
|
||||
|
||||
AddStep("Drum roll (strong)", () => SetContents(() =>
|
||||
AddStep("Drum roll (strong)", () => SetContents(_ =>
|
||||
{
|
||||
var hoc = new ScrollingHitObjectContainer();
|
||||
|
||||
|
@ -17,25 +17,25 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddStep("Centre hit", () => SetContents(() => new DrawableHit(createHitAtCurrentTime())
|
||||
AddStep("Centre hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}));
|
||||
|
||||
AddStep("Centre hit (strong)", () => SetContents(() => new DrawableHit(createHitAtCurrentTime(true))
|
||||
AddStep("Centre hit (strong)", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime(true))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}));
|
||||
|
||||
AddStep("Rim hit", () => SetContents(() => new DrawableHit(createHitAtCurrentTime())
|
||||
AddStep("Rim hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}));
|
||||
|
||||
AddStep("Rim hit (strong)", () => SetContents(() => new DrawableHit(createHitAtCurrentTime(true))
|
||||
AddStep("Rim hit (strong)", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime(true))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -54,16 +54,16 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap());
|
||||
|
||||
AddStep("clear state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Clear)));
|
||||
AddStep("idle state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Idle)));
|
||||
AddStep("kiai state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Kiai)));
|
||||
AddStep("fail state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Fail)));
|
||||
AddStep("clear state", () => SetContents(_ => new TaikoMascotAnimation(TaikoMascotAnimationState.Clear)));
|
||||
AddStep("idle state", () => SetContents(_ => new TaikoMascotAnimation(TaikoMascotAnimationState.Idle)));
|
||||
AddStep("kiai state", () => SetContents(_ => new TaikoMascotAnimation(TaikoMascotAnimationState.Kiai)));
|
||||
AddStep("fail state", () => SetContents(_ => new TaikoMascotAnimation(TaikoMascotAnimationState.Fail)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialState()
|
||||
{
|
||||
AddStep("create mascot", () => SetContents(() => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both }));
|
||||
AddStep("create mascot", () => SetContents(_ => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both }));
|
||||
|
||||
AddAssert("mascot initially idle", () => allMascotsIn(TaikoMascotAnimationState.Idle));
|
||||
}
|
||||
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap());
|
||||
|
||||
AddStep("create mascot", () => SetContents(() => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both }));
|
||||
AddStep("create mascot", () => SetContents(_ => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both }));
|
||||
|
||||
AddStep("set clear state", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear));
|
||||
AddStep("miss", () => mascots.ForEach(mascot => mascot.LastResult.Value = new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }));
|
||||
@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
Beatmap.Value.Track.Start();
|
||||
|
||||
SetContents(() =>
|
||||
SetContents(_ =>
|
||||
{
|
||||
var ruleset = new TaikoRuleset();
|
||||
return new DrawableTaikoRuleset(ruleset, Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo));
|
||||
|
@ -22,16 +22,16 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[Test]
|
||||
public void TestNormalHit()
|
||||
{
|
||||
AddStep("Great", () => SetContents(() => getContentFor(createHit(HitResult.Great))));
|
||||
AddStep("Ok", () => SetContents(() => getContentFor(createHit(HitResult.Ok))));
|
||||
AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss))));
|
||||
AddStep("Great", () => SetContents(_ => getContentFor(createHit(HitResult.Great))));
|
||||
AddStep("Ok", () => SetContents(_ => getContentFor(createHit(HitResult.Ok))));
|
||||
AddStep("Miss", () => SetContents(_ => getContentFor(createHit(HitResult.Miss))));
|
||||
}
|
||||
|
||||
[TestCase(HitResult.Great)]
|
||||
[TestCase(HitResult.Ok)]
|
||||
public void TestStrongHit(HitResult type)
|
||||
{
|
||||
AddStep("create hit", () => SetContents(() => getContentFor(createStrongHit(type))));
|
||||
AddStep("create hit", () => SetContents(_ => getContentFor(createStrongHit(type))));
|
||||
AddStep("visualise second hit",
|
||||
() => this.ChildrenOfType<HitExplosion>()
|
||||
.ForEach(e => e.VisualiseSecondHit(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()))));
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
||||
SetContents(_ => new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
|
@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[Test]
|
||||
public void TestKiaiHits()
|
||||
{
|
||||
AddStep("rim hit", () => SetContents(() => getContentFor(createHit(HitType.Rim))));
|
||||
AddStep("centre hit", () => SetContents(() => getContentFor(createHit(HitType.Centre))));
|
||||
AddStep("rim hit", () => SetContents(_ => getContentFor(createHit(HitType.Rim))));
|
||||
AddStep("centre hit", () => SetContents(_ => getContentFor(createHit(HitType.Centre))));
|
||||
}
|
||||
|
||||
private Drawable getContentFor(DrawableTestHit hit)
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
Beatmap.Value.Track.Start();
|
||||
});
|
||||
|
||||
AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo())
|
||||
AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield(new ControlPointInfo())
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
|
||||
public TestSceneTaikoScroller()
|
||||
{
|
||||
AddStep("Load scroller", () => SetContents(() =>
|
||||
AddStep("Load scroller", () => SetContents(_ =>
|
||||
new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
{
|
||||
Clock = new FramedClock(clock),
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset();
|
||||
|
||||
protected override IResourceStore<byte[]> Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples)));
|
||||
protected override IResourceStore<byte[]> RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples)));
|
||||
|
||||
[TestCase("taiko-normal-hitnormal")]
|
||||
[TestCase("normal-hitnormal")]
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -79,8 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
// Old osu! used hit sounding to determine various hit type information
|
||||
IList<HitSampleInfo> samples = obj.Samples;
|
||||
|
||||
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case IHasDistance distanceData:
|
||||
@ -94,15 +92,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
||||
{
|
||||
IList<HitSampleInfo> currentSamples = allSamples[i];
|
||||
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||
|
||||
yield return new Hit
|
||||
{
|
||||
StartTime = j,
|
||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
||||
Samples = currentSamples,
|
||||
IsStrong = strong
|
||||
};
|
||||
|
||||
i = (i + 1) % allSamples.Count;
|
||||
@ -117,7 +111,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
IsStrong = strong,
|
||||
Duration = taikoDuration,
|
||||
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
||||
};
|
||||
@ -143,16 +136,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
default:
|
||||
{
|
||||
bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
|
||||
|
||||
bool isRim = samples.Any(isRimDefinition);
|
||||
|
||||
yield return new Hit
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
||||
Samples = samples,
|
||||
IsStrong = strong
|
||||
};
|
||||
|
||||
break;
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
|
||||
{
|
||||
new Colour(mods),
|
||||
new Rhythm(mods),
|
||||
|
@ -69,7 +69,11 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
{
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
||||
if (h is Hit taikoHit)
|
||||
{
|
||||
taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
||||
EditorBeatmap.Update(h);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,14 +4,13 @@
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModAutoplay : ModAutoplay<TaikoHitObject>
|
||||
public class TaikoModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -20,10 +20,13 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
var taikoBeatmap = (TaikoBeatmap)beatmap;
|
||||
|
||||
Seed.Value ??= RNG.Next();
|
||||
var rng = new Random((int)Seed.Value);
|
||||
|
||||
foreach (var obj in taikoBeatmap.HitObjects)
|
||||
{
|
||||
if (obj is Hit hit)
|
||||
hit.Type = RNG.Next(2) == 0 ? HitType.Centre : HitType.Rim;
|
||||
hit.Type = rng.Next(2) == 0 ? HitType.Centre : HitType.Rim;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,25 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public HitType Type
|
||||
{
|
||||
get => TypeBindable.Value;
|
||||
set
|
||||
{
|
||||
TypeBindable.Value = value;
|
||||
updateSamplesFromType();
|
||||
}
|
||||
set => TypeBindable.Value = value;
|
||||
}
|
||||
|
||||
public Hit()
|
||||
{
|
||||
TypeBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||
}
|
||||
|
||||
private void updateTypeFromSamples()
|
||||
{
|
||||
Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array of any samples which would cause this object to be a "rim" type hit.
|
||||
/// </summary>
|
||||
private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
||||
|
||||
private void updateSamplesFromType()
|
||||
{
|
||||
var rimSamples = getRimSamples();
|
||||
@ -42,11 +54,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array of any samples which would cause this object to be a "rim" type hit.
|
||||
/// </summary>
|
||||
private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
|
@ -33,14 +33,21 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public bool IsStrong
|
||||
{
|
||||
get => IsStrongBindable.Value;
|
||||
set
|
||||
{
|
||||
IsStrongBindable.Value = value;
|
||||
updateSamplesFromStrong();
|
||||
}
|
||||
set => IsStrongBindable.Value = value;
|
||||
}
|
||||
|
||||
private void updateSamplesFromStrong()
|
||||
protected TaikoStrongableHitObject()
|
||||
{
|
||||
IsStrongBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||
}
|
||||
|
||||
private void updateTypeFromSamples()
|
||||
{
|
||||
IsStrong = getStrongSamples().Any();
|
||||
}
|
||||
|
||||
private void updateSamplesFromType()
|
||||
{
|
||||
var strongSamples = getStrongSamples();
|
||||
|
||||
|
@ -38,98 +38,98 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
}
|
||||
|
||||
if (!(component is TaikoSkinComponent taikoComponent))
|
||||
return null;
|
||||
|
||||
switch (taikoComponent.Component)
|
||||
if (component is TaikoSkinComponent taikoComponent)
|
||||
{
|
||||
case TaikoSkinComponents.DrumRollBody:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyDrumRoll();
|
||||
switch (taikoComponent.Component)
|
||||
{
|
||||
case TaikoSkinComponents.DrumRollBody:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyDrumRoll();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.InputDrum:
|
||||
if (GetTexture("taiko-bar-left") != null)
|
||||
return new LegacyInputDrum();
|
||||
case TaikoSkinComponents.InputDrum:
|
||||
if (GetTexture("taiko-bar-left") != null)
|
||||
return new LegacyInputDrum();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.CentreHit:
|
||||
case TaikoSkinComponents.RimHit:
|
||||
case TaikoSkinComponents.CentreHit:
|
||||
case TaikoSkinComponents.RimHit:
|
||||
|
||||
if (GetTexture("taikohitcircle") != null)
|
||||
return new LegacyHit(taikoComponent.Component);
|
||||
if (GetTexture("taikohitcircle") != null)
|
||||
return new LegacyHit(taikoComponent.Component);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.DrumRollTick:
|
||||
return this.GetAnimation("sliderscorepoint", false, false);
|
||||
case TaikoSkinComponents.DrumRollTick:
|
||||
return this.GetAnimation("sliderscorepoint", false, false);
|
||||
|
||||
case TaikoSkinComponents.HitTarget:
|
||||
if (GetTexture("taikobigcircle") != null)
|
||||
return new TaikoLegacyHitTarget();
|
||||
case TaikoSkinComponents.HitTarget:
|
||||
if (GetTexture("taikobigcircle") != null)
|
||||
return new TaikoLegacyHitTarget();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.PlayfieldBackgroundRight:
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return new TaikoLegacyPlayfieldBackgroundRight();
|
||||
case TaikoSkinComponents.PlayfieldBackgroundRight:
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return new TaikoLegacyPlayfieldBackgroundRight();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.PlayfieldBackgroundLeft:
|
||||
// This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return Drawable.Empty();
|
||||
case TaikoSkinComponents.PlayfieldBackgroundLeft:
|
||||
// This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return Drawable.Empty();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.BarLine:
|
||||
if (GetTexture("taiko-barline") != null)
|
||||
return new LegacyBarLine();
|
||||
case TaikoSkinComponents.BarLine:
|
||||
if (GetTexture("taiko-barline") != null)
|
||||
return new LegacyBarLine();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.TaikoExplosionMiss:
|
||||
case TaikoSkinComponents.TaikoExplosionMiss:
|
||||
|
||||
var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
||||
if (missSprite != null)
|
||||
return new LegacyHitExplosion(missSprite);
|
||||
var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
||||
if (missSprite != null)
|
||||
return new LegacyHitExplosion(missSprite);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.TaikoExplosionOk:
|
||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||
case TaikoSkinComponents.TaikoExplosionOk:
|
||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||
|
||||
var hitName = getHitName(taikoComponent.Component);
|
||||
var hitSprite = this.GetAnimation(hitName, true, false);
|
||||
var hitName = getHitName(taikoComponent.Component);
|
||||
var hitSprite = this.GetAnimation(hitName, true, false);
|
||||
|
||||
if (hitSprite != null)
|
||||
{
|
||||
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
||||
if (hitSprite != null)
|
||||
{
|
||||
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
||||
|
||||
return new LegacyHitExplosion(hitSprite, strongHitSprite);
|
||||
}
|
||||
return new LegacyHitExplosion(hitSprite, strongHitSprite);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.TaikoExplosionKiai:
|
||||
// suppress the default kiai explosion if the skin brings its own sprites.
|
||||
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
|
||||
if (hasExplosion.Value)
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
case TaikoSkinComponents.TaikoExplosionKiai:
|
||||
// suppress the default kiai explosion if the skin brings its own sprites.
|
||||
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
|
||||
if (hasExplosion.Value)
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.Scroller:
|
||||
if (GetTexture("taiko-slider") != null)
|
||||
return new LegacyTaikoScroller();
|
||||
case TaikoSkinComponents.Scroller:
|
||||
if (GetTexture("taiko-slider") != null)
|
||||
return new LegacyTaikoScroller();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.Mascot:
|
||||
return new DrawableTaikoMascot();
|
||||
case TaikoSkinComponents.Mascot:
|
||||
return new DrawableTaikoMascot();
|
||||
}
|
||||
}
|
||||
|
||||
return Source.GetDrawableComponent(component);
|
||||
|
@ -1,9 +1,6 @@
|
||||
// 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.Containers;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
@ -11,6 +8,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
internal class DrumRollHitContainer : ScrollingHitObjectContainer
|
||||
{
|
||||
// TODO: this usage is buggy.
|
||||
// Because `LifetimeStart` is set based on scrolling, lifetime is not same as the time when the object is created.
|
||||
// If the `Update` override is removed, it breaks in an obscure way.
|
||||
protected override bool RemoveRewoundEntry => true;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -23,14 +25,5 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
Remove(flyingHit);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e)
|
||||
{
|
||||
base.OnChildLifetimeBoundaryCrossed(e);
|
||||
|
||||
// ensure all old hits are removed on becoming alive (may miss being in the AliveInternalChildren list above).
|
||||
if (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward)
|
||||
Remove((DrawableHitObject)e.Child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +169,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||
|
||||
protected override ISkin GetSkin() => throw new NotImplementedException();
|
||||
|
||||
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,10 @@ namespace osu.Game.Tests.Chat
|
||||
[TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123#osu/456")]
|
||||
[TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123#osu/456?whatever")]
|
||||
[TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123/456")]
|
||||
[TestCase(LinkAction.External, null, "https://dev.ppy.sh/beatmapsets/abc/def")]
|
||||
[TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/abc/def", "https://dev.ppy.sh/beatmapsets/abc/def")]
|
||||
[TestCase(LinkAction.OpenBeatmapSet, "123", "https://dev.ppy.sh/beatmapsets/123")]
|
||||
[TestCase(LinkAction.OpenBeatmapSet, "123", "https://dev.ppy.sh/beatmapsets/123/whatever")]
|
||||
[TestCase(LinkAction.External, null, "https://dev.ppy.sh/beatmapsets/abc")]
|
||||
[TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/abc", "https://dev.ppy.sh/beatmapsets/abc")]
|
||||
public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link)
|
||||
{
|
||||
MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
|
||||
@ -489,5 +489,23 @@ namespace osu.Game.Tests.Chat
|
||||
Assert.AreEqual(result.Links[2].Url, "\uD83D\uDE00");
|
||||
Assert.AreEqual(result.Links[3].Url, "\uD83D\uDE20");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAbsoluteExternalLinks()
|
||||
{
|
||||
LinkDetails result = MessageFormatter.GetLinkDetails("https://google.com");
|
||||
|
||||
Assert.AreEqual(LinkAction.External, result.Action);
|
||||
Assert.AreEqual("https://google.com", result.Argument);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRelativeExternalLinks()
|
||||
{
|
||||
LinkDetails result = MessageFormatter.GetLinkDetails("/relative");
|
||||
|
||||
Assert.AreEqual(LinkAction.External, result.Action);
|
||||
Assert.AreEqual("/relative", result.Argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user