1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 13:22:55 +08:00

Merge branch 'master' into comment-deletion

This commit is contained in:
Dean Herbert 2022-10-12 15:48:26 +09:00
commit 47fe4eb0bd
173 changed files with 3387 additions and 915 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

@ -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

@ -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.1008.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1011.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

@ -137,12 +137,13 @@ 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 });
}

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

@ -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,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

@ -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

@ -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

@ -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;
@ -36,7 +37,9 @@ namespace osu.Game.Rulesets.Catch.UI
[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 override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);

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

@ -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

@ -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>
@ -108,6 +112,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 +123,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
protected override void LoadComplete()
{
base.LoadComplete();
isHitting.BindValueChanged(updateSlidingSample, true);
}
protected override void OnApply()
{
base.OnApply();
@ -322,5 +334,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,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class ManiaLegacySkinTransformer : LegacySkinTransformer
{
private readonly ManiaBeatmap beatmap;
/// <summary>
/// Mapping of <see cref="HitResult"/> to their corresponding
/// <see cref="LegacyManiaSkinConfigurationLookups"/> value.
@ -60,6 +57,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 +112,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 +149,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;
}

View File

@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Mania.UI
AutoSizeAxes = Axes.X;
Masking = true;
InternalChild = columns = new FillFlowContainer<Container>
{
RelativeSizeAxes = Axes.Y,

View File

@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
[Resolved]
private Column column { get; set; }
private Bindable<Color4> accentColour;
public DefaultColumnBackground()
{
RelativeSizeAxes = Axes.Both;
@ -55,9 +57,13 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}
};
background.Colour = column.AccentColour.Darken(5);
brightColour = column.AccentColour.Opacity(0.6f);
dimColour = column.AccentColour.Opacity(0);
accentColour = column.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(colour =>
{
background.Colour = colour.NewValue.Darken(5);
brightColour = colour.NewValue.Opacity(0.6f);
dimColour = colour.NewValue.Opacity(0);
}, true);
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);

View File

@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private Container hitTargetLine;
private Drawable hitTargetBar;
private Bindable<Color4> accentColour;
[Resolved]
private Column column { get; set; }
@ -54,12 +56,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
},
};
hitTargetLine.EdgeEffect = new EdgeEffectParameters
accentColour = column.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(colour =>
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = column.AccentColour.Opacity(0.5f),
};
hitTargetLine.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = colour.NewValue.Opacity(0.5f),
};
}, true);
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);

View File

@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private Container keyIcon;
private Drawable gradient;
private Bindable<Color4> accentColour;
[Resolved]
private Column column { get; set; }
@ -75,15 +77,19 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}
};
keyIcon.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = column.AccentColour.Opacity(0.5f),
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
accentColour = column.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(colour =>
{
keyIcon.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = colour.NewValue.Opacity(0.5f),
};
}, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)

View File

