1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 05:32:54 +08:00

Merge branch 'master' into combo-colour-brightness-limit

This commit is contained in:
Dean Herbert 2022-11-01 17:50:26 +09:00
commit 27af91003b
370 changed files with 7992 additions and 2333 deletions

View File

@ -88,7 +88,7 @@ jobs:
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
- name: Test
run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx"
run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" -- NUnit.ConsoleOut=0
shell: pwsh
# Attempt to upload results even if test fails.

View File

@ -15,6 +15,8 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.

View File

@ -3,25 +3,25 @@ GEM
specs:
CFPropertyList (3.0.5)
rexml
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.601.0)
aws-sdk-core (3.131.2)
aws-partitions (1.653.0)
aws-sdk-core (3.166.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.57.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (1.59.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-s3 (1.117.1)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.0)
aws-sigv4 (1.5.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
@ -34,10 +34,10 @@ GEM
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.92.3)
faraday (1.10.0)
excon (0.93.1)
faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@ -66,7 +66,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.206.2)
fastlane (2.210.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@ -110,9 +110,9 @@ GEM
souyuz (= 0.11.1)
fastlane-plugin-xamarin (0.6.3)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.23.0)
google-apis-core (>= 0.6, < 2.a)
google-apis-core (0.6.0)
google-apis-androidpublisher_v3 (0.29.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-core (0.9.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@ -121,27 +121,27 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.12.0)
google-apis-core (>= 0.6, < 2.a)
google-apis-playcustomapp_v1 (0.9.0)
google-apis-core (>= 0.6, < 2.a)
google-apis-storage_v1 (0.16.0)
google-apis-core (>= 0.6, < 2.a)
google-apis-iamcredentials_v1 (0.15.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-playcustomapp_v1 (0.12.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.36.2)
google-cloud-errors (1.3.0)
google-cloud-storage (1.43.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.2.0)
googleauth (1.3.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@ -154,22 +154,22 @@ GEM
httpclient (2.8.3)
jmespath (1.6.1)
json (2.6.2)
jwt (2.4.1)
jwt (2.5.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
mini_portile2 (2.7.1)
mini_portile2 (2.8.0)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
nokogiri (1.13.1)
mini_portile2 (~> 2.7.0)
nokogiri (1.13.9)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
public_suffix (4.0.7)
public_suffix (5.0.0)
racc (1.6.0)
rake (13.0.6)
representable (3.2.0)

View File

@ -9,9 +9,9 @@
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />

View File

@ -9,9 +9,9 @@
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />

View File

@ -9,9 +9,9 @@
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />

View File

@ -9,9 +9,9 @@
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />

View File

@ -138,10 +138,10 @@ platform :ios do
end
lane :testflight_prune_dry do
clean_testflight_testers(days_of_inactivity:45, dry_run: true)
clean_testflight_testers(days_of_inactivity:30, dry_run: true)
end
lane :testflight_prune do
clean_testflight_testers(days_of_inactivity: 45)
clean_testflight_testers(days_of_inactivity: 30)
end
end

View File

@ -51,11 +51,11 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.928.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1021.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1028.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
<PackageReference Include="Realm" Version="10.15.1" />
<PackageReference Include="Realm" Version="10.17.0" />
</ItemGroup>
</Project>

View File

@ -18,17 +18,9 @@ using osu.Framework;
using osu.Framework.Logging;
using osu.Game.Updater;
using osu.Desktop.Windows;
using osu.Framework.Input.Handlers;
using osu.Framework.Input.Handlers.Joystick;
using osu.Framework.Input.Handlers.Mouse;
using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Input.Handlers.Touch;
using osu.Framework.Threading;
using osu.Game.IO;
using osu.Game.IPC;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
using osu.Game.Overlays.Settings.Sections.Input;
using osu.Game.Utils;
using SDL2;
@ -137,37 +129,17 @@ namespace osu.Desktop
{
base.SetHost(host);
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
var desktopWindow = (SDL2DesktopWindow)host.Window;
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
if (iconStream != null)
desktopWindow.SetIconFromStream(iconStream);
desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(iconStream);
desktopWindow.Title = Name;
desktopWindow.DragDrop += f => fileDrop(new[] { f });
}
public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
{
switch (handler)
{
case ITabletHandler th:
return new TabletSettings(th);
case MouseHandler mh:
return new MouseSettings(mh);
case JoystickHandler jh:
return new JoystickSettings(jh);
case TouchHandler th:
return new InputSection.HandlerSection(th);
default:
return base.CreateSettingsSubsectionFor(handler);
}
}
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
private readonly List<string> importableFiles = new List<string>();

View File

@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,23 @@
// 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.Rulesets.Catch.Mods;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests.Mods
{
public class TestSceneCatchModFlashlight : ModTestScene
{
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
[TestCase(1f)]
[TestCase(0.5f)]
[TestCase(1.25f)]
[TestCase(1.5f)]
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new CatchModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
[Test]
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new CatchModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
}
}

View File

@ -1,10 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
@ -12,11 +17,11 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
public class TestSceneCatchTouchInput : OsuTestScene
{
private CatchTouchInputMapper catchTouchInputMapper = null!;
[SetUpSteps]
public void SetUpSteps()
[Test]
public void TestBasic()
{
CatchTouchInputMapper catchTouchInputMapper = null!;
AddStep("create input overlay", () =>
{
Child = new CatchInputManager(new CatchRuleset().RulesetInfo)
@ -32,12 +37,30 @@ namespace osu.Game.Rulesets.Catch.Tests
}
};
});
AddStep("show overlay", () => catchTouchInputMapper.Show());
}
[Test]
public void TestBasic()
public void TestWithoutRelax()
{
AddStep("show overlay", () => catchTouchInputMapper.Show());
AddStep("create drawable ruleset without relax mod", () =>
{
Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List<Mod>());
});
AddUntilStep("wait for load", () => Child.IsLoaded);
AddAssert("check touch input is shown", () => this.ChildrenOfType<CatchTouchInputMapper>().Any());
}
[Test]
public void TestWithRelax()
{
AddStep("create drawable ruleset with relax mod", () =>
{
Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List<Mod> { new CatchModRelax() });
});
AddUntilStep("wait for load", () => Child.IsLoaded);
AddAssert("check touch input is not shown", () => !this.ChildrenOfType<CatchTouchInputMapper>().Any());
}
}
}

View File

@ -1,10 +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.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects;
@ -12,6 +12,8 @@ using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
@ -19,15 +21,28 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneComboCounter : CatchSkinnableTestScene
{
private ScoreProcessor scoreProcessor;
private ScoreProcessor scoreProcessor = null!;
private Color4 judgedObjectColour = Color4.White;
private readonly Bindable<bool> showHud = new Bindable<bool>(true);
[BackgroundDependencyLoader]
private void load()
{
Dependencies.CacheAs<Player>(new TestPlayer
{
ShowingOverlayComponents = { BindTarget = showHud },
});
}
[SetUp]
public void SetUp() => Schedule(() =>
{
scoreProcessor = new ScoreProcessor(new CatchRuleset());
showHud.Value = true;
SetContents(_ => new CatchComboDisplay
{
Anchor = Anchor.Centre,
@ -51,9 +66,15 @@ namespace osu.Game.Rulesets.Catch.Tests
1f
);
});
AddStep("set hud to never show", () => showHud.Value = false);
AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 5);
AddStep("set hud to show", () => showHud.Value = true);
AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 5);
}
private void performJudgement(HitResult type, Judgement judgement = null)
private void performJudgement(HitResult type, Judgement? judgement = null)
{
var judgedObject = new DrawableFruit(new Fruit()) { AccentColour = { Value = judgedObjectColour } };

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>

View File

@ -17,6 +17,7 @@ using osu.Game.Rulesets.Catch.Edit;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Catch.Skinning.Argon;
using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Difficulty;
@ -188,6 +189,9 @@ namespace osu.Game.Rulesets.Catch
{
case LegacySkin:
return new CatchLegacySkinTransformer(skin);
case ArgonSkin:
return new CatchArgonSkinTransformer(skin);
}
return null;

View File

@ -36,5 +36,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return base.CreateHitObjectBlueprintFor(hitObject);
}
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Catch.Edit
{
public class CatchEditorPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
{
protected override Container<Drawable> Content => content;
private readonly Container content;
public CatchEditorPlayfieldAdjustmentContainer()
{
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
Size = new Vector2(0.8f, 0.9f);
InternalChild = new ScalingContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Child = content = new Container { RelativeSizeAxes = Axes.Both },
};
}
private class ScalingContainer : Container
{
public ScalingContainer()
{
RelativeSizeAxes = Axes.Y;
Width = CatchPlayfield.WIDTH;
}
protected override void Update()
{
base.Update();
Scale = new Vector2(Math.Min(Parent.ChildSize.X / CatchPlayfield.WIDTH, Parent.ChildSize.Y / CatchPlayfield.HEIGHT));
Height = 1 / Scale.Y;
}
}
}
}

View File