@ -32,6 +32,10 @@ namespace osu.Game.Rulesets.Mania.UI
private CircularContainer largeFaint;
private CircularContainer mainGlow1;
private CircularContainer mainGlow2;
private CircularContainer mainGlow3;
private Bindable<Color4> accentColour;
public DefaultHitExplosion()
{
@ -48,8 +52,6 @@ namespace osu.Game.Rulesets.Mania.UI
const float roundness = 80;
const float initial_height = 10;
var colour = Interpolation.ValueAt(0.4f, column.AccentColour, Color4.White, 0, 1);
InternalChildren = new Drawable[]
{
largeFaint = new CircularContainer
@ -61,13 +63,6 @@ namespace osu.Game.Rulesets.Mania.UI
// we want our size to be very small so the glow dominates it.
Size = new Vector2(default_large_faint_size),
Blending = BlendingParameters.Additive,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Interpolation.ValueAt(0.1f, column.AccentColour, Color4.White, 0, 1).Opacity(0.3f),
Roundness = 160,
Radius = 200,
},
},
mainGlow1 = new CircularContainer
{
@ -76,15 +71,8 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both,
Masking = true,
Blending = BlendingParameters.Additive,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Interpolation.ValueAt(0.6f, column.AccentColour, Color4.White, 0, 1),
Roundness = 20,
Radius = 50,
},
},
new CircularContainer
mainGlow2 = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -93,15 +81,8 @@ namespace osu.Game.Rulesets.Mania.UI
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
Rotation = RNG.NextSingle(-angle_variance, angle_variance),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = colour,
Roundness = roundness,
Radius = 40,
},
},
new CircularContainer
mainGlow3 = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -110,18 +91,44 @@ namespace osu.Game.Rulesets.Mania.UI
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
Rotation = RNG.NextSingle(-angle_variance, angle_variance),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = colour,
Roundness = roundness,
Radius = 40,
},
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
accentColour = column.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(colour =>
{
largeFaint.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Interpolation.ValueAt(0.1f, colour.NewValue, Color4.White, 0, 1).Opacity(0.3f),
Roundness = 160,
Radius = 200,
};
mainGlow1.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Interpolation.ValueAt(0.6f, colour.NewValue, Color4.White, 0, 1),
Roundness = 20,
Radius = 50,
};
mainGlow2.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Interpolation.ValueAt(0.4f, colour.NewValue, Color4.White, 0, 1),
Roundness = roundness,
Radius = 40,
};
mainGlow3.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = Interpolation.ValueAt(0.4f, colour.NewValue, Color4.White, 0, 1),
Roundness = roundness,
Radius = 40,
};
}, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)

View File