@ -3,6 +3,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@ -10,10 +11,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Edit;
@ -21,7 +23,6 @@ using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@ -33,10 +34,14 @@ namespace osu.Game.Rulesets.Catch.Edit
private CatchDistanceSnapGrid distanceSnapGrid;
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
private InputManager inputManager;
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
{
MinValue = 1,
MaxValue = 10,
};
public CatchHitObjectComposer(CatchRuleset ruleset)
: base(ruleset)
{
@ -51,7 +56,10 @@ namespace osu.Game.Rulesets.Catch.Edit
LayerBelowRuleset.Add(new PlayfieldBorder
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
Height = CatchPlayfield.HEIGHT,
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
});
@ -70,6 +78,19 @@ namespace osu.Game.Rulesets.Catch.Edit
inputManager = GetContainingInputManager();
}
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
{
// osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
// Therefore this functionality is not currently used.
//
// The implementation below is probably correct but should be checked if/when exposed via controls.
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
return actualDistance / expectedDistance;
}
protected override void Update()
{
base.Update();
@ -77,8 +98,30 @@ namespace osu.Game.Rulesets.Catch.Edit
updateDistanceSnapGrid();
}
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
switch (e.Action)
{
// Note that right now these are hard to use as the default key bindings conflict with existing editor key bindings.
// In the future we will want to expose this via UI and potentially change the key bindings to be editor-specific.
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
case GlobalAction.IncreaseScrollSpeed:
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
break;
case GlobalAction.DecreaseScrollSpeed:
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
break;
}
return base.OnPressed(e);
}
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
new DrawableCatchEditorRuleset(ruleset, beatmap, mods);
new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
{
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
};
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
{
@ -87,11 +130,6 @@ namespace osu.Game.Rulesets.Catch.Edit
new BananaShowerCompositionTool()
};
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
{
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
});
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
{
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
@ -163,7 +201,7 @@ namespace osu.Game.Rulesets.Catch.Edit
private void updateDistanceSnapGrid()
{
if (distanceSnapToggle.Value != TernaryState.True)
if (DistanceSnapToggle.Value != TernaryState.True)
{
distanceSnapGrid.Hide();
return;

View File

@ -4,6 +4,7 @@
#nullable disable
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
@ -13,11 +14,24 @@ namespace osu.Game.Rulesets.Catch.Edit
{
public class DrawableCatchEditorRuleset : DrawableCatchRuleset
{
public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
: base(ruleset, beatmap, mods)
{
}
protected override void Update()
{
base.Update();
double gamePlayTimeRange = GetTimeRange(Beatmap.Difficulty.ApproachRate);
float playfieldStretch = Playfield.DrawHeight / CatchPlayfield.HEIGHT;
TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch;
}
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer();
}
}

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override float DefaultFlashlightSize => 350;
public override float DefaultFlashlightSize => 325;
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
@ -44,7 +44,19 @@ namespace osu.Game.Rulesets.Catch.Mods
: base(modFlashlight)
{
this.playfield = playfield;
FlashlightSize = new Vector2(0, GetSizeFor(0));
FlashlightSize = new Vector2(0, GetSize());
FlashlightSmoothness = 1.4f;
}
protected override float GetComboScaleFor(int combo)
{
if (combo >= 200)
return 0.770f;
if (combo >= 100)
return 0.885f;
return 1.0f;
}
protected override void Update()
@ -54,9 +66,9 @@ namespace osu.Game.Rulesets.Catch.Mods
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
}
protected override void OnComboChange(ValueChangedEvent<int> e)
protected override void UpdateFlashlightSize(float size)
{
this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";

View File

@ -19,17 +19,20 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override LocalisableString Description => @"Use the mouse to control the catcher.";
private DrawableRuleset<CatchHitObject> drawableRuleset = null!;
private DrawableCatchRuleset drawableRuleset = null!;
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
{
this.drawableRuleset = drawableRuleset;
this.drawableRuleset = (DrawableCatchRuleset)drawableRuleset;
}
public void ApplyToPlayer(Player player)
{
if (!drawableRuleset.HasReplayLoaded.Value)
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
{
var catchPlayfield = (CatchPlayfield)drawableRuleset.Playfield;
catchPlayfield.CatcherArea.Add(new MouseInputHelper(catchPlayfield.CatcherArea));
}
}
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
@ -38,9 +41,10 @@ namespace osu.Game.Rulesets.Catch.Mods
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public MouseInputHelper(CatchPlayfield playfield)
public MouseInputHelper(CatcherArea catcherArea)
{
catcherArea = playfield.CatcherArea;
this.catcherArea = catcherArea;
RelativeSizeAxes = Axes.Both;
}

View File

@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public float DisplayRotation => Rotation;
public double DisplayStartTime => HitObject.StartTime;
/// <summary>
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
/// </summary>

View File

@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
public double DisplayStartTime => LifetimeStart;
Bindable<Color4> IHasCatchObjectState.AccentColour => AccentColour;
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();

View File

@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
PalpableCatchHitObject HitObject { get; }
double DisplayStartTime { get; }
Bindable<Color4> AccentColour { get; }
Bindable<bool> HyperDash { get; }

View File

@ -0,0 +1,122 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
internal class ArgonBananaPiece : ArgonFruitPiece
{
private Container stabilisedPieceContainer = null!;
private Drawable fadeContent = null!;
[BackgroundDependencyLoader]
private void load()
{
AddInternal(fadeContent = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
stabilisedPieceContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Circle
{
Colour = Color4.White.Opacity(0.4f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
Size = new Vector2(8),
Scale = new Vector2(25, 1),
},
new Box
{
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.8f)),
RelativeSizeAxes = Axes.X,
Blending = BlendingParameters.Additive,
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
Width = 1.6f,
Height = 2,
},
new Circle
{
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White.Opacity(0)),
RelativeSizeAxes = Axes.X,
Blending = BlendingParameters.Additive,
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
Width = 1.6f,
Height = 2,
},
}
},
new Circle
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1.2f),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Hollow = false,
Colour = Color4.White.Opacity(0.1f),
Radius = 50,
},
Child =
{
Alpha = 0,
AlwaysPresent = true,
},
BorderColour = Color4.White.Opacity(0.1f),
BorderThickness = 3,
},
}
});
}
protected override void Update()
{
base.Update();
const float parent_scale_application = 0.4f;
// relative to time on screen
const float lens_flare_start = 0.3f;
const float lens_flare_end = 0.8f;
// Undo some of the parent scale being applied to make the lens flare feel a bit better..
float scale = parent_scale_application + (1 - parent_scale_application) * (1 / (ObjectState.DisplaySize.X / (CatchHitObject.OBJECT_RADIUS * 2)));
stabilisedPieceContainer.Rotation = -ObjectState.DisplayRotation;
stabilisedPieceContainer.Scale = new Vector2(scale, 1);
double duration = ObjectState.HitObject.StartTime - ObjectState.DisplayStartTime;
fadeContent.Alpha = MathHelper.Clamp(
Interpolation.ValueAt(
Time.Current, 1f, 0f,
ObjectState.DisplayStartTime + duration * lens_flare_start,
ObjectState.DisplayStartTime + duration * lens_flare_end,
Easing.OutQuint
), 0, 1);
}
}
}

View File

@ -0,0 +1,85 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Catch.UI;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
public class ArgonCatcher : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Height = 10,
Children = new Drawable[]
{
new Circle
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.White,
Width = Catcher.ALLOWED_CATCH_RANGE,
},
new Box
{
Name = "long line left",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreRight,
Colour = Color4.White,
Alpha = 0.25f,
RelativeSizeAxes = Axes.X,
Width = 20,
Height = 1.8f,
},
new Circle
{
Name = "bumper left",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Colour = Color4.White,
RelativeSizeAxes = Axes.X,
Width = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2,
Height = 4,
},
new Box
{
Name = "long line right",
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft,
Colour = Color4.White,
Alpha = 0.25f,
RelativeSizeAxes = Axes.X,
Width = 20,
Height = 1.8f,
},
new Circle
{
Name = "bumper right",
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Colour = Color4.White,
RelativeSizeAxes = Axes.X,
Width = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2,
Height = 4,
},
}
},
};
}
}
}

View File

@ -0,0 +1,121 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Rulesets.Catch.UI;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
internal class ArgonDropletPiece : CatchHitObjectPiece
{
protected override Drawable HyperBorderPiece => hyperBorderPiece;
private Drawable hyperBorderPiece = null!;
private Container layers = null!;
private float rotationRandomness;
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Both;
const float droplet_scale_down = 0.7f;
int largeBlobSeed = RNG.Next();
InternalChildren = new[]
{
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(20),
},
layers = new Container
{
Scale = new Vector2(droplet_scale_down),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new CircularBlob
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
InnerRadius = 0.5f,
Alpha = 0.15f,
Seed = largeBlobSeed
},
new CircularBlob
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
InnerRadius = 0.4f,
Alpha = 0.5f,
Scale = new Vector2(0.7f),
Seed = RNG.Next()
},
}
},
hyperBorderPiece = new CircularBlob
{
Scale = new Vector2(droplet_scale_down),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
InnerRadius = 0.5f,
Alpha = 0.15f,
Seed = largeBlobSeed
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AccentColour.BindValueChanged(colour =>
{
foreach (var sprite in layers)
sprite.Colour = colour.NewValue;
}, true);
rotationRandomness = RNG.NextSingle(0.2f, 1);
}
protected override void Update()
{
base.Update();
// Note that droplets are rotated at a higher level, so this is mostly just to create more
// random arrangements of the multiple layers than actually rotate.
//
// Because underlying rotation is always clockwise, we apply anti-clockwise resistance to avoid
// making things spin too fast.
for (int i = 0; i < layers.Count; i++)
{
layers[i].Rotation -=
(float)Clock.ElapsedFrameTime
* 0.4f * rotationRandomness
// Each layer should alternate rotation speed.
* (i % 2 == 1 ? 0.5f : 1);
}
}
}
}

View File

@ -0,0 +1,121 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Rulesets.Catch.UI;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
internal class ArgonFruitPiece : CatchHitObjectPiece
{
protected override Drawable HyperBorderPiece => hyperBorderPiece;
private Drawable hyperBorderPiece = null!;
private Container layers = null!;
private float rotationRandomness;
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Both;
int largeBlobSeed = RNG.Next();
InternalChildren = new[]
{
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(20),
},
layers = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new CircularBlob
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Alpha = 0.15f,
InnerRadius = 0.5f,
Size = new Vector2(1.1f),
Seed = largeBlobSeed,
},
new CircularBlob
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
InnerRadius = 0.2f,
Alpha = 0.5f,
Seed = RNG.Next(),
},
new CircularBlob
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
InnerRadius = 0.05f,
Seed = RNG.Next(),
},
}
},
hyperBorderPiece = new CircularBlob
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
InnerRadius = 0.08f,
Size = new Vector2(1.15f),
Seed = largeBlobSeed
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AccentColour.BindValueChanged(colour =>
{
foreach (var sprite in layers)
sprite.Colour = colour.NewValue;
}, true);
rotationRandomness = RNG.NextSingle(0.2f, 1) * (RNG.NextBool() ? -1 : 1);
}
protected override void Update()
{
base.Update();
for (int i = 0; i < layers.Count; i++)
{
layers[i].Rotation +=
// Layers are ordered from largest to smallest. Smaller layers should rotate more.
(i * 2)
* (float)Clock.ElapsedFrameTime
* 0.02f * rotationRandomness
// Each layer should alternate rotation direction.
* (i % 2 == 1 ? 1 : -1);
}
}
}
}

View File

@ -0,0 +1,112 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
public class ArgonHitExplosion : CompositeDrawable, IHitExplosion
{
public override bool RemoveWhenNotAlive => true;
private Container tallExplosion = null!;
private Container largeFaint = null!;
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
public ArgonHitExplosion()
{
Size = new Vector2(20);
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
tallExplosion = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
Width = 0.1f,
Child = new Box
{
AlwaysPresent = true,
Alpha = 0,
RelativeSizeAxes = Axes.Both,
},
},
largeFaint = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
Child = new Box
{
AlwaysPresent = true,
Alpha = 0,
RelativeSizeAxes = Axes.Both,
},
},
};
accentColour.BindValueChanged(colour =>
{
tallExplosion.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = colour.NewValue,
Hollow = false,
Roundness = 15,
Radius = 15,
};
largeFaint.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Interpolation.ValueAt(0.2f, colour.NewValue, Color4.White, 0, 1),
Hollow = false,
Radius = 50,
};
}, true);
}
public void Animate(HitExplosionEntry entry)
{
X = entry.Position;
Scale = new Vector2(entry.HitObject.Scale);
accentColour.Value = entry.ObjectColour;
using (BeginAbsoluteSequence(entry.LifetimeStart))
{
this.FadeOutFromOne(400);
if (!(entry.HitObject is Droplet))
{
float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
tallExplosion
.ScaleTo(new Vector2(1.1f, 20 * scale), 200, Easing.OutQuint)
.Then()
.ScaleTo(new Vector2(1.1f, 1), 600, Easing.In);
}
}
}
}
}

View File

@ -0,0 +1,193 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
{
protected readonly HitResult Result;
protected SpriteText JudgementText { get; private set; } = null!;
private RingExplosion? ringExplosion;
[Resolved]
private OsuColour colours { get; set; } = null!;
public ArgonJudgementPiece(HitResult result)
{
Result = result;
Origin = Anchor.Centre;
Y = 160;
}
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
JudgementText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = Result.GetDescription().ToUpperInvariant(),
Colour = colours.ForHitResult(Result),
Blending = BlendingParameters.Additive,
Spacing = new Vector2(10, 0),
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
},
};
if (Result.IsHit())
{
AddInternal(ringExplosion = new RingExplosion(Result)
{
Colour = colours.ForHitResult(Result),
});
}
}
/// <summary>
/// Plays the default animation for this judgement piece.
/// </summary>
/// <remarks>
/// The base implementation only handles fade (for all result types) and misses.
/// Individual rulesets are recommended to implement their appropriate hit animations.
/// </remarks>
public virtual void PlayAnimation()
{
switch (Result)
{
default:
JudgementText
.ScaleTo(Vector2.One)
.ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint);
break;
case HitResult.Miss:
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
this.MoveTo(Vector2.Zero);
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
this.RotateTo(0);
this.RotateTo(40, 800, Easing.InQuint);
break;
}
this.FadeOutFromOne(800);
ringExplosion?.PlayAnimation();
}
public Drawable? GetAboveHitObjectsProxiedContent() => null;
private class RingExplosion : CompositeDrawable
{
private readonly float travel = 52;
public RingExplosion(HitResult result)
{
const float thickness = 4;
const float small_size = 9;
const float large_size = 14;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Blending = BlendingParameters.Additive;
int countSmall = 0;
int countLarge = 0;
switch (result)
{
case HitResult.Meh:
countSmall = 3;
travel *= 0.3f;
break;
case HitResult.Ok:
case HitResult.Good:
countSmall = 4;
travel *= 0.6f;
break;
case HitResult.Great:
case HitResult.Perfect:
countSmall = 4;
countLarge = 4;
break;
}
for (int i = 0; i < countSmall; i++)
AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
for (int i = 0; i < countLarge; i++)
AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
}
public void PlayAnimation()
{
foreach (var c in InternalChildren)
{
const float start_position_ratio = 0.3f;
float direction = RNG.NextSingle(0, 360);
float distance = RNG.NextSingle(travel / 2, travel);
c.MoveTo(new Vector2(
MathF.Cos(direction) * distance * start_position_ratio,
MathF.Sin(direction) * distance * start_position_ratio
));
c.MoveTo(new Vector2(
MathF.Cos(direction) * distance,
MathF.Sin(direction) * distance
), 600, Easing.OutQuint);
}
this.FadeOutFromOne(1000, Easing.OutQuint);
}
public class RingPiece : CircularContainer
{
public RingPiece(float thickness = 9)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Masking = true;
BorderThickness = thickness;
BorderColour = Color4.White;
Child = new Box
{
AlwaysPresent = true,
Alpha = 0,
RelativeSizeAxes = Axes.Both
};
}
}
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
public class CatchArgonSkinTransformer : SkinTransformer
{
public CatchArgonSkinTransformer(ISkin skin)
: base(skin)
{
}
public override Drawable? GetDrawableComponent(ISkinComponent component)
{
switch (component)
{
case CatchSkinComponent catchComponent:
// TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
switch (catchComponent.Component)
{
case CatchSkinComponents.HitExplosion:
return new ArgonHitExplosion();
case CatchSkinComponents.Catcher:
return new ArgonCatcher();
case CatchSkinComponents.Fruit:
return new ArgonFruitPiece();
case CatchSkinComponents.Banana:
return new ArgonBananaPiece();
case CatchSkinComponents.Droplet:
return new ArgonDropletPiece();
}
break;
}
return base.GetDrawableComponent(component);
}
}
}

View File