@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI
{
base.PrepareForUse();
LifetimeStart = Time.Current;
(skinnableExplosion?.Drawable as IHitExplosion)?.Animate(Result);
this.Delay(DURATION).Then().Expire();

View File

@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
@ -12,6 +13,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
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;
using osu.Game.Rulesets.Objects.Drawables;
@ -19,7 +21,6 @@ using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Mania.UI
/// </summary>
public class Stage : ScrollingPlayfield
{
[Cached]
public readonly StageDefinition Definition;
public const float COLUMN_SPACING = 1;
public const float HIT_TARGET_POSITION = 110;
@ -40,13 +44,6 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly Drawable barLineContainer;
private readonly Dictionary<ColumnType, Color4> columnColours = new Dictionary<ColumnType, Color4>
{
{ ColumnType.Even, new Color4(6, 84, 0, 255) },
{ ColumnType.Odd, new Color4(94, 0, 57, 255) },
{ ColumnType.Special, new Color4(0, 48, 63, 255) }
};
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos));
private readonly int firstColumnIndex;
@ -54,6 +51,7 @@ namespace osu.Game.Rulesets.Mania.UI
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
{
this.firstColumnIndex = firstColumnIndex;
Definition = definition;
Name = "Stage";
@ -75,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.UI
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: definition), _ => new DefaultStageBackground())
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground())
{
RelativeSizeAxes = Axes.Both
},
@ -100,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Y,
}
},
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: definition), _ => null)
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null)
{
RelativeSizeAxes = Axes.Both
},
@ -118,15 +116,13 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++)
{
var columnType = definition.GetTypeOfColumn(i);
bool isSpecial = definition.IsSpecialColumn(i);
var column = new Column(firstColumnIndex + i)
var column = new Column(firstColumnIndex + i, isSpecial)
{
RelativeSizeAxes = Axes.Both,
Width = 1,
ColumnType = columnType,
AccentColour = columnColours[columnType],
Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ }
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
};
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
@ -135,6 +131,37 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
private ISkinSource currentSkin;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
currentSkin = skin;
skin.SourceChanged += onSkinChanged;
onSkinChanged();
}
private void onSkinChanged()
{
float paddingTop = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.StagePaddingTop))?.Value ?? 0;
float paddingBottom = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.StagePaddingBottom))?.Value ?? 0;
Padding = new MarginPadding
{
Top = paddingTop,
Bottom = paddingBottom,
};
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (currentSkin != null)
currentSkin.SourceChanged -= onSkinChanged;
}
protected override void LoadComplete()
{
base.LoadComplete();

View File

@ -148,6 +148,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
});
}
[Test]
public void TestFloatEdgeCaseConversion()
{
Slider slider = null;
AddStep("select first slider", () =>
{
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
EditorClock.Seek(slider.StartTime);
EditorBeatmap.SelectedHitObjects.Add(slider);
});
AddStep("change to these specific circumstances", () =>
{
EditorBeatmap.Difficulty.SliderMultiplier = 1;
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(slider.StartTime);
timingPoint.BeatLength = 352.941176470588;
slider.Path.ControlPoints[^1].Position = new Vector2(-110, 16);
slider.Path.ExpectedDistance.Value = 100;
});
convertToStream();
AddAssert("stream created", () => streamCreatedFor(slider,
(time: 0, pathPosition: 0),
(time: 0.25, pathPosition: 0.25),
(time: 0.5, pathPosition: 0.5),
(time: 0.75, pathPosition: 0.75),
(time: 1, pathPosition: 1)));
}
private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles)
{
if (EditorBeatmap.HitObjects.Contains(slider))

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,136 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Input.States;
using osu.Framework.Logging;
using osu.Framework.Testing.Input;
using osu.Game.Rulesets.Osu.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneSmoke : OsuSkinnableTestScene
{
[Test]
public void TestSmoking()
{
addStep("Create short smoke", 2_000);
addStep("Create medium smoke", 5_000);
addStep("Create long smoke", 10_000);
}
private void addStep(string stepName, double duration)
{
var smokeContainers = new List<SmokeContainer>();
AddStep(stepName, () =>
{
smokeContainers.Clear();
SetContents(_ =>
{
smokeContainers.Add(new TestSmokeContainer
{
Duration = duration,
RelativeSizeAxes = Axes.Both
});
return new SmokingInputManager
{
Duration = duration,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.95f),
Child = smokeContainers[^1],
};
});
});
AddUntilStep("Until skinnable expires", () =>
{
if (smokeContainers.Count == 0)
return false;
Logger.Log("How many: " + smokeContainers.Count);
foreach (var smokeContainer in smokeContainers)
{
if (smokeContainer.Children.Count != 0)
return false;
}
return true;
});
}
private class SmokingInputManager : ManualInputManager
{
public double Duration { get; init; }
private double? startTime;
public SmokingInputManager()
{
UseParentInput = false;
}
protected override void LoadComplete()
{
base.LoadComplete();
MoveMouseTo(ToScreenSpace(DrawSize / 2));
}
protected override void Update()
{
base.Update();
const float spin_angle = 4 * MathF.PI;
startTime ??= Time.Current;
float fraction = (float)((Time.Current - startTime) / Duration);
float angle = fraction * spin_angle;
float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2;
Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2;
MoveMouseTo(ToScreenSpace(pos));
}
}
private class TestSmokeContainer : SmokeContainer
{
public double Duration { get; init; }
private bool isPressing;
private bool isFinished;
private double? startTime;
protected override void Update()
{
base.Update();
startTime ??= Time.Current + 0.1;
if (!isPressing && !isFinished && Time.Current > startTime)
{
OnPressed(new KeyBindingPressEvent<OsuAction>(new InputState(), OsuAction.Smoke));
isPressing = true;
isFinished = false;
}
if (isPressing && Time.Current > startTime + Duration)
{
OnReleased(new KeyBindingReleaseEvent<OsuAction>(new InputState(), OsuAction.Smoke));
isPressing = false;
isFinished = true;
}
}
}
}
}

View File

@ -1,10 +1,10 @@
<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="Moq" Version="4.18.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