@ -1,21 +1,19 @@
// 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 disable
using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class BananaPiece : CatchHitObjectPiece
{
protected override BorderPiece BorderPiece { get; }
protected override Drawable BorderPiece { get; }
public BananaPiece()
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
InternalChildren = new[]
{
new BananaPulpFormation
{

View File

@ -7,6 +7,7 @@ using System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osuTK.Graphics;
@ -26,13 +27,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
/// A part of this piece that will be faded out while falling in the playfield.
/// </summary>
[CanBeNull]
protected virtual BorderPiece BorderPiece => null;
protected virtual Drawable BorderPiece => null;
/// <summary>
/// A part of this piece that will be only visible when <see cref="HyperDash"/> is true.
/// </summary>
[CanBeNull]
protected virtual HyperBorderPiece HyperBorderPiece => null;
protected virtual Drawable HyperBorderPiece => null;
protected override void LoadComplete()
{

View File

@ -11,13 +11,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class DropletPiece : CatchHitObjectPiece
{
protected override HyperBorderPiece HyperBorderPiece { get; }
protected override Drawable HyperBorderPiece { get; }
public DropletPiece()
{
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
InternalChildren = new Drawable[]
InternalChildren = new[]
{
new Pulp
{

View File

@ -18,14 +18,14 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
protected override BorderPiece BorderPiece { get; }
protected override HyperBorderPiece HyperBorderPiece { get; }
protected override Drawable BorderPiece { get; }
protected override Drawable HyperBorderPiece { get; }
public FruitPiece()
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
InternalChildren = new[]
{
new FruitPulpFormation
{

View File

@ -13,6 +13,10 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public class CatchLegacySkinTransformer : LegacySkinTransformer
{
public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasPear;
private bool hasPear => GetTexture("fruit-pear") != null;
/// <summary>
/// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default.
/// </summary>
@ -49,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
switch (catchSkinComponent.Component)
{
case CatchSkinComponents.Fruit:
if (GetTexture("fruit-pear") != null)
if (hasPear)
return new LegacyFruitPiece();
return null;

View File

@ -1,8 +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.
#nullable disable
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.UI;

View File

@ -1,12 +1,13 @@
// 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 disable
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK.Graphics;
@ -19,14 +20,29 @@ namespace osu.Game.Rulesets.Catch.UI
{
private int currentCombo;
[CanBeNull]
public ICatchComboCounter ComboCounter => Drawable as ICatchComboCounter;
public ICatchComboCounter? ComboCounter => Drawable as ICatchComboCounter;
private readonly IBindable<bool> showCombo = new BindableBool(true);
public CatchComboDisplay()
: base(new CatchSkinComponent(CatchSkinComponents.CatchComboCounter), _ => Empty())
{
}
[Resolved(canBeNull: true)]
private Player? player { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
if (player != null)
{
showCombo.BindTo(player.ShowingOverlayComponents);
showCombo.BindValueChanged(s => this.FadeTo(s.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true);
}
}
protected override void SkinChanged(ISkinSource skin)
{
base.SkinChanged(skin);

View File

@ -23,6 +23,12 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
public const float WIDTH = 512;
/// <summary>
/// The height of the playfield.
/// This doesn't include the catcher area.
/// </summary>
public const float HEIGHT = 384;
/// <summary>
/// The center position of the playfield.
/// </summary>

View File

@ -4,6 +4,7 @@
#nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Input;
using osu.Game.Beatmaps;
@ -30,15 +31,19 @@ namespace osu.Game.Rulesets.Catch.UI
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450);
TimeRange.Value = GetTimeRange(beatmap.Difficulty.ApproachRate);
}
[BackgroundDependencyLoader]
private void load()
{
KeyBindingInputManager.Add(new CatchTouchInputMapper());
// With relax mod, input maps directly to x position and left/right buttons are not used.
if (!Mods.Any(m => m is ModRelax))
KeyBindingInputManager.Add(new CatchTouchInputMapper());
}
protected double GetTimeRange(float approachRate) => IBeatmapDifficultyInfo.DifficultyRange(approachRate, 1800, 1200, 450);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@ -30,15 +31,18 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
[Cached(typeof(IScrollingInfo))]
private IScrollingInfo scrollingInfo;
[Cached]
private readonly StageDefinition stage = new StageDefinition(5);
protected ManiaPlacementBlueprintTestScene()
{
scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo;
Add(column = new Column(0)
Add(column = new Column(0, false)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AccentColour = Color4.OrangeRed,
AccentColour = { Value = Color4.OrangeRed },
Clock = new FramedClock(new StopwatchClock()), // No scroll
});
}

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
protected ManiaSelectionBlueprintTestScene(int columns)
{
var stageDefinitions = new List<StageDefinition> { new StageDefinition { Columns = columns } };
var stageDefinitions = new List<StageDefinition> { new StageDefinition(columns) };
base.Content.Child = scrollingTestContainer = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
[Cached(typeof(EditorBeatmap))]
private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition())
private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition(2))
{
BeatmapInfo =
{
@ -56,8 +56,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
Playfield = new ManiaPlayfield(new List<StageDefinition>
{
new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 3 }
new StageDefinition(4),
new StageDefinition(3)
})
{
Clock = new FramedClock(new StopwatchClock())

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
AddStep("setup compose screen", () =>
{
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 })
var beatmap = new ManiaBeatmap(new StageDefinition(4))
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
};

View File

@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
InternalChildren = new Drawable[]
{
EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition(4))
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }
}),

View File

@ -1,52 +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.
#nullable disable
using System.Collections.Generic;
using osu.Game.Rulesets.Mania.Beatmaps;
using NUnit.Framework;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class ManiaColumnTypeTest
{
[TestCase(new[]
{
ColumnType.Special
}, 1)]
[TestCase(new[]
{
ColumnType.Odd,
ColumnType.Even,
ColumnType.Even,
ColumnType.Odd
}, 4)]
[TestCase(new[]
{
ColumnType.Odd,
ColumnType.Even,
ColumnType.Odd,
ColumnType.Special,
ColumnType.Odd,
ColumnType.Even,
ColumnType.Odd
}, 7)]
public void Test(IEnumerable<ColumnType> expected, int columns)
{
var definition = new StageDefinition
{
Columns = columns
};
var results = getResults(definition);
Assert.AreEqual(expected, results);
}
private IEnumerable<ColumnType> getResults(StageDefinition definition)
{
for (int i = 0; i < definition.Columns; i++)
yield return definition.GetTypeOfColumn(i);
}
}
}

View File

@ -3,9 +3,11 @@
#nullable disable
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
}
protected override void ReloadMappings()
protected override void ReloadMappings(IQueryable<RealmKeyBinding> realmKeyBindings)
{
KeyBindings = DefaultKeyBindings;
}

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestCase(ManiaAction.Key8)]
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
{
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 9 });
var beatmap = new ManiaBeatmap(new StageDefinition(9));
var frame = new ManiaReplayFrame(0, actions);
var legacyFrame = frame.ToLegacy(beatmap);
@ -38,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestCase(ManiaAction.Key8)]
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
{
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 5 });
beatmap.Stages.Add(new StageDefinition { Columns = 5 });
var beatmap = new ManiaBeatmap(new StageDefinition(5));
beatmap.Stages.Add(new StageDefinition(5));
var frame = new ManiaReplayFrame(0, actions);
var legacyFrame = frame.ToLegacy(beatmap);

View File

@ -0,0 +1,49 @@
// 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 disable
using System.Collections.Generic;
using osu.Game.Rulesets.Mania.Beatmaps;
using NUnit.Framework;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class ManiaSpecialColumnTest
{
[TestCase(new[]
{
true
}, 1)]
[TestCase(new[]
{
false,
false,
false,
false
}, 4)]
[TestCase(new[]
{
false,
false,
false,
true,
false,
false,
false
}, 7)]
public void Test(IEnumerable<bool> special, int columns)
{
var definition = new StageDefinition(columns);
var results = getResults(definition);
Assert.AreEqual(special, results);
}
private IEnumerable<bool> getResults(StageDefinition definition)
{
for (int i = 0; i < definition.Columns; i++)
yield return definition.IsSpecialColumn(i);
}
}
}

View File

@ -0,0 +1,23 @@
// 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.Rulesets.Mania.Mods;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests.Mods
{
public class TestSceneManiaModFlashlight : ModTestScene
{
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
[TestCase(1f)]
[TestCase(0.5f)]
[TestCase(1.5f)]
[TestCase(3f)]
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new ManiaModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
[Test]
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new ManiaModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
}
}

View File

@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
private static ManiaBeatmap createRawBeatmap()
{
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
var beatmap = new ManiaBeatmap(new StageDefinition(1));
beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60
// Add test hit objects

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
@ -24,15 +23,16 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached]
private readonly Column column;
[Cached]
private readonly StageDefinition stageDefinition = new StageDefinition(5);
public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false)
{
InternalChildren = new[]
{
this.column = new Column(column)
this.column = new Column(column, false)
{
Action = { Value = action },
AccentColour = Color4.Orange,
ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd,
Alpha = showColumn ? 1 : 0
},
content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)

View File

@ -61,7 +61,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
c.Add(CreateHitObject().With(h =>
{
h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange;
}));
})
},

View File

@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
using osu.Game.Tests.Visual;
@ -24,6 +25,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
[Cached]
private readonly StageDefinition stage = new StageDefinition(4);
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
protected ManiaSkinnableTestScene()

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
var stageDefinitions = new List<StageDefinition>
{
new StageDefinition { Columns = 4 },
new StageDefinition(4),
};
SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>

View File

@ -1,51 +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.
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneKeyArea : ManiaSkinnableTestScene
{
[BackgroundDependencyLoader]
private void load()
{
SetContents(_ => new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new ColumnTestContainer(0, ManiaAction.Key1)
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
},
},
new ColumnTestContainer(1, ManiaAction.Key2)
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
},
},
}
});
}
}
}

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
stageDefinitions = new List<StageDefinition>
{
new StageDefinition { Columns = 2 }
new StageDefinition(2)
};
SetContents(_ => new ManiaPlayfield(stageDefinitions));
@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
stageDefinitions = new List<StageDefinition>
{
new StageDefinition { Columns = 2 },
new StageDefinition { Columns = 2 }
new StageDefinition(2),
new StageDefinition(2)
};
SetContents(_ => new ManiaPlayfield(stageDefinitions));

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
{
Child = new Stage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction)
Child = new Stage(0, new StageDefinition(4), ref normalAction, ref specialAction)
};
});
}

View File

@ -5,7 +5,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning;
@ -16,7 +15,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),
_ => new DefaultStageBackground())
{
Anchor = Anchor.Centre,

View File

@ -5,7 +5,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
@ -15,7 +14,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), _ => null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - |
// | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
var beatmap = new ManiaBeatmap(new StageDefinition(1));
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * |
// | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
var beatmap = new ManiaBeatmap(new StageDefinition(1));
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | - |
// | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | * |
// | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | |
// | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | |
// | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | |
// | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });

View File

@ -11,6 +11,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Mania.Tests
[Cached(typeof(IReadOnlyList<Mod>))]
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
[Cached]
private readonly StageDefinition stage = new StageDefinition(1);
private readonly List<Column> columns = new List<Column>();
public TestSceneColumn()
@ -84,12 +88,12 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index)
{
var column = new Column(index)
var column = new Column(index, false)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Height = 0.85f,
AccentColour = Color4.OrangeRed,
AccentColour = { Value = Color4.OrangeRed },
Action = { Value = action },
};

View File

@ -4,11 +4,13 @@
#nullable disable
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Tests
private Column column;
[Cached]
private readonly StageDefinition stage = new StageDefinition(1);
[SetUp]
public void SetUp() => Schedule(() =>
{
@ -35,11 +40,11 @@ namespace osu.Game.Rulesets.Mania.Tests
RelativeSizeAxes = Axes.Y,
TimeRange = 2000,
Clock = new FramedClock(clock),
Child = column = new Column(0)
Child = column = new Column(0, false)
{
Action = { Value = ManiaAction.Key1 },
Height = 0.85f,
AccentColour = Color4.Gray
AccentColour = { Value = Color4.Gray },
},
};
});

View File

@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Tests
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
assertNoteJudgement(HitResult.IgnoreHit);
assertNoteJudgement(HitResult.IgnoreMiss);
}
/// <summary>

View File

@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Mania.Tests
performTest(objects, new List<ReplayFrame>());
addJudgementAssert(objects[0], HitResult.IgnoreHit);
addJudgementAssert(objects[1], HitResult.IgnoreHit);
addJudgementAssert(objects[0], HitResult.IgnoreMiss);
addJudgementAssert(objects[1], HitResult.IgnoreMiss);
}
[Test]
@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
AddStep("load player", () =>
{
Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition(4))
{
HitObjects = hitObjects,
BeatmapInfo =

View File

@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
var specialAction = ManiaAction.Special1;
var stage = new Stage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction);
var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction);
stages.Add(stage);
return new ScrollingTestContainer(direction)

View File

@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
const double beat_length = 500;
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
var beatmap = new ManiaBeatmap(new StageDefinition(1))
{
HitObjects =
{

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>

View File

@ -1,14 +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.
#nullable disable
namespace osu.Game.Rulesets.Mania.Beatmaps
{
public enum ColumnType
{
Even,
Odd,
Special
}
}

View File

@ -3,6 +3,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
@ -60,5 +61,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
},
};
}
public StageDefinition GetStageForColumnIndex(int column)
{
foreach (var stage in Stages)
{
if (column < stage.Columns)
return stage;
column -= stage.Columns;
}
throw new ArgumentOutOfRangeException(nameof(column), "Provided index exceeds all available stages");
}
}
}

View File

@ -93,10 +93,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override Beatmap<ManiaHitObject> CreateBeatmap()
{
beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }, originalTargetColumns);
beatmap = new ManiaBeatmap(new StageDefinition(TargetColumns), originalTargetColumns);
if (Dual)
beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns });
beatmap.Stages.Add(new StageDefinition(TargetColumns));
return beatmap;
}

View File

@ -11,32 +11,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// <summary>
/// Defines properties for each stage in a <see cref="ManiaPlayfield"/>.
/// </summary>
public struct StageDefinition
public class StageDefinition
{
/// <summary>
/// The number of <see cref="Column"/>s which this stage contains.
/// </summary>
public int Columns;
public readonly int Columns;
public StageDefinition(int columns)
{
if (columns < 1)
throw new ArgumentException("Column count must be above zero.", nameof(columns));
Columns = columns;
}
/// <summary>
/// Whether the column index is a special column for this stage.
/// </summary>
/// <param name="column">The 0-based column index.</param>
/// <returns>Whether the column is a special column.</returns>
public readonly bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
/// <summary>
/// Get the type of column given a column index.
/// </summary>
/// <param name="column">The 0-based column index.</param>
/// <returns>The type of the column.</returns>
public readonly ColumnType GetTypeOfColumn(int column)
{
if (IsSpecialColumn(column))
return ColumnType.Special;
int distanceToEdge = Math.Min(column, (Columns - 1) - column);
return distanceToEdge % 2 == 0 ? ColumnType.Odd : ColumnType.Even;
}
public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
}
}

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
scoreAccuracy = customAccuracy;
scoreAccuracy = calculateCustomAccuracy();
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
// The specific number has no intrinsic meaning and can be adjusted as needed.
@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
/// <summary>
/// Accuracy used to weight judgements independently from the score's actual accuracy.
/// </summary>
private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
private double calculateCustomAccuracy()
{
if (totalHits == 0)
return 0;
return (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
}
}
}

View File

@ -33,5 +33,7 @@ namespace osu.Game.Rulesets.Mania.Edit
}
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
}
}

View File

@ -26,6 +26,8 @@ using osu.Game.Rulesets.Mania.Edit.Setup;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.Skinning.Argon;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Mania.Skinning.Legacy;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@ -66,6 +68,15 @@ namespace osu.Game.Rulesets.Mania
{
switch (skin)
{
case TrianglesSkin:
return new ManiaTrianglesSkinTransformer(skin, beatmap);
case ArgonSkin:
return new ManiaArgonSkinTransformer(skin, beatmap);
case DefaultLegacySkin:
return new ManiaClassicSkinTransformer(skin, beatmap);
case LegacySkin:
return new ManiaLegacySkinTransformer(skin, beatmap);
}

View File

@ -3,29 +3,19 @@
#nullable disable
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania
{
public class ManiaSkinComponent : GameplaySkinComponent<ManiaSkinComponents>
{
/// <summary>
/// The intended <see cref="StageDefinition"/> for this component.
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
/// </summary>
public readonly StageDefinition? StageDefinition;
/// <summary>
/// Creates a new <see cref="ManiaSkinComponent"/>.
/// </summary>
/// <param name="component">The component.</param>
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition? stageDefinition = null)
public ManiaSkinComponent(ManiaSkinComponents component)
: base(component)
{
StageDefinition = stageDefinition;
}
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;

View File

@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public ManiaFlashlight(ManiaModFlashlight modFlashlight)
: base(modFlashlight)
{
FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
FlashlightSize = new Vector2(DrawWidth, GetSize());
AddLayout(flashlightProperties);
}
@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Mania.Mods
}
}
protected override void OnComboChange(ValueChangedEvent<int> e)
protected override void UpdateFlashlightSize(float size)
{
this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "RectangularFlashlight";

View File

@ -54,10 +54,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
protected override void UpdateInitialTransforms()
{
}
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
}
}

View File

@ -4,12 +4,14 @@
#nullable disable
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private Container<DrawableHoldNoteTail> tailContainer;
private Container<DrawableHoldNoteTick> tickContainer;
private PausableSkinnableSound slidingSample;
/// <summary>
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
/// </summary>
@ -65,6 +69,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
private double? releaseTime;
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
public DrawableHoldNote()
: this(null)
{
@ -108,6 +114,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
},
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
slidingSample = new PausableSkinnableSound { Looping = true }
});
maskedContents.AddRange(new[]
@ -118,6 +125,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
protected override void LoadComplete()
{
base.LoadComplete();
isHitting.BindValueChanged(updateSlidingSample, true);
}
protected override void OnApply()
{
base.OnApply();
@ -248,7 +262,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
tick.MissForcefully();
}
ApplyResult(r => r.Type = r.Judgement.MaxResult);
ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
endHold();
}
@ -322,5 +336,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldStartTime = null;
isHitting.Value = false;
}
protected override void LoadSamples()
{
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
if (HitObject.SampleControlPoint == null)
{
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
}
slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
}
public override void StopAllSamples()
{
base.StopAllSamples();
slidingSample?.Stop();
}
private void updateSlidingSample(ValueChangedEvent<bool> tracking)
{
if (tracking.NewValue)
slidingSample?.Play();
else
slidingSample?.Stop();
}
protected override void OnFree()
{
slidingSample.Samples = null;
base.OnFree();
}
}
}

View File

@ -30,20 +30,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public bool UpdateResult() => base.UpdateResult(true);
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
// This hitobject should never expire, so this is just a safe maximum.
LifetimeEnd = LifetimeStart + 30000;
}
protected override void UpdateHitStateTransforms(ArmedState state)
{
// suppress the base call explicitly.
// the hold note head should never change its visual state on its own due to the "freezing" mechanic
// (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line).
// it will be hidden along with its parenting hold note when required.
// Set `LifetimeEnd` explicitly to a non-`double.MaxValue` because otherwise this DHO is automatically expired.
LifetimeEnd = double.PositiveInfinity;
}
public override bool OnPressed(KeyBindingPressEvent<ManiaAction> e) => false; // Handled by the hold note

View File