@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount();
double pathPosition = positionWithRepeats - (int)positionWithRepeats;
// every second span is in the reverse direction - need to reverse the path position.
if (Precision.AlmostBigger(positionWithRepeats % 2, 1))
if (positionWithRepeats % 2 >= 1)
pathPosition = 1 - pathPosition;
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);

View File

@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override float DefaultFlashlightSize => 180;
public override float DefaultFlashlightSize => 200;
private OsuFlashlight flashlight = null!;
@ -62,7 +62,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
followDelay = modFlashlight.FollowDelay.Value;
FlashlightSize = new Vector2(0, GetSizeFor(0));
FlashlightSize = new Vector2(0, GetSize());
FlashlightSmoothness = 1.4f;
}
public void OnSliderTrackingChange(ValueChangedEvent<bool> e)
@ -82,9 +83,9 @@ namespace osu.Game.Rulesets.Osu.Mods
return base.OnMouseMove(e);
}
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

@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
{
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
public override Type[] IncompatibleMods =>
base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
/// <summary>
/// How early before a hitobject's start time to trigger a hit.
@ -51,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return;
}
osuInputManager.AllowUserPresses = false;
osuInputManager.AllowGameplayInputs = false;
}
public void Update(Playfield playfield)

View File

@ -204,12 +204,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// todo: temporary / arbitrary, used for lifetime optimisation.
this.Delay(800).FadeOut();
// in the case of an early state change, the fade should be expedited to the current point in time.
if (HitStateUpdateTime < HitObject.StartTime)
ApproachCircle.FadeOut(50);
switch (state)
{
default:
ApproachCircle.FadeOut();
break;
case ArmedState.Idle:
HitArea.HitAction = null;
break;

View File

@ -11,7 +11,9 @@ using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@ -64,6 +66,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
}
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
// Dim should only be applied at a top level, as it will be implicitly applied to nested objects.
if (ParentHitObject == null)
{
// Of note, no one noticed this was missing for years, but it definitely feels like it should still exist.
// For now this is applied across all skins, and matches stable.
// For simplicity, dim colour is applied to the DrawableHitObject itself.
// We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod).
this.FadeColour(new Color4(195, 195, 195, 255));
using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
this.FadeColour(Color4.White, 100);
}
}
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
private OsuInputManager osuActionInputManager;

View File

@ -186,17 +186,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Vector2? lastPosition;
private bool rewinding;
public void UpdateProgress(double completionProgress)
{
Position = drawableSlider.HitObject.CurvePositionAt(completionProgress);
var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f);
if (Clock.ElapsedFrameTime != 0)
rewinding = Clock.ElapsedFrameTime < 0;
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
if (diff.LengthFast < 0.01f)
return;
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0);
lastPosition = Position;
}
}

View File

@ -34,21 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override IList<HitSampleInfo> AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray();
public IList<HitSampleInfo> CreateSlidingSamples()
{
var slidingSamples = new List<HitSampleInfo>();
var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
if (normalSample != null)
slidingSamples.Add(normalSample.With("sliderslide"));
var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE);
if (whistleSample != null)
slidingSamples.Add(whistleSample.With("sliderwhistle"));
return slidingSamples;
}
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);

View File

@ -5,10 +5,12 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges.Events;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu
@ -17,9 +19,16 @@ namespace osu.Game.Rulesets.Osu
{
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
public bool AllowUserPresses
/// <summary>
/// Whether gameplay input buttons should be allowed.
/// Defaults to <c>true</c>, generally used for mods like Relax which turn off main inputs.
/// </summary>
/// <remarks>
/// Of note, auxiliary inputs like the "smoke" key are left usable.
/// </remarks>
public bool AllowGameplayInputs
{
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value;
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowGameplayInputs = value;
}
/// <summary>
@ -58,18 +67,36 @@ namespace osu.Game.Rulesets.Osu
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{
public bool AllowUserPresses = true;
private bool allowGameplayInputs = true;
/// <summary>
/// Whether gameplay input buttons should be allowed.
/// Defaults to <c>true</c>, generally used for mods like Relax which turn off main inputs.
/// </summary>
/// <remarks>
/// Of note, auxiliary inputs like the "smoke" key are left usable.
/// </remarks>
public bool AllowGameplayInputs
{
get => allowGameplayInputs;
set
{
allowGameplayInputs = value;
ReloadMappings();
}
}
public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
protected override bool Handle(UIEvent e)
protected override void ReloadMappings(IQueryable<RealmKeyBinding> realmKeyBindings)
{
if (!AllowUserPresses) return false;
base.ReloadMappings(realmKeyBindings);
return base.Handle(e);
if (!AllowGameplayInputs)
KeyBindings = KeyBindings.Where(b => b.GetAction<OsuAction>() == OsuAction.Smoke).ToList();
}
}
}
@ -80,6 +107,9 @@ namespace osu.Game.Rulesets.Osu
LeftButton,
[Description("Right button")]
RightButton
RightButton,
[Description("Smoke")]
Smoke,
}
}

View File

@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Osu
{
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
new KeyBinding(InputKey.X, OsuAction.RightButton),
new KeyBinding(InputKey.C, OsuAction.Smoke),
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
};

View File

@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu
SliderBall,
SliderBody,
SpinnerBody,
CursorSmoke,
ApproachCircle,
}
}

View File

@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Replays
Position = currentFrame.Position;
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
if (currentFrame.Smoke) Actions.Add(OsuAction.Smoke);
}
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Replays
state |= ReplayButtonState.Left1;
if (Actions.Contains(OsuAction.RightButton))
state |= ReplayButtonState.Right1;
if (Actions.Contains(OsuAction.Smoke))
state |= ReplayButtonState.Smoke;
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
}

View File

@ -1,20 +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.
#nullable disable
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
public class OsuHitWindows : HitWindows
{
/// <summary>
/// osu! ruleset has a fixed miss window regardless of difficulty settings.
/// </summary>
public const double MISS_WINDOW = 400;
private static readonly DifficultyRange[] osu_ranges =
{
new DifficultyRange(HitResult.Great, 80, 50, 20),
new DifficultyRange(HitResult.Ok, 140, 100, 60),
new DifficultyRange(HitResult.Meh, 200, 150, 100),
new DifficultyRange(HitResult.Miss, 400, 400, 400),
new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW),
};
public override bool IsHitResultAllowed(HitResult result)

View File

@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
return new ArgonJudgementPiece(resultComponent.Component);
case OsuSkinComponent osuComponent:
// TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
switch (osuComponent.Component)
{
case OsuSkinComponents.HitCircle:

View File

@ -0,0 +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.
using osu.Framework.Allocation;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
public class DefaultSmokeSegment : SmokeSegment
{
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
// ISkinSource doesn't currently fallback to global textures.
// We might want to change this in the future if the intention is to allow the user to skin this as per legacy skins.
Texture = textures.Get("Gameplay/osu/cursor-smoke");
}
}
}

View File

@ -0,0 +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.
using osu.Framework.Allocation;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
public class LegacySmokeSegment : SmokeSegment
{
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
base.LoadComplete();
Texture = skin.GetTexture("cursor-smoke");
}
}
}

View File

@ -106,6 +106,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
case OsuSkinComponents.CursorSmoke:
if (GetTexture("cursor-smoke") != null)
return new LegacySmokeSegment();
return null;
case OsuSkinComponents.HitCircleText:
if (!this.HasFont(LegacyFont.HitCircle))
return null;

View File