@ -23,10 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
// Leaving the default (10s) makes hitobjects not appear, as this offset is used for the initial state transforms.
// Calculated as DrawableManiaRuleset.MAX_TIME_RANGE + some additional allowance for velocity < 1.
protected override double InitialLifetimeOffset => 30000;
[Resolved(canBeNull: true)]
private ManiaPlayfield playfield { get; set; }
@ -69,22 +65,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Direction.BindValueChanged(OnDirectionChanged, true);
}
protected override void OnApply()
{
base.OnApply();
if (ParentHitObject != null)
AccentColour.BindTo(ParentHitObject.AccentColour);
}
protected override void OnFree()
{
base.OnFree();
if (ParentHitObject != null)
AccentColour.UnbindFrom(ParentHitObject.AccentColour);
}
protected virtual void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
{
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@ -12,26 +10,38 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
public class ColumnBackground : CompositeDrawable, IKeyBindingHandler<ManiaAction>, IHasAccentColour
public class ArgonColumnBackground : CompositeDrawable, IKeyBindingHandler<ManiaAction>
{
private readonly IBindable<ManiaAction> action = new Bindable<ManiaAction>();
private Box background;
private Box backgroundOverlay;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
[BackgroundDependencyLoader]
private void load(IBindable<ManiaAction> action, IScrollingInfo scrollingInfo)
{
this.action.BindTo(action);
private Color4 brightColour;
private Color4 dimColour;
private Box background = null!;
private Box backgroundOverlay = null!;
[Resolved]
private Column column { get; set; } = null!;
private Bindable<Color4> accentColour = null!;
public ArgonColumnBackground()
{
RelativeSizeAxes = Axes.Both;
Masking = true;
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
InternalChildren = new[]
{
background = new Box
@ -49,61 +59,42 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(dir =>
accentColour = column.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(colour =>
{
backgroundOverlay.Anchor = backgroundOverlay.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
updateColours();
background.Colour = colour.NewValue.Darken(3).Opacity(0.8f);
brightColour = colour.NewValue.Opacity(0.6f);
dimColour = colour.NewValue.Opacity(0);
}, true);
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
protected override void LoadComplete()
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
if (direction.NewValue == ScrollingDirection.Up)
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.TopLeft;
backgroundOverlay.Colour = ColourInfo.GradientVertical(brightColour, dimColour);
}
else
{
backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.BottomLeft;
backgroundOverlay.Colour = ColourInfo.GradientVertical(dimColour, brightColour);
}
}
private void updateColours()
{
if (!IsLoaded)
return;
background.Colour = AccentColour.Darken(5);
var brightPoint = AccentColour.Opacity(0.6f);
var dimPoint = AccentColour.Opacity(0);
backgroundOverlay.Colour = ColourInfo.GradientVertical(
direction.Value == ScrollingDirection.Up ? brightPoint : dimPoint,
direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
}
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{
if (e.Action == action.Value)
if (e.Action == column.Action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false;
}
public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{
if (e.Action == action.Value)
if (e.Action == column.Action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
}
}

View File

@ -0,0 +1,97 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
public class ArgonHitExplosion : CompositeDrawable, IHitExplosion
{
public override bool RemoveWhenNotAlive => true;
[Resolved]
private Column column { get; set; } = null!;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container largeFaint = null!;
private Bindable<Color4> accentColour = null!;
public ArgonHitExplosion()
{
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.X;
Height = ArgonNotePiece.NOTE_HEIGHT;
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
InternalChildren = new Drawable[]
{
largeFaint = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
Blending = BlendingParameters.Additive,
Child = new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
},
},
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
accentColour = column.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(colour =>
{
largeFaint.Colour = Interpolation.ValueAt(0.8f, colour.NewValue, Color4.White, 0, 1);
largeFaint.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = colour.NewValue,
Roundness = 40,
Radius = 60,
};
}, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (direction.NewValue == ScrollingDirection.Up)
{
Anchor = Anchor.TopCentre;
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
}
else
{
Anchor = Anchor.BottomCentre;
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
}
}
public void Animate(JudgementResult result)
{
this.FadeOutFromOne(PoolableHitExplosion.DURATION, Easing.Out);
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
public class ArgonHitTarget : CompositeDrawable
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
RelativeSizeAxes = Axes.X;
Height = ArgonNotePiece.NOTE_HEIGHT;
Masking = true;
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
InternalChildren = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.3f,
Blending = BlendingParameters.Additive,
Colour = Color4.White
},
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
Anchor = Origin = direction.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
}
}

View File

@ -0,0 +1,97 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
/// <summary>
/// Represents length-wise portion of a hold note.
/// </summary>
public class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
{
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
private Drawable background = null!;
private Box foreground = null!;
public ArgonHoldBodyPiece()
{
RelativeSizeAxes = Axes.Both;
// Without this, the width of the body will be slightly larger than the head/tail.
Masking = true;
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
Blending = BlendingParameters.Additive;
}
[BackgroundDependencyLoader(true)]
private void load(DrawableHitObject? drawableObject)
{
InternalChildren = new[]
{
background = new Box { RelativeSizeAxes = Axes.Both },
foreground = new Box
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Alpha = 0,
},
};
if (drawableObject != null)
{
var holdNote = (DrawableHoldNote)drawableObject;
AccentColour.BindTo(holdNote.AccentColour);
IsHitting.BindTo(holdNote.IsHitting);
}
AccentColour.BindValueChanged(colour =>
{
background.Colour = colour.NewValue.Darken(1.2f);
foreground.Colour = colour.NewValue.Opacity(0.2f);
}, true);
IsHitting.BindValueChanged(hitting =>
{
const float animation_length = 50;
foreground.ClearTransforms();
if (hitting.NewValue)
{
// wait for the next sync point
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
using (foreground.BeginDelayedSequence(synchronisedOffset))
{
foreground.FadeTo(1, animation_length).Then()
.FadeTo(0.5f, animation_length)
.Loop();
}
}
else
{
foreground.FadeOut(animation_length);
}
});
}
public void Recycle()
{
foreground.ClearTransforms();
foreground.Alpha = 0;
}
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
internal class ArgonHoldNoteTailPiece : CompositeDrawable
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
private readonly Box colouredBox;
private readonly Box shadow;
public ArgonHoldNoteTailPiece()
{
RelativeSizeAxes = Axes.X;
Height = ArgonNotePiece.NOTE_HEIGHT;
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
Masking = true;
InternalChildren = new Drawable[]
{
shadow = new Box
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Height = 0.82f,
Masking = true,
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
Children = new Drawable[]
{
colouredBox = new Box
{
RelativeSizeAxes = Axes.Both,
}
}
},
new Circle
{
RelativeSizeAxes = Axes.X,
Height = ArgonNotePiece.CORNER_RADIUS * 2,
},
};
}
[BackgroundDependencyLoader(true)]
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
if (drawableObject != null)
{
accentColour.BindTo(drawableObject.AccentColour);
accentColour.BindValueChanged(onAccentChanged, true);
}
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
? Anchor.TopCentre
: Anchor.BottomCentre;
}
private void onAccentChanged(ValueChangedEvent<Color4> accent)
{
colouredBox.Colour = ColourInfo.GradientVertical(
accent.NewValue,
accent.NewValue.Darken(0.1f)
);
shadow.Colour = accent.NewValue.Darken(0.5f);
}
}
}

View File

@ -0,0 +1,193 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
{
protected readonly HitResult Result;
protected SpriteText JudgementText { get; private set; } = null!;
private RingExplosion? ringExplosion;
[Resolved]
private OsuColour colours { get; set; } = null!;
public ArgonJudgementPiece(HitResult result)
{
Result = result;
Origin = Anchor.Centre;
Y = 160;
}
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
JudgementText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = Result.GetDescription().ToUpperInvariant(),
Colour = colours.ForHitResult(Result),
Blending = BlendingParameters.Additive,
Spacing = new Vector2(10, 0),
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
},
};
if (Result.IsHit())
{
AddInternal(ringExplosion = new RingExplosion(Result)
{
Colour = colours.ForHitResult(Result),
});
}
}
/// <summary>
/// Plays the default animation for this judgement piece.
/// </summary>
/// <remarks>
/// The base implementation only handles fade (for all result types) and misses.
/// Individual rulesets are recommended to implement their appropriate hit animations.
/// </remarks>
public virtual void PlayAnimation()
{
switch (Result)
{
default:
JudgementText
.ScaleTo(Vector2.One)
.ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint);
break;
case HitResult.Miss:
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
this.MoveTo(Vector2.Zero);
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
this.RotateTo(0);
this.RotateTo(40, 800, Easing.InQuint);
break;
}
this.FadeOutFromOne(800);
ringExplosion?.PlayAnimation();
}
public Drawable? GetAboveHitObjectsProxiedContent() => null;
private class RingExplosion : CompositeDrawable
{
private readonly float travel = 52;
public RingExplosion(HitResult result)
{
const float thickness = 4;
const float small_size = 9;
const float large_size = 14;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Blending = BlendingParameters.Additive;
int countSmall = 0;
int countLarge = 0;
switch (result)
{
case HitResult.Meh:
countSmall = 3;
travel *= 0.3f;
break;
case HitResult.Ok:
case HitResult.Good:
countSmall = 4;
travel *= 0.6f;
break;
case HitResult.Great:
case HitResult.Perfect:
countSmall = 4;
countLarge = 4;
break;
}
for (int i = 0; i < countSmall; i++)
AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
for (int i = 0; i < countLarge; i++)
AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
}
public void PlayAnimation()
{
foreach (var c in InternalChildren)
{
const float start_position_ratio = 0.3f;
float direction = RNG.NextSingle(0, 360);
float distance = RNG.NextSingle(travel / 2, travel);
c.MoveTo(new Vector2(
MathF.Cos(direction) * distance * start_position_ratio,
MathF.Sin(direction) * distance * start_position_ratio
));
c.MoveTo(new Vector2(
MathF.Cos(direction) * distance,
MathF.Sin(direction) * distance
), 600, Easing.OutQuint);
}
this.FadeOutFromOne(1000, Easing.OutQuint);
}
public class RingPiece : CircularContainer
{
public RingPiece(float thickness = 9)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Masking = true;
BorderThickness = thickness;
BorderColour = Color4.White;
Child = new Box
{
AlwaysPresent = true,
Alpha = 0,
RelativeSizeAxes = Axes.Both
};
}
}
}
}
}

View File