@ -0,0 +1,366 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Textures;
using osu.Framework.Utils;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning
{
public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable
{
private const int max_point_count = 18_000;
// fade anim values
private const double initial_fade_out_duration = 4000;
private const double re_fade_in_speed = 3;
private const double re_fade_in_duration = 50;
private const double final_fade_out_speed = 2;
private const double final_fade_out_duration = 8000;
private const float initial_alpha = 0.6f;
private const float re_fade_in_alpha = 1f;
private readonly int rotationSeed = RNG.Next();
// scale anim values
private const double scale_duration = 1200;
private const float initial_scale = 0.65f;
private const float final_scale = 1f;
// rotation anim values
private const double rotation_duration = 500;
private const float max_rotation = 0.25f;
public IShader? TextureShader { get; private set; }
public IShader? RoundedTextureShader { get; private set; }
protected Texture? Texture { get; set; }
private float radius => Texture?.DisplayWidth * 0.165f ?? 3;
protected readonly List<SmokePoint> SmokePoints = new List<SmokePoint>();
private float pointInterval => radius * 7f / 8;
private double smokeStartTime { get; set; } = double.MinValue;
private double smokeEndTime { get; set; } = double.MaxValue;
private float totalDistance;
private Vector2? lastPosition;
[BackgroundDependencyLoader]
private void load(ShaderManager shaders)
{
RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
}
protected override void LoadComplete()
{
base.LoadComplete();
RelativeSizeAxes = Axes.Both;
LifetimeStart = smokeStartTime = Time.Current;
totalDistance = pointInterval;
}
private Vector2 nextPointDirection()
{
float angle = RNG.NextSingle(0, 2 * MathF.PI);
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
}
public void AddPosition(Vector2 position, double time)
{
lastPosition ??= position;
float delta = (position - (Vector2)lastPosition).LengthFast;
totalDistance += delta;
int count = (int)(totalDistance / pointInterval);
if (count > 0)
{
Vector2 increment = position - (Vector2)lastPosition;
increment.NormalizeFast();
Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
increment *= pointInterval;
if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time)
{
int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer());
SmokePoints.RemoveRange(index, SmokePoints.Count - index);
}
totalDistance %= pointInterval;
for (int i = 0; i < count; i++)
{
SmokePoints.Add(new SmokePoint
{
Position = pointPos,
Time = time,
Direction = nextPointDirection(),
});
pointPos += increment;
}
Invalidate(Invalidation.DrawNode);
}
lastPosition = position;
if (SmokePoints.Count >= max_point_count)
FinishDrawing(time);
}
public void FinishDrawing(double time)
{
smokeEndTime = time;
double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, smokeEndTime - smokeStartTime);
LifetimeEnd = smokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed;
}
protected override DrawNode CreateDrawNode() => new SmokeDrawNode(this);
protected override void Update()
{
base.Update();
Invalidate(Invalidation.DrawNode);
}
protected struct SmokePoint
{
public Vector2 Position;
public double Time;
public Vector2 Direction;
public struct UpperBoundComparer : IComparer<SmokePoint>
{
public int Compare(SmokePoint x, SmokePoint target)
{
// By returning -1 when the target value is equal to x, guarantees that the
// element at BinarySearch's returned index will always be the first element
// larger. Since 0 is never returned, the target is never "found", so the return
// value will be the index's complement.
return x.Time > target.Time ? 1 : -1;
}
}
}
protected class SmokeDrawNode : TexturedShaderDrawNode
{
protected new SmokeSegment Source => (SmokeSegment)base.Source;
protected double SmokeStartTime { get; private set; }
protected double SmokeEndTime { get; private set; }
protected double CurrentTime { get; private set; }
private readonly List<SmokePoint> points = new List<SmokePoint>();
private IVertexBatch<TexturedVertex2D>? quadBatch;
private float radius;
private Vector2 drawSize;
private Texture? texture;
// anim calculation vars (color, scale, direction)
private double initialFadeOutDurationTrunc;
private double firstVisiblePointTime;
private double initialFadeOutTime;
private double reFadeInTime;
private double finalFadeOutTime;
private Random rotationRNG = new Random();
public SmokeDrawNode(ITexturedShaderDrawable source)
: base(source)
{
}
public override void ApplyState()
{
base.ApplyState();
points.Clear();
points.AddRange(Source.SmokePoints);
radius = Source.radius;
drawSize = Source.DrawSize;
texture = Source.Texture;
SmokeStartTime = Source.smokeStartTime;
SmokeEndTime = Source.smokeEndTime;
CurrentTime = Source.Clock.CurrentTime;
rotationRNG = new Random(Source.rotationSeed);
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc;
initialFadeOutTime = CurrentTime;
reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed);
finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed);
}
public sealed override void Draw(IRenderer renderer)
{
base.Draw(renderer);
if (points.Count == 0)
return;
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(max_point_count / 10, 10);
texture ??= renderer.WhitePixel;
RectangleF textureRect = texture.GetTextureRect();
var shader = GetAppropriateShader(renderer);
renderer.SetBlend(BlendingParameters.Additive);
renderer.PushLocalMatrix(DrawInfo.Matrix);
shader.Bind();
texture.Bind();
foreach (var point in points)
drawPointQuad(point, textureRect);
shader.Unbind();
renderer.PopLocalMatrix();
}
protected Color4 ColourAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour
? ((SRGBColour)DrawColourInfo.Colour).Linear
: DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, drawSize)).Linear;
protected virtual Color4 PointColour(SmokePoint point)
{
var color = Color4.White;
double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time;
if (timeDoingInitialFadeOut > 0)
{
float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
color.A = (1 - fraction) * initial_alpha;
}
if (color.A > 0)
{
double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
if (timeDoingFinalFadeOut > 0)
{
float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
fraction = MathF.Pow(fraction, 5);
color.A = (1 - fraction) * re_fade_in_alpha;
}
else if (timeDoingReFadeIn > 0)
{
float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
fraction = 1 - MathF.Pow(1 - fraction, 5);
color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
}
}
return color;
}
protected virtual float PointScale(SmokePoint point)
{
double timeDoingScale = CurrentTime - point.Time;
float fraction = Math.Clamp((float)(timeDoingScale / scale_duration), 0, 1);
fraction = 1 - MathF.Pow(1 - fraction, 5);
return fraction * (final_scale - initial_scale) + initial_scale;
}
protected virtual Vector2 PointDirection(SmokePoint point)
{
float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X);
float finalAngle = initialAngle + nextRotation();
double timeDoingRotation = CurrentTime - point.Time;
float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1);
fraction = 1 - MathF.Pow(1 - fraction, 5);
float angle = fraction * (finalAngle - initialAngle) + initialAngle;
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
}
private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1);
private void drawPointQuad(SmokePoint point, RectangleF textureRect)
{
Debug.Assert(quadBatch != null);
var colour = PointColour(point);
float scale = PointScale(point);
var dir = PointDirection(point);
var ortho = dir.PerpendicularLeft;
if (colour.A == 0 || scale == 0)
return;
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
var localBotRight = point.Position + (radius * scale * (ortho + dir));
quadBatch.Add(new TexturedVertex2D
{
Position = localTopLeft,
TexturePosition = textureRect.TopLeft,
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopLeft), colour),
});
quadBatch.Add(new TexturedVertex2D
{
Position = localTopRight,
TexturePosition = textureRect.TopRight,
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopRight), colour),
});
quadBatch.Add(new TexturedVertex2D
{
Position = localBotRight,
TexturePosition = textureRect.BottomRight,
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotRight), colour),
});
quadBatch.Add(new TexturedVertex2D
{
Position = localBotLeft,
TexturePosition = textureRect.BottomLeft,
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotLeft), colour),
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
quadBatch?.Dispose();
}
}
}
}

View File

@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI
InternalChildren = new Drawable[]
{
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
new SmokeContainer { RelativeSizeAxes = Axes.Both },
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },

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