@ -0,0 +1,272 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
public class ArgonKeyArea : CompositeDrawable, IKeyBindingHandler<ManiaAction>
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container directionContainer = null!;
private Drawable background = null!;
private Circle hitTargetLine = null!;
private Container<Circle> bottomIcon = null!;
private CircularContainer topIcon = null!;
private Bindable<Color4> accentColour = null!;
[Resolved]
private Column column { get; set; } = null!;
public ArgonKeyArea()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
const float icon_circle_size = 8;
const float icon_spacing = 7;
const float icon_vertical_offset = -30;
InternalChild = directionContainer = new Container
{
RelativeSizeAxes = Axes.X,
// Ensure the area is tall enough to put the target line in the correct location.
// This is to also allow the main background component to overlap the target line
// and avoid an inner corner radius being shown below the target line.
Height = Stage.HIT_TARGET_POSITION + ArgonNotePiece.CORNER_RADIUS * 2,
Children = new[]
{
new Container
{
Masking = true,
RelativeSizeAxes = Axes.Both,
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
Child = background = new Box
{
Name = "Key gradient",
Alpha = 0,
RelativeSizeAxes = Axes.Both,
},
},
hitTargetLine = new Circle
{
RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Colour = OsuColour.Gray(196 / 255f),
Height = ArgonNotePiece.CORNER_RADIUS * 2,
Masking = true,
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
},
new Container
{
Name = "Icons",
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Children = new Drawable[]
{
bottomIcon = new Container<Circle>
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
Y = icon_vertical_offset,
Children = new[]
{
new Circle
{
Size = new Vector2(icon_circle_size),
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
},
new Circle
{
X = -icon_spacing,
Y = icon_spacing * 1.2f,
Size = new Vector2(icon_circle_size),
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
},
new Circle
{
X = icon_spacing,
Y = icon_spacing * 1.2f,
Size = new Vector2(icon_circle_size),
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
},
}
},
topIcon = new CircularContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
Y = -icon_vertical_offset,
Size = new Vector2(22, 14),
Masking = true,
BorderThickness = 4,
BorderColour = Color4.White,
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
},
},
}
}
},
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
accentColour = column.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(colour =>
{
background.Colour = colour.NewValue.Darken(0.2f);
bottomIcon.Colour = colour.NewValue;
},
true);
// Yes, proxy everything.
column.TopLevelContainer.Add(CreateProxy());
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
switch (direction.NewValue)
{
case ScrollingDirection.Up:
directionContainer.Scale = new Vector2(1, -1);
directionContainer.Anchor = Anchor.TopLeft;
directionContainer.Origin = Anchor.BottomLeft;
break;
case ScrollingDirection.Down:
directionContainer.Scale = new Vector2(1, 1);
directionContainer.Anchor = Anchor.BottomLeft;
directionContainer.Origin = Anchor.BottomLeft;
break;
}
}
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{
if (e.Action != column.Action.Value) return false;
const double lighting_fade_in_duration = 70;
Color4 lightingColour = getLightingColour();
background
.FlashColour(accentColour.Value.Lighten(0.8f), 200, Easing.OutQuint)
.FadeTo(1, lighting_fade_in_duration, Easing.OutQuint)
.Then()
.FadeTo(0.8f, 500);
hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint);
hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = lightingColour.Opacity(0.4f),
Radius = 20,
}, lighting_fade_in_duration, Easing.OutQuint);
topIcon.ScaleTo(0.9f, lighting_fade_in_duration, Easing.OutQuint);
topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = lightingColour.Opacity(0.1f),
Radius = 20,
}, lighting_fade_in_duration, Easing.OutQuint);
bottomIcon.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint);
foreach (var circle in bottomIcon)
{
circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = lightingColour.Opacity(0.2f),
Radius = 60,
}, lighting_fade_in_duration, Easing.OutQuint);
}
return false;
}
public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{
if (e.Action != column.Action.Value) return;
const double lighting_fade_out_duration = 800;
Color4 lightingColour = getLightingColour().Opacity(0);
// background fades out faster than lighting elements to give better definition to the player.
background.FadeTo(0.3f, 50, Easing.OutQuint)
.Then()
.FadeOut(lighting_fade_out_duration, Easing.OutQuint);
topIcon.ScaleTo(1f, 200, Easing.OutQuint);
topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = lightingColour,
Radius = 20,
}, lighting_fade_out_duration, Easing.OutQuint);
hitTargetLine.FadeColour(OsuColour.Gray(196 / 255f), lighting_fade_out_duration, Easing.OutQuint);
hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = lightingColour,
Radius = 25,
}, lighting_fade_out_duration, Easing.OutQuint);
bottomIcon.FadeColour(accentColour.Value, lighting_fade_out_duration, Easing.OutQuint);
foreach (var circle in bottomIcon)
{
circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = lightingColour,
Radius = 30,
}, lighting_fade_out_duration, Easing.OutQuint);
}
}
private Color4 getLightingColour() => Interpolation.ValueAt(0.2f, accentColour.Value, Color4.White, 0, 1);
}
}

View File

@ -0,0 +1,110 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
internal class ArgonNotePiece : CompositeDrawable
{
public const float NOTE_HEIGHT = 42;
public const float CORNER_RADIUS = 3.4f;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
private readonly Box colouredBox;
private readonly Box shadow;
public ArgonNotePiece()
{
RelativeSizeAxes = Axes.X;
Height = NOTE_HEIGHT;
CornerRadius = CORNER_RADIUS;
Masking = true;
InternalChildren = new Drawable[]
{
shadow = new Box
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Height = 0.82f,
Masking = true,
CornerRadius = CORNER_RADIUS,
Children = new Drawable[]
{
colouredBox = new Box
{
RelativeSizeAxes = Axes.Both,
}
}
},
new Circle
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = CORNER_RADIUS * 2,
},
new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Y = 4,
Icon = FontAwesome.Solid.AngleDown,
Size = new Vector2(20),
Scale = new Vector2(1, 0.7f)
}
};
}
[BackgroundDependencyLoader(true)]
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
if (drawableObject != null)
{
accentColour.BindTo(drawableObject.AccentColour);
accentColour.BindValueChanged(onAccentChanged, true);
}
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
? Anchor.TopCentre
: Anchor.BottomCentre;
}
private void onAccentChanged(ValueChangedEvent<Color4> accent)
{
colouredBox.Colour = ColourInfo.GradientVertical(
accent.NewValue.Lighten(0.1f),
accent.NewValue
);
shadow.Colour = accent.NewValue.Darken(0.5f);
}
}
}

View File

@ -0,0 +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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
public class ArgonStageBackground : CompositeDrawable
{
public ArgonStageBackground()
{
RelativeSizeAxes = Axes.Both;
}
}
}

View File

@ -0,0 +1,141 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
public class ManiaArgonSkinTransformer : SkinTransformer
{
private readonly ManiaBeatmap beatmap;
public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap)
: base(skin)
{
this.beatmap = (ManiaBeatmap)beatmap;
}
public override Drawable? GetDrawableComponent(ISkinComponent component)
{
switch (component)
{
case GameplaySkinComponent<HitResult> resultComponent:
return new ArgonJudgementPiece(resultComponent.Component);
case ManiaSkinComponent maniaComponent:
// TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
switch (maniaComponent.Component)
{
case ManiaSkinComponents.StageBackground:
return new ArgonStageBackground();
case ManiaSkinComponents.ColumnBackground:
return new ArgonColumnBackground();
case ManiaSkinComponents.HoldNoteBody:
return new ArgonHoldBodyPiece();
case ManiaSkinComponents.HoldNoteTail:
return new ArgonHoldNoteTailPiece();
case ManiaSkinComponents.HoldNoteHead:
case ManiaSkinComponents.Note:
return new ArgonNotePiece();
case ManiaSkinComponents.HitTarget:
return new ArgonHitTarget();
case ManiaSkinComponents.KeyArea:
return new ArgonKeyArea();
case ManiaSkinComponents.HitExplosion:
return new ArgonHitExplosion();
}
break;
}
return base.GetDrawableComponent(component);
}
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
{
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
{
int column = maniaLookup.ColumnIndex ?? 0;
var stage = beatmap.GetStageForColumnIndex(column);
switch (maniaLookup.Lookup)
{
case LegacyManiaSkinConfigurationLookups.ColumnSpacing:
return SkinUtils.As<TValue>(new Bindable<float>(2));
case LegacyManiaSkinConfigurationLookups.StagePaddingBottom:
case LegacyManiaSkinConfigurationLookups.StagePaddingTop:
return SkinUtils.As<TValue>(new Bindable<float>(30));
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
return SkinUtils.As<TValue>(new Bindable<float>(
stage.IsSpecialColumn(column) ? 120 : 60
));
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
Color4 colour;
const int total_colours = 7;
if (stage.IsSpecialColumn(column))
colour = new Color4(159, 101, 255, 255);
else
{
switch (column % total_colours)
{
case 0:
colour = new Color4(240, 216, 0, 255);
break;
case 1:
colour = new Color4(240, 101, 0, 255);
break;
case 2:
colour = new Color4(240, 0, 130, 255);
break;
case 3:
colour = new Color4(192, 0, 240, 255);
break;
case 4:
colour = new Color4(0, 96, 240, 255);
break;
case 5:
colour = new Color4(0, 226, 240, 255);
break;
case 6:
colour = new Color4(0, 240, 96, 255);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
}
}
return base.GetConfig<TLookup, TValue>(lookup);
}
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Default
{
public class ManiaTrianglesSkinTransformer : SkinTransformer
{
private readonly ManiaBeatmap beatmap;
public ManiaTrianglesSkinTransformer(ISkin skin, IBeatmap beatmap)
: base(skin)
{
this.beatmap = (ManiaBeatmap)beatmap;
}
private readonly Color4 colourEven = new Color4(6, 84, 0, 255);
private readonly Color4 colourOdd = new Color4(94, 0, 57, 255);
private readonly Color4 colourSpecial = new Color4(0, 48, 63, 255);
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
{
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
{
switch (maniaLookup.Lookup)
{
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
int column = maniaLookup.ColumnIndex ?? 0;
var stage = beatmap.GetStageForColumnIndex(column);
if (stage.IsSpecialColumn(column))
return SkinUtils.As<TValue>(new Bindable<Color4>(colourSpecial));
int distanceToEdge = Math.Min(column, (stage.Columns - 1) - column);
return SkinUtils.As<TValue>(new Bindable<Color4>(distanceToEdge % 2 == 0 ? colourOdd : colourEven));
}
}
return base.GetConfig<TLookup, TValue>(lookup);
}
}
}

View File

@ -3,6 +3,7 @@
#nullable disable
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
@ -20,6 +21,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
[Resolved]
protected Column Column { get; private set; }
[Resolved]
private StageDefinition stage { get; set; }
/// <summary>
/// The column type identifier to use for texture lookups, in the case of no user-provided configuration.
/// </summary>
@ -28,19 +32,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
[BackgroundDependencyLoader]
private void load()
{
switch (Column.ColumnType)
if (Column.IsSpecial)
FallbackColumnIndex = "S";
else
{
case ColumnType.Special:
FallbackColumnIndex = "S";
break;
case ColumnType.Odd:
FallbackColumnIndex = "1";
break;
case ColumnType.Even:
FallbackColumnIndex = "2";
break;
int distanceToEdge = Math.Min(Column.Index, (stage.Columns - 1) - Column.Index);
FallbackColumnIndex = distanceToEdge % 2 == 0 ? "1" : "2";
}
}

View File

@ -18,20 +18,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyStageBackground : CompositeDrawable
{
private readonly StageDefinition stageDefinition;
private Drawable leftSprite;
private Drawable rightSprite;
private ColumnFlow<Drawable> columnBackgrounds;
public LegacyStageBackground(StageDefinition stageDefinition)
public LegacyStageBackground()
{
this.stageDefinition = stageDefinition;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
private void load(ISkinSource skin, StageDefinition stageDefinition)
{
string leftImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value
?? "mania-stage-left";

View File

@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class ManiaClassicSkinTransformer : ManiaLegacySkinTransformer
{
public ManiaClassicSkinTransformer(ISkin skin, IBeatmap beatmap)
: base(skin, beatmap)
{
}
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
{
var baseLookup = base.GetConfig<TLookup, TValue>(lookup);
if (baseLookup != null)
return baseLookup;
// default provisioning.
switch (maniaLookup.Lookup)
{
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
return SkinUtils.As<TValue>(new Bindable<Color4>(Color4.Black));
}
}
return base.GetConfig<TLookup, TValue>(lookup);
}
}
}

View File

@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class ManiaLegacySkinTransformer : LegacySkinTransformer
{
private readonly ManiaBeatmap beatmap;
public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasKeyTexture.Value;
/// <summary>
/// Mapping of <see cref="HitResult"/> to their corresponding
@ -60,6 +59,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
/// </summary>
private readonly Lazy<bool> hasKeyTexture;
private readonly ManiaBeatmap beatmap;
public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap)
: base(skin)
{
@ -113,8 +114,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return new LegacyHitExplosion();
case ManiaSkinComponents.StageBackground:
Debug.Assert(maniaComponent.StageDefinition != null);
return new LegacyStageBackground(maniaComponent.StageDefinition.Value);
return new LegacyStageBackground();
case ManiaSkinComponents.StageForeground:
return new LegacyStageForeground();
@ -151,7 +151,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
return base.GetConfig<LegacyManiaSkinConfigurationLookup, TValue>(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn));
{
return base.GetConfig<LegacyManiaSkinConfigurationLookup, TValue>(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex));
}
return base.GetConfig<TLookup, TValue>(lookup);
}

View File

@ -15,9 +15,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
/// </summary>
/// <param name="skin">The skin from which configuration is retrieved.</param>
/// <param name="lookup">The value to retrieve.</param>
/// <param name="index">If not null, denotes the index of the column to which the entry applies.</param>
public static IBindable<T> GetManiaSkinConfig<T>(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
/// <param name="columnIndex">If not null, denotes the index of the column to which the entry applies.</param>
public static IBindable<T> GetManiaSkinConfig<T>(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? columnIndex = null)
=> skin.GetConfig<ManiaSkinConfigurationLookup, T>(
new ManiaSkinConfigurationLookup(lookup, index));
new ManiaSkinConfigurationLookup(lookup, columnIndex));
}
}

View File

@ -16,20 +16,21 @@ namespace osu.Game.Rulesets.Mania.Skinning
public readonly LegacyManiaSkinConfigurationLookups Lookup;
/// <summary>
/// The intended <see cref="Column"/> index for the configuration.
/// The column which is being looked up.
/// May be null if the configuration does not apply to a <see cref="Column"/>.
/// Note that this is the absolute index across all stages.
/// </summary>
public readonly int? TargetColumn;
public readonly int? ColumnIndex;
/// <summary>
/// Creates a new <see cref="ManiaSkinConfigurationLookup"/>.
/// </summary>
/// <param name="lookup">The lookup value.</param>
/// <param name="targetColumn">The intended <see cref="Column"/> index for the configuration. May be null if the configuration does not apply to a <see cref="Column"/>.</param>
public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null)
/// <param name="columnIndex">The intended <see cref="Column"/> index for the configuration. May be null if the configuration does not apply to a <see cref="Column"/>.</param>
public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? columnIndex = null)
{
Lookup = lookup;
TargetColumn = targetColumn;
ColumnIndex = columnIndex;
}
}
}

View File

@ -3,30 +3,30 @@
#nullable disable
using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Platform;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
[Cached]
public class Column : ScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
public class Column : ScrollingPlayfield, IKeyBindingHandler<ManiaAction>
{
public const float COLUMN_WIDTH = 80;
public const float SPECIAL_COLUMN_WIDTH = 70;
@ -39,23 +39,46 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
public readonly ColumnHitObjectArea HitObjectArea;
internal readonly Container TopLevelContainer;
private readonly DrawablePool<PoolableHitExplosion> hitExplosionPool;
internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both };
private DrawablePool<PoolableHitExplosion> hitExplosionPool;
private readonly OrderedHitPolicy hitPolicy;
public Container UnderlayElements => HitObjectArea.UnderlayElements;
private readonly GameplaySampleTriggerSource sampleTriggerSource;
private GameplaySampleTriggerSource sampleTriggerSource;
public Column(int index)
/// <summary>
/// Whether this is a special (ie. scratch) column.
/// </summary>
public readonly bool IsSpecial;
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>(Color4.Black);
public Column(int index, bool isSpecial)
{
Index = index;
IsSpecial = isSpecial;
RelativeSizeAxes = Axes.Y;
Width = COLUMN_WIDTH;
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both };
}
[Resolved]
private ISkinSource skin { get; set; }
[BackgroundDependencyLoader]
private void load(GameHost host)
{
SkinnableDrawable keyArea;
skin.SourceChanged += onSourceChanged;
onSourceChanged();
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
{
RelativeSizeAxes = Axes.Both
RelativeSizeAxes = Axes.Both,
};
InternalChildren = new[]
@ -64,17 +87,18 @@ namespace osu.Game.Rulesets.Mania.UI
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both },
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
HitObjectArea,
keyArea = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
RelativeSizeAxes = Axes.Both,
},
background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both },
TopLevelContainer,
new ColumnTouchInputArea(this)
};
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
applyGameWideClock(background);
applyGameWideClock(keyArea);
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
@ -83,20 +107,38 @@ namespace osu.Game.Rulesets.Mania.UI
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
RegisterPool<HoldNoteTick, DrawableHoldNoteTick>(50, 250);
// Some elements don't handle rewind correctly and fixing them is non-trivial.
// In the future we need a better solution to this, but as a temporary work-around, give these components the game-wide
// clock so they don't need to worry about rewind.
// This only works because they handle OnPressed/OnReleased which results in a correct state while rewinding.
//
// This is kinda dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind.
void applyGameWideClock(Drawable drawable)
{
drawable.Clock = host.UpdateThread.Clock;
drawable.ProcessCustomClock = false;
}
}
private void onSourceChanged()
{
AccentColour.Value = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, Index)?.Value ?? Color4.Black;
}
protected override void LoadComplete()
{
base.LoadComplete();
NewResult += OnNewResult;
}
public ColumnType ColumnType { get; set; }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
public bool IsSpecial => ColumnType == ColumnType.Special;
public Color4 AccentColour { get; set; }
if (skin != null)
skin.SourceChanged -= onSourceChanged;
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@ -111,7 +153,7 @@ namespace osu.Game.Rulesets.Mania.UI
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject;
maniaObject.AccentColour.Value = AccentColour;
maniaObject.AccentColour.BindTo(AccentColour);
maniaObject.CheckHittable = hitPolicy.IsHittable;
}

Some files were not shown because too many files have changed in this diff Show More